@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.
@@ -3,7 +3,7 @@ import {
3
3
  BaichuanRtspServer,
4
4
  ReolinkBaichuanApi,
5
5
  autoDetectDeviceType
6
- } from "../chunk-WKETZFBI.js";
6
+ } from "../chunk-65HTCC62.js";
7
7
  import "../chunk-2JNXKT3C.js";
8
8
  import {
9
9
  __require
package/dist/index.cjs CHANGED
@@ -19560,14 +19560,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
19560
19560
  // _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
19561
19561
  detectionEventListeners = /* @__PURE__ */ new Set();
19562
19562
  detectionEventStreamHooks = /* @__PURE__ */ new Map();
19563
- // Auto-managed substream for `onObjectDetections` listeners. Reference-counted
19564
- // by the listener set: the substream is opened on the first listener and torn
19565
- // down with the last one. Mirrors `onSimpleEvent`'s subscribe/unsubscribe
19566
- // lifecycle so a caller never has to manage a video stream just to read AI
19567
- // detections.
19568
- objectDetectionListeners = /* @__PURE__ */ new Set();
19569
- objectDetectionStream;
19570
- objectDetectionStreamStartInFlight;
19563
+ // Auto-managed substreams for `onObjectDetections` listeners. One entry per
19564
+ // `(channel, profile)` tuple required for NVR/Hub setups where AI boxes
19565
+ // live on a specific channel, and for callers that need detections off a
19566
+ // profile other than the default `sub`. The substream is opened on the first
19567
+ // listener of a tuple and torn down when the last one for that tuple leaves.
19568
+ objectDetectionSubs = /* @__PURE__ */ new Map();
19569
+ // Single bridge installed into `detectionEventListeners` while at least one
19570
+ // object-detection subscription is active. It routes events to the correct
19571
+ // tuple's listener set based on `event.channel` / `event.profile`.
19571
19572
  objectDetectionInternalListener;
19572
19573
  simpleEventSubscribeInFlight;
19573
19574
  simpleEventUnsubscribeInFlight;
@@ -21035,87 +21036,129 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
21035
21036
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
21036
21037
  * with class label and confidence) without managing a video stream yourself.
21037
21038
  *
21038
- * Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
21039
- * substream behind the scenes on the first listener, forwards every box-bearing
21040
- * `additionalHeader` to your callback, and tears the stream down when the last
21041
- * listener unsubscribes. The substream is the lightest profile (typically
21042
- * 640×360) so the additional bandwidth/CPU overhead is minimal.
21039
+ * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
21040
+ * `(channel, profile)` tuple the API ensures the corresponding video stream
21041
+ * is running (the pool socket may already be shared with a regular consumer),
21042
+ * forwards every box-bearing `additionalHeader` to your callback, and tears
21043
+ * the stream down when the last listener for that tuple unsubscribes.
21044
+ *
21045
+ * Defaults — `channel: 0`, `profile: "sub"` — match a single-lens standalone
21046
+ * camera. **For NVR/Hub child cameras you must pass the channel explicitly**,
21047
+ * otherwise the substream opens on channel 0 and never sees the AI boxes for
21048
+ * the other channels. The `sub` profile is recommended (lighter bandwidth)
21049
+ * but `main` / `ext` are accepted if you specifically need detections off a
21050
+ * different feed.
21043
21051
  *
21044
21052
  * Each event carries normalized `[0, 1]` box coordinates, a class label, and
21045
21053
  * a confidence score — render-ready without further conversion.
21046
21054
  */
