@apocaliss92/nodelink-js 0.4.14 → 0.4.16

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.
@@ -11285,14 +11285,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
11285
11285
  // _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
11286
11286
  detectionEventListeners = /* @__PURE__ */ new Set();
11287
11287
  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;
11288
+ // Auto-managed substreams for `onObjectDetections` listeners. One entry per
11289
+ // `(channel, profile)` tuple required for NVR/Hub setups where AI boxes
11290
+ // live on a specific channel, and for callers that need detections off a
11291
+ // profile other than the default `sub`. The substream is opened on the first
11292
+ // listener of a tuple and torn down when the last one for that tuple leaves.
11293
+ objectDetectionSubs = /* @__PURE__ */ new Map();
11294
+ // Single bridge installed into `detectionEventListeners` while at least one
11295
+ // object-detection subscription is active. It routes events to the correct
11296
+ // tuple's listener set based on `event.channel` / `event.profile`.
11296
11297
  objectDetectionInternalListener;
11297
11298
  simpleEventSubscribeInFlight;
11298
11299
  simpleEventUnsubscribeInFlight;
@@ -12760,87 +12761,129 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
12760
12761
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
12761
12762
  * with class label and confidence) without managing a video stream yourself.
12762
12763
  *
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.
12764
+ * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
12765
+ * `(channel, profile)` tuple the API ensures the corresponding video stream
12766
+ * is running (the pool socket may already be shared with a regular consumer),
12767
+ * forwards every box-bearing `additionalHeader` to your callback, and tears
12768
+ * the stream down when the last listener for that tuple unsubscribes.
12769
+ *
12770
+ * Defaults — `channel: 0`, `profile: "sub"` — match a single-lens standalone
12771
+ * camera. **For NVR/Hub child cameras you must pass the channel explicitly**,
12772
+ * otherwise the substream opens on channel 0 and never sees the AI boxes for
12773
+ * the other channels. The `sub` profile is recommended (lighter bandwidth)
12774
+ * but `main` / `ext` are accepted if you specifically need detections off a
12775
+ * different feed.
12768
12776
  *
12769
12777
  * Each event carries normalized `[0, 1]` box coordinates, a class label, and
12770
12778
  * a confidence score — render-ready without further conversion.
12771
12779
  */
