@apocaliss92/nodelink-js 0.4.1 → 0.4.3

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,10 +3,10 @@ import {
3
3
  BaichuanRtspServer,
4
4
  ReolinkBaichuanApi,
5
5
  autoDetectDeviceType
6
- } from "../chunk-SDRNJQ5U.js";
6
+ } from "../chunk-UHFJPQA4.js";
7
7
  import {
8
8
  __require
9
- } from "../chunk-DUHWTZ7U.js";
9
+ } from "../chunk-DEOMUWBN.js";
10
10
 
11
11
  // src/cli/rtsp-server.ts
12
12
  function parseArgs() {
package/dist/index.cjs CHANGED
@@ -5641,7 +5641,7 @@ async function captureModelFixtures(params) {
5641
5641
  () => api.getDualLensChannelInfo(channel),
5642
5642
  (v) => writeJsonSafe(path4.join(outDir, "dual-lens-info.json"), v)
5643
5643
  );
5644
- await capture("streamCombinationTest", async () => {
5644
+ if (!params.skipStreamCombinationTest) await capture("streamCombinationTest", async () => {
5645
5645
  let dualLensInfo;
5646
5646
  try {
5647
5647
  dualLensInfo = await api.getDualLensChannelInfo(channel);
@@ -13410,6 +13410,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
13410
13410
  flow;
13411
13411
  deviceId;
13412
13412
  dedicatedSessionRelease;
13413
+ externalListener;
13413
13414
  // Authentication
13414
13415
  authCredentials = [];
13415
13416
  requireAuth;
@@ -13452,6 +13453,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
13452
13453
  // Shared native stream fan-out (single camera stream, multiple RTSP clients)
13453
13454
  nativeFanout = null;
13454
13455
  noClientAutoStopTimer;
13456
+ /** After last RTSP client; 0 = never auto-stop native stream. */
13457
+ nativeStreamIdleStopMs;
13458
+ /** Primed-but-no-PLAY timeout; 0 = disabled. */
13459
+ nativeStreamPrimeIdleStopMs;
13455
13460
  // Prebuffer: rolling ring of recent video frames for IDR-aligned fast startup.
13456
13461
  // When a new client connects while the stream is already running it does not need
13457
13462
  // to wait up to one full GOP interval for the next keyframe — we replay frames
@@ -13577,6 +13582,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
13577
13582
  this.logger = options.logger ?? console;
13578
13583
  this.tcpRtpFraming = options.tcpRtpFraming ?? "rfc4571";
13579
13584
  this.deviceId = options.deviceId;
13585
+ this.externalListener = options.externalListener ?? false;
13586
+ this.nativeStreamIdleStopMs = options.nativeStreamIdleStopMs ?? 3e4;
13587
+ this.nativeStreamPrimeIdleStopMs = options.nativeStreamPrimeIdleStopMs ?? (this.nativeStreamIdleStopMs > 0 ? 15e3 : 0);
13580
13588
  this.authCredentials = options.credentials ?? [];
13581
13589
  this.requireAuth = options.requireAuth ?? this.authCredentials.length > 0;
13582
13590
  const transport = this.api.client.getTransport();
@@ -13707,41 +13715,56 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
13707
13715
  this.streamMetadata = { frameRate: 25 };
13708
13716
  this.setFlowVideoType("H264", "metadata unavailable");
13709
13717
  }
13710
- this.clientConnectionServer = net2.createServer((socket) => {
13711
- this.handleRtspConnection(socket);
13712
- });
13713
- await new Promise((resolve, reject) => {
13714
- this.clientConnectionServer.listen(
13715
- this.listenPort,
13716
- this.listenHost,
13717
- () => {
13718
- const address = this.clientConnectionServer.address();
13719
- if (address && typeof address === "object" && "port" in address) {
13720
- this.listenPort = address.port;
13718
+ if (!this.externalListener) {
13719
+ this.clientConnectionServer = net2.createServer((socket) => {
13720
+ this.handleRtspConnection(socket);
13721
+ });
13722
+ await new Promise((resolve, reject) => {
13723
+ this.clientConnectionServer.listen(
13724
+ this.listenPort,
13725
+ this.listenHost,
13726
+ () => {
13727
+ const address = this.clientConnectionServer.address();
13728
+ if (address && typeof address === "object" && "port" in address) {
13729
+ this.listenPort = address.port;
13730
+ }
13731
+ resolve();
13721
13732
  }
13722
- resolve();
13723
- }
13724
- );
13725
- this.clientConnectionServer.on("error", (error) => {
13726
- reject(error);
13733
+ );
13734
+ this.clientConnectionServer.on("error", (error) => {
13735
+ reject(error);
13736
+ });
13727
13737
  });
13728
- });
13738
+ }
13729
13739
  this.active = true;
13730
13740
  this.logger.info(
13731
13741
  `[BaichuanRtspServer] RTSP server started on ${this.listenHost}:${this.listenPort}, path: ${this.path}`
13732
13742
  );
13733
13743
  }
13744
+ /**
13745
+ * Accept an externally-routed RTSP connection.
13746
+ * Used in directHandoff mode where RtspProxyServer routes sockets here.
13747
+ * @param socket - The client TCP socket (already authenticated by proxy)
13748
+ * @param initialBuffer - Any bytes already read during path parsing/auth
13749
+ */
13750
+ acceptConnection(socket, initialBuffer) {
13751
+ if (!this.active) {
13752
+ socket.end("RTSP/1.0 503 Service Unavailable\r\n\r\n");
13753
+ return;
13754
+ }
13755
+ this.handleRtspConnection(socket, initialBuffer);
13756
+ }
13734
13757
  /**
13735
13758
  * Handle RTSP connection from a client.
13736
13759
  */
13737
- handleRtspConnection(socket) {
13760
+ handleRtspConnection(socket, initialBuffer) {
13738
13761
  const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
13739
13762
  const connectTime = Date.now();
13740
13763
  this.logger.info(
13741
13764
  `[rebroadcast] client connected client=${clientId} path=${this.path} profile=${this.profile} channel=${this.channel}`
13742
13765
  );
13743
13766
  let sessionId = "";
13744
- let buffer = Buffer.alloc(0);
13767
+ let buffer = initialBuffer ?? Buffer.alloc(0);
13745
13768
  let clientFfmpeg;
13746
13769
  let useTcpInterleaved = false;
13747
13770
  let clientUdpSocket = null;
@@ -13826,8 +13849,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
13826
13849
  }
13827
13850
  cleanup();
13828
13851
  });
13829
- socket.on("data", async (data) => {
13830
- buffer = Buffer.concat([buffer, data]);
13852
+ const processBuffer = async () => {
13831
13853
  while (buffer.includes("\r\n\r\n")) {
13832
13854
  const endIndex = buffer.indexOf("\r\n\r\n");
13833
13855
  const requestText = buffer.subarray(0, endIndex).toString();
@@ -14102,7 +14124,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
14102
14124
  sendResponse(501, "Not Implemented");
14103
14125
  }
14104
14126
  }
14127
+ };
14128
+ socket.on("data", (data) => {
14129
+ buffer = Buffer.concat([buffer, data]);
14130
+ void processBuffer();
14105
14131
  });
14132
+ if (buffer.includes("\r\n\r\n")) {
14133
+ void processBuffer();
14134
+ }
14106
14135
  }
14107
14136
  /**
14108
14137
  * Generate SDP (Session Description Protocol) for RTSP DESCRIBE.
@@ -15070,34 +15099,42 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
15070
15099
  res.seenFirstVideoKeyframe = false;
15071
15100
  res.rtpSentVideoConfig = false;
15072
15101
  }
15073
- if (this.dedicatedSessionRelease) {
15074
- const release = this.dedicatedSessionRelease;
15075
- this.dedicatedSessionRelease = void 0;
15076
- release().catch(() => {
15077
- });
15078
- }
15079
15102
  this.logger.info(
15080
15103
  `[rebroadcast] native stream ended (camera sleeping or connection lost) profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
15081
15104
  );
15082
- if (this.connectedClients.size > 0) {
15083
- this.logger.info(
15084
- `[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
15085
- );
15086
- setImmediate(() => void this.startNativeStream());
15087
- }
15105
+ const releaseAndRestart = async () => {
15106
+ if (this.dedicatedSessionRelease) {
15107
+ const release = this.dedicatedSessionRelease;
15108
+ this.dedicatedSessionRelease = void 0;
15109
+ try {
15110
+ await release();
15111
+ } catch {
15112
+ }
15113
+ }
15114
+ if (this.connectedClients.size > 0) {
15115
+ this.logger.info(
15116
+ `[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
15117
+ );
15118
+ await new Promise((r) => setTimeout(r, 500));
15119
+ void this.startNativeStream();
15120
+ }
15121
+ };
15122
+ void releaseAndRestart();
15088
15123
  }
15089
15124
  });
15090
15125
  this.nativeFanout.start();
15091
15126
  this.clearNoClientAutoStopTimer();
15092
- this.noClientAutoStopTimer = setTimeout(() => {
15093
- if (this.connectedClients.size === 0) {
15094
- this.rtspDebugLog(
15095
- `Auto-stopping primed native stream (no clients connected)`
15096
- );
15097
- void this.stopNativeStream();
15098
- }
15099
- }, 15e3);
15100
- this.noClientAutoStopTimer?.unref?.();
15127
+ if (this.nativeStreamPrimeIdleStopMs > 0) {
15128
+ this.noClientAutoStopTimer = setTimeout(() => {
15129
+ if (this.connectedClients.size === 0) {
15130
+ this.rtspDebugLog(
15131
+ `Auto-stopping primed native stream (no clients connected)`
15132
+ );
15133
+ void this.stopNativeStream();
15134
+ }
15135
+ }, this.nativeStreamPrimeIdleStopMs);
15136
+ this.noClientAutoStopTimer?.unref?.();
15137
+ }
15101
15138
  }
15102
15139
  markFirstFrameReceived() {
15103
15140
  if (!this.firstFrameReceived && this.firstFrameResolve) {
@@ -15172,12 +15209,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
15172
15209
  this.emit("clientDisconnected", clientId);
15173
15210
  if (this.connectedClients.size === 0) {
15174
15211
  this.clearNoClientAutoStopTimer();
15175
- this.noClientAutoStopTimer = setTimeout(() => {
15176
- if (this.connectedClients.size === 0) {
15177
- void this.stopNativeStream();
15178
- }
15179
- }, 3e4);
15180
- this.noClientAutoStopTimer?.unref?.();
15212
+ if (this.nativeStreamIdleStopMs > 0) {
15213
+ this.noClientAutoStopTimer = setTimeout(() => {
15214
+ if (this.connectedClients.size === 0) {
15215
+ void this.stopNativeStream();
15216
+ }
15217
+ }, this.nativeStreamIdleStopMs);
15218
+ this.noClientAutoStopTimer?.unref?.();
15219
+ }
15181
15220
  }
15182
15221
  }
15183
15222
  }
@@ -15308,6 +15347,31 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
15308
15347
  getClientCount() {
15309
15348
  return this.connectedClients.size;
15310
15349
  }
15350
+ /**
15351
+ * Subscribe to the raw native stream for diagnostic purposes.
15352
+ * The subscriber receives the same frames as RTSP clients.
15353
+ * Counts as a "consumer" for lifecycle — prevents auto-stop while subscribed.
15354
+ * If the native stream is not active, starts it automatically.
15355
+ */
15356
+ async subscribeDiagnostic(id) {
15357
+ this.connectedClients.add(`diag:${id}`);
15358
+ if (!this.nativeStreamActive) {
15359
+ await this.startNativeStream();
15360
+ }
15361
+ return this.nativeFanout.subscribe(`diag:${id}`);
15362
+ }
15363
+ /**
15364
+ * Unsubscribe a diagnostic session.
15365
+ */
15366
+ unsubscribeDiagnostic(id) {
15367
+ this.removeClient(`diag:${id}`);
15368
+ }
15369
+ /**
15370
+ * Returns detected audio metadata (available after first audio frame).
15371
+ */
15372
+ getAudioInfo() {
15373
+ return this.audioInfo;
15374
+ }
15311
15375
  };
15312
15376
 
15313
15377
  // src/reolink/baichuan/ReolinkBaichuanApi.ts