@apocaliss92/scrypted-reolink-native 0.1.19 → 0.1.21
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/.vscode/settings.json +1 -1
- package/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +3 -2
- package/src/camera.ts +0 -6
- package/src/common.ts +45 -91
- package/src/connect.ts +1 -199
- package/src/main.ts +9 -16
- package/src/multifocal.ts +66 -71
- package/src/nvr.ts +39 -12
- package/src/stream-utils.ts +0 -141
- package/src/utils.ts +19 -0
package/src/multifocal.ts
CHANGED
|
@@ -41,12 +41,12 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
|
|
|
41
41
|
},
|
|
42
42
|
diagnosticsRun: {
|
|
43
43
|
subgroup: 'Diagnostics',
|
|
44
|
-
title: 'Run
|
|
45
|
-
description: 'Collect
|
|
44
|
+
title: 'Run Diagnostics',
|
|
45
|
+
description: 'Collect diagnostics and display results in logs.',
|
|
46
46
|
type: 'button',
|
|
47
47
|
immediate: true,
|
|
48
48
|
onPut: async () => {
|
|
49
|
-
await this.
|
|
49
|
+
await this.runDiagnostics();
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
52
|
});
|
|
@@ -201,64 +201,70 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
|
|
|
201
201
|
const logger = this.getBaichuanLogger();
|
|
202
202
|
|
|
203
203
|
try {
|
|
204
|
-
const channelsInfo = await api.getNvrChannelsInfo();
|
|
205
|
-
const deviceInfo = await api.getInfo();
|
|
204
|
+
// const channelsInfo = await api.getNvrChannelsInfo();
|
|
205
|
+
// const deviceInfo = await api.getInfo();
|
|
206
206
|
const { support } = await api.getDeviceCapabilities();
|
|
207
207
|
const channelNum = support?.channelNum ?? 1;
|
|
208
208
|
logger.log(`Sync entities from remote for ${channelNum} channels`);
|
|
209
209
|
const channels = Array.from({ length: channelNum }, (_, i) => i + 1);
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
// const isBattery = !!(abilities?.battery?.ver ?? 0);
|
|
218
|
-
|
|
219
|
-
// const nativeId = this.buildNativeId(channel, uid, isBattery);
|
|
220
|
-
// const interfaces = [ScryptedInterface.VideoCamera];
|
|
221
|
-
// if (isBattery) {
|
|
222
|
-
// interfaces.push(ScryptedInterface.Battery);
|
|
223
|
-
// }
|
|
224
|
-
// const type = abilities.supportDoorbellLight ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
|
|
225
|
-
|
|
226
|
-
// const device: Device = {
|
|
227
|
-
// nativeId,
|
|
228
|
-
// name,
|
|
229
|
-
// providerNativeId: this.nativeId,
|
|
230
|
-
// interfaces,
|
|
231
|
-
// type,
|
|
232
|
-
// info: {
|
|
233
|
-
// manufacturer: 'Reolink',
|
|
234
|
-
// model: channelInfo?.typeInfo,
|
|
235
|
-
// serialNumber: uid,
|
|
236
|
-
// }
|
|
237
|
-
// };
|
|
238
|
-
|
|
239
|
-
// this.channelToNativeIdMap.set(channel, nativeId);
|
|
240
|
-
|
|
241
|
-
// if (sdk.deviceManager.getNativeIds().includes(nativeId)) {
|
|
242
|
-
// continue;
|
|
243
|
-
// }
|
|
244
|
-
|
|
245
|
-
// if (this.discoveredDevices.has(nativeId)) {
|
|
246
|
-
// continue;
|
|
247
|
-
// }
|
|
248
|
-
|
|
249
|
-
// this.discoveredDevices.set(nativeId, {
|
|
250
|
-
// device,
|
|
251
|
-
// description: `${name} (Channel ${channel})`,
|
|
252
|
-
// rtspChannel: channel,
|
|
253
|
-
// deviceData: devicesData[channel],
|
|
254
|
-
// });
|
|
255
|
-
|
|
256
|
-
// logger.debug(`Discovered channel ${channel}: ${name}`);
|
|
257
|
-
// } catch (e: any) {
|
|
258
|
-
// logger.debug(`Error processing channel ${channel}: ${e?.message || String(e)}`);
|
|
259
|
-
// }
|
|
211
|
+
const multifocalInfo = await api.getDualLensChannelInfo();
|
|
212
|
+
|
|
213
|
+
logger.log(`Multichannel info: ${JSON.stringify(multifocalInfo)}`);
|
|
214
|
+
|
|
215
|
+
// if (channelNum === 2) {
|
|
216
|
+
|
|
260
217
|
// }
|
|
261
218
|
|
|
219
|
+
for (const channel of channels) {
|
|
220
|
+
// try {
|
|
221
|
+
// const name = deviceInfo?.name || `Channel ${channel}`;
|
|
222
|
+
// const uid = deviceInfo?.uid;
|
|
223
|
+
// const isBattery = !!(abilities?.battery?.ver ?? 0);
|
|
224
|
+
|
|
225
|
+
// const nativeId = this.buildNativeId(channel, uid, isBattery);
|
|
226
|
+
// const interfaces = [ScryptedInterface.VideoCamera];
|
|
227
|
+
// if (isBattery) {
|
|
228
|
+
// interfaces.push(ScryptedInterface.Battery);
|
|
229
|
+
// }
|
|
230
|
+
// const type = abilities.supportDoorbellLight ? ScryptedDeviceType.Doorbell : ScryptedDeviceType.Camera;
|
|
231
|
+
|
|
232
|
+
// const device: Device = {
|
|
233
|
+
// nativeId,
|
|
234
|
+
// name,
|
|
235
|
+
// providerNativeId: this.nativeId,
|
|
236
|
+
// interfaces,
|
|
237
|
+
// type,
|
|
238
|
+
// info: {
|
|
239
|
+
// manufacturer: 'Reolink',
|
|
240
|
+
// model: channelInfo?.typeInfo,
|
|
241
|
+
// serialNumber: uid,
|
|
242
|
+
// }
|
|
243
|
+
// };
|
|
244
|
+
|
|
245
|
+
// this.channelToNativeIdMap.set(channel, nativeId);
|
|
246
|
+
|
|
247
|
+
// if (sdk.deviceManager.getNativeIds().includes(nativeId)) {
|
|
248
|
+
// continue;
|
|
249
|
+
// }
|
|
250
|
+
|
|
251
|
+
// if (this.discoveredDevices.has(nativeId)) {
|
|
252
|
+
// continue;
|
|
253
|
+
// }
|
|
254
|
+
|
|
255
|
+
// this.discoveredDevices.set(nativeId, {
|
|
256
|
+
// device,
|
|
257
|
+
// description: `${name} (Channel ${channel})`,
|
|
258
|
+
// rtspChannel: channel,
|
|
259
|
+
// deviceData: devicesData[channel],
|
|
260
|
+
// });
|
|
261
|
+
|
|
262
|
+
// logger.debug(`Discovered channel ${channel}: ${name}`);
|
|
263
|
+
// } catch (e: any) {
|
|
264
|
+
// logger.debug(`Error processing channel ${channel}: ${e?.message || String(e)}`);
|
|
265
|
+
// }
|
|
266
|
+
}
|
|
267
|
+
|
|
262
268
|
// logger.log(`Channel discovery completed. ${JSON.stringify({ devicesData, channels })}`);
|
|
263
269
|
} catch (e) {
|
|
264
270
|
logger.error('Failed to sync entities from remote', e);
|
|
@@ -391,7 +397,7 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
|
|
|
391
397
|
|
|
392
398
|
const nativeId = this.channelToNativeIdMap.get(channel);
|
|
393
399
|
if (!nativeId) {
|
|
394
|
-
logger.
|
|
400
|
+
logger.debug(`No camera found for channel ${channel}, ignoring event`);
|
|
395
401
|
return;
|
|
396
402
|
}
|
|
397
403
|
|
|
@@ -413,9 +419,9 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
|
|
|
413
419
|
await this.subscribeToEvents();
|
|
414
420
|
}
|
|
415
421
|
|
|
416
|
-
private async
|
|
422
|
+
private async runDiagnostics(): Promise<void> {
|
|
417
423
|
const logger = this.getBaichuanLogger();
|
|
418
|
-
logger.log(`Starting
|
|
424
|
+
logger.log(`Starting Multifocal diagnostics...`);
|
|
419
425
|
|
|
420
426
|
try {
|
|
421
427
|
const { ipAddress, username, password } = this.storageSettings.values;
|
|
@@ -423,23 +429,12 @@ export class ReolinkNativeMultiFocalDevice extends BaseBaichuanClass implements
|
|
|
423
429
|
throw new Error('Missing device credentials');
|
|
424
430
|
}
|
|
425
431
|
|
|
426
|
-
const
|
|
427
|
-
const cgiApi = new ReolinkCgiApi({
|
|
428
|
-
host: ipAddress,
|
|
429
|
-
username,
|
|
430
|
-
password,
|
|
431
|
-
});
|
|
432
|
+
const api = await this.ensureBaichuanClient();
|
|
432
433
|
|
|
433
|
-
await
|
|
434
|
-
|
|
435
|
-
const diagnostics = await cgiApi.collectNvrDiagnostics({
|
|
436
|
-
logger: this.console,
|
|
437
|
-
});
|
|
434
|
+
const multifocalDiagnostics = await api.collectMultifocalDiagnostics(logger);
|
|
438
435
|
|
|
439
436
|
logger.log(`NVR diagnostics completed successfully.`);
|
|
440
|
-
|
|
441
|
-
// Print diagnostics to console
|
|
442
|
-
cgiApi.printNvrDiagnostics(diagnostics, this.console);
|
|
437
|
+
logger.log(JSON.stringify(multifocalDiagnostics));
|
|
443
438
|
} catch (e) {
|
|
444
439
|
logger.error('Failed to run NVR diagnostics', e);
|
|
445
440
|
throw e;
|
package/src/nvr.ts
CHANGED
|
@@ -208,7 +208,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
208
208
|
const targetCamera = nativeId ? this.cameraNativeMap.get(nativeId) : undefined;
|
|
209
209
|
|
|
210
210
|
if (!targetCamera) {
|
|
211
|
-
logger.
|
|
211
|
+
logger.debug(`No camera found for channel ${channel}, ignoring event`);
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -262,18 +262,13 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
async subscribeToAllEvents(): Promise<void> {
|
|
265
|
-
const logger = this.getBaichuanLogger();
|
|
266
265
|
const eventSource = this.storageSettings.values.eventSource || 'Native';
|
|
267
266
|
|
|
268
|
-
// Only subscribe if Native is selected
|
|
269
267
|
if (eventSource !== 'Native') {
|
|
270
268
|
await this.unsubscribeFromAllEvents();
|
|
271
|
-
|
|
269
|
+
} else {
|
|
270
|
+
await super.subscribeToEvents();
|
|
272
271
|
}
|
|
273
|
-
|
|
274
|
-
// Use base class implementation
|
|
275
|
-
await super.subscribeToEvents();
|
|
276
|
-
logger.log('Subscribed to all events for NVR cameras');
|
|
277
272
|
}
|
|
278
273
|
|
|
279
274
|
private async runNvrDiagnostics(): Promise<void> {
|
|
@@ -288,7 +283,7 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
288
283
|
});
|
|
289
284
|
|
|
290
285
|
logger.log(`NVR diagnostics completed successfully.`);
|
|
291
|
-
|
|
286
|
+
|
|
292
287
|
cgiApi.printNvrDiagnostics(diagnostics, this.console);
|
|
293
288
|
} catch (e) {
|
|
294
289
|
logger.error('Failed to run NVR diagnostics', e);
|
|
@@ -340,8 +335,12 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
340
335
|
}
|
|
341
336
|
|
|
342
337
|
async init() {
|
|
343
|
-
const api = await this.ensureClient();
|
|
344
338
|
const logger = this.getBaichuanLogger();
|
|
339
|
+
|
|
340
|
+
// Ensure both APIs are ready before proceeding
|
|
341
|
+
const api = await this.ensureClient();
|
|
342
|
+
await this.ensureBaichuanClient();
|
|
343
|
+
|
|
345
344
|
await this.updateDeviceInfo();
|
|
346
345
|
|
|
347
346
|
// Initialize event subscriptions based on selected source
|
|
@@ -473,12 +472,40 @@ export class ReolinkNativeNvrDevice extends BaseBaichuanClass implements Setting
|
|
|
473
472
|
}
|
|
474
473
|
|
|
475
474
|
async syncEntitiesFromRemote() {
|
|
476
|
-
const api = await this.ensureClient();
|
|
477
475
|
const logger = this.getBaichuanLogger();
|
|
476
|
+
|
|
477
|
+
// Ensure both APIs are ready before syncing
|
|
478
|
+
const api = await this.ensureClient();
|
|
479
|
+
const baichuanApi = await this.ensureBaichuanClient();
|
|
480
|
+
|
|
481
|
+
// Wait for Baichuan connection to be fully established
|
|
482
|
+
if (baichuanApi?.client) {
|
|
483
|
+
// Check if already connected
|
|
484
|
+
if (!baichuanApi.client.isSocketConnected()) {
|
|
485
|
+
logger.debug('Waiting for Baichuan connection to be established...');
|
|
486
|
+
// Wait up to 5 seconds for connection
|
|
487
|
+
let attempts = 0;
|
|
488
|
+
while (!baichuanApi.client.isSocketConnected() && attempts < 50) {
|
|
489
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
490
|
+
attempts++;
|
|
491
|
+
}
|
|
492
|
+
if (!baichuanApi.client.isSocketConnected()) {
|
|
493
|
+
logger.warn('Baichuan connection not established after waiting, proceeding anyway');
|
|
494
|
+
} else {
|
|
495
|
+
logger.debug('Baichuan connection established');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
478
499
|
|
|
479
500
|
const { devicesData, channels } = await api.getDevicesInfo();
|
|
501
|
+
|
|
502
|
+
if (!channels.length) {
|
|
503
|
+
logger.debug(`No channels found, ${JSON.stringify({ devicesData, channels })}`);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
480
507
|
logger.log(`Sync entities from remote for ${channels.length} channels`);
|
|
481
|
-
|
|
508
|
+
|
|
482
509
|
for (const channel of channels) {
|
|
483
510
|
try {
|
|
484
511
|
const { channelStatus, channelInfo, abilities } = devicesData[channel];
|
package/src/stream-utils.ts
CHANGED
|
@@ -10,9 +10,7 @@ import sdk, {
|
|
|
10
10
|
type RequestMediaStreamOptions,
|
|
11
11
|
type ResponseMediaStreamOptions,
|
|
12
12
|
} from "@scrypted/sdk";
|
|
13
|
-
|
|
14
13
|
import type { UrlMediaStreamOptions } from "../../scrypted/plugins/rtsp/src/rtsp";
|
|
15
|
-
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
16
14
|
|
|
17
15
|
export interface StreamManagerOptions {
|
|
18
16
|
/**
|
|
@@ -60,145 +58,6 @@ export function parseStreamProfileFromId(id: string | undefined): StreamProfile
|
|
|
60
58
|
return;
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
/**
|
|
64
|
-
* Check if a stream ID represents a native Baichuan stream (prefixed with "native_")
|
|
65
|
-
*/
|
|
66
|
-
export function isNativeStreamId(id: string | undefined): boolean {
|
|
67
|
-
return id?.startsWith('native_') ?? false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function fetchVideoStreamOptionsFromApi(
|
|
71
|
-
client: ReolinkBaichuanApi,
|
|
72
|
-
channel: number,
|
|
73
|
-
logger: Console,
|
|
74
|
-
): Promise<UrlMediaStreamOptions[]> {
|
|
75
|
-
const streamMetadata = await client.getStreamMetadata(channel);
|
|
76
|
-
|
|
77
|
-
const streams: UrlMediaStreamOptions[] = [];
|
|
78
|
-
const list = streamMetadata?.streams || [];
|
|
79
|
-
|
|
80
|
-
for (const stream of list) {
|
|
81
|
-
const profile = stream.profile as StreamProfile;
|
|
82
|
-
const codec = String(stream.videoEncType || '').includes('264')
|
|
83
|
-
? 'h264'
|
|
84
|
-
: String(stream.videoEncType || '').includes('265')
|
|
85
|
-
? 'h265'
|
|
86
|
-
: String(stream.videoEncType || '').toLowerCase();
|
|
87
|
-
|
|
88
|
-
streams.push({
|
|
89
|
-
name: `Native ${profile}`,
|
|
90
|
-
id: `native_${profile}`,
|
|
91
|
-
container: 'rtp',
|
|
92
|
-
video: { codec, width: stream.width, height: stream.height },
|
|
93
|
-
url: ``,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return streams;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function buildVideoStreamOptions(
|
|
101
|
-
props: {
|
|
102
|
-
client: ReolinkBaichuanApi,
|
|
103
|
-
ipAddress: string,
|
|
104
|
-
cachedNetPort: { rtsp?: { port?: number; enable?: number }; rtmp?: { port?: number; enable?: number } },
|
|
105
|
-
nvrDevice?: ReolinkNativeNvrDevice,
|
|
106
|
-
rtspChannel: number,
|
|
107
|
-
logger: Console
|
|
108
|
-
},
|
|
109
|
-
): Promise<UrlMediaStreamOptions[]> {
|
|
110
|
-
const { client, ipAddress, cachedNetPort, rtspChannel, logger, nvrDevice } = props;
|
|
111
|
-
const rtspStreams: UrlMediaStreamOptions[] = [];
|
|
112
|
-
const rtmpStreams: UrlMediaStreamOptions[] = [];
|
|
113
|
-
|
|
114
|
-
// Use cached net port if provided, otherwise fetch it
|
|
115
|
-
const netPort = cachedNetPort || await client.getNetPort();
|
|
116
|
-
const rtspEnabled = netPort.rtsp?.enable === 1;
|
|
117
|
-
const rtmpEnabled = netPort.rtmp?.enable === 1;
|
|
118
|
-
const rtspPort = netPort.rtsp?.port ?? 554;
|
|
119
|
-
const rtmpPort = netPort.rtmp?.port ?? 1935;
|
|
120
|
-
|
|
121
|
-
// Get stream metadata to build options
|
|
122
|
-
const streamMetadata = await client.getStreamMetadata(rtspChannel);
|
|
123
|
-
const list = streamMetadata?.streams || [];
|
|
124
|
-
|
|
125
|
-
for (const stream of list) {
|
|
126
|
-
const profile = stream.profile as StreamProfile;
|
|
127
|
-
const codec = String(stream.videoEncType || '').includes('264')
|
|
128
|
-
? 'h264'
|
|
129
|
-
: String(stream.videoEncType || '').includes('265')
|
|
130
|
-
? 'h265'
|
|
131
|
-
: String(stream.videoEncType || '').toLowerCase();
|
|
132
|
-
|
|
133
|
-
// Build RTSP URL if enabled (RTSP doesn't support ext stream, only main and sub)
|
|
134
|
-
if (rtspEnabled && profile !== 'ext') {
|
|
135
|
-
// RTSP format: rtsp://ip:port/h264Preview_XX_profile
|
|
136
|
-
// XX is 1-based channel with 2-digit padding
|
|
137
|
-
const channelStr = String(rtspChannel + 1).padStart(2, '0');
|
|
138
|
-
const profileStr = profile === 'main' ? 'main' : 'sub';
|
|
139
|
-
const rtspPath = `/h264Preview_${channelStr}_${profileStr}`;
|
|
140
|
-
const rtspId = `h264Preview_${channelStr}_${profileStr}`;
|
|
141
|
-
|
|
142
|
-
rtspStreams.push({
|
|
143
|
-
name: `RTSP ${rtspId}`,
|
|
144
|
-
id: rtspId,
|
|
145
|
-
container: 'rtsp',
|
|
146
|
-
video: { codec, width: stream.width, height: stream.height },
|
|
147
|
-
url: `rtsp://${ipAddress}:${rtspPort}${rtspPath}`,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Build RTMP URL if enabled (RTMP supports main, sub, and ext streams)
|
|
152
|
-
if (rtmpEnabled) {
|
|
153
|
-
// RTMP format: /bcs/channelX_stream.bcs?channel=X&stream=stream_type&user=username&password=password
|
|
154
|
-
// Based on reolink_aio api.py line 3295-3298:
|
|
155
|
-
// - stream in path is "main", "sub", or "ext" (not "main.bcs")
|
|
156
|
-
// - stream_type in query: 0 for main/ext, 1 for sub
|
|
157
|
-
// - credentials: user and password as query parameters
|
|
158
|
-
const streamName = profile === 'main' ? 'main' : profile === 'sub' ? 'sub' : 'ext';
|
|
159
|
-
const streamType = profile === 'sub' ? 1 : 0; // 0 for main/ext, 1 for sub
|
|
160
|
-
const rtmpId = `${streamName}.bcs`; // ID for Scrypted (main.bcs, sub.bcs, ext.bcs)
|
|
161
|
-
|
|
162
|
-
// Use channel directly (0-based) in path, matching reolink_aio behavior
|
|
163
|
-
const rtmpPath = `/bcs/channel${rtspChannel}_${streamName}.bcs`;
|
|
164
|
-
const rtmpUrl = new URL(`rtmp://${ipAddress}:${rtmpPort}${rtmpPath}`);
|
|
165
|
-
const params = rtmpUrl.searchParams;
|
|
166
|
-
params.set('channel', rtspChannel.toString());
|
|
167
|
-
params.set('stream', streamType.toString());
|
|
168
|
-
// Credentials will be added by addRtspCredentials as user/password query params
|
|
169
|
-
|
|
170
|
-
rtmpStreams.push({
|
|
171
|
-
name: `RTMP ${rtmpId}`,
|
|
172
|
-
id: rtmpId,
|
|
173
|
-
container: 'rtmp',
|
|
174
|
-
video: { codec, width: stream.width, height: stream.height },
|
|
175
|
-
url: rtmpUrl.toString(),
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const nativeStreams = await fetchVideoStreamOptionsFromApi(client, rtspChannel, logger);
|
|
182
|
-
|
|
183
|
-
let streams: UrlMediaStreamOptions[] = [];
|
|
184
|
-
|
|
185
|
-
if (nvrDevice && nvrDevice.info.model === 'HOMEHUB') {
|
|
186
|
-
streams = [
|
|
187
|
-
...nativeStreams,
|
|
188
|
-
...rtspStreams,
|
|
189
|
-
...rtmpStreams,
|
|
190
|
-
];
|
|
191
|
-
} else {
|
|
192
|
-
streams = [
|
|
193
|
-
...rtspStreams,
|
|
194
|
-
...rtmpStreams,
|
|
195
|
-
...nativeStreams,
|
|
196
|
-
];
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return streams;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
61
|
export function selectStreamOption(
|
|
203
62
|
vsos: UrlMediaStreamOptions[] | undefined,
|
|
204
63
|
request: RequestMediaStreamOptions,
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import type { DeviceCapabilities, ReolinkDeviceInfo } from "@apocaliss92/reolink-baichuan-js" with { "resolution-mode": "import" };
|
|
2
2
|
import { DeviceBase, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Enumeration of operation types that may require specific channel assignments
|
|
6
|
+
*/
|
|
7
|
+
export enum OperationChannelType {
|
|
8
|
+
PAN = 'pan',
|
|
9
|
+
TILT = 'tilt',
|
|
10
|
+
ZOOM = 'zoom',
|
|
11
|
+
INTERCOM = 'intercom',
|
|
12
|
+
GOTO = 'goto',
|
|
13
|
+
PRESET = 'preset',
|
|
14
|
+
PATROL = 'patrol',
|
|
15
|
+
TRACK = 'track',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Type for channel-specific operation mappings
|
|
20
|
+
*/
|
|
21
|
+
export type OperationChannelMap = Partial<Record<OperationChannelType, number>>;
|
|
22
|
+
|
|
4
23
|
export const nvrSuffix = `-nvr`;
|
|
5
24
|
export const batteryCameraSuffix = `-battery-cam`;
|
|
6
25
|
export const multifocalSuffix = `-multifocal`;
|