@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.
@@ -18939,14 +18939,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
18939
18939
  // _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
18940
18940
  detectionEventListeners = /* @__PURE__ */ new Set();
18941
18941
  detectionEventStreamHooks = /* @__PURE__ */ new Map();
18942
- // Auto-managed substream for `onObjectDetections` listeners. Reference-counted
18943
- // by the listener set: the substream is opened on the first listener and torn
18944
- // down with the last one. Mirrors `onSimpleEvent`'s subscribe/unsubscribe
18945
- // lifecycle so a caller never has to manage a video stream just to read AI
18946
- // detections.
18947
- objectDetectionListeners = /* @__PURE__ */ new Set();
18948
- objectDetectionStream;
18949
- objectDetectionStreamStartInFlight;
18942
+ // Auto-managed substreams for `onObjectDetections` listeners. One entry per
18943
+ // `(channel, profile)` tuple required for NVR/Hub setups where AI boxes
18944
+ // live on a specific channel, and for callers that need detections off a
18945
+ // profile other than the default `sub`. The substream is opened on the first
18946
+ // listener of a tuple and torn down when the last one for that tuple leaves.
18947
+ objectDetectionSubs = /* @__PURE__ */ new Map();
18948
+ // Single bridge installed into `detectionEventListeners` while at least one
18949
+ // object-detection subscription is active. It routes events to the correct
18950
+ // tuple's listener set based on `event.channel` / `event.profile`.
18950
18951
  objectDetectionInternalListener;
18951
18952
  simpleEventSubscribeInFlight;
18952
18953
  simpleEventUnsubscribeInFlight;
@@ -20414,87 +20415,129 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
20414
20415
  * Subscribe to AI object detections (people / vehicle / animal / face boxes
20415
20416
  * with class label and confidence) without managing a video stream yourself.
20416
20417
  *
20417
- * Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
20418
- * substream behind the scenes on the first listener, forwards every box-bearing
20419
- * `additionalHeader` to your callback, and tears the stream down when the last
20420
- * listener unsubscribes. The substream is the lightest profile (typically
20421
- * 640×360) so the additional bandwidth/CPU overhead is minimal.
20418
+ * Mirrors {@link onSimpleEvent} end-to-end: on the first listener for a given
20419
+ * `(channel, profile)` tuple the API ensures the corresponding video stream
20420
+ * is running (the pool socket may already be shared with a regular consumer),
20421
+ * forwards every box-bearing `additionalHeader` to your callback, and tears
20422
+ * the stream down when the last listener for that tuple unsubscribes.
20423
+ *
20424
+ * Defaults — `channel: 0`, `profile: "sub"` — match a single-lens standalone
20425
+ * camera. **For NVR/Hub child cameras you must pass the channel explicitly**,
20426
+ * otherwise the substream opens on channel 0 and never sees the AI boxes for
20427
+ * the other channels. The `sub` profile is recommended (lighter bandwidth)
20428
+ * but `main` / `ext` are accepted if you specifically need detections off a
20429
+ * different feed.
20422
20430
  *
20423
20431
  * Each event carries normalized `[0, 1]` box coordinates, a class label, and
20424
20432
  * a confidence score — render-ready without further conversion.
20425
20433
  */
