@apocaliss92/nodelink-js 0.4.15 → 0.4.17

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.
@@ -12,7 +12,7 @@ import {
12
12
  sampleStreams,
13
13
  sanitizeFixtureData,
14
14
  testChannelStreams
15
- } from "./chunk-2JNXKT3C.js";
15
+ } from "./chunk-S2UTGNFN.js";
16
16
  import "./chunk-C57QV7IL.js";
17
17
  export {
18
18
  captureModelFixtures,
@@ -29,4 +29,4 @@ export {
29
29
  sanitizeFixtureData,
30
30
  testChannelStreams
31
31
  };
32
- //# sourceMappingURL=DiagnosticsTools-BQOWJ23L.js.map
32
+ //# sourceMappingURL=DiagnosticsTools-HO3VI6D5.js.map
@@ -30,7 +30,7 @@ import {
30
30
  runAllDiagnosticsConsecutively,
31
31
  runMultifocalDiagnosticsConsecutively,
32
32
  xmlEscape
33
- } from "./chunk-2JNXKT3C.js";
33
+ } from "./chunk-S2UTGNFN.js";
34
34
  import {
35
35
  BC_CLASS_FILE_DOWNLOAD,
36
36
  BC_CLASS_LEGACY,
@@ -10919,7 +10919,15 @@ var applyFloodlightSettingsToXml = (xml, settings) => {
10919
10919
  // src/reolink/baichuan/ReolinkBaichuanApi.ts
10920
10920
  var DUAL_LENS_DUAL_MOTION_MODELS = /* @__PURE__ */ new Set([
10921
10921
  "Reolink Duo PoE",
10922
- "Reolink Duo WiFi"
10922
+ "Reolink Duo WiFi",
10923
+ // Duo 2 family
10924
+ "Reolink Duo 2 PoE",
10925
+ "Reolink Duo 2 WiFi",
10926
+ // Duo 3 family — physically dual-sensor, marketed as a stitched 16MP feed
10927
+ // (the firmware exposes a single logical channel, so callers should still
10928
+ // check `getDualLensChannelInfo` for the actual channel topology).
10929
+ "Reolink Duo 3 PoE",
10930
+ "Reolink Duo 3 WiFi"
10923
10931
  ]);
10924
10932
  var DUAL_LENS_SINGLE_MOTION_MODELS = /* @__PURE__ */ new Set([
10925
10933
  "Reolink TrackMix",
@@ -10934,7 +10942,10 @@ var DUAL_LENS_MODELS = /* @__PURE__ */ new Set([
10934
10942
  ]);
10935
10943
  var isDualLenseModel = (model) => {
10936
10944
  const lower = model.toLowerCase();
10937
- return Array.from(DUAL_LENS_MODELS).some((m) => m.toLowerCase() === lower) || lower.includes("trackmix") || lower.includes("trackflex");
10945
+ if (Array.from(DUAL_LENS_MODELS).some((m) => m.toLowerCase() === lower)) {
10946
+ return true;
10947
+ }
10948
+ return lower.includes("trackmix") || lower.includes("trackflex") || /\bduo\b/.test(lower);
10938
10949
  };
10939
10950
  var NVR_HUB_EXACT_TYPES = ["NVR", "WIFI_NVR", "HOMEHUB"];
10940
10951
  var NVR_HUB_MODEL_PATTERNS = [
@@ -11285,14 +11296,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
11285
11296
  // _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
11286
11297
  detectionEventListeners = /* @__PURE__ */ new Set();
11287
11298
  detectionEventStreamHooks = /* @__PURE__ */ new Map();
11288
- // Auto-managed substream for `onObjectDetections` listeners. Reference-counted
11289
- // by the listener set: the substream is opened on the first listener and torn
11290
- // down with the last one. Mirrors `onSimpleEvent`'s subscribe/unsubscribe
11291
- // lifecycle so a caller never has to manage a video stream just to read AI
11292
- // detections.
11293
- objectDetectionListeners = /* @__PURE__ */ new Set();
11294
- objectDetectionStream;
11295
- objectDetectionStreamStartInFlight;
11299
+ // Auto-managed substreams for `onObjectDetections` listeners. One entry per
11300
+ // `(channel, profile)` tuple required for NVR/Hub setups where AI boxes
11301
+ // live on a specific channel, and for callers that need detections off a
11302
+ // profile other than the default `sub`. The substream is opened on the first
11303
+ // listener of a tuple and torn down when the last one for that tuple leaves.
11304
+ objectDetectionSubs = /* @__PURE__ */ new Map();
11305
+ // Single bridge installed into `detectionEventListeners` while at least one
11306
+ // object-detection subscription is active. It routes events to the correct
11307
+ // tuple's listener set based on `event.channel` / `event.profile`.
11296
11308
  objectDetectionInternalListener;
11297
11309
  simpleEventSubscribeInFlight;
11298
11310
  simpleEventUnsubscribeInFlight;
@@ -12760,87 +12772,129 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
12760
12772
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
12761
12773
  * with class label and confidence) without managing a video stream yourself.
12762
12774
  *
12763
- * Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
12764
- * substream behind the scenes on the first listener, forwards every box-bearing
12765
- * `additionalHeader` to your callback, and tears the stream down when the last
12766
- * listener unsubscribes. The substream is the lightest profile (typically
12767
- * 640×360) so the additional bandwidth/CPU overhead is minimal.
12775
+ * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
12776
+ * `(channel, profile)` tuple the API ensures the corresponding video stream
12777
+ * is running (the pool socket may already be shared with a regular consumer),
12778
+ * forwards every box-bearing `additionalHeader` to your callback, and tears
12779
+ * the stream down when the last listener for that tuple unsubscribes.
12780
+ *
12781
+ * Defaults — `channel: 0`, `profile: "sub"` — match a single-lens standalone
12782
+ * camera. **For NVR/Hub child cameras you must pass the channel explicitly**,
12783
+ * otherwise the substream opens on channel 0 and never sees the AI boxes for
12784
+ * the other channels. The `sub` profile is recommended (lighter bandwidth)
12785
+ * but `main` / `ext` are accepted if you specifically need detections off a
12786
+ * different feed.
12768
12787
  *
12769
12788
  * Each event carries normalized `[0, 1]` box coordinates, a class label, and
12770
12789
  * a confidence score — render-ready without further conversion.
12771
12790
  */
12772
- async onObjectDetections(callback) {
12773
- this.objectDetectionListeners.add(callback);
12791
+ async onObjectDetections(callback, options) {
12792
+ const channel = options?.channel ?? 0;
12793
+ const profile = options?.profile ?? "sub";
12794
+ const key = this.objectDetectionKey(channel, profile);
12795
+ let entry = this.objectDetectionSubs.get(key);
12796
+ if (!entry) {
12797
+ entry = { channel, profile, listeners: /* @__PURE__ */ new Set() };
12798
+ this.objectDetectionSubs.set(key, entry);
12799
+ }
12800
+ entry.listeners.add(callback);
12774
12801
  this.logger.debug?.(
12775
- `[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
12802
+ `[ReolinkBaichuanApi] onObjectDetections: registering listener for ch${channel}/${profile} (total=${entry.listeners.size})`
12776
12803
  );
12777
- await this.ensureObjectDetectionStream();
12804
+ await this.ensureObjectDetectionStream(key);
12778
12805
  }
12779
12806
  /**
12780
- * Remove one detection callback, or all of them if `callback` is omitted.
12781
- * When the last listener is removed the auto-managed substream is closed.
12807
+ * Remove a detection callback for a given `(channel, profile)` tuple — or,
12808
+ * if `options` is omitted, remove the callback from every active tuple. When
12809
+ * `callback` is also omitted, every listener on the targeted tuples is
12810
+ * cleared. The auto-managed substream of a tuple is closed when its last
12811
+ * listener is removed.
12782
12812
  */
12783
- async offObjectDetections(callback) {
12784
- if (callback) {
12785
- this.objectDetectionListeners.delete(callback);
12786
- } else {
12787
- this.objectDetectionListeners.clear();
12788
- }
12789
- if (this.objectDetectionListeners.size === 0) {
12790
- await this.tearDownObjectDetectionStream();
12813
+ async offObjectDetections(callback, options) {
12814
+ const targetKeys = options ? [
12815
+ this.objectDetectionKey(
12816
+ options.channel ?? 0,
12817
+ options.profile ?? "sub"
12818
+ )
12819
+ ] : [...this.objectDetectionSubs.keys()];
12820
+ for (const key of targetKeys) {
12821
+ const entry = this.objectDetectionSubs.get(key);
12822
+ if (!entry) continue;
12823
+ if (callback) {
12824
+ entry.listeners.delete(callback);
12825
+ } else {
12826
+ entry.listeners.clear();
12827
+ }
12828
+ if (entry.listeners.size === 0) {
12829
+ await this.tearDownObjectDetectionStream(key);
12830
+ }
12791
12831
  }
12792
12832
  }
12793
- async ensureObjectDetectionStream() {
12794
- if (this.objectDetectionStream) return;
12795
- if (this.objectDetectionStreamStartInFlight) {
12796
- await this.objectDetectionStreamStartInFlight;
12833
+ objectDetectionKey(channel, profile) {
12834
+ return `${channel}:${profile}`;
12835
+ }
12836
+ ensureObjectDetectionInternalListener() {
12837
+ if (this.objectDetectionInternalListener) return;
12838
+ const internal = (event) => {
12839
+ const key = this.objectDetectionKey(event.channel, event.profile);
12840
+ const entry = this.objectDetectionSubs.get(key);
12841
+ if (!entry || entry.listeners.size === 0) return;
12842
+ for (const cb of entry.listeners) {
12843
+ try {
12844
+ void Promise.resolve(cb(event)).catch((e) => {
12845
+ (this.logger.warn ?? this.logger.error).call(
12846
+ this.logger,
12847
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
12848
+ formatErrorForLog(e)
12849
+ );
12850
+ });
12851
+ } catch (e) {
12852
+ (this.logger.warn ?? this.logger.error).call(
12853
+ this.logger,
12854
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
12855
+ formatErrorForLog(e)
12856
+ );
12857
+ }
12858
+ }
12859
+ };
12860
+ this.objectDetectionInternalListener = internal;
12861
+ this.detectionEventListeners.add(internal);
12862
+ }
12863
+ maybeDropObjectDetectionInternalListener() {
12864
+ if (this.objectDetectionSubs.size > 0) return;
12865
+ if (!this.objectDetectionInternalListener) return;
12866
+ this.detectionEventListeners.delete(this.objectDetectionInternalListener);
12867
+ this.objectDetectionInternalListener = void 0;
12868
+ }
12869
+ async ensureObjectDetectionStream(key) {
12870
+ const entry = this.objectDetectionSubs.get(key);
12871
+ if (!entry) return;
12872
+ if (entry.stream) return;
12873
+ if (entry.startInFlight) {
12874
+ await entry.startInFlight;
12797
12875
  return;
12798
12876
  }
12799
- this.objectDetectionStreamStartInFlight = (async () => {
12877
+ entry.startInFlight = (async () => {
12800
12878
  const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-HGPU2MZ3.js");
12801
- const sessionKey = `live:object-detections:ch0:sub`;
12879
+ const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
12802
12880
  const dedicated = await this.createDedicatedSession(sessionKey);
12803
12881
  const stream = new BaichuanVideoStream2({
12804
12882
  client: dedicated.client,
12805
12883
  api: this,
12806
- channel: 0,
12807
- profile: "sub",
12884
+ channel: entry.channel,
12885
+ profile: entry.profile,
12808
12886
  logger: this.logger
12809
12887
  });
12810
- this.objectDetectionInternalListener = (event) => {
12811
- for (const cb of this.objectDetectionListeners) {
12812
- try {
12813
- void Promise.resolve(cb(event)).catch((e) => {
12814
- (this.logger.warn ?? this.logger.error).call(
12815
- this.logger,
12816
- "[ReolinkBaichuanApi] onObjectDetections handler error",
12817
- formatErrorForLog(e)
12818
- );
12819
- });
12820
- } catch (e) {
12821
- (this.logger.warn ?? this.logger.error).call(
12822
- this.logger,
12823
- "[ReolinkBaichuanApi] onObjectDetections handler error",
12824
- formatErrorForLog(e)
12825
- );
12826
- }
12827
- }
12828
- };
12829
- this.detectionEventListeners.add(this.objectDetectionInternalListener);
12888
+ this.ensureObjectDetectionInternalListener();
12830
12889
  try {
12831
12890
  await stream.start();
12832
12891
  } catch (e) {
12833
- if (this.objectDetectionInternalListener) {
12834
- this.detectionEventListeners.delete(
12835
- this.objectDetectionInternalListener
12836
- );
12837
- this.objectDetectionInternalListener = void 0;
12838
- }
12839
12892
  await dedicated.release().catch(() => {
12840
12893
  });
12894
+ this.maybeDropObjectDetectionInternalListener();
12841
12895
  throw e;
12842
12896
  }
12843
- this.objectDetectionStream = {
12897
+ entry.stream = {
12844
12898
  stop: () => stream.stop(),
12845
12899
  release: () => dedicated.release()
12846
12900
  };
@@ -12849,35 +12903,36 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
12849
12903
  );
12850
12904
  })();
12851
12905
  try {
12852
- await this.objectDetectionStreamStartInFlight;
12906
+ await entry.startInFlight;
12853
12907
  } finally {
12854
- this.objectDetectionStreamStartInFlight = void 0;
12908
+ delete entry.startInFlight;
12855
12909
  }
12856
12910
  }
12857
- async tearDownObjectDetectionStream() {
12858
- const handle = this.objectDetectionStream;
12859
- this.objectDetectionStream = void 0;
12860
- if (this.objectDetectionInternalListener) {
12861
- this.detectionEventListeners.delete(this.objectDetectionInternalListener);
12862
- this.objectDetectionInternalListener = void 0;
12863
- }
12864
- if (!handle) return;
12865
- try {
12866
- await handle.stop();
12867
- } catch (e) {
12868
- this.logger.debug?.(
12869
- `[ReolinkBaichuanApi] onObjectDetections: stream stop error: ${formatErrorForLog(e)}`
12870
- );
12871
- }
12872
- try {
12873
- await handle.release();
12874
- } catch (e) {
12875
- this.logger.debug?.(
12876
- `[ReolinkBaichuanApi] onObjectDetections: session release error: ${formatErrorForLog(e)}`
12877
- );
12911
+ async tearDownObjectDetectionStream(key) {
12912
+ const entry = this.objectDetectionSubs.get(key);
12913
+ if (!entry) return;
12914
+ const handle = entry.stream;
12915
+ delete entry.stream;
12916
+ this.objectDetectionSubs.delete(key);
12917
+ if (handle) {
12918
+ try {
12919
+ await handle.stop();
12920
+ } catch (e) {
12921
+ this.logger.debug?.(
12922
+ `[ReolinkBaichuanApi] onObjectDetections: stream stop error (key=${key}): ${formatErrorForLog(e)}`
12923
+ );
12924
+ }
12925
+ try {
12926
+ await handle.release();
12927
+ } catch (e) {
12928
+ this.logger.debug?.(
12929
+ `[ReolinkBaichuanApi] onObjectDetections: session release error (key=${key}): ${formatErrorForLog(e)}`
12930
+ );
12931
+ }
12878
12932
  }
12933
+ this.maybeDropObjectDetectionInternalListener();
12879
12934
  this.logger.debug?.(
12880
- `[ReolinkBaichuanApi] onObjectDetections: substream torn down`
12935
+ `[ReolinkBaichuanApi] onObjectDetections: substream torn down (key=${key})`
12881
12936
  );
12882
12937
  }
12883
12938
  /**
@@ -13310,9 +13365,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
13310
13365
  this.stopUdpSleepInference();
13311
13366
  this.stopSimpleEventWatchdog();
13312
13367
  this.stopSimpleEventResubscribeTimer();
13313
- this.objectDetectionListeners.clear();
13314
- await this.tearDownObjectDetectionStream().catch(() => {
13315
- });
13368
+ for (const key of [...this.objectDetectionSubs.keys()]) {
13369
+ await this.tearDownObjectDetectionStream(key).catch(() => {
13370
+ });
13371
+ }
13316
13372
  await this.cleanup();
13317
13373
  await this.stopAllActiveStreams();
13318
13374
  await this.cleanupSocketPool();
@@ -18688,6 +18744,13 @@ ${xml}`
18688
18744
  model = deviceInfo.type?.trim();
18689
18745
  } catch {
18690
18746
  }
18747
+ if (!model) {
18748
+ try {
18749
+ const deviceInfoBase = await this.getInfo(void 0, { tags: ["type"] });
18750
+ model = deviceInfoBase.type?.trim();
18751
+ } catch {
18752
+ }
18753
+ }
18691
18754
  try {
18692
18755
  const capabilities = await this.getDeviceCapabilities(channel);
18693
18756
  channelNum = capabilities.support?.channelNum;
@@ -19393,7 +19456,7 @@ ${xml}`
19393
19456
  * @returns Test results for all stream types and profiles
19394
19457
  */
19395
19458
  async testChannelStreams(channel, logger) {
19396
- const { testChannelStreams } = await import("./DiagnosticsTools-BQOWJ23L.js");
19459
+ const { testChannelStreams } = await import("./DiagnosticsTools-HO3VI6D5.js");
19397
19460
  return await testChannelStreams({
19398
19461
  api: this,
19399
19462
  channel: this.normalizeChannel(channel),
@@ -19409,7 +19472,7 @@ ${xml}`
19409
19472
  * @returns Complete diagnostics for all channels and streams
19410
19473
  */
19411
19474
  async collectMultifocalDiagnostics(logger) {
19412
- const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-BQOWJ23L.js");
19475
+ const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-HO3VI6D5.js");
19413
19476
  return await collectMultifocalDiagnostics({
19414
19477
  api: this,
19415
19478
  logger
@@ -23882,4 +23945,4 @@ export {
23882
23945
  isTcpFailureThatShouldFallbackToUdp,
23883
23946
  autoDetectDeviceType
23884
23947
  };
23885
- //# sourceMappingURL=chunk-6KYLA4YI.js.map
23948
+ //# sourceMappingURL=chunk-EF6BCSCZ.js.map