@apocaliss92/scrypted-reolink-native 0.2.7 → 0.2.9
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 +66 -9
- 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
|
@@ -618,15 +618,38 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
618
618
|
},
|
|
619
619
|
pipMargin: {
|
|
620
620
|
title: 'PIP Margin',
|
|
621
|
-
description: 'Margin from edge
|
|
621
|
+
description: 'Margin from edge as a fraction of the output size (e.g. 0.01 = 1%). Values > 1 are treated as pixels (legacy).',
|
|
622
622
|
type: 'number',
|
|
623
|
-
defaultValue:
|
|
623
|
+
defaultValue: 0.01,
|
|
624
624
|
group: 'Composite stream',
|
|
625
625
|
hide: true,
|
|
626
626
|
onPut: async () => {
|
|
627
627
|
this.scheduleStreamManagerRestart('pipMargin changed');
|
|
628
628
|
},
|
|
629
629
|
},
|
|
630
|
+
|
|
631
|
+
compositeAssumeH264: {
|
|
632
|
+
title: 'Composite: Assume H.264 Inputs',
|
|
633
|
+
description: 'Assume both wider+tele inputs are H.264 (skips codec detection). Recommended when using sub+sub on TrackMix. If inputs are actually H.265, the composite may fail to start.',
|
|
634
|
+
type: 'boolean',
|
|
635
|
+
defaultValue: true,
|
|
636
|
+
group: 'Composite stream',
|
|
637
|
+
hide: true,
|
|
638
|
+
onPut: async () => {
|
|
639
|
+
this.scheduleStreamManagerRestart('compositeAssumeH264 changed');
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
compositeDisableTranscode: {
|
|
643
|
+
title: 'Composite: Disable Codec Transcode (Best-effort)',
|
|
644
|
+
description: 'Best-effort knob. Overlay requires re-encode in ffmpeg; this option only avoids HEVC->H264 codec assumptions when possible. Leave off unless you know what you are doing.',
|
|
645
|
+
type: 'boolean',
|
|
646
|
+
defaultValue: false,
|
|
647
|
+
group: 'Composite stream',
|
|
648
|
+
hide: true,
|
|
649
|
+
onPut: async () => {
|
|
650
|
+
this.scheduleStreamManagerRestart('compositeDisableTranscode changed');
|
|
651
|
+
},
|
|
652
|
+
},
|
|
630
653
|
});
|
|
631
654
|
|
|
632
655
|
ptzPresets = new ReolinkPtzPresets(this);
|
|
@@ -1478,10 +1501,12 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1478
1501
|
}
|
|
1479
1502
|
|
|
1480
1503
|
// Case 4: Standalone camera -> create its own socket using base class method
|
|
1481
|
-
// For battery cameras,
|
|
1482
|
-
//
|
|
1483
|
-
//
|
|
1484
|
-
|
|
1504
|
+
// For battery (BCUDP) cameras, streaming must be keyed by streamKey.
|
|
1505
|
+
// Do NOT reuse ensureClient(): composite needs two concurrent streams, and single-lens streams
|
|
1506
|
+
// should reuse the same API that composite already created for that same streamKey.
|
|
1507
|
+
if (this.isBattery) {
|
|
1508
|
+
return await super.createStreamClient(streamKey);
|
|
1509
|
+
}
|
|
1485
1510
|
|
|
1486
1511
|
// For TCP standalone cameras, use base class createStreamClient which manages stream clients per streamKey
|
|
1487
1512
|
return await super.createStreamClient(streamKey);
|
|
@@ -1539,20 +1564,52 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1539
1564
|
};
|
|
1540
1565
|
|
|
1541
1566
|
if (this.isMultiFocal) {
|
|
1542
|
-
const { pipPosition, pipSize, pipMargin, rtspChannel } = this.storageSettings.values;
|
|
1567
|
+
const { pipPosition, pipSize, pipMargin, rtspChannel, compositeAssumeH264, compositeDisableTranscode } = this.storageSettings.values;
|
|
1543
1568
|
|
|
1544
1569
|
// On NVR/Hub, TrackMix lenses are selected via stream variant, not via a separate channel.
|
|
1545
1570
|
// Use rtspChannel for BOTH wide and tele so the library can request tele via streamType/variant.
|
|
1546
1571
|
const wider = this.isOnNvr ? rtspChannel : undefined;
|
|
1547
1572
|
const tele = this.isOnNvr ? rtspChannel : undefined;
|
|
1548
1573
|
|
|
1574
|
+
// On standalone TrackMix/Duo, lens channels are often separate, but they are not always 0/1.
|
|
1575
|
+
// Prefer using the discovered multifocalInfo mapping when available.
|
|
1576
|
+
let derivedWider: number | undefined = wider;
|
|
1577
|
+
let derivedTele: number | undefined = tele;
|
|
1578
|
+
if (!this.isOnNvr) {
|
|
1579
|
+
try {
|
|
1580
|
+
const info: any = this.storageSettings.values.multifocalInfo;
|
|
1581
|
+
const channels: any[] = Array.isArray(info?.channels) ? info.channels : [];
|
|
1582
|
+
|
|
1583
|
+
const wideCh = channels.find((c) => c?.lensType === 'wide')?.channel
|
|
1584
|
+
?? channels.find((c) => c?.variantType === 'default')?.channel;
|
|
1585
|
+
const teleCh = channels.find((c) => c?.lensType === 'telephoto')?.channel
|
|
1586
|
+
?? channels.find((c) => c?.variantType === 'telephoto')?.channel;
|
|
1587
|
+
|
|
1588
|
+
if (Number.isFinite(wideCh)) derivedWider = wideCh;
|
|
1589
|
+
if (Number.isFinite(teleCh)) derivedTele = teleCh;
|
|
1590
|
+
|
|
1591
|
+
// Avoid setting nonsense; leave undefined to fall back to library defaults.
|
|
1592
|
+
if (derivedWider === derivedTele) {
|
|
1593
|
+
// Keep undefined behavior (defaults inside the library) unless we are on NVR.
|
|
1594
|
+
derivedWider = undefined;
|
|
1595
|
+
derivedTele = undefined;
|
|
1596
|
+
}
|
|
1597
|
+
} catch {
|
|
1598
|
+
// ignore and fall back to defaults
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1549
1602
|
baseOptions.compositeOptions = {
|
|
1550
|
-
widerChannel:
|
|
1551
|
-
teleChannel:
|
|
1603
|
+
widerChannel: derivedWider,
|
|
1604
|
+
teleChannel: derivedTele,
|
|
1552
1605
|
pipPosition,
|
|
1553
1606
|
pipSize,
|
|
1554
1607
|
pipMargin,
|
|
1555
1608
|
onNvr: this.isOnNvr,
|
|
1609
|
+
// Prefer H.264 for composite (sub+sub by default) to reduce GOP latency.
|
|
1610
|
+
forceH264: true,
|
|
1611
|
+
assumeH264Inputs: compositeAssumeH264 ?? true,
|
|
1612
|
+
disableTranscode: compositeDisableTranscode ?? false,
|
|
1556
1613
|
};
|
|
1557
1614
|
}
|
|
1558
1615
|
|
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,
|