@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/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apocaliss92/scrypted-reolink-native",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Use any reolink camera with Scrypted, even older/unsupported models without HTTP protocol support",
5
5
  "author": "@apocaliss92",
6
6
  "license": "Apache",
@@ -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().warn(`Siren toggle: turnOff failed (device=${this.nativeId})`, e?.message || String(e));
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().warn(`Siren toggle: turnOn failed (device=${this.nativeId})`, e?.message || String(e));
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
- // Use listEnrichedVodFiles which properly parses filenames and extracts detection info
789
- logger.debug(`[NVR VOD] Searching for video clips: channel=${channel}, start=${start.toISOString()}, end=${end.toISOString()}`);
790
- // Filter to only include recordings within the requested time window
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
- streamType: "main",
796
- source: "baichuan"
798
+ channels: [channel],
799
+ streamType: "mainStream",
797
800
  });
798
801
 
799
- logger.debug(`[NVR VOD] Found ${enrichedRecordings.length} enriched recordings from 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 VOD] Converted ${clips.length} video clips (limit: ${count || 'none'})`);
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
- // Notify consumers (e.g. prebuffer) that stream configuration changed.
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.warn('Failed to restart StreamManager after settings change', e);
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, nativeVariant, lens } = supportedStream;
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 = id.startsWith('composite_') || lens === 'composite';
2448
- const codec = isComposite
2449
- ? 'h264'
2450
- : String(metadata.videoEncType || "").includes("264")
2451
- ? "h264"
2452
- : String(metadata.videoEncType || "").includes("265")
2453
- ? "h265"
2454
- : String(metadata.videoEncType || "").toLowerCase();
2455
-
2456
- // Preserve variant information for native RTP streams by ensuring the URL contains it.
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
- // Check if this is a composite stream request (for multifocal devices)
2570
- // const isComposite = selected.id?.startsWith('composite_');
2571
- // if (isComposite && this.options && (this.options.type === 'multi-focal' || this.options.type === 'multi-focal-battery')) {
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
- // Regular stream for single channel
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
- // if (!!this.multiFocalDevice) {
2771
- // const allSettingKeys = Object.keys(this.storageSettings.settings);
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.warn('Failed to subscribe to Baichuan events', e);
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.warn('Failed to initialize StreamManager', e);
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().warn('Error closing Baichuan client during reset', e?.message || String(e));
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().warn(`Baichuan client reset requested: ${message}`);
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().warn('Failed to ensure client connection for battery update');
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.warn('Failed to wake up camera:', wakeError);
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.warn('Camera did not wake up within timeout, skipping update');
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.warn('Failed to get battery info during periodic update:', e);
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.warn('Failed to align auxiliary devices state:', e);
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.warn('Failed to update snapshot during periodic update:', snapshotError);
2987
+ logger.error('Failed to update snapshot during periodic update:', snapshotError?.message || String(snapshotError));
3089
2988
  }
3090
2989
  } catch (e) {
3091
- logger.warn('Failed to update battery and snapshot', e);
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.warn('Error checking sleeping state:', e?.message || String(e));
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.warn('Error updating battery and snapshot:', e?.message || String(e));
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.warn('Error aligning auxiliary devices state:', e?.message || String(e));
3047
+ logger.error('Error aligning auxiliary devices state:', e?.message || String(e));
3149
3048
  }
3150
3049
  }, 10_000);
3151
3050