@apocaliss92/scrypted-reolink-native 0.2.9 → 0.2.11
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 +83 -184
- 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 +59 -100
- 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, channel, profile, nativeVariant } = 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
|
-
const isComposite =
|
|
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
|
|
2445
|
+
const isComposite = lens === 'composite' || channel === undefined;
|
|
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,
|
|
@@ -2479,7 +2472,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2479
2472
|
container,
|
|
2480
2473
|
video: { codec, width: metadata.width, height: metadata.height },
|
|
2481
2474
|
// audio: { codec: metadata.audioCodec }
|
|
2482
|
-
|
|
2475
|
+
|
|
2476
|
+
// Provide explicit RFC4571 metadata so stream-utils can avoid parsing the streamKey.
|
|
2477
|
+
reolinkRfc4571: {
|
|
2478
|
+
channel,
|
|
2479
|
+
profile,
|
|
2480
|
+
variant: nativeVariant,
|
|
2481
|
+
},
|
|
2482
|
+
} as any)
|
|
2483
2483
|
}
|
|
2484
2484
|
} catch (e) {
|
|
2485
2485
|
if (!this.isRecoverableBaichuanError?.(e)) {
|
|
@@ -2513,43 +2513,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2513
2513
|
|
|
2514
2514
|
const selected = selectStreamOption(vsos, vso);
|
|
2515
2515
|
|
|
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
2516
|
logger.log(`Selected stream: id='${selected.id}', url='${selected.url}'`);
|
|
2554
2517
|
|
|
2555
2518
|
if (selected.url && (selected.container === 'rtsp' || selected.container === 'rtmp')) {
|
|
@@ -2566,76 +2529,21 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2566
2529
|
throw new Error('StreamManager not initialized');
|
|
2567
2530
|
}
|
|
2568
2531
|
|
|
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);
|
|
2532
|
+
const streamKey = selected.id;
|
|
2533
|
+
if (!streamKey) {
|
|
2534
|
+
throw new Error('Missing streamKey (selected.id) for RTP stream');
|
|
2593
2535
|
}
|
|
2594
2536
|
|
|
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}`);
|
|
2537
|
+
logger.log(`Creating RFC4571 stream: streamKey='${streamKey}'`);
|
|
2626
2538
|
|
|
2539
|
+
return await this.withBaichuanRetry(async () => {
|
|
2627
2540
|
return await createRfc4571MediaObjectFromStreamManager({
|
|
2628
2541
|
streamManager: this.streamManager!,
|
|
2629
|
-
channel,
|
|
2630
|
-
profile,
|
|
2631
2542
|
streamKey,
|
|
2632
|
-
variant,
|
|
2633
2543
|
selected,
|
|
2634
2544
|
sourceId: this.id,
|
|
2635
2545
|
});
|
|
2636
|
-
};
|
|
2637
|
-
|
|
2638
|
-
return await this.withBaichuanRetry(createStreamFn);
|
|
2546
|
+
});
|
|
2639
2547
|
}
|
|
2640
2548
|
|
|
2641
2549
|
async ensureClient(): Promise<ReolinkBaichuanApi> {
|
|
@@ -2646,7 +2554,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2646
2554
|
return await this.multiFocalDevice.ensureClient();
|
|
2647
2555
|
}
|
|
2648
2556
|
|
|
2649
|
-
// Use base class implementation
|
|
2650
2557
|
return await this.ensureBaichuanClient();
|
|
2651
2558
|
}
|
|
2652
2559
|
|
|
@@ -2654,7 +2561,6 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2654
2561
|
this.cachedVideoStreamOptions = undefined;
|
|
2655
2562
|
}
|
|
2656
2563
|
|
|
2657
|
-
// PTZ Presets methods
|
|
2658
2564
|
getSelectedPresetId(): number | undefined {
|
|
2659
2565
|
const s = this.storageSettings.values.ptzSelectedPreset;
|
|
2660
2566
|
if (!s) return undefined;
|
|
@@ -2767,16 +2673,9 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2767
2673
|
this.storageSettings.settings.clipsSource.hide = !this.nvrDevice;
|
|
2768
2674
|
this.storageSettings.settings.clipsSource.defaultValue = this.nvrDevice ? "NVR" : "Device";
|
|
2769
2675
|
|
|
2770
|
-
|
|
2771
|
-
|
|
2676
|
+
this.storageSettings.settings.diagnosticsRun.hide = !!this.multiFocalDevice;
|
|
2677
|
+
this.storageSettings.settings.diagnosticsOutputPath.hide = !!this.multiFocalDevice;
|
|
2772
2678
|
|
|
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
2679
|
this.storageSettings.settings.enableVideoclips.hide = !!this.multiFocalDevice;
|
|
2781
2680
|
this.storageSettings.settings.videoclipsDaysToPreload.hide = !!this.multiFocalDevice;
|
|
2782
2681
|
this.storageSettings.settings.videoclipsRegularChecks.hide = !!this.multiFocalDevice;
|
|
@@ -2817,14 +2716,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2817
2716
|
await this.subscribeToEvents();
|
|
2818
2717
|
}
|
|
2819
2718
|
catch (e) {
|
|
2820
|
-
logger.
|
|
2719
|
+
logger.error('Failed to subscribe to Baichuan events', e?.message || String(e));
|
|
2821
2720
|
}
|
|
2822
2721
|
|
|
2823
2722
|
try {
|
|
2824
2723
|
this.initStreamManager();
|
|
2825
2724
|
}
|
|
2826
2725
|
catch (e) {
|
|
2827
|
-
logger.
|
|
2726
|
+
logger.error('Failed to initialize StreamManager', e?.message || String(e));
|
|
2828
2727
|
}
|
|
2829
2728
|
|
|
2830
2729
|
const { hasIntercom, hasPtz } = this.getAbilities();
|
|
@@ -2897,7 +2796,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2897
2796
|
}
|
|
2898
2797
|
} catch (e) {
|
|
2899
2798
|
// Silently ignore errors in sleep check to avoid spam
|
|
2900
|
-
this.getBaichuanLogger().debug('Error in updateSleepingState:', e);
|
|
2799
|
+
this.getBaichuanLogger().debug('Error in updateSleepingState:', e?.message || String(e));
|
|
2901
2800
|
}
|
|
2902
2801
|
}
|
|
2903
2802
|
|
|
@@ -2912,7 +2811,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2912
2811
|
}
|
|
2913
2812
|
} catch (e) {
|
|
2914
2813
|
// Silently ignore errors in sleep check to avoid spam
|
|
2915
|
-
this.getBaichuanLogger().debug('Error in updateOnlineState:', e);
|
|
2814
|
+
this.getBaichuanLogger().debug('Error in updateOnlineState:', e?.message || String(e));
|
|
2916
2815
|
}
|
|
2917
2816
|
}
|
|
2918
2817
|
|
|
@@ -2922,7 +2821,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2922
2821
|
await this.baichuanApi?.close();
|
|
2923
2822
|
}
|
|
2924
2823
|
catch (e) {
|
|
2925
|
-
this.getBaichuanLogger().
|
|
2824
|
+
this.getBaichuanLogger().error('Error closing Baichuan client during reset', e?.message || String(e));
|
|
2926
2825
|
}
|
|
2927
2826
|
finally {
|
|
2928
2827
|
this.baichuanApi = undefined;
|
|
@@ -2932,7 +2831,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
2932
2831
|
|
|
2933
2832
|
if (reason) {
|
|
2934
2833
|
const message = reason?.message || reason?.toString?.() || reason;
|
|
2935
|
-
this.getBaichuanLogger().
|
|
2834
|
+
this.getBaichuanLogger().error(`Baichuan client reset requested: ${message}`);
|
|
2936
2835
|
}
|
|
2937
2836
|
}
|
|
2938
2837
|
|
|
@@ -3022,7 +2921,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3022
2921
|
// Ensure we have a client connection
|
|
3023
2922
|
const api = await this.ensureClient();
|
|
3024
2923
|
if (!api) {
|
|
3025
|
-
this.getBaichuanLogger().
|
|
2924
|
+
this.getBaichuanLogger().error('Failed to ensure client connection for battery update');
|
|
3026
2925
|
return;
|
|
3027
2926
|
}
|
|
3028
2927
|
|
|
@@ -3036,7 +2935,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3036
2935
|
await api.wakeUp(channel, { waitAfterWakeMs: 2000 });
|
|
3037
2936
|
logger.log('Wake command sent, waiting for camera to wake up...');
|
|
3038
2937
|
} catch (wakeError) {
|
|
3039
|
-
logger.
|
|
2938
|
+
logger.error('Failed to wake up camera:', wakeError?.message || String(wakeError));
|
|
3040
2939
|
return;
|
|
3041
2940
|
}
|
|
3042
2941
|
|
|
@@ -3057,7 +2956,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3057
2956
|
}
|
|
3058
2957
|
|
|
3059
2958
|
if (!awake) {
|
|
3060
|
-
logger.
|
|
2959
|
+
logger.error('Camera did not wake up within timeout, skipping update');
|
|
3061
2960
|
return;
|
|
3062
2961
|
}
|
|
3063
2962
|
} else if (sleepStatus.state === 'awake') {
|
|
@@ -3069,14 +2968,14 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3069
2968
|
try {
|
|
3070
2969
|
await this.updateBatteryInfo();
|
|
3071
2970
|
} catch (e) {
|
|
3072
|
-
logger.
|
|
2971
|
+
logger.error('Failed to get battery info during periodic update:', e?.message || String(e));
|
|
3073
2972
|
}
|
|
3074
2973
|
|
|
3075
2974
|
// 2. Align auxiliary devices state
|
|
3076
2975
|
try {
|
|
3077
2976
|
await this.alignAuxDevicesState();
|
|
3078
2977
|
} catch (e) {
|
|
3079
|
-
logger.
|
|
2978
|
+
logger.error('Failed to align auxiliary devices state:', e?.message || String(e));
|
|
3080
2979
|
}
|
|
3081
2980
|
|
|
3082
2981
|
// 3. Update snapshot
|
|
@@ -3085,10 +2984,10 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3085
2984
|
await this.takePicture();
|
|
3086
2985
|
logger.log('Snapshot updated during periodic update');
|
|
3087
2986
|
} catch (snapshotError) {
|
|
3088
|
-
logger.
|
|
2987
|
+
logger.error('Failed to update snapshot during periodic update:', snapshotError?.message || String(snapshotError));
|
|
3089
2988
|
}
|
|
3090
2989
|
} catch (e) {
|
|
3091
|
-
logger.
|
|
2990
|
+
logger.error('Failed to update battery and snapshot', e?.message || String(e));
|
|
3092
2991
|
} finally {
|
|
3093
2992
|
// Clear the promise when done (success or failure)
|
|
3094
2993
|
this.batteryUpdatePromise = undefined;
|
|
@@ -3123,7 +3022,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3123
3022
|
const sleepStatus = api.getSleepStatus({ channel });
|
|
3124
3023
|
await this.updateSleepingState(sleepStatus);
|
|
3125
3024
|
} catch (e) {
|
|
3126
|
-
logger.
|
|
3025
|
+
logger.error('Error checking sleeping state:', e?.message || String(e));
|
|
3127
3026
|
}
|
|
3128
3027
|
}, 5_000);
|
|
3129
3028
|
}
|
|
@@ -3135,7 +3034,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3135
3034
|
try {
|
|
3136
3035
|
await this.updateBatteryAndSnapshot();
|
|
3137
3036
|
} catch (e) {
|
|
3138
|
-
logger.
|
|
3037
|
+
logger.error('Error updating battery and snapshot:', e?.message || String(e));
|
|
3139
3038
|
}
|
|
3140
3039
|
}, updateIntervalMs);
|
|
3141
3040
|
|
|
@@ -3145,7 +3044,7 @@ export class ReolinkCamera extends BaseBaichuanClass implements VideoCamera, Cam
|
|
|
3145
3044
|
try {
|
|
3146
3045
|
await this.alignAuxDevicesState();
|
|
3147
3046
|
} catch (e) {
|
|
3148
|
-
logger.
|
|
3047
|
+
logger.error('Error aligning auxiliary devices state:', e?.message || String(e));
|
|
3149
3048
|
}
|
|
3150
3049
|
}, 10_000);
|
|
3151
3050
|
|