@apocaliss92/scrypted-reolink-native 0.2.6 → 0.2.8
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/dist/main.nodejs.js +1 -1
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/camera.ts +55 -12
- package/src/multiFocal.ts +8 -0
- package/src/stream-utils.ts +56 -24
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/camera.ts
CHANGED
|
@@ -207,6 +207,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
207
207
|
// Basic connection settings
|
|
208
208
|
ipAddress: {
|
|
209
209
|
title: 'IP Address',
|
|
210
|
+
hide: true,
|
|
210
211
|
type: 'string',
|
|
211
212
|
onPut: async () => {
|
|
212
213
|
await this.credentialsChanged();
|
|
@@ -214,6 +215,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
214
215
|
},
|
|
215
216
|
username: {
|
|
216
217
|
type: 'string',
|
|
218
|
+
hide: true,
|
|
217
219
|
title: 'Username',
|
|
218
220
|
onPut: async () => {
|
|
219
221
|
await this.credentialsChanged();
|
|
@@ -221,6 +223,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
221
223
|
},
|
|
222
224
|
password: {
|
|
223
225
|
type: 'password',
|
|
226
|
+
hide: true,
|
|
224
227
|
title: 'Password',
|
|
225
228
|
onPut: async () => {
|
|
226
229
|
await this.credentialsChanged();
|
|
@@ -1475,10 +1478,12 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1475
1478
|
}
|
|
1476
1479
|
|
|
1477
1480
|
// Case 4: Standalone camera -> create its own socket using base class method
|
|
1478
|
-
// For battery cameras,
|
|
1479
|
-
//
|
|
1480
|
-
//
|
|
1481
|
-
|
|
1481
|
+
// For battery (BCUDP) cameras, streaming must be keyed by streamKey.
|
|
1482
|
+
// Do NOT reuse ensureClient(): composite needs two concurrent streams, and single-lens streams
|
|
1483
|
+
// should reuse the same API that composite already created for that same streamKey.
|
|
1484
|
+
if (this.isBattery) {
|
|
1485
|
+
return await super.createStreamClient(streamKey);
|
|
1486
|
+
}
|
|
1482
1487
|
|
|
1483
1488
|
// For TCP standalone cameras, use base class createStreamClient which manages stream clients per streamKey
|
|
1484
1489
|
return await super.createStreamClient(streamKey);
|
|
@@ -1543,9 +1548,37 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1543
1548
|
const wider = this.isOnNvr ? rtspChannel : undefined;
|
|
1544
1549
|
const tele = this.isOnNvr ? rtspChannel : undefined;
|
|
1545
1550
|
|
|
1551
|
+
// On standalone TrackMix/Duo, lens channels are often separate, but they are not always 0/1.
|
|
1552
|
+
// Prefer using the discovered multifocalInfo mapping when available.
|
|
1553
|
+
let derivedWider: number | undefined = wider;
|
|
1554
|
+
let derivedTele: number | undefined = tele;
|
|
1555
|
+
if (!this.isOnNvr) {
|
|
1556
|
+
try {
|
|
1557
|
+
const info: any = this.storageSettings.values.multifocalInfo;
|
|
1558
|
+
const channels: any[] = Array.isArray(info?.channels) ? info.channels : [];
|
|
1559
|
+
|
|
1560
|
+
const wideCh = channels.find((c) => c?.lensType === 'wide')?.channel
|
|
1561
|
+
?? channels.find((c) => c?.variantType === 'default')?.channel;
|
|
1562
|
+
const teleCh = channels.find((c) => c?.lensType === 'telephoto')?.channel
|
|
1563
|
+
?? channels.find((c) => c?.variantType === 'telephoto')?.channel;
|
|
1564
|
+
|
|
1565
|
+
if (Number.isFinite(wideCh)) derivedWider = wideCh;
|
|
1566
|
+
if (Number.isFinite(teleCh)) derivedTele = teleCh;
|
|
1567
|
+
|
|
1568
|
+
// Avoid setting nonsense; leave undefined to fall back to library defaults.
|
|
1569
|
+
if (derivedWider === derivedTele) {
|
|
1570
|
+
// Keep undefined behavior (defaults inside the library) unless we are on NVR.
|
|
1571
|
+
derivedWider = undefined;
|
|
1572
|
+
derivedTele = undefined;
|
|
1573
|
+
}
|
|
1574
|
+
} catch {
|
|
1575
|
+
// ignore and fall back to defaults
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1546
1579
|
baseOptions.compositeOptions = {
|
|
1547
|
-
widerChannel:
|
|
1548
|
-
teleChannel:
|
|
1580
|
+
widerChannel: derivedWider,
|
|
1581
|
+
teleChannel: derivedTele,
|
|
1549
1582
|
pipPosition,
|
|
1550
1583
|
pipSize,
|
|
1551
1584
|
pipMargin,
|
|
@@ -2538,11 +2571,20 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2538
2571
|
// Extract variant from stream ID or URL if present (e.g., "autotrack" from "native_autotrack_main" or "?variant=autotrack")
|
|
2539
2572
|
let variant = extractVariantFromStreamId(selected.id, selected.url);
|
|
2540
2573
|
|
|
2541
|
-
// Fallback: if no variant found in stream ID/URL, use variantType from device settings
|
|
2542
|
-
//
|
|
2574
|
+
// Fallback: if no variant found in stream ID/URL, use variantType from device settings.
|
|
2575
|
+
// IMPORTANT:
|
|
2576
|
+
// - On NVR/Hub multifocal setups, the tele lens is often selected via a variant (autotrack/telephoto) on the same channel.
|
|
2577
|
+
// - On standalone TrackMix (no NVR), the tele lens is selected via channel=1 (no variant).
|
|
2578
|
+
// Forcing a variant on standalone can result in a started stream with no frames.
|
|
2543
2579
|
if (!variant && this.storageSettings.values.variantType && this.storageSettings.values.variantType !== 'default') {
|
|
2544
|
-
|
|
2545
|
-
|
|
2580
|
+
if (this.isOnNvr) {
|
|
2581
|
+
variant = this.storageSettings.values.variantType as 'autotrack' | 'telephoto';
|
|
2582
|
+
logger.log(`Using variant from device settings: '${variant}' (not found in stream ID/URL)`);
|
|
2583
|
+
} else {
|
|
2584
|
+
logger.log(
|
|
2585
|
+
`Ignoring device variantType '${this.storageSettings.values.variantType}' for standalone stream (channel-based lens selection)`
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2546
2588
|
}
|
|
2547
2589
|
|
|
2548
2590
|
logger.log(`Stream selection: id='${selected.id}', profile='${profile}', channel=${channel}, variant='${variant || 'default'}'`);
|
|
@@ -2725,8 +2767,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2725
2767
|
this.storageSettings.settings.pipSize.hide = !this.isMultiFocal;
|
|
2726
2768
|
this.storageSettings.settings.pipMargin.hide = !this.isMultiFocal;
|
|
2727
2769
|
|
|
2728
|
-
|
|
2729
|
-
this.storageSettings.settings.
|
|
2770
|
+
const hideUid = !this.isBattery || this.isOnNvr || !!this.multiFocalDevice
|
|
2771
|
+
this.storageSettings.settings.uid.hide = hideUid;
|
|
2772
|
+
this.storageSettings.settings.discoveryMethod.hide = hideUid;
|
|
2730
2773
|
|
|
2731
2774
|
if (this.isBattery && !this.storageSettings.values.mixinsSetup) {
|
|
2732
2775
|
try {
|
package/src/multiFocal.ts
CHANGED
|
@@ -224,6 +224,14 @@ export class ReolinkNativeMultiFocalDevice extends ReolinkCamera implements Sett
|
|
|
224
224
|
return await this.nvrDevice.createStreamClient(streamKey);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
// For multifocal battery cams (BCUDP), reuse the main client to avoid D2C_DISC storms.
|
|
228
|
+
if (this.isBattery) {
|
|
229
|
+
// For battery (BCUDP) cameras, streaming must be keyed by streamKey.
|
|
230
|
+
// Do NOT reuse ensureClient(): composite needs two concurrent streams, and single-lens streams
|
|
231
|
+
// should reuse the same API that composite already created for that same streamKey.
|
|
232
|
+
return await super.createStreamClient(streamKey);
|
|
233
|
+
}
|
|
234
|
+
|
|
227
235
|
// Otherwise, use base class createStreamClient which manages stream clients per streamKey
|
|
228
236
|
return await super.createStreamClient(streamKey);
|
|
229
237
|
}
|
package/src/stream-utils.ts
CHANGED
|
@@ -337,25 +337,60 @@ export class StreamManager {
|
|
|
337
337
|
|
|
338
338
|
const isComposite = options.channel === undefined;
|
|
339
339
|
|
|
340
|
-
// For composite streams,
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
340
|
+
// For composite streams, we may want two distinct Baichuan sessions (wider + tele)
|
|
341
|
+
// to avoid frame mixing on some firmwares. On BCUDP/battery devices, extra sessions
|
|
342
|
+
// can be harmful; in that case, createStreamClient may return the same underlying client.
|
|
343
|
+
//
|
|
344
|
+
// IMPORTANT: Use the same per-lens streamKey format as regular streams so that later
|
|
345
|
+
// requests for a single lens can reuse these same cached APIs.
|
|
346
|
+
const compositeWiderChannel = options.compositeOptions?.widerChannel ?? 0;
|
|
347
|
+
const compositeTeleChannel = options.compositeOptions?.teleChannel ?? 1;
|
|
348
|
+
const compositeTeleIsVariantOnSameChannel =
|
|
349
|
+
Boolean(options.compositeOptions?.onNvr) || compositeTeleChannel === compositeWiderChannel;
|
|
350
|
+
|
|
351
|
+
const compositeWiderStreamKey = `${compositeWiderChannel}_${profile}`;
|
|
352
|
+
const compositeTeleVariant = compositeTeleIsVariantOnSameChannel
|
|
353
|
+
? (options.variant && options.variant !== 'default' ? options.variant : 'telephoto')
|
|
350
354
|
: undefined;
|
|
355
|
+
const compositeTeleStreamKey = compositeTeleVariant
|
|
356
|
+
? `${compositeTeleChannel}_${compositeTeleVariant}_${profile}`
|
|
357
|
+
: `${compositeTeleChannel}_${profile}`;
|
|
358
|
+
|
|
359
|
+
// For composite streams, using two distinct Baichuan sessions can avoid frame mixing on some firmwares.
|
|
360
|
+
// However, for UDP/battery devices extra BCUDP sessions can trigger storms; if we detect the same
|
|
361
|
+
// underlying client, fall back to single-session composite.
|
|
362
|
+
let compositeApis:
|
|
363
|
+
| {
|
|
364
|
+
widerApi: ReolinkBaichuanApi;
|
|
365
|
+
teleApi: ReolinkBaichuanApi;
|
|
366
|
+
}
|
|
367
|
+
| undefined;
|
|
368
|
+
if (isComposite) {
|
|
369
|
+
try {
|
|
370
|
+
const widerApi = await this.opts.createStreamClient(compositeWiderStreamKey);
|
|
371
|
+
const teleApi = await this.opts.createStreamClient(compositeTeleStreamKey);
|
|
372
|
+
|
|
373
|
+
const sameApiObject = widerApi === teleApi;
|
|
374
|
+
const sameUnderlyingClient = (widerApi as any)?.client && (teleApi as any)?.client
|
|
375
|
+
? (widerApi as any).client === (teleApi as any).client
|
|
376
|
+
: false;
|
|
377
|
+
|
|
378
|
+
if (!sameApiObject && !sameUnderlyingClient) {
|
|
379
|
+
compositeApis = { widerApi, teleApi };
|
|
380
|
+
} else {
|
|
381
|
+
// Likely a shared/battery connection: avoid forcing multi-session behavior.
|
|
382
|
+
compositeApis = undefined;
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
// Best-effort: if creating dedicated sessions fails, fall back to single-session composite.
|
|
386
|
+
compositeApis = undefined;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
351
389
|
|
|
352
|
-
// For non-composite streams, create a single API client
|
|
353
|
-
// For composite streams, api
|
|
354
|
-
// Pass streamKey to createStreamClient - it contains all necessary information (profile, variantType, channel)
|
|
355
|
-
// For composite streams, streamKey format: composite_${variantType}_${profile}
|
|
356
|
-
// For regular streams, streamKey format: channel_${channel}_${profile}_${variantType} or similar
|
|
390
|
+
// For non-composite streams, create a single API client.
|
|
391
|
+
// For composite streams, base api must be a real lens streamKey (not the composite RFC key).
|
|
357
392
|
const api = isComposite
|
|
358
|
-
? compositeApis
|
|
393
|
+
? (compositeApis?.widerApi ?? await this.opts.createStreamClient(compositeWiderStreamKey))
|
|
359
394
|
: await this.opts.createStreamClient(streamKey);
|
|
360
395
|
|
|
361
396
|
const { createRfc4571TcpServer } = await import('@apocaliss92/reolink-baichuan-js');
|
|
@@ -364,17 +399,14 @@ export class StreamManager {
|
|
|
364
399
|
|
|
365
400
|
// If connection is shared, don't close it when stream teardown happens
|
|
366
401
|
// For composite, we create dedicated APIs even if the device uses a shared main connection.
|
|
367
|
-
//
|
|
368
|
-
const closeApiOnTeardown = isComposite
|
|
402
|
+
// On battery/BCUDP (sharedConnection=true), prefer keeping them alive to avoid reconnect storms.
|
|
403
|
+
const closeApiOnTeardown = isComposite
|
|
404
|
+
? (Boolean(compositeApis) && !(this.opts.sharedConnection ?? false))
|
|
405
|
+
: !(this.opts.sharedConnection ?? false);
|
|
369
406
|
|
|
370
407
|
let created: any;
|
|
371
408
|
try {
|
|
372
|
-
const compositeOptions = isComposite
|
|
373
|
-
? {
|
|
374
|
-
...(options.compositeOptions ?? {}),
|
|
375
|
-
forceH264: true,
|
|
376
|
-
}
|
|
377
|
-
: undefined;
|
|
409
|
+
const compositeOptions = isComposite ? options.compositeOptions : undefined;
|
|
378
410
|
|
|
379
411
|
created = await createRfc4571TcpServer({
|
|
380
412
|
api,
|