20426
- async onObjectDetections(callback) {
20427
- this.objectDetectionListeners.add(callback);
20434
+ async onObjectDetections(callback, options) {
20435
+ const channel = options?.channel ?? 0;
20436
+ const profile = options?.profile ?? "sub";
20437
+ const key = this.objectDetectionKey(channel, profile);
20438
+ let entry = this.objectDetectionSubs.get(key);
20439
+ if (!entry) {
20440
+ entry = { channel, profile, listeners: /* @__PURE__ */ new Set() };
20441
+ this.objectDetectionSubs.set(key, entry);
20442
+ }
20443
+ entry.listeners.add(callback);
20428
20444
  this.logger.debug?.(
20429
- `[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
20445
+ `[ReolinkBaichuanApi] onObjectDetections: registering listener for ch${channel}/${profile} (total=${entry.listeners.size})`
20430
20446
  );
20431
- await this.ensureObjectDetectionStream();
20447
+ await this.ensureObjectDetectionStream(key);
20432
20448
  }
20433
20449
  /**
20434
- * Remove one detection callback, or all of them if `callback` is omitted.
20435
- * When the last listener is removed the auto-managed substream is closed.
20450
+ * Remove a detection callback for a given `(channel, profile)` tuple — or,
20451
+ * if `options` is omitted, remove the callback from every active tuple. When
20452
+ * `callback` is also omitted, every listener on the targeted tuples is
20453
+ * cleared. The auto-managed substream of a tuple is closed when its last
20454
+ * listener is removed.
20436
20455
  */
20437
- async offObjectDetections(callback) {
20438
- if (callback) {
20439
- this.objectDetectionListeners.delete(callback);
20440
- } else {
20441
- this.objectDetectionListeners.clear();
20442
- }
20443
- if (this.objectDetectionListeners.size === 0) {
20444
- await this.tearDownObjectDetectionStream();
20456
+ async offObjectDetections(callback, options) {
20457
+ const targetKeys = options ? [
20458
+ this.objectDetectionKey(
20459
+ options.channel ?? 0,
20460
+ options.profile ?? "sub"
20461
+ )
20462
+ ] : [...this.objectDetectionSubs.keys()];
20463
+ for (const key of targetKeys) {
20464
+ const entry = this.objectDetectionSubs.get(key);
20465
+ if (!entry) continue;
20466
+ if (callback) {
20467
+ entry.listeners.delete(callback);
20468
+ } else {
20469
+ entry.listeners.clear();
20470
+ }
20471
+ if (entry.listeners.size === 0) {
20472
+ await this.tearDownObjectDetectionStream(key);
20473
+ }
20445
20474
  }
20446
20475
  }
20447
- async ensureObjectDetectionStream() {
20448
- if (this.objectDetectionStream) return;
20449
- if (this.objectDetectionStreamStartInFlight) {
20450
- await this.objectDetectionStreamStartInFlight;
20476
+ objectDetectionKey(channel, profile) {
20477
+ return `${channel}:${profile}`;
20478
+ }
20479
+ ensureObjectDetectionInternalListener() {
20480
+ if (this.objectDetectionInternalListener) return;
20481
+ const internal = (event) => {
20482
+ const key = this.objectDetectionKey(event.channel, event.profile);
20483
+ const entry = this.objectDetectionSubs.get(key);
20484
+ if (!entry || entry.listeners.size === 0) return;
20485
+ for (const cb of entry.listeners) {
20486
+ try {
20487
+ void Promise.resolve(cb(event)).catch((e) => {
20488
+ (this.logger.warn ?? this.logger.error).call(
20489
+ this.logger,
20490
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
20491
+ formatErrorForLog(e)
20492
+ );
20493
+ });
20494
+ } catch (e) {
20495
+ (this.logger.warn ?? this.logger.error).call(
20496
+ this.logger,
20497
+ "[ReolinkBaichuanApi] onObjectDetections handler error",
20498
+ formatErrorForLog(e)
20499
+ );
20500
+ }
20501
+ }
20502
+ };
20503
+ this.objectDetectionInternalListener = internal;
20504
+ this.detectionEventListeners.add(internal);
20505
+ }
20506
+ maybeDropObjectDetectionInternalListener() {
20507
+ if (this.objectDetectionSubs.size > 0) return;
20508
+ if (!this.objectDetectionInternalListener) return;
20509
+ this.detectionEventListeners.delete(this.objectDetectionInternalListener);
20510
+ this.objectDetectionInternalListener = void 0;
20511
+ }
20512
+ async ensureObjectDetectionStream(key) {
20513
+ const entry = this.objectDetectionSubs.get(key);
20514
+ if (!entry) return;
20515
+ if (entry.stream) return;
20516
+ if (entry.startInFlight) {
20517
+ await entry.startInFlight;
20451
20518
  return;
20452
20519
  }
20453
- this.objectDetectionStreamStartInFlight = (async () => {
20520
+ entry.startInFlight = (async () => {
20454
20521
  const { BaichuanVideoStream: BaichuanVideoStream2 } = await Promise.resolve().then(() => (init_BaichuanVideoStream(), BaichuanVideoStream_exports));
20455
- const sessionKey = `live:object-detections:ch0:sub`;
20522
+ const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
20456
20523
  const dedicated = await this.createDedicatedSession(sessionKey);
20457
20524
  const stream = new BaichuanVideoStream2({
20458
20525
  client: dedicated.client,
20459
20526
  api: this,
20460
- channel: 0,
20461
- profile: "sub",
20527
+ channel: entry.channel,
20528
+ profile: entry.profile,
20462
20529
  logger: this.logger
20463
20530
  });
20464
- this.objectDetectionInternalListener = (event) => {
20465
- for (const cb of this.objectDetectionListeners) {
20466
- try {
20467
- void Promise.resolve(cb(event)).catch((e) => {
20468
- (this.logger.warn ?? this.logger.error).call(
20469
- this.logger,
20470
- "[ReolinkBaichuanApi] onObjectDetections handler error",
20471
- formatErrorForLog(e)
20472
- );
20473
- });
20474
- } catch (e) {
20475
- (this.logger.warn ?? this.logger.error).call(
20476
- this.logger,
20477
- "[ReolinkBaichuanApi] onObjectDetections handler error",
20478
- formatErrorForLog(e)
20479
- );
20480
- }
20481
- }
20482
- };
20483
- this.detectionEventListeners.add(this.objectDetectionInternalListener);
20531
+ this.ensureObjectDetectionInternalListener();
20484
20532
  try {
20485
20533
  await stream.start();
20486
20534
  } catch (e) {
20487
- if (this.objectDetectionInternalListener) {
20488
- this.detectionEventListeners.delete(
20489
- this.objectDetectionInternalListener
20490
- );
20491
- this.objectDetectionInternalListener = void 0;
20492
- }
20493
20535
  await dedicated.release().catch(() => {
20494
20536
  });
20537
+ this.maybeDropObjectDetectionInternalListener();
20495
20538
  throw e;
20496
20539
  }
20497
- this.objectDetectionStream = {
20540
+ entry.stream = {
20498
20541
  stop: () => stream.stop(),
20499
20542
  release: () => dedicated.release()
20500
20543
  };
@@ -20503,35 +20546,36 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
20503
20546
  );
20504
20547
  })();
20505
20548
  try {
20506
- await this.objectDetectionStreamStartInFlight;
20549
+ await entry.startInFlight;
20507
20550
  } finally {
20508
- this.objectDetectionStreamStartInFlight = void 0;
20551
+ delete entry.startInFlight;
20509
20552
  }
20510
20553
  }
20511
- async tearDownObjectDetectionStream() {
20512
- const handle = this.objectDetectionStream;
20513
- this.objectDetectionStream = void 0;
20514
- if (this.objectDetectionInternalListener) {
20515
- this.detectionEventListeners.delete(this.objectDetectionInternalListener);
20516
- this.objectDetectionInternalListener = void 0;
20517
- }
20518
- if (!handle) return;
20519
- try {
20520
- await handle.stop();
20521
- } catch (e) {
20522
- this.logger.debug?.(
20523
- `[ReolinkBaichuanApi] onObjectDetections: stream stop error: ${formatErrorForLog(e)}`
20524
- );
20525
- }
20526
- try {
20527
- await handle.release();
20528
- } catch (e) {
20529
- this.logger.debug?.(
20530
- `[ReolinkBaichuanApi] onObjectDetections: session release error: ${formatErrorForLog(e)}`
20531
- );
20554
+ async tearDownObjectDetectionStream(key) {
20555
+ const entry = this.objectDetectionSubs.get(key);
20556
+ if (!entry) return;
20557
+ const handle = entry.stream;
20558
+ delete entry.stream;
20559
+ this.objectDetectionSubs.delete(key);
20560
+ if (handle) {
20561
+ try {
20562
+ await handle.stop();
20563
+ } catch (e) {
20564
+ this.logger.debug?.(
20565
+ `[ReolinkBaichuanApi] onObjectDetections: stream stop error (key=${key}): ${formatErrorForLog(e)}`
20566
+ );
20567
+ }
20568
+ try {
20569
+ await handle.release();
20570
+ } catch (e) {
20571
+ this.logger.debug?.(
20572
+ `[ReolinkBaichuanApi] onObjectDetections: session release error (key=${key}): ${formatErrorForLog(e)}`
20573
+ );
20574
+ }
20532
20575
  }
20576
+ this.maybeDropObjectDetectionInternalListener();
20533
20577
  this.logger.debug?.(
20534
- `[ReolinkBaichuanApi] onObjectDetections: substream torn down`
20578
+ `[ReolinkBaichuanApi] onObjectDetections: substream torn down (key=${key})`
20535
20579
  );
20536
20580
  }
20537
20581
  /**
@@ -20964,9 +21008,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
20964
21008
  this.stopUdpSleepInference();
20965
21009
  this.stopSimpleEventWatchdog();
20966
21010
  this.stopSimpleEventResubscribeTimer();
20967
- this.objectDetectionListeners.clear();
20968
- await this.tearDownObjectDetectionStream().catch(() => {
20969
- });
21011
+ for (const key of [...this.objectDetectionSubs.keys()]) {
21012
+ await this.tearDownObjectDetectionStream(key).catch(() => {
21013
+ });
21014
+ }
20970
21015
  await this.cleanup();
20971
21016
  await this.stopAllActiveStreams();
20972
21017
  await this.cleanupSocketPool();
@@ -27447,6 +27492,7 @@ ${xml}`
27447
27492
  async setAutoFocus(channel, disable, options) {
27448
27493
  const ch = this.normalizeChannel(channel);
27449
27494
  const disableVal = disable ? 1 : 0;
27495
+ const extensionXml = buildChannelExtensionXml(ch);
27450
27496
  const payloadXml = `<?xml version="1.0" encoding="UTF-8" ?>
27451
27497
  <body>
27452
27498
  <AutoFocus version="1.1">
@@ -27457,6 +27503,7 @@ ${xml}`
27457
27503
  await this.sendXml({
27458
27504
  cmdId: BC_CMD_ID_SET_AUTO_FOCUS,
27459
27505
  channel: ch,
27506
+ extensionXml,
27460
27507
  payloadXml,
27461
27508
  ...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
27462
27509
  });