21047
- async onObjectDetections(callback) {
21048
- this.objectDetectionListeners.add(callback);
21055
+ async onObjectDetections(callback, options) {
21056
+ const channel = options?.channel ?? 0;
21057
+ const profile = options?.profile ?? "sub";
21058
+ const key = this.objectDetectionKey(channel, profile);
21059
+ let entry = this.objectDetectionSubs.get(key);
21060
+ if (!entry) {
21061
+ entry = { channel, profile, listeners: /* @__PURE__ */ new Set() };
21062
+ this.objectDetectionSubs.set(key, entry);
21063
+ }
21064
+ entry.listeners.add(callback);
21049
21065
  this.logger.debug?.(
21050
- `[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
21066
+ `[ReolinkBaichuanApi] onObjectDetections: registering listener for ch${channel}/${profile} (total=${entry.listeners.size})`
21051
21067
  );
21052
- await this.ensureObjectDetectionStream();
21068
+ await this.ensureObjectDetectionStream(key);
21053
21069
  }
21054
21070
  /**
21055
- * Remove one detection callback, or all of them if `callback` is omitted.
21056
- * When the last listener is removed the auto-managed substream is closed.
21071
+ * Remove a detection callback for a given `(channel, profile)` tuple — or,
21072
+ * if `options` is omitted, remove the callback from every active tuple. When
21073
+ * `callback` is also omitted, every listener on the targeted tuples is
21074
+ * cleared. The auto-managed substream of a tuple is closed when its last
21075
+ * listener is removed.
21057
21076
  */
21058
- async offObjectDetections(callback) {
21059
- if (callback) {
21060
- this.objectDetectionListeners.delete(callback);
21061
- } else {
21062
- this.objectDetectionListeners.clear();
21063
- }
21064
- if (this.objectDetectionListeners.size === 0) {
21065
- await this.tearDownObjectDetectionStream();
21077
+ async offObjectDetections(callback, options) {
21078
+ const targetKeys = options ? [
21079
+ this.objectDetectionKey(
21080
+ options.channel ?? 0,
21081
+ options.profile ?? "sub"
21082
+ )
21083
+ ] : [...this.objectDetectionSubs.keys()];
21084
+ for (const key of targetKeys) {
21085
+ const entry = this.objectDetectionSubs.get(key);
21086
+ if (!entry) continue;
21087
+ if (callback) {
21088
+ entry.listeners.delete(callback);
21089
+ } else {
21090
+ entry.listeners.clear();
21091
+ }
21092
+ if (entry.listeners.size === 0) {
21093
+ await this.tearDownObjectDetectionStream(key);
21094
+ }
21066
21095
  }
21067
21096
  }
21068
- async ensureObjectDetectionStream() {
21069
- if (this.objectDetectionStream) return;
21070
- if (this.objectDetectionStreamStartInFlight) {
21071
- await this.objectDetectionStreamStartInFlight;
21097
+ objectDetectionKey(channel, profile) {
21098
+ return `${channel}:${profile}`;
21099
+ }
21100
+ ensureObjectDetectionInternalListener() {
21101
+ if (this.objectDetectionInternalListener) return;
21102
+ const internal = (event) => {
21103
+ const key = this.objectDetectionKey(event.channel, event.profile);
21104
+ const entry = this.objectDetectionSubs.get(key);
21105
+ if (!entry || entry.listeners.size === 0) return;
21106
+ for (const cb of entry.listeners) {
21107
+ try {
21108
+ void Promise.resolve(cb(event)).catch((e) => {
21109
+ (this.logger.warn ?? this.logger.error).call(
21110
+ this.logger,
21111
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
21112
+ formatErrorForLog(e)
21113
+ );
21114
+ });
21115
+ } catch (e) {
21116
+ (this.logger.warn ?? this.logger.error).call(
21117
+ this.logger,
21118
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
21119
+ formatErrorForLog(e)
21120
+ );
21121
+ }
21122
+ }
21123
+ };
21124
+ this.objectDetectionInternalListener = internal;
21125
+ this.detectionEventListeners.add(internal);
21126
+ }
21127
+ maybeDropObjectDetectionInternalListener() {
21128
+ if (this.objectDetectionSubs.size > 0) return;
21129
+ if (!this.objectDetectionInternalListener) return;
21130
+ this.detectionEventListeners.delete(this.objectDetectionInternalListener);
21131
+ this.objectDetectionInternalListener = void 0;
21132
+ }
21133
+ async ensureObjectDetectionStream(key) {
21134
+ const entry = this.objectDetectionSubs.get(key);
21135
+ if (!entry) return;
21136
+ if (entry.stream) return;
21137
+ if (entry.startInFlight) {
21138
+ await entry.startInFlight;
21072
21139
  return;
21073
21140
  }
21074
- this.objectDetectionStreamStartInFlight = (async () => {
21141
+ entry.startInFlight = (async () => {
21075
21142
  const { BaichuanVideoStream: BaichuanVideoStream2 } = await Promise.resolve().then(() => (init_BaichuanVideoStream(), BaichuanVideoStream_exports));
21076
- const sessionKey = `live:object-detections:ch0:sub`;
21143
+ const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
21077
21144
  const dedicated = await this.createDedicatedSession(sessionKey);
21078
21145
  const stream = new BaichuanVideoStream2({
21079
21146
  client: dedicated.client,
21080
21147
  api: this,
21081
- channel: 0,
21082
- profile: "sub",
21148
+ channel: entry.channel,
21149
+ profile: entry.profile,
21083
21150
  logger: this.logger
21084
21151
  });
21085
- this.objectDetectionInternalListener = (event) => {
21086
- for (const cb of this.objectDetectionListeners) {
21087
- try {
21088
- void Promise.resolve(cb(event)).catch((e) => {
21089
- (this.logger.warn ?? this.logger.error).call(
21090
- this.logger,
21091
- "[ReolinkBaichuanApi] onObjectDetections handler error",
21092
- formatErrorForLog(e)
21093
- );
21094
- });
21095
- } catch (e) {
21096
- (this.logger.warn ?? this.logger.error).call(
21097
- this.logger,
21098
- "[ReolinkBaichuanApi] onObjectDetections handler error",
21099
- formatErrorForLog(e)
21100
- );
21101
- }
21102
- }
21103
- };
21104
- this.detectionEventListeners.add(this.objectDetectionInternalListener);
21152
+ this.ensureObjectDetectionInternalListener();
21105
21153
  try {
21106
21154
  await stream.start();
21107
21155
  } catch (e) {
21108
- if (this.objectDetectionInternalListener) {
21109
- this.detectionEventListeners.delete(
21110
- this.objectDetectionInternalListener
21111
- );
21112
- this.objectDetectionInternalListener = void 0;
21113
- }
21114
21156
  await dedicated.release().catch(() => {
21115
21157
  });
21158
+ this.maybeDropObjectDetectionInternalListener();
21116
21159
  throw e;
21117
21160
  }
21118
- this.objectDetectionStream = {
21161
+ entry.stream = {
21119
21162
  stop: () => stream.stop(),
21120
21163
  release: () => dedicated.release()
21121
21164
  };
@@ -21124,35 +21167,36 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
21124
21167
  );
21125
21168
  })();
21126
21169
  try {
21127
- await this.objectDetectionStreamStartInFlight;
21170
+ await entry.startInFlight;
21128
21171
  } finally {
21129
- this.objectDetectionStreamStartInFlight = void 0;
21172
+ delete entry.startInFlight;
21130
21173
  }
21131
21174
  }
21132
- async tearDownObjectDetectionStream() {
21133
- const handle = this.objectDetectionStream;
21134
- this.objectDetectionStream = void 0;
21135
- if (this.objectDetectionInternalListener) {
21136
- this.detectionEventListeners.delete(this.objectDetectionInternalListener);
21137
- this.objectDetectionInternalListener = void 0;
21138
- }
21139
- if (!handle) return;
21140
- try {
21141
- await handle.stop();
21142
- } catch (e) {
21143
- this.logger.debug?.(
21144
- `[ReolinkBaichuanApi] onObjectDetections: stream stop error: ${formatErrorForLog(e)}`
21145
- );
21146
- }
21147
- try {
21148
- await handle.release();
21149
- } catch (e) {
21150
- this.logger.debug?.(
21151
- `[ReolinkBaichuanApi] onObjectDetections: session release error: ${formatErrorForLog(e)}`
21152
- );
21175
+ async tearDownObjectDetectionStream(key) {
21176
+ const entry = this.objectDetectionSubs.get(key);
21177
+ if (!entry) return;
21178
+ const handle = entry.stream;
21179
+ delete entry.stream;
21180
+ this.objectDetectionSubs.delete(key);
21181
+ if (handle) {
21182
+ try {
21183
+ await handle.stop();
21184
+ } catch (e) {
21185
+ this.logger.debug?.(
21186
+ `[ReolinkBaichuanApi] onObjectDetections: stream stop error (key=${key}): ${formatErrorForLog(e)}`
21187
+ );
21188
+ }
21189
+ try {
21190
+ await handle.release();
21191
+ } catch (e) {
21192
+ this.logger.debug?.(
21193
+ `[ReolinkBaichuanApi] onObjectDetections: session release error (key=${key}): ${formatErrorForLog(e)}`
21194
+ );
21195
+ }
21153
21196
  }
21197
+ this.maybeDropObjectDetectionInternalListener();
21154
21198
  this.logger.debug?.(
21155
- `[ReolinkBaichuanApi] onObjectDetections: substream torn down`
21199
+ `[ReolinkBaichuanApi] onObjectDetections: substream torn down (key=${key})`
21156
21200
  );
21157
21201
  }
21158
21202
  /**
@@ -21585,9 +21629,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
21585
21629
  this.stopUdpSleepInference();
21586
21630
  this.stopSimpleEventWatchdog();
21587
21631
  this.stopSimpleEventResubscribeTimer();
21588
- this.objectDetectionListeners.clear();
21589
- await this.tearDownObjectDetectionStream().catch(() => {
21590
- });
21632
+ for (const key of [...this.objectDetectionSubs.keys()]) {
21633
+ await this.tearDownObjectDetectionStream(key).catch(() => {
21634
+ });
21635
+ }
21591
21636
  await this.cleanup();
21592
21637
  await this.stopAllActiveStreams();
21593
21638
  await this.cleanupSocketPool();
@@ -28068,6 +28113,7 @@ ${xml}`
28068
28113
  async setAutoFocus(channel, disable, options) {
28069
28114
  const ch = this.normalizeChannel(channel);
28070
28115
  const disableVal = disable ? 1 : 0;
28116
+ const extensionXml = buildChannelExtensionXml(ch);
28071
28117
  const payloadXml = `<?xml version="1.0" encoding="UTF-8" ?>
28072
28118
  <body>
28073
28119
  <AutoFocus version="1.1">
@@ -28078,6 +28124,7 @@ ${xml}`
28078
28124
  await this.sendXml({
28079
28125
  cmdId: BC_CMD_ID_SET_AUTO_FOCUS,
28080
28126
  channel: ch,
28127
+ extensionXml,
28081
28128
  payloadXml,
28082
28129
  ...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
28083
28130
  });