@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.
- package/dist/{chunk-MN7GUZT7.js → chunk-RWYEGEWG.js} +83 -12
- package/dist/chunk-RWYEGEWG.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +82 -11
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +82 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MN7GUZT7.js.map +0 -1
|
@@ -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 :
|
|
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
|
|
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-
|
|
21017
|
+
//# sourceMappingURL=chunk-RWYEGEWG.js.map
|