@apocaliss92/nodelink-js 0.2.5 → 0.3.5

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.
@@ -8079,14 +8079,16 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
8079
8079
  `;
8080
8080
  }
8081
8081
  if (body) {
8082
- response += `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
8082
+ const bodyBuf = Buffer.from(body, "utf8");
8083
+ response += `Content-Length: ${bodyBuf.length}\r
8083
8084
  `;
8085
+ response += "\r\n";
8086
+ socket.write(response);
8087
+ socket.write(bodyBuf);
8088
+ } else {
8089
+ response += "\r\n";
8090
+ socket.write(response);
8084
8091
  }
8085
- response += "\r\n";
8086
- if (body) {
8087
- response += body;
8088
- }
8089
- socket.write(response);
8090
8092
  };
8091
8093
  this.rtspDebugLog(`RTSP ${method} ${url}`);
8092
8094
  if (this.requireAuth) {
@@ -8296,10 +8298,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
8296
8298
  );
8297
8299
  }
8298
8300
  }
8299
- sendResponse(200, "OK", {
8300
- Session: sessionId,
8301
- Range: "npt=0.000-"
8302
- });
8301
+ {
8302
+ const baseUrl = `rtsp://${this.listenHost}:${this.listenPort}${this.path}`;
8303
+ const resources = this.clientResources.get(clientId);
8304
+ const rtpInfoParts = [];
8305
+ if (resources?.setupTrack0) {
8306
+ rtpInfoParts.push(`url=${baseUrl}/track0`);
8307
+ }
8308
+ if (resources?.setupTrack1) {
8309
+ rtpInfoParts.push(`url=${baseUrl}/track1`);
8310
+ }
8311
+ const playHeaders = {
8312
+ Session: sessionId,
8313
+ Range: "npt=now-"
8314
+ };
8315
+ if (rtpInfoParts.length > 0) {
8316
+ playHeaders["RTP-Info"] = rtpInfoParts.join(",");
8317
+ }
8318
+ sendResponse(200, "OK", playHeaders);
8319
+ }
8303
8320
  } else if (method === "TEARDOWN") {
8304
8321
  this.logger.info(
8305
8322
  `[rebroadcast] TEARDOWN client=${clientId} session=${sessionId}`
@@ -8329,6 +8346,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
8329
8346
  sdp += `c=IN IP4 ${this.listenHost}\r
8330
8347
  `;
8331
8348
  sdp += "t=0 0\r\n";
8349
+ sdp += "a=range:npt=now-\r\n";
8350
+ sdp += "a=control:*\r\n";
8332
8351
  sdp += `m=video 0 RTP/AVP ${videoPayloadType}\r
8333
8352
  `;
8334
8353
  sdp += `a=rtpmap:${videoPayloadType} ${codec}/90000\r
@@ -9271,7 +9290,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
9271
9290
  this.firstFramePromise = null;
9272
9291
  this.firstFrameResolve = null;
9273
9292
  this.nativeFanout = null;
9274
- this.prebuffer = [];
9293
+ for (const [, resources] of this.clientResources) {
9294
+ const res = resources;
9295
+ res.rtpVideoBaseMicroseconds = void 0;
9296
+ res.rtpVideoBaseTimestamp = void 0;
9297
+ res.rtpVideoLastTimestamp = void 0;
9298
+ res.seenFirstVideoKeyframe = false;
9299
+ res.rtpSentVideoConfig = false;
9300
+ }
9275
9301
  if (this.dedicatedSessionRelease) {
9276
9302
  const release = this.dedicatedSessionRelease;
9277
9303
  this.dedicatedSessionRelease = void 0;
@@ -11345,6 +11371,22 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
11345
11371
  static coverPreviewBackoffMs = /* @__PURE__ */ new Map();
11346
11372
  static COVER_PREVIEW_INITIAL_BACKOFF_MS = 1e3;
11347
11373
  static COVER_PREVIEW_MAX_BACKOFF_MS = 3e4;
11374
+ /**
11375
+ * Per-client snapshot (cmd_id=109) serialization queue.
11376
+ *
11377
+ * WHY: On NVR/multi-camera devices sharing one socket, concurrent snapshot requests
11378
+ * can cause JPEG data to mix (even with per-request msgNum filtering):
11379
+ * - Camera A and B both send frames on same socket
11380
+ * - Frame listener is global per socket
11381
+ * - Timing quirks can cause chunk reordering or listener confusion
11382
+ *
11383
+ * FIX: Serialize all cmd_id=109 requests on THIS client instance.
11384
+ * Each snapshot waits for previous one to complete before starting.
11385
+ * This ensures clean frame sequences per request, zero data corruption.
11386
+ *
11387
+ * Impact: Snapshots are ~0–50ms slower per camera (negligible for users).
11388
+ */
11389
+ snapshotQueueTail = Promise.resolve();
11348
11390
  opts;
11349
11391
  debugCfg;
11350
11392
  logger;
@@ -13826,6 +13868,20 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
13826
13868
  });
13827
13869
  }
13828
13870
  async sendBinarySnapshot109(params) {
13871
+ const prevTail = this.snapshotQueueTail;
13872
+ let resolve;
13873
+ const newTail = new Promise((r) => {
13874
+ resolve = r;
13875
+ });
13876
+ this.snapshotQueueTail = newTail;
13877
+ try {
13878
+ await prevTail;
13879
+ return await this.sendBinarySnapshot109Impl(params);
13880
+ } finally {
13881
+ resolve();
13882
+ }
13883
+ }
13884
+ async sendBinarySnapshot109Impl(params) {
13829
13885
  await this.connect();
13830
13886
  const channel = params.channel ?? this.opts.channel ?? 0;
13831
13887
  const channelId = params.channelIdOverride ?? (params.channel == null ? this.hostChannelId : channel + 1);
@@ -13885,7 +13941,8 @@ var BaichuanClient = class _BaichuanClient extends import_node_events4.EventEmit
13885
13941
  };
13886
13942
  const onFrame = (frame) => {
13887
13943
  if (frame.header.cmdId !== cmdId) return;
13888
- if (frame.header.msgNum === msgNum && frame.header.responseCode >= 400) {
13944
+ if (frame.header.msgNum !== msgNum) return;
13945
+ if (frame.header.responseCode >= 400) {
13889
13946
  fail(
13890
13947
  new Error(
13891
13948
  `Baichuan snapshot request rejected (cmdId=${cmdId} msgNum=${msgNum} responseCode=${frame.header.responseCode})`
@@ -22003,13 +22060,13 @@ ${stderr}`)
22003
22060
  */
22004
22061
  async muxToMp4(params) {
22005
22062
  const { spawn: spawn4 } = await import("child_process");
22006
- const { randomUUID: randomUUID2 } = await import("crypto");
22063
+ const { randomUUID: randomUUID3 } = await import("crypto");
22007
22064
  const fs5 = await import("fs/promises");
22008
22065
  const os = await import("os");
22009
22066
  const path5 = await import("path");
22010
22067
  const ffmpeg = params.ffmpegPath ?? "ffmpeg";
22011
22068
  const tmpDir = os.tmpdir();
22012
- const id = randomUUID2();
22069
+ const id = randomUUID3();
22013
22070
  const videoFormat = params.videoCodec === "H265" ? "hevc" : "h264";
22014
22071
  const videoPath = path5.join(tmpDir, `reolink-${id}.${videoFormat}`);
22015
22072
  const outputPath = path5.join(tmpDir, `reolink-${id}.mp4`);
@@ -26991,8 +27048,13 @@ ${scheduleItems}
26991
27048
  init_constants();
26992
27049
 
26993
27050
  // src/reolink/discovery.ts
27051
+ var import_node_child_process4 = require("child_process");
27052
+ var import_node_crypto3 = require("crypto");
26994
27053
  var import_node_dgram2 = __toESM(require("dgram"), 1);
27054
+ var net3 = __toESM(require("net"), 1);
26995
27055
  var import_node_os2 = require("os");
27056
+ var import_node_util = require("util");
27057
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process4.execFile);
26996
27058
  async function discoverViaUdpDirect(host, options) {
26997
27059
  if (!options.enableUdpDiscovery) return [];
26998
27060
  const logger = options.logger;
@@ -27234,8 +27296,8 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
27234
27296
  async function pingHost(host, timeoutMs = 3e3) {
27235
27297
  return new Promise((resolve) => {
27236
27298
  const { exec } = require("child_process");
27237
- const platform = process.platform;
27238
- const pingCmd = platform === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform === "darwin" ? (
27299
+ const platform2 = process.platform;
27300
+ const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
27239
27301
  // macOS: -W is in milliseconds (Linux: seconds)
27240
27302
  `ping -c 1 -W ${timeoutMs} ${host}`
27241
27303
  ) : (