@apocaliss92/nodelink-js 0.4.5 → 0.4.6

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.
@@ -1567,6 +1567,19 @@ var init_BaichuanVideoStream = __esm({
1567
1567
  // Stateful AES decryptor for fragmented BcMedia packets (full_aes mode)
1568
1568
  // In CFB mode, continuation frames must use the cipher state from previous frames.
1569
1569
  aesStreamDecryptor = null;
1570
+ /**
1571
+ * Pending startup error stashed when emitSafeError is called before any
1572
+ * "error" listener is registered (e.g. camera returns 400 during start()).
1573
+ * The rfc4571-server's waitForKeyframe can consume this immediately instead
1574
+ * of waiting for the full keyframe timeout.
1575
+ */
1576
+ _pendingStartupError;
1577
+ /** Consume and clear any pending startup error. */
1578
+ consumePendingStartupError() {
1579
+ const err = this._pendingStartupError;
1580
+ this._pendingStartupError = void 0;
1581
+ return err;
1582
+ }
1570
1583
  emitSafeError(err) {
1571
1584
  if (!this.active) {
1572
1585
  this.logger?.warn?.(
@@ -1578,6 +1591,7 @@ var init_BaichuanVideoStream = __esm({
1578
1591
  this.logger?.warn?.(
1579
1592
  `[BaichuanVideoStream] Unhandled stream error: ${err.message}`
1580
1593
  );
1594
+ this._pendingStartupError = err;
1581
1595
  return;
1582
1596
  }
1583
1597
  this.emit("error", err);
@@ -10657,6 +10671,9 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
10657
10671
  resendTimer;
10658
10672
  hbTimer;
10659
10673
  discoveryTid;
10674
+ // Track discovery-phase timers so close() can cancel them even if
10675
+ // discovery is still in progress (prevents ERR_SOCKET_DGRAM_NOT_RUNNING).
10676
+ discoveryTimers = [];
10660
10677
  acceptSent = false;
10661
10678
  lastAcceptAtMs;
10662
10679
  ackScheduled = false;
@@ -10705,9 +10722,31 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
10705
10722
  });
10706
10723
  sock.on("error", (e) => this.emit("error", e));
10707
10724
  sock.on("close", () => this.emit("close"));
10708
- await new Promise(
10709
- (resolve) => sock.bind(0, "0.0.0.0", () => resolve())
10710
- );
10725
+ const portRange = Array.from({ length: 500 }, (_, i) => 53500 + i);
10726
+ for (let i = portRange.length - 1; i > 0; i--) {
10727
+ const j = Math.floor(Math.random() * (i + 1));
10728
+ [portRange[i], portRange[j]] = [portRange[j], portRange[i]];
10729
+ }
10730
+ let bound = false;
10731
+ for (const port of portRange) {
10732
+ try {
10733
+ await new Promise((resolve, reject) => {
10734
+ sock.once("error", reject);
10735
+ sock.bind(port, "0.0.0.0", () => {
10736
+ sock.removeListener("error", reject);
10737
+ resolve();
10738
+ });
10739
+ });
10740
+ bound = true;
10741
+ break;
10742
+ } catch {
10743
+ }
10744
+ }
10745
+ if (!bound) {
10746
+ await new Promise(
10747
+ (resolve) => sock.bind(0, "0.0.0.0", () => resolve())
10748
+ );
10749
+ }
10711
10750
  if (this.opts.mode === "direct") {
10712
10751
  this.remote = { host: this.opts.host, port: this.opts.port };
10713
10752
  this.clientId = this.opts.clientId;
@@ -11127,7 +11166,24 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
11127
11166
  BCUDP_DISCOVERY_PORT_LOCAL_ANY,
11128
11167
  BCUDP_DISCOVERY_PORT_LOCAL_UID
11129
11168
  ];
11130
- const broadcastHost = "255.255.255.255";
11169
+ const broadcastHosts = ["255.255.255.255"];
11170
+ const ifaces = (0, import_node_os.networkInterfaces)();
11171
+ for (const name of Object.keys(ifaces)) {
11172
+ const entries = ifaces[name];
11173
+ if (!entries) continue;
11174
+ for (const addr2 of entries) {
11175
+ if (addr2.family === "IPv4" && !addr2.internal && addr2.cidr) {
11176
+ const ipParts = addr2.address.split(".").map(Number);
11177
+ const maskParts = addr2.netmask.split(".").map(Number);
11178
+ if (ipParts.length === 4 && maskParts.length === 4) {
11179
+ const bcast = ipParts.map((octet, i) => octet | ~maskParts[i] & 255).join(".");
11180
+ if (!broadcastHosts.includes(bcast)) {
11181
+ broadcastHosts.push(bcast);
11182
+ }
11183
+ }
11184
+ }
11185
+ }
11186
+ }
11131
11187
  const directHost = (this.opts.directHost ?? "").trim();
11132
11188
  const localMode = opts?.localMode ?? "local-broadcast";
11133
11189
  const directFirstWindowMs = localMode === "local-direct" && directHost ? 3e3 : 0;
@@ -11154,6 +11210,7 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
11154
11210
  )
11155
11211
  );
11156
11212
  }, discoveryTimeout);
