@apocaliss92/scrypted-reolink-native 0.2.7 → 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 +36 -6
- 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
|
@@ -1478,10 +1478,12 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1478
1478
|
}
|
|
1479
1479
|
|
|
1480
1480
|
// Case 4: Standalone camera -> create its own socket using base class method
|
|
1481
|
-
// For battery cameras,
|
|
1482
|
-
//
|
|
1483
|
-
//
|
|
1484
|
-
|
|
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
|
+
}
|
|
1485
1487
|
|
|
1486
1488
|
// For TCP standalone cameras, use base class createStreamClient which manages stream clients per streamKey
|
|
1487
1489
|
return await super.createStreamClient(streamKey);
|
|
@@ -1546,9 +1548,37 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1546
1548
|
const wider = this.isOnNvr ? rtspChannel : undefined;
|
|
1547
1549
|
const tele = this.isOnNvr ? rtspChannel : undefined;
|
|
1548
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
|
+
|
|
1549
1579
|
baseOptions.compositeOptions = {
|
|
1550
|
-
widerChannel:
|
|
1551
|
-
teleChannel:
|
|
1580
|
+
widerChannel: derivedWider,
|
|
1581
|
+
teleChannel: derivedTele,
|
|
1552
1582
|
pipPosition,
|
|
1553
1583
|
pipSize,
|
|
1554
1584
|
pipMargin,
|
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,
|