@apocaliss92/scrypted-reolink-native 0.2.8 → 0.2.10
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/baichuan-base.ts +3 -3
- package/src/camera.ts +105 -186
- package/src/debug-options.ts +4 -12
- package/src/intercom.ts +1 -1
- package/src/main.ts +2 -2
- package/src/multiFocal.ts +19 -3
- package/src/stream-utils.ts +129 -98
- package/src/utils.ts +2 -2
package/src/camera.ts
CHANGED
|
@@ -17,10 +17,7 @@ import { ReolinkNativeMultiFocalDevice } from "./multiFocal";
|
|
|
17
17
|
import { ReolinkNativeNvrDevice } from "./nvr";
|
|
18
18
|
import { ReolinkPtzPresets } from "./presets";
|
|
19
19
|
import {
|
|
20
|
-
createRfc4571CompositeMediaObjectFromStreamManager,
|
|
21
20
|
createRfc4571MediaObjectFromStreamManager,
|
|
22
|
-
extractVariantFromStreamId,
|
|
23
|
-
parseStreamProfileFromId,
|
|
24
21
|
selectStreamOption,
|
|
25
22
|
StreamManager,
|
|
26
23
|
StreamManagerOptions
|
|
@@ -48,7 +45,7 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
|
|
|
48
45
|
this.camera.getBaichuanLogger().log(`Siren toggle: turnOff ok (device=${this.nativeId})`);
|
|
49
46
|
}
|
|
50
47
|
catch (e) {
|
|
51
|
-
this.camera.getBaichuanLogger().
|
|
48
|
+
this.camera.getBaichuanLogger().error(`Siren toggle: turnOff failed (device=${this.nativeId})`, e?.message || String(e));
|
|
52
49
|
throw e;
|
|
53
50
|
}
|
|
54
51
|
}
|
|
@@ -61,7 +58,7 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
|
|
|
61
58
|
this.camera.getBaichuanLogger().log(`Siren toggle: turnOn ok (device=${this.nativeId})`);
|
|
62
59
|
}
|
|
63
60
|
catch (e) {
|
|
64
|
-
this.camera.getBaichuanLogger().
|
|
61
|
+
this.camera.getBaichuanLogger().error(`Siren toggle: turnOn failed (device=${this.nativeId})`, e?.message || String(e));
|
|
65
62
|
throw e;
|
|
66
63
|
}
|
|
67
64
|
}
|
|
@@ -513,6 +510,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
513
510
|
subgroup: 'Diagnostics',
|
|
514
511
|
description: "Directory where diagnostics files will be saved (default: plugin volume).",
|
|
515
512
|
type: "string",
|
|
513
|
+
hide: true,
|
|
516
514
|
defaultValue: path.join(process.env.SCRYPTED_PLUGIN_VOLUME, 'diagnostics', this.name),
|
|
517
515
|
},
|
|
518
516
|
enableVideoclips: {
|
|
@@ -521,6 +519,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
521
519
|
type: "boolean",
|
|
522
520
|
defaultValue: false,
|
|
523
521
|
immediate: true,
|
|
522
|
+
hide: true,
|
|
524
523
|
onPut: async () => {
|
|
525
524
|
this.updateVideoClipsAutoLoad();
|
|
526
525
|
},
|
|
@@ -532,6 +531,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
532
531
|
type: "string",
|
|
533
532
|
choices: ["NVR", "Device"],
|
|
534
533
|
immediate: true,
|
|
534
|
+
hide: true,
|
|
535
535
|
},
|
|
536
536
|
loadVideoclips: {
|
|
537
537
|
title: "Auto-load Video Clips",
|
|
@@ -540,6 +540,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
540
540
|
type: "boolean",
|
|
541
541
|
defaultValue: false,
|
|
542
542
|
immediate: true,
|
|
543
|
+
hide: true,
|
|
543
544
|
onPut: async () => {
|
|
544
545
|
this.updateVideoClipsAutoLoad();
|
|
545
546
|
},
|
|
@@ -550,6 +551,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
550
551
|
description: "How often to check for new video clips and download thumbnails (default: 30 minutes).",
|
|
551
552
|
type: "number",
|
|
552
553
|
defaultValue: 30,
|
|
554
|
+
hide: true,
|
|
553
555
|
onPut: async () => {
|
|
554
556
|
this.updateVideoClipsAutoLoad();
|
|
555
557
|
},
|
|
@@ -561,6 +563,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
561
563
|
type: "boolean",
|
|
562
564
|
defaultValue: false,
|
|
563
565
|
immediate: true,
|
|
566
|
+
hide: true,
|
|
564
567
|
onPut: async () => {
|
|
565
568
|
this.updateVideoClipsAutoLoad();
|
|
566
569
|
},
|
|
@@ -571,6 +574,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
571
574
|
description: "Number of days to preload video clips and thumbnails (default: 1, only today).",
|
|
572
575
|
type: "number",
|
|
573
576
|
defaultValue: 3,
|
|
577
|
+
hide: true,
|
|
574
578
|
onPut: async () => {
|
|
575
579
|
this.updateVideoClipsAutoLoad();
|
|
576
580
|
},
|
|
@@ -580,6 +584,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
580
584
|
title: 'Run Diagnostics',
|
|
581
585
|
description: 'Run all diagnostics and save results to the output path.',
|
|
582
586
|
type: 'button',
|
|
587
|
+
hide: true,
|
|
583
588
|
immediate: true,
|
|
584
589
|
onPut: async () => {
|
|
585
590
|
await this.runDiagnostics();
|
|
@@ -618,15 +623,38 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
618
623
|
},
|
|
619
624
|
pipMargin: {
|
|
620
625
|
title: 'PIP Margin',
|
|
621
|
-
description: 'Margin from edge
|
|
626
|
+
description: 'Margin from edge as a fraction of the output size (e.g. 0.01 = 1%). Values > 1 are treated as pixels (legacy).',
|
|
622
627
|
type: 'number',
|
|
623
|
-
defaultValue:
|
|
628
|
+
defaultValue: 0.01,
|
|
624
629
|
group: 'Composite stream',
|
|
625
630
|
hide: true,
|
|
626
631
|
onPut: async () => {
|
|
627
632
|
this.scheduleStreamManagerRestart('pipMargin changed');
|
|
628
633
|
},
|
|
629
634
|
},
|
|
635
|
+
|
|
636
|
+
compositeAssumeH264: {
|
|
637
|
+
title: 'Composite: Assume H.264 Inputs',
|
|
638
|
+
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.',
|
|
639
|
+
type: 'boolean',
|
|
640
|
+
defaultValue: true,
|
|
641
|
+
group: 'Composite stream',
|
|
642
|
+
hide: true,
|
|
643
|
+
onPut: async () => {
|
|
644
|
+
this.scheduleStreamManagerRestart('compositeAssumeH264 changed');
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
compositeDisableTranscode: {
|
|
648
|
+
title: 'Composite: Disable Codec Transcode (Best-effort)',
|
|
649
|
+
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.',
|
|
650
|
+
type: 'boolean',
|
|
651
|
+
defaultValue: false,
|
|
652
|
+
group: 'Composite stream',
|
|
653
|
+
hide: true,
|
|
654
|
+
onPut: async () => {
|
|
655
|
+
this.scheduleStreamManagerRestart('compositeDisableTranscode changed');
|
|
656
|
+
},
|
|
657
|
+
},
|
|
630
658
|
});
|
|
631
659
|
|
|
632
660
|
ptzPresets = new ReolinkPtzPresets(this);
|
|
@@ -762,18 +790,16 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
762
790
|
// Fetch from NVR using listEnrichedVodFiles (library handles parsing correctly)
|
|
763
791
|
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
764
792
|
|
|
765
|
-
//
|
|
766
|
-
logger.debug(`[NVR
|
|
767
|
-
|
|
768
|
-
const enrichedRecordings = await api.listNvrRecordings({
|
|
769
|
-
channel,
|
|
793
|
+
// Prefer Hub-like event listing (alarm events) instead of full VOD.
|
|
794
|
+
logger.debug(`[NVR EVENTS] Searching for alarm events: channel=${channel}, start=${start.toISOString()}, end=${end.toISOString()}`);
|
|
795
|
+
const enrichedRecordings = await api.listNvrAlarmEventsEnrichedViaBaichuan({
|
|
770
796
|
start,
|
|
771
797
|
end,
|
|
772
|
-
|
|
773
|
-
|
|
798
|
+
channels: [channel],
|
|
799
|
+
streamType: "mainStream",
|
|
774
800
|
});
|
|
775
801
|
|
|
776
|
-
logger.debug(`[NVR
|
|
802
|
+
logger.debug(`[NVR EVENTS] Found ${enrichedRecordings.length} enriched alarm events from NVR`);
|
|
777
803
|
|
|
778
804
|
// Convert enriched recordings to VideoClip array using the shared parser
|
|
779
805
|
const clips = await recordingsToVideoClips(enrichedRecordings, {
|
|
@@ -785,7 +811,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
785
811
|
count,
|
|
786
812
|
});
|
|
787
813
|
|
|
788
|
-
logger.debug(`[NVR
|
|
814
|
+
logger.debug(`[NVR EVENTS] Converted ${clips.length} video clips (limit: ${count || 'none'})`);
|
|
789
815
|
|
|
790
816
|
return clips;
|
|
791
817
|
} else {
|
|
@@ -870,7 +896,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
870
896
|
try {
|
|
871
897
|
await fs.promises.unlink(cachePath);
|
|
872
898
|
} catch (unlinkErr) {
|
|
873
|
-
logger.warn(`[VideoClip] Failed to delete small cached file: fileId=${videoId}`, unlinkErr);
|
|
899
|
+
logger.warn(`[VideoClip] Failed to delete small cached file: fileId=${videoId}`, unlinkErr?.message || String(unlinkErr));
|
|
874
900
|
}
|
|
875
901
|
} else {
|
|
876
902
|
logger.debug(`[VideoClip] Using cached file: fileId=${videoId}, size=${stats.size} bytes`);
|
|
@@ -988,7 +1014,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
988
1014
|
});
|
|
989
1015
|
|
|
990
1016
|
ffmpeg.on('error', (error) => {
|
|
991
|
-
logger.error(`ffmpeg spawn error for video clip ${videoId}`, error);
|
|
1017
|
+
logger.error(`ffmpeg spawn error for video clip ${videoId}`, error?.message || String(error));
|
|
992
1018
|
reject(error);
|
|
993
1019
|
});
|
|
994
1020
|
|
|
@@ -1056,7 +1082,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1056
1082
|
try {
|
|
1057
1083
|
await fs.promises.unlink(cachePath);
|
|
1058
1084
|
} catch (unlinkErr) {
|
|
1059
|
-
logger.warn(`[Thumbnail] Failed to delete small cached thumbnail: fileId=${thumbnailId}`, unlinkErr);
|
|
1085
|
+
logger.warn(`[Thumbnail] Failed to delete small cached thumbnail: fileId=${thumbnailId}`, unlinkErr?.message || String(unlinkErr));
|
|
1060
1086
|
}
|
|
1061
1087
|
} else {
|
|
1062
1088
|
logger.debug(`[Thumbnail] Using cached: fileId=${thumbnailId}, size=${stats.size} bytes`);
|
|
@@ -1541,7 +1567,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1541
1567
|
};
|
|
1542
1568
|
|
|
1543
1569
|
if (this.isMultiFocal) {
|
|
1544
|
-
const { pipPosition, pipSize, pipMargin, rtspChannel } = this.storageSettings.values;
|
|
1570
|
+
const { pipPosition, pipSize, pipMargin, rtspChannel, compositeAssumeH264, compositeDisableTranscode } = this.storageSettings.values;
|
|
1545
1571
|
|
|
1546
1572
|
// On NVR/Hub, TrackMix lenses are selected via stream variant, not via a separate channel.
|
|
1547
1573
|
// Use rtspChannel for BOTH wide and tele so the library can request tele via streamType/variant.
|
|
@@ -1583,6 +1609,10 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1583
1609
|
pipSize,
|
|
1584
1610
|
pipMargin,
|
|
1585
1611
|
onNvr: this.isOnNvr,
|
|
1612
|
+
// Prefer H.264 for composite (sub+sub by default) to reduce GOP latency.
|
|
1613
|
+
forceH264: true,
|
|
1614
|
+
assumeH264Inputs: compositeAssumeH264 ?? true,
|
|
1615
|
+
disableTranscode: compositeDisableTranscode ?? false,
|
|
1586
1616
|
};
|
|
1587
1617
|
}
|
|
1588
1618
|
|
|
@@ -1618,14 +1648,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1618
1648
|
this.lastPicture = undefined;
|
|
1619
1649
|
}
|
|
1620
1650
|
|
|
1621
|
-
|
|
1622
|
-
try {
|
|
1623
|
-
this.onDeviceEvent(ScryptedInterface.VideoCamera, undefined);
|
|
1624
|
-
} catch {
|
|
1625
|
-
// best-effort
|
|
1626
|
-
}
|
|
1651
|
+
this.onDeviceEvent(ScryptedInterface.VideoCamera, undefined);
|
|
1627
1652
|
} catch (e) {
|
|
1628
|
-
logger.
|
|
1653
|
+
logger.error('Failed to restart StreamManager after settings change', e?.message || String(e));
|
|
1629
1654
|
}
|
|
1630
1655
|
}, 500);
|
|
1631
1656
|
}
|
|
@@ -1686,14 +1711,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1686
1711
|
reason: ev?.type === 'sleeping' ? 'sleeping' : 'awake',
|
|
1687
1712
|
state: ev.type === 'sleeping' ? 'sleeping' : 'awake',
|
|
1688
1713
|
}).catch((e) => {
|
|
1689
|
-
logger.warn('Error updating sleeping state', e);
|
|
1714
|
+
logger.warn('Error updating sleeping state', e?.message || String(e));
|
|
1690
1715
|
});
|
|
1691
1716
|
return;
|
|
1692
1717
|
|
|
1693
1718
|
case 'offline':
|
|
1694
1719
|
case 'online':
|
|
1695
1720
|
this.updateOnlineState(ev.type === 'online').catch((e) => {
|
|
1696
|
-
logger.warn('Error updating online state', e);
|
|
1721
|
+
logger.warn('Error updating online state', e?.message || String(e));
|
|
1697
1722
|
});
|
|
1698
1723
|
return;
|
|
1699
1724
|
|
|
@@ -1722,11 +1747,11 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1722
1747
|
}
|
|
1723
1748
|
|
|
1724
1749
|
this.processEvents({ motion, objects }).catch((e) => {
|
|
1725
|
-
logger.warn('Error processing events', e);
|
|
1750
|
+
logger.warn('Error processing events', e?.message || String(e));
|
|
1726
1751
|
});
|
|
1727
1752
|
}
|
|
1728
1753
|
catch (e) {
|
|
1729
|
-
logger.warn('Error in onSimpleEvent handler', e);
|
|
1754
|
+
logger.warn('Error in onSimpleEvent handler', e?.message || String(e));
|
|
1730
1755
|
}
|
|
1731
1756
|
}
|
|
1732
1757
|
|
|
@@ -1774,7 +1799,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1774
1799
|
logger.log(`Subscribed to events (${selection.join(', ')}) on ${this.protocol} connection`);
|
|
1775
1800
|
}
|
|
1776
1801
|
catch (e) {
|
|
1777
|
-
logger.warn('Failed to subscribe to Baichuan events', e);
|
|
1802
|
+
logger.warn('Failed to subscribe to Baichuan events', e?.message || String(e));
|
|
1778
1803
|
return;
|
|
1779
1804
|
}
|
|
1780
1805
|
}
|
|
@@ -2114,7 +2139,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2114
2139
|
return await this.takePictureInternal(client);
|
|
2115
2140
|
});
|
|
2116
2141
|
} catch (e) {
|
|
2117
|
-
this.getBaichuanLogger().error('Error taking snapshot', e);
|
|
2142
|
+
this.getBaichuanLogger().error('Error taking snapshot', e?.message || String(e));
|
|
2118
2143
|
throw e;
|
|
2119
2144
|
}
|
|
2120
2145
|
} else {
|
|
@@ -2190,7 +2215,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2190
2215
|
});
|
|
2191
2216
|
|
|
2192
2217
|
} catch (e) {
|
|
2193
|
-
logger.warn('Failed to fetch device info', e);
|
|
2218
|
+
logger.warn('Failed to fetch device info', e?.message || String(e));
|
|
2194
2219
|
}
|
|
2195
2220
|
}
|
|
2196
2221
|
|
|
@@ -2413,37 +2438,32 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2413
2438
|
// logger.log({ supportedStreams, variantType, lensParam, rtspChannel, onNvr: this.isOnNvr, nativeStreams: nativeStreams.map(s => ({ id: s.id, nativeVariant: s.nativeVariant, lens: s.lens })), rtspStreams: rtspStreams.map(s => ({ id: s.id, lens: s.lens })), rtmpStreams: rtmpStreams.map(s => ({ id: s.id, lens: s.lens })) });
|
|
2414
2439
|
|
|
2415
2440
|
for (const supportedStream of supportedStreams) {
|
|
2416
|
-
const { id, metadata, url, name, container,
|
|
2441
|
+
const { id, metadata, url, name, container, lens } = supportedStream;
|
|
2417
2442
|
|
|
2418
2443
|
// Composite streams are re-encoded to H.264 by the library (ffmpeg/libx264).
|
|
2419
2444
|
// Do not infer codec from underlying camera metadata.
|
|
2420
2445
|
const isComposite = id.startsWith('composite_') || lens === 'composite';
|
|
2421
|
-
const codec =
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
let finalUrl = url;
|
|
2431
|
-
const variantFromIdOrUrl = extractVariantFromStreamId(id, url);
|
|
2432
|
-
const variantToInject = (nativeVariant && nativeVariant !== 'default')
|
|
2433
|
-
? nativeVariant
|
|
2434
|
-
: variantFromIdOrUrl;
|
|
2435
|
-
|
|
2436
|
-
if (variantToInject && container === 'rtp') {
|
|
2437
|
-
try {
|
|
2438
|
-
const urlObj = new URL(url);
|
|
2439
|
-
if (!urlObj.searchParams.has('variant')) {
|
|
2440
|
-
urlObj.searchParams.set('variant', variantToInject);
|
|
2441
|
-
finalUrl = urlObj.toString();
|
|
2442
|
-
}
|
|
2443
|
-
} catch {
|
|
2444
|
-
// Invalid URL, use original
|
|
2446
|
+
const codec = (() => {
|
|
2447
|
+
if (isComposite) return 'h264';
|
|
2448
|
+
|
|
2449
|
+
const enc = (metadata as any)?.videoEncType;
|
|
2450
|
+
// Many firmwares expose videoEncType as a numeric enum.
|
|
2451
|
+
// Observed: 0 => H.264, 1 => H.265.
|
|
2452
|
+
if (typeof enc === 'number') {
|
|
2453
|
+
if (enc === 0) return 'h264';
|
|
2454
|
+
if (enc === 1) return 'h265';
|
|
2445
2455
|
}
|
|
2446
|
-
|
|
2456
|
+
|
|
2457
|
+
const s = String(enc ?? '').toLowerCase();
|
|
2458
|
+
if (s === '0') return 'h264';
|
|
2459
|
+
if (s === '1') return 'h265';
|
|
2460
|
+
if (s.includes('264')) return 'h264';
|
|
2461
|
+
if (s.includes('265')) return 'h265';
|
|
2462
|
+
return s;
|
|
2463
|
+
})();
|
|
2464
|
+
|
|
2465
|
+
// For RTP (native RFC4571), stream identification happens via `id` (streamKey), not URL.
|
|
2466
|
+
const finalUrl = url;
|
|
2447
2467
|
|
|
2448
2468
|
streams.push({
|
|
2449
2469
|
id,
|
|
@@ -2486,43 +2506,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2486
2506
|
|
|
2487
2507
|
const selected = selectStreamOption(vsos, vso);
|
|
2488
2508
|
|
|
2489
|
-
// If the request explicitly asks for a variant (e.g. native_telephoto_main),
|
|
2490
|
-
// never override it with the device's variantType preference.
|
|
2491
|
-
// const requestedVariant = vso?.id ? extractVariantFromStreamId(vso.id, undefined) : undefined;
|
|
2492
|
-
|
|
2493
|
-
// If we have variantType set and the selected stream doesn't have the variant,
|
|
2494
|
-
// try to find a stream with the correct variant that matches the profile
|
|
2495
|
-
// const variantType = this.storageSettings.values.variantType;
|
|
2496
|
-
// if (!requestedVariant && variantType && variantType !== 'default') {
|
|
2497
|
-
// const profile = parseStreamProfileFromId(selected.id) || 'main';
|
|
2498
|
-
|
|
2499
|
-
// // On NVR, firmwares vary: some expose the tele lens as 'autotrack', others as 'telephoto'.
|
|
2500
|
-
// // When variantType is set, prefer that variant but fall back to the other tele variant if present.
|
|
2501
|
-
// const preferred = variantType as 'autotrack' | 'telephoto';
|
|
2502
|
-
// const fallbacks: Array<'autotrack' | 'telephoto'> = this.isOnNvr && preferred === 'telephoto'
|
|
2503
|
-
// ? ['telephoto', 'autotrack']
|
|
2504
|
-
// : this.isOnNvr && preferred === 'autotrack'
|
|
2505
|
-
// ? ['autotrack', 'telephoto']
|
|
2506
|
-
// : [preferred];
|
|
2507
|
-
|
|
2508
|
-
// const extractedVariant = extractVariantFromStreamId(selected.id, selected.url);
|
|
2509
|
-
// for (const v of fallbacks) {
|
|
2510
|
-
// const variantId = `native_${v}_${profile}`;
|
|
2511
|
-
// const variantStream = vsos?.find(s => s.id === variantId);
|
|
2512
|
-
// if (!variantStream) {
|
|
2513
|
-
// logger.debug(`Variant stream '${variantId}' not found in available streams`);
|
|
2514
|
-
// continue;
|
|
2515
|
-
// }
|
|
2516
|
-
// // Only use variant stream if the selected one doesn't already have a variant,
|
|
2517
|
-
// // or if the selected one has a different variant than what we want.
|
|
2518
|
-
// if (!extractedVariant || extractedVariant !== v) {
|
|
2519
|
-
// logger.log(`Preferring variant stream: '${variantId}' over '${selected.id}' (variantType='${variantType}')`);
|
|
2520
|
-
// selected = variantStream;
|
|
2521
|
-
// }
|
|
2522
|
-
// break;
|
|
2523
|
-
// }
|
|
2524
|
-
// }
|
|
2525
|
-
|
|
2526
2509
|
logger.log(`Selected stream: id='${selected.id}', url='${selected.url}'`);
|
|
2527
2510
|
|
|
2528
2511
|
if (selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
|
|
@@ -2539,76 +2522,21 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2539
2522
|
throw new Error('StreamManager not initialized');
|
|
2540
2523
|
}
|
|
2541
2524
|
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
if (selected.id?.startsWith('composite_')) {
|
|
2546
|
-
const profile = parseStreamProfileFromId(selected.id.replace('composite_', '')) || 'main';
|
|
2547
|
-
// Include variantType in streamKey to ensure each variantType has its own unique socket
|
|
2548
|
-
// This is important for multifocal devices where different variantTypes may request composite streams
|
|
2549
|
-
const variantType = this.storageSettings.values.variantType || 'default';
|
|
2550
|
-
const streamKey = `composite_${variantType}_${profile}`;
|
|
2551
|
-
|
|
2552
|
-
logger.log(`Creating composite stream: profile=${profile}, variantType=${variantType}, streamKey=${streamKey}`);
|
|
2553
|
-
|
|
2554
|
-
const createStreamFn = async () => {
|
|
2555
|
-
return await createRfc4571CompositeMediaObjectFromStreamManager({
|
|
2556
|
-
streamManager: this.streamManager,
|
|
2557
|
-
profile,
|
|
2558
|
-
streamKey,
|
|
2559
|
-
selected,
|
|
2560
|
-
sourceId: this.id,
|
|
2561
|
-
variantType,
|
|
2562
|
-
});
|
|
2563
|
-
};
|
|
2564
|
-
|
|
2565
|
-
return await this.withBaichuanRetry(createStreamFn);
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
// Regular stream for single channel
|
|
2569
|
-
const profile = parseStreamProfileFromId(selected.id) || 'main';
|
|
2570
|
-
const channel = this.storageSettings.values.rtspChannel;
|
|
2571
|
-
// Extract variant from stream ID or URL if present (e.g., "autotrack" from "native_autotrack_main" or "?variant=autotrack")
|
|
2572
|
-
let variant = extractVariantFromStreamId(selected.id, selected.url);
|
|
2573
|
-
|
|
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.
|
|
2579
|
-
if (!variant && this.storageSettings.values.variantType && this.storageSettings.values.variantType !== 'default') {
|
|
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
|
-
}
|
|
2525
|
+
const streamKey = selected.id;
|
|
2526
|
+
if (!streamKey) {
|
|
2527
|
+
throw new Error('Missing streamKey (selected.id) for RTP stream');
|
|
2588
2528
|
}
|
|
2589
2529
|
|
|
2590
|
-
logger.log(`
|
|
2591
|
-
|
|
2592
|
-
// Include variant in streamKey to distinguish streams with different variants
|
|
2593
|
-
const streamKey = variant ? `${channel}_${variant}_${profile}` : `${channel}_${profile}`;
|
|
2594
|
-
|
|
2595
|
-
const createStreamFn = async () => {
|
|
2596
|
-
// Honor the requested variant. Some NVR firmwares label the tele lens as either
|
|
2597
|
-
// 'autotrack' or 'telephoto', and the library exposes both when available.
|
|
2598
|
-
logger.log(`Creating RFC4571 stream: channel=${channel}, profile=${profile}, variant=${variant || 'default'}, streamKey=${streamKey}`);
|
|
2530
|
+
logger.log(`Creating RFC4571 stream: streamKey='${streamKey}'`);
|
|
2599
2531
|
|
|
2532
|
+
return await this.withBaichuanRetry(async () => {
|
|
2600
2533
|
return await createRfc4571MediaObjectFromStreamManager({
|
|
2601
2534
|
streamManager: this.streamManager!,
|
|
2602
|
-
channel,
|
|
2603
|
-
profile,
|
|
2604
2535
|
streamKey,
|
|
2605
|
-
variant,
|
|
2606
2536
|
selected,
|
|
2607
2537
|
sourceId: this.id,
|
|
2608
2538
|
});
|
|
2609
|
-
};
|
|
2610
|
-
|
|
2611
|
-
return await this.withBaichuanRetry(createStreamFn);
|
|
2539
|
+
});
|
|
2612
2540
|
}
|
|
2613
2541
|
|
|
2614
2542
|
async ensureClient(): Promise<ReolinkBaichuanApi> {
|
|
@@ -2619,7 +2547,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2619
2547
|
return await this.multiFocalDevice.ensureClient();
|
|
2620
2548
|
}
|
|
2621
2549
|
|
|
2622
|
-
// Use base class implementation
|
|
2623
2550
|
return await this.ensureBaichuanClient();
|
|
2624
2551
|
}
|
|
2625
2552
|
|
|
@@ -2627,7 +2554,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2627
2554
|
this.cachedVideoStreamOptions = undefined;
|
|
2628
2555
|
}
|
|
2629
2556
|
|
|
2630
|
-
// PTZ Presets methods
|
|
2631
2557
|
getSelectedPresetId(): number | undefined {
|
|
2632
2558
|
const s = this.storageSettings.values.ptzSelectedPreset;
|
|
2633
2559
|
if (!s) return undefined;
|
|
@@ -2740,16 +2666,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2740
2666
|
this.storageSettings.settings.clipsSource.hide = !this.nvrDevice;
|
|
2741
2667
|
this.storageSettings.settings.clipsSource.defaultValue = this.nvrDevice ? "NVR" : "Device";
|
|
2742
2668
|
|
|
2743
|
-
|
|
2744
|
-
|
|
2669
|
+
this.storageSettings.settings.diagnosticsRun.hide = !!this.multiFocalDevice;
|
|
2670
|
+
this.storageSettings.settings.diagnosticsOutputPath.hide = !!this.multiFocalDevice;
|
|
2745
2671
|
|
|
2746
|
-
// for (const key of allSettingKeys) {
|
|
2747
|
-
// const setting = this.storageSettings.settings[key];
|
|
2748
|
-
// if (['Videoclips', 'PTZ'].includes(setting.subgroup)) {
|
|
2749
|
-
// setting.hide = true;
|
|
2750
|
-
// }
|
|
2751
|
-
// }
|
|
2752
|
-
// }
|
|
2753
2672
|
this.storageSettings.settings.enableVideoclips.hide = !!this.multiFocalDevice;
|
|
2754
2673
|
this.storageSettings.settings.videoclipsDaysToPreload.hide = !!this.multiFocalDevice;
|
|
2755
2674
|
this.storageSettings.settings.videoclipsRegularChecks.hide = !!this.multiFocalDevice;
|
|
@@ -2790,14 +2709,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2790
2709
|
await this.subscribeToEvents();
|
|
2791
2710
|
}
|
|
2792
2711
|
catch (e) {
|
|
2793
|
-
logger.
|
|
2712
|
+
logger.error('Failed to subscribe to Baichuan events', e?.message || String(e));
|
|
2794
2713
|
}
|
|
2795
2714
|
|
|
2796
2715
|
try {
|
|
2797
2716
|
this.initStreamManager();
|
|
2798
2717
|
}
|
|
2799
2718
|
catch (e) {
|
|
2800
|
-
logger.
|
|
2719
|
+
logger.error('Failed to initialize StreamManager', e?.message || String(e));
|
|
2801
2720
|
}
|
|
2802
2721
|
|
|
2803
2722
|
const { hasIntercom, hasPtz } = this.getAbilities();
|
|
@@ -2806,7 +2725,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2806
2725
|
this.intercom = new ReolinkBaichuanIntercom(this);
|
|
2807
2726
|
}
|
|
2808
2727
|
|
|
2809
|
-
if (hasPtz
|
|
2728
|
+
if (hasPtz) {
|
|
2810
2729
|
const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
|
|
2811
2730
|
|
|
2812
2731
|
this.storageSettings.settings.presets.choices = choices;
|
|
@@ -2870,7 +2789,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2870
2789
|
}
|
|
2871
2790
|
} catch (e) {
|
|
2872
2791
|
// Silently ignore errors in sleep check to avoid spam
|
|
2873
|
-
this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
|
|
2792
|
+
this.getBaichuanLogger().debug('Error in updateSleepingState:', e?.message || String(e));
|
|
2874
2793
|
}
|
|
2875
2794
|
}
|
|
2876
2795
|
|
|
@@ -2885,7 +2804,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2885
2804
|
}
|
|
2886
2805
|
} catch (e) {
|
|
2887
2806
|
// Silently ignore errors in sleep check to avoid spam
|
|
2888
|
-
this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
|
|
2807
|
+
this.getBaichuanLogger().debug('Error in updateOnlineState:', e?.message || String(e));
|
|
2889
2808
|
}
|
|
2890
2809
|
}
|
|
2891
2810
|
|
|
@@ -2895,7 +2814,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2895
2814
|
await this.baichuanApi?.close();
|
|
2896
2815
|
}
|
|
2897
2816
|
catch (e) {
|
|
2898
|
-
this.getBaichuanLogger().
|
|
2817
|
+
this.getBaichuanLogger().error('Error closing Baichuan client during reset', e?.message || String(e));
|
|
2899
2818
|
}
|
|
2900
2819
|
finally {
|
|
2901
2820
|
this.baichuanApi = undefined;
|
|
@@ -2905,7 +2824,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2905
2824
|
|
|
2906
2825
|
if (reason) {
|
|
2907
2826
|
const message = reason?.message || reason?.toString?.() || reason;
|
|
2908
|
-
this.getBaichuanLogger().
|
|
2827
|
+
this.getBaichuanLogger().error(`Baichuan client reset requested: ${message}`);
|
|
2909
2828
|
}
|
|
2910
2829
|
}
|
|
2911
2830
|
|
|
@@ -2995,7 +2914,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2995
2914
|
// Ensure we have a client connection
|
|
2996
2915
|
const api = await this.ensureClient();
|
|
2997
2916
|
if (!api) {
|
|
2998
|
-
this.getBaichuanLogger().
|
|
2917
|
+
this.getBaichuanLogger().error('Failed to ensure client connection for battery update');
|
|
2999
2918
|
return;
|
|
3000
2919
|
}
|
|
3001
2920
|
|
|
@@ -3009,7 +2928,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3009
2928
|
await api.wakeUp(channel, { waitAfterWakeMs: 2000 });
|
|
3010
2929
|
logger.log('Wake command sent, waiting for camera to wake up...');
|
|
3011
2930
|
} catch (wakeError) {
|
|
3012
|
-
logger.
|
|
2931
|
+
logger.error('Failed to wake up camera:', wakeError?.message || String(wakeError));
|
|
3013
2932
|
return;
|
|
3014
2933
|
}
|
|
3015
2934
|
|
|
@@ -3030,7 +2949,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3030
2949
|
}
|
|
3031
2950
|
|
|
3032
2951
|
if (!awake) {
|
|
3033
|
-
logger.
|
|
2952
|
+
logger.error('Camera did not wake up within timeout, skipping update');
|
|
3034
2953
|
return;
|
|
3035
2954
|
}
|
|
3036
2955
|
} else if (sleepStatus.state === 'awake') {
|
|
@@ -3042,14 +2961,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3042
2961
|
try {
|
|
3043
2962
|
await this.updateBatteryInfo();
|
|
3044
2963
|
} catch (e) {
|
|
3045
|
-
logger.
|
|
2964
|
+
logger.error('Failed to get battery info during periodic update:', e?.message || String(e));
|
|
3046
2965
|
}
|
|
3047
2966
|
|
|
3048
2967
|
// 2. Align auxiliary devices state
|
|
3049
2968
|
try {
|
|
3050
2969
|
await this.alignAuxDevicesState();
|
|
3051
2970
|
} catch (e) {
|
|
3052
|
-
logger.
|
|
2971
|
+
logger.error('Failed to align auxiliary devices state:', e?.message || String(e));
|
|
3053
2972
|
}
|
|
3054
2973
|
|
|
3055
2974
|
// 3. Update snapshot
|
|
@@ -3058,10 +2977,10 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3058
2977
|
await this.takePicture();
|
|
3059
2978
|
logger.log('Snapshot updated during periodic update');
|
|
3060
2979
|
} catch (snapshotError) {
|
|
3061
|
-
logger.
|
|
2980
|
+
logger.error('Failed to update snapshot during periodic update:', snapshotError?.message || String(snapshotError));
|
|
3062
2981
|
}
|
|
3063
2982
|
} catch (e) {
|
|
3064
|
-
logger.
|
|
2983
|
+
logger.error('Failed to update battery and snapshot', e?.message || String(e));
|
|
3065
2984
|
} finally {
|
|
3066
2985
|
// Clear the promise when done (success or failure)
|
|
3067
2986
|
this.batteryUpdatePromise = undefined;
|
|
@@ -3096,7 +3015,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3096
3015
|
const sleepStatus = api.getSleepStatus({ channel });
|
|
3097
3016
|
await this.updateSleepingState(sleepStatus);
|
|
3098
3017
|
} catch (e) {
|
|
3099
|
-
logger.
|
|
3018
|
+
logger.error('Error checking sleeping state:', e?.message || String(e));
|
|
3100
3019
|
}
|
|
3101
3020
|
}, 5_000);
|
|
3102
3021
|
}
|
|
@@ -3108,7 +3027,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3108
3027
|
try {
|
|
3109
3028
|
await this.updateBatteryAndSnapshot();
|
|
3110
3029
|
} catch (e) {
|
|
3111
|
-
logger.
|
|
3030
|
+
logger.error('Error updating battery and snapshot:', e?.message || String(e));
|
|
3112
3031
|
}
|
|
3113
3032
|
}, updateIntervalMs);
|
|
3114
3033
|
|
|
@@ -3118,7 +3037,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3118
3037
|
try {
|
|
3119
3038
|
await this.alignAuxDevicesState();
|
|
3120
3039
|
} catch (e) {
|
|
3121
|
-
logger.
|
|
3040
|
+
logger.error('Error aligning auxiliary devices state:', e?.message || String(e));
|
|
3122
3041
|
}
|
|
3123
3042
|
}, 10_000);
|
|
3124
3043
|
|