11213
+ this.discoveryTimers.push(timeout);
11157
11214
  let retryTimer;
11158
11215
  let retryCount = 0;
11159
11216
  let discoveredSid;
@@ -11330,11 +11387,11 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
11330
11387
  if (directHost) {
11331
11388
  if (directFirstWindowMs > 0 && elapsedMs < directFirstWindowMs)
11332
11389
  return [directHost];
11333
- return [directHost, broadcastHost];
11390
+ return [directHost, ...broadcastHosts];
11334
11391
  }
11335
- return [broadcastHost];
11392
+ return broadcastHosts;
11336
11393
  }
11337
- return [broadcastHost];
11394
+ return broadcastHosts;
11338
11395
  })();
11339
11396
  for (const host of Array.from(new Set(hosts))) {
11340
11397
  for (const port of ports) {
@@ -11342,8 +11399,7 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
11342
11399
  sock.send(packet, port, host);
11343
11400
  retryCount++;
11344
11401
  this.emit("debug", "discovery_send", { retryCount, host, port });
11345
- } catch (e) {
11346
- this.emit("error", e instanceof Error ? e : new Error(String(e)));
11402
+ } catch {
11347
11403
  }
11348
11404
  }
11349
11405
  }
@@ -11352,6 +11408,7 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
11352
11408
  retryTimer = (0, import_node_timers.setInterval)(() => {
11353
11409
  sendDiscovery();
11354
11410
  }, retryInterval);
11411
+ this.discoveryTimers.push(retryTimer);
11355
11412
  });
11356
11413
  this.clientId = reply.cid;
11357
11414
  this.cameraId = reply.did;
@@ -11668,6 +11725,10 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
11668
11725
  this.ackTimer = void 0;
11669
11726
  this.resendTimer = void 0;
11670
11727
  this.hbTimer = void 0;
11728
+ for (const t of this.discoveryTimers) {
11729
+ clearInterval(t);
11730
+ }
11731
+ this.discoveryTimers = [];
11671
11732
  const s = this.sock;
11672
11733
  this.sock = void 0;
11673
11734
  if (!s) return;
@@ -18077,6 +18138,20 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
18077
18138
  const prefix = basename.substring(0, 10).toUpperCase();
18078
18139
  return prefix.includes("S") ? "subStream" : "mainStream";
18079
18140
  }
18141
+ /**
18142
+ * Stream profiles that the device explicitly rejected (response_code 400).
18143
+ * Keyed by `"ch:profile"` (e.g. `"0:ext"`). Once a profile is in this set
18144
+ * it is excluded from `buildVideoStreamOptions()` results and no further
18145
+ * start attempts are made until the API instance is recreated.
18146
+ */
18147
+ _rejectedStreamProfiles = /* @__PURE__ */ new Set();
18148
+ /**
18149
+ * Check whether a stream profile was rejected by the device at runtime
18150
+ * (e.g. ext returned response_code 400).
18151
+ */
18152
+ isStreamProfileRejected(channel, profile) {
18153
+ return this._rejectedStreamProfiles.has(`${channel}:${profile}`);
18154
+ }
18080
18155
  /**
18081
18156
  * Cache for buildVideoStreamOptions.
18082
18157
  *
@@ -23201,6 +23276,16 @@ ${stderr}`)
23201
23276
  }
23202
23277
  if (!frame) frame = await targetClient.sendFrame(baseParams);
23203
23278
  if (frame.header.responseCode !== 200) {
23279
+ if (frame.header.responseCode === 400) {
23280
+ const rejKey = `${ch}:${profile}`;
23281
+ if (!this._rejectedStreamProfiles.has(rejKey)) {
23282
+ this._rejectedStreamProfiles.add(rejKey);
23283
+ this.videoStreamOptionsCache.clear();
23284
+ this.logger?.warn?.(
23285
+ `[ReolinkBaichuanApi] Stream profile rejected by device: channel=${ch} profile=${profile} (response_code 400). This profile will be excluded from available streams. The camera may not support this stream profile with the current firmware.`
23286
+ );
23287
+ }
23288
+ }
23204
23289
  throw new Error(
23205
23290
  `Video stream request rejected (response_code ${frame.header.responseCode}). Expected response_code 200, camera returned ${frame.header.responseCode}`
23206
23291
  );
@@ -25222,6 +25307,8 @@ ${xml}`
25222
25307
  for (const metadata of params.metadatas) {
25223
25308
  const profile = metadata.profile;
25224
25309
  if (isMultiFocal && profile === "ext") continue;
25310
+ if (this._rejectedStreamProfiles.has(`${params.channel}:${profile}`))
25311
+ continue;
25225
25312
  if (params.includeRtsp && profile !== "ext") {
25226
25313
  const streamName = profile === "main" ? "main" : "sub";
25227
25314
  pushRtsp({