12772
- async onObjectDetections(callback) {
12773
- this.objectDetectionListeners.add(callback);
12780
+ async onObjectDetections(callback, options) {
12781
+ const channel = options?.channel ?? 0;
12782
+ const profile = options?.profile ?? "sub";
12783
+ const key = this.objectDetectionKey(channel, profile);
12784
+ let entry = this.objectDetectionSubs.get(key);
12785
+ if (!entry) {
12786
+ entry = { channel, profile, listeners: /* @__PURE__ */ new Set() };
12787
+ this.objectDetectionSubs.set(key, entry);
12788
+ }
12789
+ entry.listeners.add(callback);
12774
12790
  this.logger.debug?.(
12775
- `[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
12791
+ `[ReolinkBaichuanApi] onObjectDetections: registering listener for ch${channel}/${profile} (total=${entry.listeners.size})`
12776
12792
  );
12777
- await this.ensureObjectDetectionStream();
12793
+ await this.ensureObjectDetectionStream(key);
12778
12794
  }
12779
12795
  /**
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.
12796
+ * Remove a detection callback for a given `(channel, profile)` tuple — or,
12797
+ * if `options` is omitted, remove the callback from every active tuple. When
12798
+ * `callback` is also omitted, every listener on the targeted tuples is
12799
+ * cleared. The auto-managed substream of a tuple is closed when its last
12800
+ * listener is removed.
12782
12801
  */
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();
12802
+ async offObjectDetections(callback, options) {
12803
+ const targetKeys = options ? [
12804
+ this.objectDetectionKey(
12805
+ options.channel ?? 0,
12806
+ options.profile ?? "sub"
12807
+ )
12808
+ ] : [...this.objectDetectionSubs.keys()];
12809
+ for (const key of targetKeys) {
12810
+ const entry = this.objectDetectionSubs.get(key);
12811
+ if (!entry) continue;
12812
+ if (callback) {
12813
+ entry.listeners.delete(callback);
12814
+ } else {
12815
+ entry.listeners.clear();
12816
+ }
12817
+ if (entry.listeners.size === 0) {
12818
+ await this.tearDownObjectDetectionStream(key);
12819
+ }
12791
12820
  }
12792
12821
  }
12793
- async ensureObjectDetectionStream() {
12794
- if (this.objectDetectionStream) return;
12795
- if (this.objectDetectionStreamStartInFlight) {
12796
- await this.objectDetectionStreamStartInFlight;
12822
+ objectDetectionKey(channel, profile) {
12823
+ return `${channel}:${profile}`;
12824
+ }
12825
+ ensureObjectDetectionInternalListener() {
12826
+ if (this.objectDetectionInternalListener) return;
12827
+ const internal = (event) => {
12828
+ const key = this.objectDetectionKey(event.channel, event.profile);
12829
+ const entry = this.objectDetectionSubs.get(key);
12830
+ if (!entry || entry.listeners.size === 0) return;
12831
+ for (const cb of entry.listeners) {
12832
+ try {
12833
+ void Promise.resolve(cb(event)).catch((e) => {
12834
+ (this.logger.warn ?? this.logger.error).call(
12835
+ this.logger,
12836
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
12837
+ formatErrorForLog(e)
12838
+ );
12839
+ });
12840
+ } catch (e) {
12841
+ (this.logger.warn ?? this.logger.error).call(
12842
+ this.logger,
12843
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
12844
+ formatErrorForLog(e)
12845
+ );
12846
+ }
12847
+ }
12848
+ };
12849
+ this.objectDetectionInternalListener = internal;
12850
+ this.detectionEventListeners.add(internal);
12851
+ }
12852
+ maybeDropObjectDetectionInternalListener() {
12853
+ if (this.objectDetectionSubs.size > 0) return;
12854
+ if (!this.objectDetectionInternalListener) return;
12855
+ this.detectionEventListeners.delete(this.objectDetectionInternalListener);
12856
+ this.objectDetectionInternalListener = void 0;
12857
+ }
12858
+ async ensureObjectDetectionStream(key) {
12859
+ const entry = this.objectDetectionSubs.get(key);
12860
+ if (!entry) return;
12861
+ if (entry.stream) return;
12862
+ if (entry.startInFlight) {
12863
+ await entry.startInFlight;
12797
12864
  return;
12798
12865
  }
12799
- this.objectDetectionStreamStartInFlight = (async () => {
12866
+ entry.startInFlight = (async () => {
12800
12867
  const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-HGPU2MZ3.js");
12801
- const sessionKey = `live:object-detections:ch0:sub`;
12868
+ const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
12802
12869
  const dedicated = await this.createDedicatedSession(sessionKey);
12803
12870
  const stream = new BaichuanVideoStream2({
12804
12871
  client: dedicated.client,
12805
12872
  api: this,
12806
- channel: 0,
12807
- profile: "sub",
12873
+ channel: entry.channel,
12874
+ profile: entry.profile,
12808
12875
  logger: this.logger
12809
12876
  });
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);
12877
+ this.ensureObjectDetectionInternalListener();
12830
12878
  try {
12831
12879
  await stream.start();
12832
12880
  } catch (e) {
12833
- if (this.objectDetectionInternalListener) {
12834
- this.detectionEventListeners.delete(
12835
- this.objectDetectionInternalListener
12836
- );
12837
- this.objectDetectionInternalListener = void 0;
12838
- }
12839
12881
  await dedicated.release().catch(() => {
12840
12882
  });
12883
+ this.maybeDropObjectDetectionInternalListener();
12841
12884
  throw e;
12842
12885
  }
12843
- this.objectDetectionStream = {
12886
+ entry.stream = {
12844
12887
  stop: () => stream.stop(),
12845
12888
  release: () => dedicated.release()
12846
12889
  };
@@ -12849,35 +12892,36 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
12849
12892
  );
12850
12893
  })();
12851
12894
  try {
12852
- await this.objectDetectionStreamStartInFlight;
12895
+ await entry.startInFlight;
12853
12896
  } finally {
12854
- this.objectDetectionStreamStartInFlight = void 0;
12897
+ delete entry.startInFlight;
12855
12898
  }
12856
12899
  }
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
- );
12900
+ async tearDownObjectDetectionStream(key) {
12901
+ const entry = this.objectDetectionSubs.get(key);
12902
+ if (!entry) return;
12903
+ const handle = entry.stream;
12904
+ delete entry.stream;
12905
+ this.objectDetectionSubs.delete(key);
12906
+ if (handle) {
12907
+ try {
12908
+ await handle.stop();
12909
+ } catch (e) {
12910
+ this.logger.debug?.(
12911
+ `[ReolinkBaichuanApi] onObjectDetections: stream stop error (key=${key}): ${formatErrorForLog(e)}`
12912
+ );
12913
+ }
12914
+ try {
12915
+ await handle.release();
12916
+ } catch (e) {
12917
+ this.logger.debug?.(
12918
+ `[ReolinkBaichuanApi] onObjectDetections: session release error (key=${key}): ${formatErrorForLog(e)}`
12919
+ );
12920
+ }
12878
12921
  }
12922
+ this.maybeDropObjectDetectionInternalListener();
12879
12923
  this.logger.debug?.(
12880
- `[ReolinkBaichuanApi] onObjectDetections: substream torn down`
12924
+ `[ReolinkBaichuanApi] onObjectDetections: substream torn down (key=${key})`
12881
12925
  );
12882
12926
  }
12883
12927
  /**
@@ -13310,9 +13354,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
13310
13354
  this.stopUdpSleepInference();
13311
13355
  this.stopSimpleEventWatchdog();
13312
13356
  this.stopSimpleEventResubscribeTimer();
13313
- this.objectDetectionListeners.clear();
13314
- await this.tearDownObjectDetectionStream().catch(() => {
13315
- });
13357
+ for (const key of [...this.objectDetectionSubs.keys()]) {
13358
+ await this.tearDownObjectDetectionStream(key).catch(() => {
13359
+ });
13360
+ }
13316
13361
  await this.cleanup();
13317
13362
  await this.stopAllActiveStreams();
13318
13363
  await this.cleanupSocketPool();
@@ -19793,6 +19838,7 @@ ${xml}`
19793
19838
  async setAutoFocus(channel, disable, options) {
19794
19839
  const ch = this.normalizeChannel(channel);
19795
19840
  const disableVal = disable ? 1 : 0;
19841
+ const extensionXml = buildChannelExtensionXml(ch);
19796
19842
  const payloadXml = `<?xml version="1.0" encoding="UTF-8" ?>
19797
19843
  <body>
19798
19844
  <AutoFocus version="1.1">
@@ -19803,6 +19849,7 @@ ${xml}`
19803
19849
  await this.sendXml({
19804
19850
  cmdId: BC_CMD_ID_SET_AUTO_FOCUS,
19805
19851
  channel: ch,
19852
+ extensionXml,
19806
19853
  payloadXml,
19807
19854
  ...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
19808
19855
  });
@@ -23880,4 +23927,4 @@ export {
23880
23927
  isTcpFailureThatShouldFallbackToUdp,
23881
23928
  autoDetectDeviceType
23882
23929
  };
23883
- //# sourceMappingURL=chunk-WKETZFBI.js.map
23930
+ //# sourceMappingURL=chunk-65HTCC62.js.map