@apocaliss92/nodelink-js 0.2.2 → 0.2.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.
@@ -5501,6 +5501,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
5501
5501
  // Shared native stream fan-out (single camera stream, multiple RTSP clients)
5502
5502
  nativeFanout = null;
5503
5503
  noClientAutoStopTimer;
5504
+ // Prebuffer: rolling ring of recent video frames for IDR-aligned fast startup.
5505
+ // When a new client connects while the stream is already running it does not need
5506
+ // to wait up to one full GOP interval for the next keyframe — we replay frames
5507
+ // from the last IDR in the prebuffer immediately.
5508
+ PREBUFFER_MAX_MS = 3e3;
5509
+ prebuffer = [];
5504
5510
  static isAdtsAacFrame(b) {
5505
5511
  return b.length >= 2 && b[0] === 255 && (b[1] & 240) === 240;
5506
5512
  }
@@ -5535,6 +5541,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
5535
5541
  );
5536
5542
  return { sampleRate, channels, configHex };
5537
5543
  }
5544
+ /** Returns true if the raw (packed/Annex B) frame is an IDR (H.264) or IRAP (H.265). */
5545
+ isRawFrameKeyframe(frame) {
5546
+ try {
5547
+ if (frame.videoType === "H264") {
5548
+ const nals = _BaichuanRtspServer.splitAnnexBNals(
5549
+ convertToAnnexB(frame.data)
5550
+ );
5551
+ return nals.some((n) => n.length >= 1 && (n[0] & 31) === 5);
5552
+ }
5553
+ if (frame.videoType === "H265") {
5554
+ const nals = splitAnnexBToNalPayloads2(convertToAnnexB2(frame.data));
5555
+ return nals.some(
5556
+ (n) => n.length >= 2 && isH265Irap(n[0] >> 1 & 63)
5557
+ );
5558
+ }
5559
+ } catch {
5560
+ }
5561
+ return false;
5562
+ }
5538
5563
  static parseInterleavedChannels(transportHeader) {
5539
5564
  const m = transportHeader.match(/interleaved\s*=\s*(\d+)\s*-\s*(\d+)/i);
5540
5565
  if (!m) return null;
@@ -5929,7 +5954,11 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
5929
5954
  }
5930
5955
  const { hasParamSets } = this.flow.getFmtp();
5931
5956
  if (!hasParamSets) {
5932
- const primingMs = this.api.client.getTransport() === "udp" ? 4e3 : 1500;
5957
+ const primingMs = this.api.client.getTransport() === "udp" ? 4e3 : 3e3;
5958
+ const primingStart = Date.now();
5959
+ this.logger.info(
5960
+ `[rebroadcast] DESCRIBE priming: waiting up to ${primingMs}ms for SPS/PPS client=${clientId} path=${this.path}`
5961
+ );
5933
5962
  try {
5934
5963
  await Promise.race([
5935
5964
  this.firstFramePromise || Promise.resolve(),
@@ -5937,6 +5966,17 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
5937
5966
  ]);
5938
5967
  } catch {
5939
5968
  }
5969
+ const primingElapsed = Date.now() - primingStart;
5970
+ const { hasParamSets: hasParamSetsAfter } = this.flow.getFmtp();
5971
+ if (hasParamSetsAfter) {
5972
+ this.logger.info(
5973
+ `[rebroadcast] DESCRIBE priming: SPS/PPS received after ${primingElapsed}ms client=${clientId} path=${this.path}`
5974
+ );
5975
+ } else {
5976
+ this.logger.warn(
5977
+ `[rebroadcast] DESCRIBE priming: timed out after ${primingElapsed}ms without SPS/PPS \u2014 SDP will lack sprop-parameter-sets, downstream decoder may hang client=${clientId} path=${this.path}`
5978
+ );
5979
+ }
5940
5980
  }
5941
5981
  }
5942
5982
  {
@@ -5945,11 +5985,6 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
5945
5985
  this.logger.info(
5946
5986
  `[BaichuanRtspServer] DESCRIBE SDP for ${clientId} path=${this.path} codec=${this.flow.sdpCodec} hasParamSets=${hasParamSets} fmtp=${fmtpPreview}`
5947
5987
  );
5948
- if (!hasParamSets) {
5949
- this.rtspDebugLog(
5950
- `DESCRIBE responding without parameter sets yet (client=${clientId}, path=${this.path}, flow=${this.flow.key})`
5951
- );
5952
- }
5953
5988
  }
5954
5989
  const sdp = this.generateSdp();
5955
5990
  sendResponse(
@@ -6154,10 +6189,6 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
6154
6189
  sdp += `a=control:track1\r
6155
6190
  `;
6156
6191
  }
