camstreamerlib 3.5.2 → 4.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CamOverlayAPI.d.ts +11 -28
- package/CamOverlayAPI.js +116 -138
- package/CamOverlayDrawingAPI.js +26 -18
- package/CamOverlayPainter/Frame.js +167 -182
- package/CamOverlayPainter/Painter.js +80 -101
- package/CamOverlayPainter/ResourceManager.js +31 -46
- package/CamScripterAPI.d.ts +19 -0
- package/CamScripterAPI.js +66 -0
- package/CamScripterAPICameraEventsGenerator.js +22 -16
- package/CamStreamerAPI.d.ts +5 -27
- package/CamStreamerAPI.js +45 -71
- package/CamSwitcherAPI.d.ts +38 -71
- package/CamSwitcherAPI.js +329 -91
- package/CamSwitcherEvents.d.ts +15 -33
- package/CamSwitcherEvents.js +53 -97
- package/CreatePackage.js +5 -7
- package/README.md +3 -1
- package/VapixAPI.d.ts +66 -0
- package/VapixAPI.js +454 -0
- package/VapixEvents.js +18 -16
- package/errors/errors.d.ts +34 -0
- package/errors/errors.js +66 -0
- package/events/AxisCameraStationEvents.js +29 -42
- package/events/GenetecAgent.d.ts +14 -15
- package/events/GenetecAgent.js +81 -100
- package/internal/Digest.js +5 -11
- package/internal/ProxyClient.d.ts +11 -0
- package/internal/ProxyClient.js +40 -0
- package/internal/common.d.ts +19 -4
- package/internal/common.js +11 -26
- package/internal/constants.d.ts +1 -0
- package/internal/constants.js +1 -0
- package/internal/transformers.d.ts +5 -0
- package/internal/transformers.js +25 -0
- package/internal/utils.d.ts +11 -0
- package/internal/utils.js +34 -0
- package/internal/versionCompare.d.ts +6 -0
- package/internal/versionCompare.js +44 -0
- package/node/DefaultClient.d.ts +15 -0
- package/node/DefaultClient.js +50 -0
- package/{internal → node}/HttpRequestSender.d.ts +2 -2
- package/node/HttpRequestSender.js +85 -0
- package/{HttpServer.d.ts → node/HttpServer.d.ts} +1 -1
- package/{HttpServer.js → node/HttpServer.js} +22 -24
- package/{internal → node}/WsClient.d.ts +1 -1
- package/{internal → node}/WsClient.js +32 -39
- package/node/WsEventClient.d.ts +13 -0
- package/node/WsEventClient.js +18 -0
- package/package.json +7 -3
- package/types/CamOverlayAPI.d.ts +188 -0
- package/types/CamOverlayAPI.js +44 -0
- package/types/CamScripterAPI.d.ts +67 -0
- package/types/CamScripterAPI.js +17 -0
- package/types/CamStreamerAPI.d.ts +139 -0
- package/types/CamStreamerAPI.js +25 -0
- package/types/CamSwitcherAPI.d.ts +814 -0
- package/types/CamSwitcherAPI.js +134 -0
- package/types/CamswitcherEvents.d.ts +491 -0
- package/types/CamswitcherEvents.js +59 -0
- package/types/VapixAPI.d.ts +1704 -0
- package/types/VapixAPI.js +129 -0
- package/types/common.d.ts +37 -0
- package/types/common.js +11 -0
- package/web/DefaultClient.d.ts +6 -0
- package/web/DefaultClient.js +16 -0
- package/web/WsClient.d.ts +13 -0
- package/web/WsClient.js +58 -0
- package/CameraVapix.d.ts +0 -98
- package/CameraVapix.js +0 -441
- package/DefaultAgent.d.ts +0 -15
- package/DefaultAgent.js +0 -68
- package/internal/HttpRequestSender.js +0 -117
package/CamSwitcherAPI.js
CHANGED
|
@@ -1,112 +1,350 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
audio: 'audios',
|
|
18
|
-
playlist: 'playlists',
|
|
19
|
-
clip: 'clips',
|
|
20
|
-
tracker: 'trackers',
|
|
21
|
-
};
|
|
22
|
-
class CamSwitcherAPI {
|
|
23
|
-
constructor(options = {}) {
|
|
24
|
-
if ((0, common_1.isClient)(options)) {
|
|
25
|
-
this.client = options;
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
this.client = new DefaultAgent_1.DefaultAgent(options);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
generateSilence(sampleRate, channels) {
|
|
32
|
-
return this.get('/local/camswitcher/generate_silence.cgi', { sample_rate: sampleRate.toString(), channels });
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AddNewClipError } from './errors/errors';
|
|
3
|
+
import { isNullish, responseStringify } from './internal/common';
|
|
4
|
+
import { storageInfoListSchema, outputInfoSchema, audioPushInfoSchema, clipListSchema, playlistQueueSchema, streamSaveLoadSchema, clipSaveLoadSchema, playlistSaveLoadSchema, trackerSaveLoadSchema, } from './types/CamSwitcherAPI';
|
|
5
|
+
import { networkCameraListSchema } from './types/common';
|
|
6
|
+
import { VapixAPI } from './VapixAPI';
|
|
7
|
+
import { isFirmwareVersionAtLeast } from './internal/versionCompare';
|
|
8
|
+
import { isClip } from './internal/utils';
|
|
9
|
+
import { FIRMWARE_WITH_BITRATE_MODES_SUPPORT } from './internal/constants';
|
|
10
|
+
const baseUrl = '/local/camswitcher/api';
|
|
11
|
+
export class CamSwitcherAPI {
|
|
12
|
+
client;
|
|
13
|
+
vapixAgent;
|
|
14
|
+
constructor(client) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.vapixAgent = new VapixAPI(client, () => '');
|
|
33
17
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
static getProxyUrl = () => `${baseUrl}/proxy.cgi`;
|
|
19
|
+
static getWsEventsUrl = () => `/local/camswitcher/events`;
|
|
20
|
+
static getClipPreview = (id, storage) => `${baseUrl}/clip_preview.cgi?clip_name=${id}&storage=${storage}`;
|
|
21
|
+
async generateSilence(sampleRate, channels) {
|
|
22
|
+
await this.client.get(`${baseUrl}/generate_silence.cgi`, {
|
|
23
|
+
sample_rate: sampleRate.toString(),
|
|
24
|
+
channels,
|
|
37
25
|
});
|
|
38
26
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
27
|
+
async checkCameraTime() {
|
|
28
|
+
const data = await this.get(`${baseUrl}/camera_time.cgi`);
|
|
29
|
+
return z.boolean().parse(data);
|
|
43
30
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
31
|
+
async getIpListFromNetworkCheck() {
|
|
32
|
+
const data = await this.get(`${baseUrl}/network_camera_list.cgi`);
|
|
33
|
+
return networkCameraListSchema.parse(data);
|
|
48
34
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
async getMaxFps(source) {
|
|
36
|
+
const data = await this.get(`${baseUrl}/get_max_framerate.cgi`, {
|
|
37
|
+
video_source: source.toString(),
|
|
52
38
|
});
|
|
39
|
+
return z.number().parse(data);
|
|
53
40
|
}
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
async getStorageInfo() {
|
|
42
|
+
const data = await this.get(`${baseUrl}/get_storage.cgi`);
|
|
43
|
+
return storageInfoListSchema.parse(data);
|
|
56
44
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
45
|
+
async wsAuthorization() {
|
|
46
|
+
const data = await this.get(`${baseUrl}/ws_authorization.cgi`);
|
|
47
|
+
return z.string().parse(data);
|
|
61
48
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
49
|
+
async getOutputInfo() {
|
|
50
|
+
const data = await this.get(`${baseUrl}/output_info.cgi`);
|
|
51
|
+
return outputInfoSchema.parse(data);
|
|
66
52
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
53
|
+
async getAudioPushInfo() {
|
|
54
|
+
const data = await this.get(`${baseUrl}/audio_push_info.cgi`);
|
|
55
|
+
return audioPushInfoSchema.parse(data);
|
|
71
56
|
}
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
async getStreamSaveList() {
|
|
58
|
+
const data = await this.get(`${baseUrl}/streams.cgi`, { action: 'get' });
|
|
59
|
+
return streamSaveLoadSchema.parse(data);
|
|
74
60
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
61
|
+
async getClipSaveList() {
|
|
62
|
+
const data = await this.get(`${baseUrl}/clips.cgi`, { action: 'get' });
|
|
63
|
+
return clipSaveLoadSchema.parse(data);
|
|
79
64
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
65
|
+
async getPlaylistSaveList() {
|
|
66
|
+
const data = await this.get(`${baseUrl}/playlists.cgi`, { action: 'get' });
|
|
67
|
+
return playlistSaveLoadSchema.parse(data);
|
|
68
|
+
}
|
|
69
|
+
async getTrackerSaveList() {
|
|
70
|
+
const data = await this.get(`${baseUrl}/trackers.cgi`, { action: 'get' });
|
|
71
|
+
return trackerSaveLoadSchema.parse(data);
|
|
72
|
+
}
|
|
73
|
+
async setStreamSaveList(data) {
|
|
74
|
+
return await this.set(`${baseUrl}/streams.cgi`, data, { action: 'set' });
|
|
75
|
+
}
|
|
76
|
+
async setClipSaveList(data) {
|
|
77
|
+
return await this.set(`${baseUrl}/clips.cgi`, data, { action: 'set' });
|
|
78
|
+
}
|
|
79
|
+
async setPlaylistSaveList(data) {
|
|
80
|
+
return await this.set(`${baseUrl}/playlists.cgi`, data, { action: 'set' });
|
|
81
|
+
}
|
|
82
|
+
async setTrackerSaveList(data) {
|
|
83
|
+
return await this.set(`${baseUrl}/trackers.cgi`, data, { action: 'set' });
|
|
84
|
+
}
|
|
85
|
+
async playlistSwitch(playlistName) {
|
|
86
|
+
await this.get(`${baseUrl}/playlist_switch.cgi?playlist_name=${playlistName}`);
|
|
87
|
+
}
|
|
88
|
+
async playlistQueuePush(playlistName) {
|
|
89
|
+
await this.get(`${baseUrl}/playlist_queue_push.cgi?playlist_name=${playlistName}`);
|
|
90
|
+
}
|
|
91
|
+
async playlistQueueClear() {
|
|
92
|
+
await this.get(`${baseUrl}/playlist_queue_clear.cgi`);
|
|
93
|
+
}
|
|
94
|
+
async playlistQueueList() {
|
|
95
|
+
const data = await this.get(`${baseUrl}/playlist_queue_list.cgi`);
|
|
96
|
+
return playlistQueueSchema.parse(data).playlistQueueList;
|
|
97
|
+
}
|
|
98
|
+
async playlistQueuePlayNext() {
|
|
99
|
+
await this.get(`${baseUrl}/playlist_queue_play_next.cgi`);
|
|
100
|
+
}
|
|
101
|
+
async addNewClip(file, clipType, storage, id, fileName) {
|
|
102
|
+
const formData = new FormData();
|
|
103
|
+
formData.append('clip_name', id);
|
|
104
|
+
formData.append('clip_type', clipType);
|
|
105
|
+
formData.append('file', file, fileName);
|
|
106
|
+
const path = `${baseUrl}/clip_upload.cgi?storage=${storage}`;
|
|
107
|
+
const res = await this.client.post(path, formData);
|
|
108
|
+
const output = (await res.json());
|
|
109
|
+
if (output.status !== 200) {
|
|
110
|
+
throw new AddNewClipError(output.message);
|
|
111
|
+
}
|
|
96
112
|
}
|
|
97
113
|
removeClip(id, storage) {
|
|
98
|
-
return this.get(
|
|
114
|
+
return this.get(`${baseUrl}/clip_remove.cgi`, { clip_name: id, storage });
|
|
99
115
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
async getClipList() {
|
|
117
|
+
const data = await this.get(`${baseUrl}/clip_list.cgi`);
|
|
118
|
+
return clipListSchema.parse(data).clip_list;
|
|
119
|
+
}
|
|
120
|
+
setCamSwitchOptions(data, cameraFWVersion) {
|
|
121
|
+
const bitrateVapixParams = parseBitrateOptionsToBitrateVapixParams(cameraFWVersion, data.bitrateMode, data);
|
|
122
|
+
const saveData = {
|
|
123
|
+
video: {
|
|
124
|
+
resolution: data.resolution,
|
|
125
|
+
h264Profile: data.h264Profile,
|
|
126
|
+
fps: data.fps,
|
|
127
|
+
compression: data.compression,
|
|
128
|
+
govLength: data.govLength,
|
|
129
|
+
videoClipQuality: data.maximumBitRate,
|
|
130
|
+
bitrateVapixParams: bitrateVapixParams,
|
|
131
|
+
},
|
|
132
|
+
audio: {
|
|
133
|
+
sampleRate: data.audioSampleRate,
|
|
134
|
+
channelCount: data.audioChannelCount,
|
|
135
|
+
},
|
|
136
|
+
keyboard: data.keyboard,
|
|
137
|
+
};
|
|
138
|
+
return this.setParamFromCameraJSON(CSW_PARAM_NAMES.SETTINGS, saveData);
|
|
139
|
+
}
|
|
140
|
+
setGlobalAudioSettings(settings) {
|
|
141
|
+
let acceptedType = 'NONE';
|
|
142
|
+
if (settings.type === 'source' && settings.source) {
|
|
143
|
+
if (isClip(settings.source)) {
|
|
144
|
+
acceptedType = 'CLIP';
|
|
105
145
|
}
|
|
106
146
|
else {
|
|
107
|
-
|
|
147
|
+
acceptedType = 'STREAM';
|
|
108
148
|
}
|
|
109
|
-
}
|
|
149
|
+
}
|
|
150
|
+
const data = {
|
|
151
|
+
type: acceptedType,
|
|
152
|
+
stream_name: settings.source,
|
|
153
|
+
clip_name: settings.source,
|
|
154
|
+
storage: settings.storage,
|
|
155
|
+
};
|
|
156
|
+
return this.setParamFromCameraJSON(CSW_PARAM_NAMES.MASTER_AUDIO, data);
|
|
157
|
+
}
|
|
158
|
+
setSecondaryAudioSettings(settings) {
|
|
159
|
+
const data = {
|
|
160
|
+
type: settings.type,
|
|
161
|
+
stream_name: settings.streamName ?? '',
|
|
162
|
+
clip_name: settings.clipName ?? '',
|
|
163
|
+
storage: settings.storage,
|
|
164
|
+
secondary_audio_level: settings.secondaryAudioLevel,
|
|
165
|
+
master_audio_level: settings.masterAudioLevel,
|
|
166
|
+
};
|
|
167
|
+
return this.setParamFromCameraJSON(CSW_PARAM_NAMES.SECONDARY_AUDIO, data);
|
|
168
|
+
}
|
|
169
|
+
setDefaultPlaylist(id) {
|
|
170
|
+
const value = JSON.stringify({ default_playlist_id: id });
|
|
171
|
+
return this.vapixAgent.setParameter({
|
|
172
|
+
[CSW_PARAM_NAMES.DEFAULT_PLAYLIST]: value,
|
|
173
|
+
}, null);
|
|
174
|
+
}
|
|
175
|
+
setPermanentRtspUrlToken(token) {
|
|
176
|
+
return this.vapixAgent.setParameter({ [CSW_PARAM_NAMES.RTSP_TOKEN]: token }, null);
|
|
177
|
+
}
|
|
178
|
+
async getCamSwitchOptions() {
|
|
179
|
+
const saveData = await this.getParamFromCameraAndJSONParse(CSW_PARAM_NAMES.SETTINGS);
|
|
180
|
+
if (isNullish(saveData.video)) {
|
|
181
|
+
return saveData;
|
|
182
|
+
}
|
|
183
|
+
if (!isNullish(saveData.video?.bitrateVapixParams)) {
|
|
184
|
+
const bitrateOptions = parseVapixParamsToBitrateOptions(saveData.video.bitrateVapixParams);
|
|
185
|
+
saveData.video.bitrateMode = bitrateOptions.bitrateMode;
|
|
186
|
+
saveData.video.maximumBitRate = bitrateOptions.maximumBitRate;
|
|
187
|
+
saveData.video.retentionTime = bitrateOptions.retentionTime;
|
|
188
|
+
saveData.video.bitRateLimit = bitrateOptions.bitRateLimit;
|
|
189
|
+
}
|
|
190
|
+
if (!isNullish(saveData.video?.bitrateLimit)) {
|
|
191
|
+
saveData.video.maximumBitRate = saveData.video.bitrateLimit;
|
|
192
|
+
saveData.video.bitrateMode = 'MBR';
|
|
193
|
+
}
|
|
194
|
+
if (!isNullish(saveData.video?.videoClipQuality)) {
|
|
195
|
+
saveData.video.maximumBitRate = saveData.video.videoClipQuality;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...saveData.video,
|
|
199
|
+
audioSampleRate: saveData.audio.sampleRate,
|
|
200
|
+
audioChannelCount: saveData.audio.channelCount,
|
|
201
|
+
keyboard: saveData.keyboard,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async getGlobalAudioSettings() {
|
|
205
|
+
const settings = {
|
|
206
|
+
type: 'fromSource',
|
|
207
|
+
source: 'fromSource',
|
|
208
|
+
};
|
|
209
|
+
const res = await this.getParamFromCameraAndJSONParse(CSW_PARAM_NAMES.MASTER_AUDIO);
|
|
210
|
+
if (res.type === 'STREAM') {
|
|
211
|
+
settings.type = 'source';
|
|
212
|
+
settings.source = res.stream_name;
|
|
213
|
+
}
|
|
214
|
+
else if (res.type === 'CLIP') {
|
|
215
|
+
settings.type = 'source';
|
|
216
|
+
settings.source = res.clip_name;
|
|
217
|
+
settings.storage = res.storage;
|
|
218
|
+
}
|
|
219
|
+
return settings;
|
|
220
|
+
}
|
|
221
|
+
async getSecondaryAudioSettings() {
|
|
222
|
+
const res = await this.getParamFromCameraAndJSONParse(CSW_PARAM_NAMES.SECONDARY_AUDIO);
|
|
223
|
+
const settings = {
|
|
224
|
+
type: res.type ?? 'NONE',
|
|
225
|
+
streamName: res.stream_name,
|
|
226
|
+
clipName: res.clip_name,
|
|
227
|
+
storage: res.storage,
|
|
228
|
+
secondaryAudioLevel: res.secondary_audio_level ?? 1,
|
|
229
|
+
masterAudioLevel: res.master_audio_level ?? 1,
|
|
230
|
+
};
|
|
231
|
+
return settings;
|
|
232
|
+
}
|
|
233
|
+
async getPermanentRtspUrlToken() {
|
|
234
|
+
const paramName = CSW_PARAM_NAMES.RTSP_TOKEN;
|
|
235
|
+
const res = await this.vapixAgent.getParameter([paramName], null);
|
|
236
|
+
return res[paramName] ?? '';
|
|
237
|
+
}
|
|
238
|
+
async get(path, parameters = {}) {
|
|
239
|
+
const res = await this.client.get(path, parameters);
|
|
240
|
+
if (res.ok) {
|
|
241
|
+
const d = (await res.json());
|
|
242
|
+
return d.data;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
throw new Error(await responseStringify(res));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async set(path, data, parameters = {}) {
|
|
249
|
+
const res = await this.client.post(path, JSON.stringify(data), parameters);
|
|
250
|
+
if (res.ok) {
|
|
251
|
+
const parsed = await res.json();
|
|
252
|
+
return parsed.message === 'OK';
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
throw new Error(await responseStringify(res));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
setParamFromCameraJSON(paramName, data) {
|
|
259
|
+
const params = {};
|
|
260
|
+
params[paramName] = JSON.stringify(data);
|
|
261
|
+
return this.vapixAgent.setParameter(params, null);
|
|
262
|
+
}
|
|
263
|
+
async getParamFromCameraAndJSONParse(paramName) {
|
|
264
|
+
const data = await this.vapixAgent.getParameter([paramName], null);
|
|
265
|
+
if (data[paramName] !== undefined) {
|
|
266
|
+
try {
|
|
267
|
+
if (data[paramName] === '') {
|
|
268
|
+
return {};
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
return JSON.parse(data[paramName] + '');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
throw new Error('Error: in JSON parsing of ' + paramName + '. Cannot parse: ' + data[paramName]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
throw new Error("Error: no parametr '" + paramName + "' was found");
|
|
110
279
|
}
|
|
111
280
|
}
|
|
112
|
-
|
|
281
|
+
const CSW_PARAM_NAMES = {
|
|
282
|
+
SETTINGS: 'Camswitcher.Settings',
|
|
283
|
+
MASTER_AUDIO: 'Camswitcher.MasterAudio',
|
|
284
|
+
SECONDARY_AUDIO: 'Camswitcher.SecondaryAudio',
|
|
285
|
+
RTSP_TOKEN: 'Camswitcher.RTSPAccessToken',
|
|
286
|
+
DEFAULT_PLAYLIST: 'Camswitcher.DefaultPlaylist',
|
|
287
|
+
};
|
|
288
|
+
const parseBitrateOptionsToBitrateVapixParams = (firmWareVersion, bitrateMode, cameraOptions) => {
|
|
289
|
+
if (!isFirmwareVersionAtLeast(firmWareVersion, FIRMWARE_WITH_BITRATE_MODES_SUPPORT)) {
|
|
290
|
+
return `videomaxbitrate=${cameraOptions.maximumBitRate}`;
|
|
291
|
+
}
|
|
292
|
+
if (bitrateMode === null) {
|
|
293
|
+
return '';
|
|
294
|
+
}
|
|
295
|
+
if (bitrateMode === 'VBR') {
|
|
296
|
+
return 'videobitratemode=vbr';
|
|
297
|
+
}
|
|
298
|
+
if (bitrateMode === 'MBR') {
|
|
299
|
+
return `videobitratemode=mbr&videomaxbitrate=${cameraOptions.maximumBitRate}`;
|
|
300
|
+
}
|
|
301
|
+
if (bitrateMode === 'ABR') {
|
|
302
|
+
return `videobitratemode=abr&videoabrtargetbitrate=${cameraOptions.maximumBitRate}&videoabrretentiontime=${cameraOptions.retentionTime}&videoabrmaxbitrate=${cameraOptions.bitRateLimit}`;
|
|
303
|
+
}
|
|
304
|
+
throw new Error('Unknown bitrateMode param in getVapixParams method.');
|
|
305
|
+
};
|
|
306
|
+
const parseVapixParamsToBitrateOptions = (bitrateVapixParams) => {
|
|
307
|
+
const params = {};
|
|
308
|
+
const searchParams = new URLSearchParams(bitrateVapixParams);
|
|
309
|
+
searchParams.forEach((value, key) => {
|
|
310
|
+
params[key] = value;
|
|
311
|
+
});
|
|
312
|
+
const bitrateMode = params['videobitratemode'] !== undefined ? params['videobitratemode'].toUpperCase() : undefined;
|
|
313
|
+
const hasLowerFw = bitrateMode === undefined && params['videomaxbitrate'] !== undefined;
|
|
314
|
+
if (hasLowerFw) {
|
|
315
|
+
const maximumBitRate = parseInt(params['videomaxbitrate'], 10);
|
|
316
|
+
return {
|
|
317
|
+
bitrateMode: 'MBR',
|
|
318
|
+
maximumBitRate: maximumBitRate,
|
|
319
|
+
retentionTime: 1,
|
|
320
|
+
bitRateLimit: Math.floor(maximumBitRate * 1.1),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (bitrateMode === 'ABR') {
|
|
324
|
+
const maximumBitRate = parseInt(params['videoabrtargetbitrate'], 10);
|
|
325
|
+
const retentionTime = parseInt(params['videoabrretentiontime'], 10);
|
|
326
|
+
const bitrateLimit = parseInt(params['videoabrmaxbitrate'], 10);
|
|
327
|
+
return {
|
|
328
|
+
bitrateMode: bitrateMode,
|
|
329
|
+
maximumBitRate: maximumBitRate,
|
|
330
|
+
retentionTime: retentionTime,
|
|
331
|
+
bitRateLimit: bitrateLimit,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
else if (bitrateMode === 'MBR') {
|
|
335
|
+
const maximumBitRate = parseInt(params['videomaxbitrate'], 10);
|
|
336
|
+
const oldMaximumBitrateParamValue = parseInt(params['videombrmaxbitrate'], 10);
|
|
337
|
+
return {
|
|
338
|
+
bitrateMode: bitrateMode,
|
|
339
|
+
maximumBitRate: maximumBitRate ?? oldMaximumBitrateParamValue,
|
|
340
|
+
retentionTime: 1,
|
|
341
|
+
bitRateLimit: Math.floor(maximumBitRate ?? oldMaximumBitrateParamValue * 1.1),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
bitrateMode: bitrateMode,
|
|
346
|
+
retentionTime: 1,
|
|
347
|
+
maximumBitRate: 0,
|
|
348
|
+
bitRateLimit: 0,
|
|
349
|
+
};
|
|
350
|
+
};
|
package/CamSwitcherEvents.d.ts
CHANGED
|
@@ -1,36 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type: string;
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
export interface CamSwitcherEvents {
|
|
12
|
-
on(event: 'open', listener: () => void): this;
|
|
13
|
-
on(event: 'close', listener: () => void): this;
|
|
14
|
-
on(event: 'event', listener: (data: TEvent) => void): this;
|
|
15
|
-
on(event: 'error', listener: (err: Error) => void): this;
|
|
16
|
-
emit(event: 'open'): boolean;
|
|
17
|
-
emit(event: 'close'): boolean;
|
|
18
|
-
emit(event: 'event', data: TEvent): boolean;
|
|
19
|
-
emit(event: 'error', err: Error): boolean;
|
|
20
|
-
}
|
|
21
|
-
export declare class CamSwitcherEvents extends EventEmitter {
|
|
22
|
-
private tls;
|
|
23
|
-
private tlsInsecure;
|
|
24
|
-
private ip;
|
|
25
|
-
private port;
|
|
26
|
-
private user;
|
|
27
|
-
private pass;
|
|
28
|
-
private client;
|
|
1
|
+
import { IWebsocket } from './internal/common';
|
|
2
|
+
import { TCamSwitcherEventOfType, TCamSwitcherEventType } from './types/CamswitcherEvents';
|
|
3
|
+
type TListenerFunction<T extends TCamSwitcherEventType> = (data: TCamSwitcherEventOfType<T>, isInit: boolean) => void;
|
|
4
|
+
export declare class CamSwitcherEvents<Event extends {
|
|
5
|
+
data: string;
|
|
6
|
+
}> {
|
|
7
|
+
isDestroyed: boolean;
|
|
29
8
|
private ws;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
disconnect(): void;
|
|
9
|
+
private listeners;
|
|
10
|
+
setWebsocket(ws: IWebsocket<Event>): void;
|
|
33
11
|
resendInitData(): void;
|
|
34
|
-
|
|
35
|
-
|
|
12
|
+
addListener<T extends TCamSwitcherEventType>(type: T, listener: TListenerFunction<T>, id: string): void;
|
|
13
|
+
removeListener<T extends TCamSwitcherEventType>(type: T, id: string): void;
|
|
14
|
+
private onMessage;
|
|
15
|
+
private processMessage;
|
|
16
|
+
destroy(): void;
|
|
36
17
|
}
|
|
18
|
+
export {};
|
package/CamSwitcherEvents.js
CHANGED
|
@@ -1,107 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
exports.CamSwitcherEvents = void 0;
|
|
13
|
-
const EventEmitter = require("events");
|
|
14
|
-
const WsClient_1 = require("./internal/WsClient");
|
|
15
|
-
const DefaultAgent_1 = require("./DefaultAgent");
|
|
16
|
-
class CamSwitcherEvents extends EventEmitter {
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
var _a, _b, _c, _d, _e, _f;
|
|
19
|
-
super();
|
|
20
|
-
this.tls = (_a = options.tls) !== null && _a !== void 0 ? _a : false;
|
|
21
|
-
this.tlsInsecure = (_b = options.tlsInsecure) !== null && _b !== void 0 ? _b : false;
|
|
22
|
-
this.ip = (_c = options.ip) !== null && _c !== void 0 ? _c : '127.0.0.1';
|
|
23
|
-
this.port = (_d = options.port) !== null && _d !== void 0 ? _d : (this.tls ? 443 : 80);
|
|
24
|
-
this.user = (_e = options.user) !== null && _e !== void 0 ? _e : 'root';
|
|
25
|
-
this.pass = (_f = options.pass) !== null && _f !== void 0 ? _f : '';
|
|
26
|
-
this.client = new DefaultAgent_1.DefaultAgent(options);
|
|
27
|
-
this.createWsClient();
|
|
28
|
-
EventEmitter.call(this);
|
|
29
|
-
}
|
|
30
|
-
connect() {
|
|
31
|
-
this.ws.open();
|
|
32
|
-
}
|
|
33
|
-
disconnect() {
|
|
34
|
-
this.ws.close();
|
|
1
|
+
import { cswEventsSchema, } from './types/CamswitcherEvents';
|
|
2
|
+
export class CamSwitcherEvents {
|
|
3
|
+
isDestroyed = false;
|
|
4
|
+
ws = null;
|
|
5
|
+
listeners = {};
|
|
6
|
+
setWebsocket(ws) {
|
|
7
|
+
if (this.ws) {
|
|
8
|
+
this.ws.destroy();
|
|
9
|
+
}
|
|
10
|
+
this.ws = ws;
|
|
11
|
+
this.ws.onmessage = (e) => this.onMessage(e);
|
|
35
12
|
}
|
|
36
13
|
resendInitData() {
|
|
37
14
|
const request = {
|
|
38
15
|
command: 'sendInitData',
|
|
39
16
|
};
|
|
40
|
-
this.ws
|
|
17
|
+
this.ws?.send(JSON.stringify(request));
|
|
41
18
|
}
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
tlsInsecure: this.tlsInsecure,
|
|
50
|
-
address: '/local/camswitcher/events',
|
|
51
|
-
protocol: 'events',
|
|
52
|
-
};
|
|
53
|
-
this.ws = new WsClient_1.WsClient(options);
|
|
54
|
-
this.ws.on('open', () => __awaiter(this, void 0, void 0, function* () {
|
|
55
|
-
try {
|
|
56
|
-
const token = yield this.get('/local/camswitcher/ws_authorization.cgi');
|
|
57
|
-
this.ws.send(JSON.stringify({ authorization: token }));
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
this.emit('error', err);
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
this.ws.on('message', (data) => {
|
|
64
|
-
try {
|
|
65
|
-
const parsedData = JSON.parse(data.toString());
|
|
66
|
-
if (parsedData.type === 'authorization') {
|
|
67
|
-
if (parsedData.state === 'OK') {
|
|
68
|
-
this.emit('open');
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
this.emit('error', new Error(data.toString()));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
this.emit('event', parsedData);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
this.emit('error', err);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
this.ws.on('error', (err) => {
|
|
83
|
-
this.emit('error', err);
|
|
84
|
-
});
|
|
85
|
-
this.ws.on('close', () => {
|
|
86
|
-
this.emit('close');
|
|
87
|
-
});
|
|
19
|
+
addListener(type, listener, id) {
|
|
20
|
+
const typeList = this.listeners[type];
|
|
21
|
+
if (typeList === undefined) {
|
|
22
|
+
this.listeners[type] = { [id]: listener };
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
typeList[id] = listener;
|
|
88
26
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
27
|
+
removeListener(type, id) {
|
|
28
|
+
const typeList = this.listeners[type];
|
|
29
|
+
if (typeList) {
|
|
30
|
+
delete typeList[id];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
onMessage(evt) {
|
|
34
|
+
if (this.isDestroyed) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const eventData = JSON.parse(evt.data);
|
|
39
|
+
const data = cswEventsSchema.parse(eventData);
|
|
40
|
+
if (data.type === 'init') {
|
|
41
|
+
this.processMessage(data.data, true);
|
|
42
|
+
return;
|
|
103
43
|
}
|
|
104
|
-
|
|
44
|
+
this.processMessage(data, false);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('Error parsing event data:', evt.data, error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
processMessage(event, isInit) {
|
|
51
|
+
const listeners = this.listeners[event.type];
|
|
52
|
+
const list = Object.values(listeners ?? {});
|
|
53
|
+
list.forEach((listener) => listener(event, isInit));
|
|
54
|
+
}
|
|
55
|
+
destroy() {
|
|
56
|
+
this.isDestroyed = true;
|
|
57
|
+
if (this.ws) {
|
|
58
|
+
this.ws.destroy();
|
|
59
|
+
this.ws = null;
|
|
60
|
+
}
|
|
61
|
+
this.listeners = {};
|
|
105
62
|
}
|
|
106
63
|
}
|
|
107
|
-
exports.CamSwitcherEvents = CamSwitcherEvents;
|