@apocaliss92/scrypted-reolink-native 0.2.9 → 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 +75 -183
- 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/dist/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/baichuan-base.ts
CHANGED
|
@@ -366,7 +366,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
catch (e) {
|
|
369
|
-
logger.debug(`Could not get connection state: ${e}`);
|
|
369
|
+
logger.debug(`Could not get connection state: ${e?.message || String(e)}`);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
const now = Date.now();
|
|
@@ -500,7 +500,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
500
500
|
logger.log('Subscribed to Baichuan events');
|
|
501
501
|
}
|
|
502
502
|
catch (e) {
|
|
503
|
-
logger.warn('Failed to subscribe to events', e);
|
|
503
|
+
logger.warn('Failed to subscribe to events', e?.message || String(e));
|
|
504
504
|
this.eventSubscriptionActive = false;
|
|
505
505
|
}
|
|
506
506
|
}
|
|
@@ -519,7 +519,7 @@ export abstract class BaseBaichuanClass extends ScryptedDeviceBase {
|
|
|
519
519
|
logger.debug('Unsubscribed from Baichuan events');
|
|
520
520
|
}
|
|
521
521
|
catch (e) {
|
|
522
|
-
logger.warn('Error unsubscribing from events', e);
|
|
522
|
+
logger.warn('Error unsubscribing from events', e?.message || String(e));
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
525
|
|
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();
|
|
@@ -785,18 +790,16 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
785
790
|
// Fetch from NVR using listEnrichedVodFiles (library handles parsing correctly)
|
|
786
791
|
const channel = this.storageSettings.values.rtspChannel ?? 0;
|
|
787
792
|
|
|
788
|
-
//
|
|
789
|
-
logger.debug(`[NVR
|
|
790
|
-
|
|
791
|
-
const enrichedRecordings = await api.listNvrRecordings({
|
|
792
|
-
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({
|
|
793
796
|
start,
|
|
794
797
|
end,
|
|
795
|
-
|
|
796
|
-
|
|
798
|
+
channels: [channel],
|
|
799
|
+
streamType: "mainStream",
|
|
797
800
|
});
|
|
798
801
|
|
|
799
|
-
logger.debug(`[NVR
|
|
802
|
+
logger.debug(`[NVR EVENTS] Found ${enrichedRecordings.length} enriched alarm events from NVR`);
|
|
800
803
|
|
|
801
804
|
// Convert enriched recordings to VideoClip array using the shared parser
|
|
802
805
|
const clips = await recordingsToVideoClips(enrichedRecordings, {
|
|
@@ -808,7 +811,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
808
811
|
count,
|
|
809
812
|
});
|
|
810
813
|
|
|
811
|
-
logger.debug(`[NVR
|
|
814
|
+
logger.debug(`[NVR EVENTS] Converted ${clips.length} video clips (limit: ${count || 'none'})`);
|
|
812
815
|
|
|
813
816
|
return clips;
|
|
814
817
|
} else {
|
|
@@ -893,7 +896,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
893
896
|
try {
|
|
894
897
|
await fs.promises.unlink(cachePath);
|
|
895
898
|
} catch (unlinkErr) {
|
|
896
|
-
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));
|
|
897
900
|
}
|
|
898
901
|
} else {
|
|
899
902
|
logger.debug(`[VideoClip] Using cached file: fileId=${videoId}, size=${stats.size} bytes`);
|
|
@@ -1011,7 +1014,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1011
1014
|
});
|
|
1012
1015
|
|
|
1013
1016
|
ffmpeg.on('error', (error) => {
|
|
1014
|
-
logger.error(`ffmpeg spawn error for video clip ${videoId}`, error);
|
|
1017
|
+
logger.error(`ffmpeg spawn error for video clip ${videoId}`, error?.message || String(error));
|
|
1015
1018
|
reject(error);
|
|
1016
1019
|
});
|
|
1017
1020
|
|
|
@@ -1079,7 +1082,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1079
1082
|
try {
|
|
1080
1083
|
await fs.promises.unlink(cachePath);
|
|
1081
1084
|
} catch (unlinkErr) {
|
|
1082
|
-
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));
|
|
1083
1086
|
}
|
|
1084
1087
|
} else {
|
|
1085
1088
|
logger.debug(`[Thumbnail] Using cached: fileId=${thumbnailId}, size=${stats.size} bytes`);
|
|
@@ -1645,14 +1648,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1645
1648
|
this.lastPicture = undefined;
|
|
1646
1649
|
}
|
|
1647
1650
|
|
|
1648
|
-
|
|
1649
|
-
try {
|
|
1650
|
-
this.onDeviceEvent(ScryptedInterface.VideoCamera, undefined);
|
|
1651
|
-
} catch {
|
|
1652
|
-
// best-effort
|
|
1653
|
-
}
|
|
1651
|
+
this.onDeviceEvent(ScryptedInterface.VideoCamera, undefined);
|
|
1654
1652
|
} catch (e) {
|
|
1655
|
-
logger.
|
|
1653
|
+
logger.error('Failed to restart StreamManager after settings change', e?.message || String(e));
|
|
1656
1654
|
}
|
|
1657
1655
|
}, 500);
|
|
1658
1656
|
}
|
|
@@ -1713,14 +1711,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1713
1711
|
reason: ev?.type === 'sleeping' ? 'sleeping' : 'awake',
|
|
1714
1712
|
state: ev.type === 'sleeping' ? 'sleeping' : 'awake',
|
|
1715
1713
|
}).catch((e) => {
|
|
1716
|
-
logger.warn('Error updating sleeping state', e);
|
|
1714
|
+
logger.warn('Error updating sleeping state', e?.message || String(e));
|
|
1717
1715
|
});
|
|
1718
1716
|
return;
|
|
1719
1717
|
|
|
1720
1718
|
case 'offline':
|
|
1721
1719
|
case 'online':
|
|
1722
1720
|
this.updateOnlineState(ev.type === 'online').catch((e) => {
|
|
1723
|
-
logger.warn('Error updating online state', e);
|
|
1721
|
+
logger.warn('Error updating online state', e?.message || String(e));
|
|
1724
1722
|
});
|
|
1725
1723
|
return;
|
|
1726
1724
|
|
|
@@ -1749,11 +1747,11 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1749
1747
|
}
|
|
1750
1748
|
|
|
1751
1749
|
this.processEvents({ motion, objects }).catch((e) => {
|
|
1752
|
-
logger.warn('Error processing events', e);
|
|
1750
|
+
logger.warn('Error processing events', e?.message || String(e));
|
|
1753
1751
|
});
|
|
1754
1752
|
}
|
|
1755
1753
|
catch (e) {
|
|
1756
|
-
logger.warn('Error in onSimpleEvent handler', e);
|
|
1754
|
+
logger.warn('Error in onSimpleEvent handler', e?.message || String(e));
|
|
1757
1755
|
}
|
|
1758
1756
|
}
|
|
1759
1757
|
|
|
@@ -1801,7 +1799,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
1801
1799
|
logger.log(`Subscribed to events (${selection.join(', ')}) on ${this.protocol} connection`);
|
|
1802
1800
|
}
|
|
1803
1801
|
catch (e) {
|
|
1804
|
-
logger.warn('Failed to subscribe to Baichuan events', e);
|
|
1802
|
+
logger.warn('Failed to subscribe to Baichuan events', e?.message || String(e));
|
|
1805
1803
|
return;
|
|
1806
1804
|
}
|
|
1807
1805
|
}
|
|
@@ -2141,7 +2139,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2141
2139
|
return await this.takePictureInternal(client);
|
|
2142
2140
|
});
|
|
2143
2141
|
} catch (e) {
|
|
2144
|
-
this.getBaichuanLogger().error('Error taking snapshot', e);
|
|
2142
|
+
this.getBaichuanLogger().error('Error taking snapshot', e?.message || String(e));
|
|
2145
2143
|
throw e;
|
|
2146
2144
|
}
|
|
2147
2145
|
} else {
|
|
@@ -2217,7 +2215,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2217
2215
|
});
|
|
2218
2216
|
|
|
2219
2217
|
} catch (e) {
|
|
2220
|
-
logger.warn('Failed to fetch device info', e);
|
|
2218
|
+
logger.warn('Failed to fetch device info', e?.message || String(e));
|
|
2221
2219
|
}
|
|
2222
2220
|
}
|
|
2223
2221
|
|
|
@@ -2440,37 +2438,32 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2440
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 })) });
|
|
2441
2439
|
|
|
2442
2440
|
for (const supportedStream of supportedStreams) {
|
|
2443
|
-
const { id, metadata, url, name, container,
|
|
2441
|
+
const { id, metadata, url, name, container, lens } = supportedStream;
|
|
2444
2442
|
|
|
2445
2443
|
// Composite streams are re-encoded to H.264 by the library (ffmpeg/libx264).
|
|
2446
2444
|
// Do not infer codec from underlying camera metadata.
|
|
2447
2445
|
const isComposite = id.startsWith('composite_') || lens === 'composite';
|
|
2448
|
-
const codec =
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
let finalUrl = url;
|
|
2458
|
-
const variantFromIdOrUrl = extractVariantFromStreamId(id, url);
|
|
2459
|
-
const variantToInject = (nativeVariant && nativeVariant !== 'default')
|
|
2460
|
-
? nativeVariant
|
|
2461
|
-
: variantFromIdOrUrl;
|
|
2462
|
-
|
|
2463
|
-
if (variantToInject && container === 'rtp') {
|
|
2464
|
-
try {
|
|
2465
|
-
const urlObj = new URL(url);
|
|
2466
|
-
if (!urlObj.searchParams.has('variant')) {
|
|
2467
|
-
urlObj.searchParams.set('variant', variantToInject);
|
|
2468
|
-
finalUrl = urlObj.toString();
|
|
2469
|
-
}
|
|
2470
|
-
} catch {
|
|
2471
|
-
// 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';
|
|
2472
2455
|
}
|
|
2473
|
-
|
|
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;
|
|
2474
2467
|
|
|
2475
2468
|
streams.push({
|
|
2476
2469
|
id,
|
|
@@ -2513,43 +2506,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2513
2506
|
|
|
2514
2507
|
const selected = selectStreamOption(vsos, vso);
|
|
2515
2508
|
|
|
2516
|
-
// If the request explicitly asks for a variant (e.g. native_telephoto_main),
|
|
2517
|
-
// never override it with the device's variantType preference.
|
|
2518
|
-
// const requestedVariant = vso?.id ? extractVariantFromStreamId(vso.id, undefined) : undefined;
|
|
2519
|
-
|
|
2520
|
-
// If we have variantType set and the selected stream doesn't have the variant,
|
|
2521
|
-
// try to find a stream with the correct variant that matches the profile
|
|
2522
|
-
// const variantType = this.storageSettings.values.variantType;
|
|
2523
|
-
// if (!requestedVariant && variantType && variantType !== 'default') {
|
|
2524
|
-
// const profile = parseStreamProfileFromId(selected.id) || 'main';
|
|
2525
|
-
|
|
2526
|
-
// // On NVR, firmwares vary: some expose the tele lens as 'autotrack', others as 'telephoto'.
|
|
2527
|
-
// // When variantType is set, prefer that variant but fall back to the other tele variant if present.
|
|
2528
|
-
// const preferred = variantType as 'autotrack' | 'telephoto';
|
|
2529
|
-
// const fallbacks: Array<'autotrack' | 'telephoto'> = this.isOnNvr && preferred === 'telephoto'
|
|
2530
|
-
// ? ['telephoto', 'autotrack']
|
|
2531
|
-
// : this.isOnNvr && preferred === 'autotrack'
|
|
2532
|
-
// ? ['autotrack', 'telephoto']
|
|
2533
|
-
// : [preferred];
|
|
2534
|
-
|
|
2535
|
-
// const extractedVariant = extractVariantFromStreamId(selected.id, selected.url);
|
|
2536
|
-
// for (const v of fallbacks) {
|
|
2537
|
-
// const variantId = `native_${v}_${profile}`;
|
|
2538
|
-
// const variantStream = vsos?.find(s => s.id === variantId);
|
|
2539
|
-
// if (!variantStream) {
|
|
2540
|
-
// logger.debug(`Variant stream '${variantId}' not found in available streams`);
|
|
2541
|
-
// continue;
|
|
2542
|
-
// }
|
|
2543
|
-
// // Only use variant stream if the selected one doesn't already have a variant,
|
|
2544
|
-
// // or if the selected one has a different variant than what we want.
|
|
2545
|
-
// if (!extractedVariant || extractedVariant !== v) {
|
|
2546
|
-
// logger.log(`Preferring variant stream: '${variantId}' over '${selected.id}' (variantType='${variantType}')`);
|
|
2547
|
-
// selected = variantStream;
|
|
2548
|
-
// }
|
|
2549
|
-
// break;
|
|
2550
|
-
// }
|
|
2551
|
-
// }
|
|
2552
|
-
|
|
2553
2509
|
logger.log(`Selected stream: id='${selected.id}', url='${selected.url}'`);
|
|
2554
2510
|
|
|
2555
2511
|
if (selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
|
|
@@ -2566,76 +2522,21 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2566
2522
|
throw new Error('StreamManager not initialized');
|
|
2567
2523
|
}
|
|
2568
2524
|
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
if (selected.id?.startsWith('composite_')) {
|
|
2573
|
-
const profile = parseStreamProfileFromId(selected.id.replace('composite_', '')) || 'main';
|
|
2574
|
-
// Include variantType in streamKey to ensure each variantType has its own unique socket
|
|
2575
|
-
// This is important for multifocal devices where different variantTypes may request composite streams
|
|
2576
|
-
const variantType = this.storageSettings.values.variantType || 'default';
|
|
2577
|
-
const streamKey = `composite_${variantType}_${profile}`;
|
|
2578
|
-
|
|
2579
|
-
logger.log(`Creating composite stream: profile=${profile}, variantType=${variantType}, streamKey=${streamKey}`);
|
|
2580
|
-
|
|
2581
|
-
const createStreamFn = async () => {
|
|
2582
|
-
return await createRfc4571CompositeMediaObjectFromStreamManager({
|
|
2583
|
-
streamManager: this.streamManager,
|
|
2584
|
-
profile,
|
|
2585
|
-
streamKey,
|
|
2586
|
-
selected,
|
|
2587
|
-
sourceId: this.id,
|
|
2588
|
-
variantType,
|
|
2589
|
-
});
|
|
2590
|
-
};
|
|
2591
|
-
|
|
2592
|
-
return await this.withBaichuanRetry(createStreamFn);
|
|
2525
|
+
const streamKey = selected.id;
|
|
2526
|
+
if (!streamKey) {
|
|
2527
|
+
throw new Error('Missing streamKey (selected.id) for RTP stream');
|
|
2593
2528
|
}
|
|
2594
2529
|
|
|
2595
|
-
|
|
2596
|
-
const profile = parseStreamProfileFromId(selected.id) || 'main';
|
|
2597
|
-
const channel = this.storageSettings.values.rtspChannel;
|
|
2598
|
-
// Extract variant from stream ID or URL if present (e.g., "autotrack" from "native_autotrack_main" or "?variant=autotrack")
|
|
2599
|
-
let variant = extractVariantFromStreamId(selected.id, selected.url);
|
|
2600
|
-
|
|
2601
|
-
// Fallback: if no variant found in stream ID/URL, use variantType from device settings.
|
|
2602
|
-
// IMPORTANT:
|
|
2603
|
-
// - On NVR/Hub multifocal setups, the tele lens is often selected via a variant (autotrack/telephoto) on the same channel.
|
|
2604
|
-
// - On standalone TrackMix (no NVR), the tele lens is selected via channel=1 (no variant).
|
|
2605
|
-
// Forcing a variant on standalone can result in a started stream with no frames.
|
|
2606
|
-
if (!variant && this.storageSettings.values.variantType && this.storageSettings.values.variantType !== 'default') {
|
|
2607
|
-
if (this.isOnNvr) {
|
|
2608
|
-
variant = this.storageSettings.values.variantType as 'autotrack' | 'telephoto';
|
|
2609
|
-
logger.log(`Using variant from device settings: '${variant}' (not found in stream ID/URL)`);
|
|
2610
|
-
} else {
|
|
2611
|
-
logger.log(
|
|
2612
|
-
`Ignoring device variantType '${this.storageSettings.values.variantType}' for standalone stream (channel-based lens selection)`
|
|
2613
|
-
);
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
logger.log(`Stream selection: id='${selected.id}', profile='${profile}', channel=${channel}, variant='${variant || 'default'}'`);
|
|
2618
|
-
|
|
2619
|
-
// Include variant in streamKey to distinguish streams with different variants
|
|
2620
|
-
const streamKey = variant ? `${channel}_${variant}_${profile}` : `${channel}_${profile}`;
|
|
2621
|
-
|
|
2622
|
-
const createStreamFn = async () => {
|
|
2623
|
-
// Honor the requested variant. Some NVR firmwares label the tele lens as either
|
|
2624
|
-
// 'autotrack' or 'telephoto', and the library exposes both when available.
|
|
2625
|
-
logger.log(`Creating RFC4571 stream: channel=${channel}, profile=${profile}, variant=${variant || 'default'}, streamKey=${streamKey}`);
|
|
2530
|
+
logger.log(`Creating RFC4571 stream: streamKey='${streamKey}'`);
|
|
2626
2531
|
|
|
2532
|
+
return await this.withBaichuanRetry(async () => {
|
|
2627
2533
|
return await createRfc4571MediaObjectFromStreamManager({
|
|
2628
2534
|
streamManager: this.streamManager!,
|
|
2629
|
-
channel,
|
|
2630
|
-
profile,
|
|
2631
2535
|
streamKey,
|
|
2632
|
-
variant,
|
|
2633
2536
|
selected,
|
|
2634
2537
|
sourceId: this.id,
|
|
2635
2538
|
});
|
|
2636
|
-
};
|
|
2637
|
-
|
|
2638
|
-
return await this.withBaichuanRetry(createStreamFn);
|
|
2539
|
+
});
|
|
2639
2540
|
}
|
|
2640
2541
|
|
|
2641
2542
|
async ensureClient(): Promise<ReolinkBaichuanApi> {
|
|
@@ -2646,7 +2547,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2646
2547
|
return await this.multiFocalDevice.ensureClient();
|
|
2647
2548
|
}
|
|
2648
2549
|
|
|
2649
|
-
// Use base class implementation
|
|
2650
2550
|
return await this.ensureBaichuanClient();
|
|
2651
2551
|
}
|
|
2652
2552
|
|
|
@@ -2654,7 +2554,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2654
2554
|
this.cachedVideoStreamOptions = undefined;
|
|
2655
2555
|
}
|
|
2656
2556
|
|
|
2657
|
-
// PTZ Presets methods
|
|
2658
2557
|
getSelectedPresetId(): number | undefined {
|
|
2659
2558
|
const s = this.storageSettings.values.ptzSelectedPreset;
|
|
2660
2559
|
if (!s) return undefined;
|
|
@@ -2767,16 +2666,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2767
2666
|
this.storageSettings.settings.clipsSource.hide = !this.nvrDevice;
|
|
2768
2667
|
this.storageSettings.settings.clipsSource.defaultValue = this.nvrDevice ? "NVR" : "Device";
|
|
2769
2668
|
|
|
2770
|
-
|
|
2771
|
-
|
|
2669
|
+
this.storageSettings.settings.diagnosticsRun.hide = !!this.multiFocalDevice;
|
|
2670
|
+
this.storageSettings.settings.diagnosticsOutputPath.hide = !!this.multiFocalDevice;
|
|
2772
2671
|
|
|
2773
|
-
// for (const key of allSettingKeys) {
|
|
2774
|
-
// const setting = this.storageSettings.settings[key];
|
|
2775
|
-
// if (['Videoclips', 'PTZ'].includes(setting.subgroup)) {
|
|
2776
|
-
// setting.hide = true;
|
|
2777
|
-
// }
|
|
2778
|
-
// }
|
|
2779
|
-
// }
|
|
2780
2672
|
this.storageSettings.settings.enableVideoclips.hide = !!this.multiFocalDevice;
|
|
2781
2673
|
this.storageSettings.settings.videoclipsDaysToPreload.hide = !!this.multiFocalDevice;
|
|
2782
2674
|
this.storageSettings.settings.videoclipsRegularChecks.hide = !!this.multiFocalDevice;
|
|
@@ -2817,14 +2709,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2817
2709
|
await this.subscribeToEvents();
|
|
2818
2710
|
}
|
|
2819
2711
|
catch (e) {
|
|
2820
|
-
logger.
|
|
2712
|
+
logger.error('Failed to subscribe to Baichuan events', e?.message || String(e));
|
|
2821
2713
|
}
|
|
2822
2714
|
|
|
2823
2715
|
try {
|
|
2824
2716
|
this.initStreamManager();
|
|
2825
2717
|
}
|
|
2826
2718
|
catch (e) {
|
|
2827
|
-
logger.
|
|
2719
|
+
logger.error('Failed to initialize StreamManager', e?.message || String(e));
|
|
2828
2720
|
}
|
|
2829
2721
|
|
|
2830
2722
|
const { hasIntercom, hasPtz } = this.getAbilities();
|
|
@@ -2833,7 +2725,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2833
2725
|
this.intercom = new ReolinkBaichuanIntercom(this);
|
|
2834
2726
|
}
|
|
2835
2727
|
|
|
2836
|
-
if (hasPtz
|
|
2728
|
+
if (hasPtz) {
|
|
2837
2729
|
const choices = (this.presets || []).map((preset: any) => preset.id + '=' + preset.name);
|
|
2838
2730
|
|
|
2839
2731
|
this.storageSettings.settings.presets.choices = choices;
|
|
@@ -2897,7 +2789,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2897
2789
|
}
|
|
2898
2790
|
} catch (e) {
|
|
2899
2791
|
// Silently ignore errors in sleep check to avoid spam
|
|
2900
|
-
this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
|
|
2792
|
+
this.getBaichuanLogger().debug('Error in updateSleepingState:', e?.message || String(e));
|
|
2901
2793
|
}
|
|
2902
2794
|
}
|
|
2903
2795
|
|
|
@@ -2912,7 +2804,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2912
2804
|
}
|
|
2913
2805
|
} catch (e) {
|
|
2914
2806
|
// Silently ignore errors in sleep check to avoid spam
|
|
2915
|
-
this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
|
|
2807
|
+
this.getBaichuanLogger().debug('Error in updateOnlineState:', e?.message || String(e));
|
|
2916
2808
|
}
|
|
2917
2809
|
}
|
|
2918
2810
|
|
|
@@ -2922,7 +2814,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2922
2814
|
await this.baichuanApi?.close();
|
|
2923
2815
|
}
|
|
2924
2816
|
catch (e) {
|
|
2925
|
-
this.getBaichuanLogger().
|
|
2817
|
+
this.getBaichuanLogger().error('Error closing Baichuan client during reset', e?.message || String(e));
|
|
2926
2818
|
}
|
|
2927
2819
|
finally {
|
|
2928
2820
|
this.baichuanApi = undefined;
|
|
@@ -2932,7 +2824,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2932
2824
|
|
|
2933
2825
|
if (reason) {
|
|
2934
2826
|
const message = reason?.message || reason?.toString?.() || reason;
|
|
2935
|
-
this.getBaichuanLogger().
|
|
2827
|
+
this.getBaichuanLogger().error(`Baichuan client reset requested: ${message}`);
|
|
2936
2828
|
}
|
|
2937
2829
|
}
|
|
2938
2830
|
|
|
@@ -3022,7 +2914,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3022
2914
|
// Ensure we have a client connection
|
|
3023
2915
|
const api = await this.ensureClient();
|
|
3024
2916
|
if (!api) {
|
|
3025
|
-
this.getBaichuanLogger().
|
|
2917
|
+
this.getBaichuanLogger().error('Failed to ensure client connection for battery update');
|
|
3026
2918
|
return;
|
|
3027
2919
|
}
|
|
3028
2920
|
|
|
@@ -3036,7 +2928,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3036
2928
|
await api.wakeUp(channel, { waitAfterWakeMs: 2000 });
|
|
3037
2929
|
logger.log('Wake command sent, waiting for camera to wake up...');
|
|
3038
2930
|
} catch (wakeError) {
|
|
3039
|
-
logger.
|
|
2931
|
+
logger.error('Failed to wake up camera:', wakeError?.message || String(wakeError));
|
|
3040
2932
|
return;
|
|
3041
2933
|
}
|
|
3042
2934
|
|
|
@@ -3057,7 +2949,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3057
2949
|
}
|
|
3058
2950
|
|
|
3059
2951
|
if (!awake) {
|
|
3060
|
-
logger.
|
|
2952
|
+
logger.error('Camera did not wake up within timeout, skipping update');
|
|
3061
2953
|
return;
|
|
3062
2954
|
}
|
|
3063
2955
|
} else if (sleepStatus.state === 'awake') {
|
|
@@ -3069,14 +2961,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3069
2961
|
try {
|
|
3070
2962
|
await this.updateBatteryInfo();
|
|
3071
2963
|
} catch (e) {
|
|
3072
|
-
logger.
|
|
2964
|
+
logger.error('Failed to get battery info during periodic update:', e?.message || String(e));
|
|
3073
2965
|
}
|
|
3074
2966
|
|
|
3075
2967
|
// 2. Align auxiliary devices state
|
|
3076
2968
|
try {
|
|
3077
2969
|
await this.alignAuxDevicesState();
|
|
3078
2970
|
} catch (e) {
|
|
3079
|
-
logger.
|
|
2971
|
+
logger.error('Failed to align auxiliary devices state:', e?.message || String(e));
|
|
3080
2972
|
}
|
|
3081
2973
|
|
|
3082
2974
|
// 3. Update snapshot
|
|
@@ -3085,10 +2977,10 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3085
2977
|
await this.takePicture();
|
|
3086
2978
|
logger.log('Snapshot updated during periodic update');
|
|
3087
2979
|
} catch (snapshotError) {
|
|
3088
|
-
logger.
|
|
2980
|
+
logger.error('Failed to update snapshot during periodic update:', snapshotError?.message || String(snapshotError));
|
|
3089
2981
|
}
|
|
3090
2982
|
} catch (e) {
|
|
3091
|
-
logger.
|
|
2983
|
+
logger.error('Failed to update battery and snapshot', e?.message || String(e));
|
|
3092
2984
|
} finally {
|
|
3093
2985
|
// Clear the promise when done (success or failure)
|
|
3094
2986
|
this.batteryUpdatePromise = undefined;
|
|
@@ -3123,7 +3015,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3123
3015
|
const sleepStatus = api.getSleepStatus({ channel });
|
|
3124
3016
|
await this.updateSleepingState(sleepStatus);
|
|
3125
3017
|
} catch (e) {
|
|
3126
|
-
logger.
|
|
3018
|
+
logger.error('Error checking sleeping state:', e?.message || String(e));
|
|
3127
3019
|
}
|
|
3128
3020
|
}, 5_000);
|
|
3129
3021
|
}
|
|
@@ -3135,7 +3027,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3135
3027
|
try {
|
|
3136
3028
|
await this.updateBatteryAndSnapshot();
|
|
3137
3029
|
} catch (e) {
|
|
3138
|
-
logger.
|
|
3030
|
+
logger.error('Error updating battery and snapshot:', e?.message || String(e));
|
|
3139
3031
|
}
|
|
3140
3032
|
}, updateIntervalMs);
|
|
3141
3033
|
|
|
@@ -3145,7 +3037,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3145
3037
|
try {
|
|
3146
3038
|
await this.alignAuxDevicesState();
|
|
3147
3039
|
} catch (e) {
|
|
3148
|
-
logger.
|
|
3040
|
+
logger.error('Error aligning auxiliary devices state:', e?.message || String(e));
|
|
3149
3041
|
}
|
|
3150
3042
|
}, 10_000);
|
|
3151
3043
|
|