@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
package/dist/cli/rtsp-server.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -12951,6 +12951,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
12951
12951
|
// Shared native stream fan-out (single camera stream, multiple RTSP clients)
|
|
12952
12952
|
nativeFanout = null;
|
|
12953
12953
|
noClientAutoStopTimer;
|
|
12954
|
+
// Prebuffer: rolling ring of recent video frames for IDR-aligned fast startup.
|
|
12955
|
+
// When a new client connects while the stream is already running it does not need
|
|
12956
|
+
// to wait up to one full GOP interval for the next keyframe — we replay frames
|
|
12957
|
+
// from the last IDR in the prebuffer immediately.
|
|
12958
|
+
PREBUFFER_MAX_MS = 3e3;
|
|
12959
|
+
prebuffer = [];
|
|
12954
12960
|
static isAdtsAacFrame(b) {
|
|
12955
12961
|
return b.length >= 2 && b[0] === 255 && (b[1] & 240) === 240;
|
|
12956
12962
|
}
|
|
@@ -12985,6 +12991,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
12985
12991
|
);
|
|
12986
12992
|
return { sampleRate, channels, configHex };
|
|
12987
12993
|
}
|
|
12994
|
+
/** Returns true if the raw (packed/Annex B) frame is an IDR (H.264) or IRAP (H.265). */
|
|
12995
|
+
isRawFrameKeyframe(frame) {
|
|
12996
|
+
try {
|
|
12997
|
+
if (frame.videoType === "H264") {
|
|
12998
|
+
const nals = _BaichuanRtspServer.splitAnnexBNals(
|
|
12999
|
+
convertToAnnexB(frame.data)
|
|
13000
|
+
);
|
|
13001
|
+
return nals.some((n) => n.length >= 1 && (n[0] & 31) === 5);
|
|
13002
|
+
}
|
|
13003
|
+
if (frame.videoType === "H265") {
|
|
13004
|
+
const nals = splitAnnexBToNalPayloads2(convertToAnnexB2(frame.data));
|
|
13005
|
+
return nals.some(
|
|
13006
|
+
(n) => n.length >= 2 && isH265Irap(n[0] >> 1 & 63)
|
|
13007
|
+
);
|
|
13008
|
+
}
|
|
13009
|
+
} catch {
|
|
13010
|
+
}
|
|
13011
|
+
return false;
|
|
13012
|
+
}
|
|
12988
13013
|
static parseInterleavedChannels(transportHeader) {
|
|
12989
13014
|
const m = transportHeader.match(/interleaved\s*=\s*(\d+)\s*-\s*(\d+)/i);
|
|
12990
13015
|
if (!m) return null;
|
|
@@ -13379,7 +13404,11 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13379
13404
|
}
|
|
13380
13405
|
const { hasParamSets: hasParamSets2 } = this.flow.getFmtp();
|
|
13381
13406
|
if (!hasParamSets2) {
|
|
13382
|
-
const primingMs = this.api.client.getTransport() === "udp" ? 4e3 :
|
|
13407
|
+
const primingMs = this.api.client.getTransport() === "udp" ? 4e3 : 3e3;
|
|
13408
|
+
const primingStart = Date.now();
|
|
13409
|
+
this.logger.info(
|
|
13410
|
+
`[rebroadcast] DESCRIBE priming: waiting up to ${primingMs}ms for SPS/PPS client=${clientId} path=${this.path}`
|
|
13411
|
+
);
|
|
13383
13412
|
try {
|
|
13384
13413
|
await Promise.race([
|
|
13385
13414
|
this.firstFramePromise || Promise.resolve(),
|
|
@@ -13387,6 +13416,17 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13387
13416
|
]);
|
|
13388
13417
|
} catch {
|
|
13389
13418
|
}
|
|
13419
|
+
const primingElapsed = Date.now() - primingStart;
|
|
13420
|
+
const { hasParamSets: hasParamSetsAfter } = this.flow.getFmtp();
|
|
13421
|
+
if (hasParamSetsAfter) {
|
|
13422
|
+
this.logger.info(
|
|
13423
|
+
`[rebroadcast] DESCRIBE priming: SPS/PPS received after ${primingElapsed}ms client=${clientId} path=${this.path}`
|
|
13424
|
+
);
|
|
13425
|
+
} else {
|
|
13426
|
+
this.logger.warn(
|
|
13427
|
+
`[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}`
|
|
13428
|
+
);
|
|
13429
|
+
}
|
|
13390
13430
|
}
|
|
13391
13431
|
}
|
|
13392
13432
|
{
|
|
@@ -13395,11 +13435,6 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13395
13435
|
this.logger.info(
|
|
13396
13436
|
`[BaichuanRtspServer] DESCRIBE SDP for ${clientId} path=${this.path} codec=${this.flow.sdpCodec} hasParamSets=${hasParamSets2} fmtp=${fmtpPreview}`
|
|
13397
13437
|
);
|
|
13398
|
-
if (!hasParamSets2) {
|
|
13399
|
-
this.rtspDebugLog(
|
|
13400
|
-
`DESCRIBE responding without parameter sets yet (client=${clientId}, path=${this.path}, flow=${this.flow.key})`
|
|
13401
|
-
);
|
|
13402
|
-
}
|
|
13403
13438
|
}
|
|
13404
13439
|
const sdp = this.generateSdp();
|
|
13405
13440
|
sendResponse(
|
|
@@ -13604,10 +13639,6 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13604
13639
|
sdp += `a=control:track1\r
|
|
13605
13640
|
`;
|
|
13606
13641
|
}
|
|
13607
|
-
sdp += `a=setup:passive\r
|
|
13608
|
-
`;
|
|
13609
|
-
sdp += `a=connection:new\r
|
|
13610
|
-
`;
|
|
13611
13642
|
return sdp;
|
|
13612
13643
|
}
|
|
13613
13644
|
/**
|
|
@@ -13678,6 +13709,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13678
13709
|
return false;
|
|
13679
13710
|
if (channel === audioRtpChannel && !resources2?.setupTrack1)
|
|
13680
13711
|
return false;
|
|
13712
|
+
const buffered = rtspSocket.writableLength;
|
|
13713
|
+
if (buffered > 10 * 1024 * 1024) {
|
|
13714
|
+
this.logger.warn(
|
|
13715
|
+
`[rebroadcast] backpressure: ${Math.round(buffered / 1024)}KB buffered for client=${clientId} \u2014 disconnecting`
|
|
13716
|
+
);
|
|
13717
|
+
rtspSocket.destroy();
|
|
13718
|
+
return false;
|
|
13719
|
+
}
|
|
13681
13720
|
try {
|
|
13682
13721
|
return rtspSocket.write(frameRtpOverTcp(channel, msg));
|
|
13683
13722
|
} catch (error) {
|
|
@@ -14107,6 +14146,24 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14107
14146
|
let frameCount = 0;
|
|
14108
14147
|
let lastFrameTime = Date.now();
|
|
14109
14148
|
const targetFrameInterval = streamMetadata && streamMetadata.frameRate > 0 ? 1e3 / streamMetadata.frameRate : 40;
|
|
14149
|
+
const prebufferSnap = this.prebuffer.slice();
|
|
14150
|
+
let lastIdrIdx = -1;
|
|
14151
|
+
for (let i = prebufferSnap.length - 1; i >= 0; i--) {
|
|
14152
|
+
if (prebufferSnap[i].isKeyframe) {
|
|
14153
|
+
lastIdrIdx = i;
|
|
14154
|
+
break;
|
|
14155
|
+
}
|
|
14156
|
+
}
|
|
14157
|
+
const prebufferFrames = lastIdrIdx >= 0 ? prebufferSnap.slice(lastIdrIdx) : [];
|
|
14158
|
+
if (prebufferFrames.length > 0) {
|
|
14159
|
+
this.logger.info(
|
|
14160
|
+
`[rebroadcast] prebuffer replay client=${clientId} frames=${prebufferFrames.length} starting from IDR`
|
|
14161
|
+
);
|
|
14162
|
+
}
|
|
14163
|
+
const combined = async function* () {
|
|
14164
|
+
for (const entry of prebufferFrames) yield entry.frame;
|
|
14165
|
+
for await (const f of clientGenerator) yield f;
|
|
14166
|
+
};
|
|
14110
14167
|
const feedFrames = async () => {
|
|
14111
14168
|
try {
|
|
14112
14169
|
this.rtspDebugLog(
|
|
@@ -14118,7 +14175,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14118
14175
|
let firstVideoFrameSeenLogged = false;
|
|
14119
14176
|
let h265WaitParamSetsLogged = false;
|
|
14120
14177
|
let h265WaitIrapLogged = false;
|
|
14121
|
-
for await (const frame of
|
|
14178
|
+
for await (const frame of combined()) {
|
|
14122
14179
|
if (!this.connectedClients.has(clientId)) {
|
|
14123
14180
|
this.rtspDebugLog(
|
|
14124
14181
|
`Client ${clientId} disconnected, stopping frame feed`
|
|
@@ -14442,6 +14499,18 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14442
14499
|
if (hasParamSets2) {
|
|
14443
14500
|
this.markFirstFrameReceived();
|
|
14444
14501
|
}
|
|
14502
|
+
const isKeyframe = this.isRawFrameKeyframe(frame);
|
|
14503
|
+
this.prebuffer.push({
|
|
14504
|
+
frame: { ...frame, data: Buffer.from(frame.data) },
|
|
14505
|
+
time: Date.now(),
|
|
14506
|
+
isKeyframe
|
|
14507
|
+
});
|
|
14508
|
+
const cutoff = Date.now() - this.PREBUFFER_MAX_MS;
|
|
14509
|
+
let trimIdx = 0;
|
|
14510
|
+
while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx].time < cutoff) {
|
|
14511
|
+
trimIdx++;
|
|
14512
|
+
}
|
|
14513
|
+
if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);
|
|
14445
14514
|
},
|
|
14446
14515
|
onError: (error) => {
|
|
14447
14516
|
this.logger.warn(
|
|
@@ -14455,6 +14524,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14455
14524
|
this.firstFramePromise = null;
|
|
14456
14525
|
this.firstFrameResolve = null;
|
|
14457
14526
|
this.nativeFanout = null;
|
|
14527
|
+
this.prebuffer = [];
|
|
14458
14528
|
this.logger.info(
|
|
14459
14529
|
`[rebroadcast] native stream ended (camera sleeping or connection lost) profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
14460
14530
|
);
|
|
@@ -14523,6 +14593,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14523
14593
|
this.nativeFanout = null;
|
|
14524
14594
|
await fanout.stop();
|
|
14525
14595
|
}
|
|
14596
|
+
this.prebuffer = [];
|
|
14526
14597
|
if (this.tempStreamGenerator) {
|
|
14527
14598
|
try {
|
|
14528
14599
|
await this.tempStreamGenerator.return(void 0);
|