6157
- sdp += `a=setup:passive\r
6158
- `;
6159
- sdp += `a=connection:new\r
6160
- `;
6161
6192
  return sdp;
6162
6193
  }
6163
6194
  /**
@@ -6228,6 +6259,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
6228
6259
  return false;
6229
6260
  if (channel === audioRtpChannel && !resources2?.setupTrack1)
6230
6261
  return false;
6262
+ const buffered = rtspSocket.writableLength;
6263
+ if (buffered > 10 * 1024 * 1024) {
6264
+ this.logger.warn(
6265
+ `[rebroadcast] backpressure: ${Math.round(buffered / 1024)}KB buffered for client=${clientId} \u2014 disconnecting`
6266
+ );
6267
+ rtspSocket.destroy();
6268
+ return false;
6269
+ }
6231
6270
  try {
6232
6271
  return rtspSocket.write(frameRtpOverTcp(channel, msg));
6233
6272
  } catch (error) {
@@ -6657,6 +6696,24 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
6657
6696
  let frameCount = 0;
6658
6697
  let lastFrameTime = Date.now();
6659
6698
  const targetFrameInterval = streamMetadata && streamMetadata.frameRate > 0 ? 1e3 / streamMetadata.frameRate : 40;
6699
+ const prebufferSnap = this.prebuffer.slice();
6700
+ let lastIdrIdx = -1;
6701
+ for (let i = prebufferSnap.length - 1; i >= 0; i--) {
6702
+ if (prebufferSnap[i].isKeyframe) {
6703
+ lastIdrIdx = i;
6704
+ break;
6705
+ }
6706
+ }
6707
+ const prebufferFrames = lastIdrIdx >= 0 ? prebufferSnap.slice(lastIdrIdx) : [];
6708
+ if (prebufferFrames.length > 0) {
6709
+ this.logger.info(
6710
+ `[rebroadcast] prebuffer replay client=${clientId} frames=${prebufferFrames.length} starting from IDR`
6711
+ );
6712
+ }
6713
+ const combined = async function* () {
6714
+ for (const entry of prebufferFrames) yield entry.frame;
6715
+ for await (const f of clientGenerator) yield f;
6716
+ };
6660
6717
  const feedFrames = async () => {
6661
6718
  try {
6662
6719
  this.rtspDebugLog(
@@ -6668,7 +6725,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
6668
6725
  let firstVideoFrameSeenLogged = false;
6669
6726
  let h265WaitParamSetsLogged = false;
6670
6727
  let h265WaitIrapLogged = false;
6671
- for await (const frame of clientGenerator) {
6728
+ for await (const frame of combined()) {
6672
6729
  if (!this.connectedClients.has(clientId)) {
6673
6730
  this.rtspDebugLog(
6674
6731
  `Client ${clientId} disconnected, stopping frame feed`
@@ -6992,6 +7049,18 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
6992
7049
  if (hasParamSets) {
6993
7050
  this.markFirstFrameReceived();
6994
7051
  }
7052
+ const isKeyframe = this.isRawFrameKeyframe(frame);
7053
+ this.prebuffer.push({
7054
+ frame: { ...frame, data: Buffer.from(frame.data) },
7055
+ time: Date.now(),
7056
+ isKeyframe
7057
+ });
7058
+ const cutoff = Date.now() - this.PREBUFFER_MAX_MS;
7059
+ let trimIdx = 0;
7060
+ while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx].time < cutoff) {
7061
+ trimIdx++;
7062
+ }
7063
+ if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);
6995
7064
  },
6996
7065
  onError: (error) => {
6997
7066
  this.logger.warn(
@@ -7005,6 +7074,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
7005
7074
  this.firstFramePromise = null;
7006
7075
  this.firstFrameResolve = null;
7007
7076
  this.nativeFanout = null;
7077
+ this.prebuffer = [];
7008
7078
  this.logger.info(
7009
7079
  `[rebroadcast] native stream ended (camera sleeping or connection lost) profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
7010
7080
  );
@@ -7073,6 +7143,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
7073
7143
  this.nativeFanout = null;
7074
7144
  await fanout.stop();
7075
7145
  }
7146
+ this.prebuffer = [];
7076
7147
  if (this.tempStreamGenerator) {
7077
7148
  try {
7078
7149
  await this.tempStreamGenerator.return(void 0);
@@ -20943,4 +21014,4 @@ export {
20943
21014
  isTcpFailureThatShouldFallbackToUdp,
20944
21015
  autoDetectDeviceType
20945
21016
  };
20946
- //# sourceMappingURL=chunk-MN7GUZT7.js.map
21017
+ //# sourceMappingURL=chunk-RWYEGEWG.js.map