@apocaliss92/nodelink-js 0.6.7 → 0.6.8-beta.0
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/BaichuanVideoStream-6Y2JSBLN.js +7 -0
- package/dist/{DiagnosticsTools-QJ3CRYGA.js → DiagnosticsTools-G3S6L3FX.js} +3 -3
- package/dist/{chunk-IQVVVSXO.js → chunk-27ZKJZFH.js} +2 -2
- package/dist/{chunk-MZUSWKF3.js → chunk-4LZABN6I.js} +30 -1
- package/dist/chunk-4LZABN6I.js.map +1 -0
- package/dist/{chunk-T22QCNBR.js → chunk-OJ4YR45R.js} +109 -12
- package/dist/chunk-OJ4YR45R.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +132 -6
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -3
- package/dist/index.cjs +132 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/dist/BaichuanVideoStream-OCLOM452.js +0 -7
- package/dist/chunk-MZUSWKF3.js.map +0 -1
- package/dist/chunk-T22QCNBR.js.map +0 -1
- /package/dist/{BaichuanVideoStream-OCLOM452.js.map → BaichuanVideoStream-6Y2JSBLN.js.map} +0 -0
- /package/dist/{DiagnosticsTools-QJ3CRYGA.js.map → DiagnosticsTools-G3S6L3FX.js.map} +0 -0
- /package/dist/{chunk-IQVVVSXO.js.map → chunk-27ZKJZFH.js.map} +0 -0
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runAllDiagnosticsConsecutively,
|
|
31
31
|
runMultifocalDiagnosticsConsecutively,
|
|
32
32
|
xmlEscape
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-27ZKJZFH.js";
|
|
34
34
|
import {
|
|
35
35
|
BC_CLASS_FILE_DOWNLOAD,
|
|
36
36
|
BC_CLASS_LEGACY,
|
|
@@ -178,7 +178,7 @@ import {
|
|
|
178
178
|
splitAnnexBToNalPayloads2,
|
|
179
179
|
talkTraceLog,
|
|
180
180
|
traceLog
|
|
181
|
-
} from "./chunk-
|
|
181
|
+
} from "./chunk-4LZABN6I.js";
|
|
182
182
|
|
|
183
183
|
// src/protocol/framing.ts
|
|
184
184
|
function encodeHeader(h) {
|
|
@@ -2473,6 +2473,16 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
2473
2473
|
// Public to allow ReolinkBaichuanApi to check subscription status
|
|
2474
2474
|
keepAliveTimer;
|
|
2475
2475
|
keepAlivePingInFlight = false;
|
|
2476
|
+
// Consecutive TCP keepalive (PING) failures with no response. A half-open
|
|
2477
|
+
// socket after a camera reboot is NOT reported as `destroyed`, so
|
|
2478
|
+
// `isSocketConnected()` stays stale-true and recovery never triggers. We
|
|
2479
|
+
// treat repeated unanswered pings as a hard liveness failure and tear the
|
|
2480
|
+
// socket down so the 'close' handler fires (resetting loggedIn/subscribed
|
|
2481
|
+
// and emitting "close"), which lets ensureConnected() rebuild it.
|
|
2482
|
+
consecutiveKeepalivePingFailures = 0;
|
|
2483
|
+
// Number of unanswered keepalive pings tolerated before the TCP socket is
|
|
2484
|
+
// declared dead. 2 misses ≈ up to ~2 keepalive intervals of silence.
|
|
2485
|
+
static KEEPALIVE_MAX_PING_FAILURES = 2;
|
|
2476
2486
|
// Battery/BCUDP cameras may actively terminate sessions (D2C_DISC) when overwhelmed
|
|
2477
2487
|
// or sleeping. Without a cooldown, callers can end up in tight reconnect loops.
|
|
2478
2488
|
udpReconnectCooldownUntilMs = 0;
|
|
@@ -3101,8 +3111,21 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
3101
3111
|
// Keepalive has empty body
|
|
3102
3112
|
internal: true
|
|
3103
3113
|
});
|
|
3114
|
+
this.consecutiveKeepalivePingFailures = 0;
|
|
3104
3115
|
} catch (e) {
|
|
3105
|
-
this.
|
|
3116
|
+
this.consecutiveKeepalivePingFailures++;
|
|
3117
|
+
this.logDebug("keepalive_ping_failed", {
|
|
3118
|
+
error: e,
|
|
3119
|
+
consecutiveFailures: this.consecutiveKeepalivePingFailures
|
|
3120
|
+
});
|
|
3121
|
+
if (this.transport === "tcp" && this.consecutiveKeepalivePingFailures >= _BaichuanClient.KEEPALIVE_MAX_PING_FAILURES && this.tcpSocket && !this.tcpSocket.destroyed) {
|
|
3122
|
+
this.logFixed(
|
|
3123
|
+
"keepalive_dead",
|
|
3124
|
+
`transport=tcp host=${this.opts.host} consecutiveFailures=${this.consecutiveKeepalivePingFailures} \u2014 destroying half-open socket`
|
|
3125
|
+
);
|
|
3126
|
+
this.consecutiveKeepalivePingFailures = 0;
|
|
3127
|
+
this.tcpSocket.destroy();
|
|
3128
|
+
}
|
|
3106
3129
|
}
|
|
3107
3130
|
}
|
|
3108
3131
|
async connect() {
|
|
@@ -3195,6 +3218,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
3195
3218
|
this.tcpSocket = sock;
|
|
3196
3219
|
this.transport = "tcp";
|
|
3197
3220
|
this.socketClosed = false;
|
|
3221
|
+
this.consecutiveKeepalivePingFailures = 0;
|
|
3198
3222
|
this.socketSessionId = this.newSocketSessionId("tcp");
|
|
3199
3223
|
try {
|
|
3200
3224
|
sock.setKeepAlive(true, 3e4);
|
|
@@ -6676,6 +6700,20 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter4 {
|
|
|
6676
6700
|
// from the last IDR in the prebuffer immediately.
|
|
6677
6701
|
PREBUFFER_MAX_MS = 3e3;
|
|
6678
6702
|
prebuffer = [];
|
|
6703
|
+
/**
|
|
6704
|
+
* Hard cap on the userspace TCP send buffer for a single RTSP client. A
|
|
6705
|
+
* healthy consumer drains near-instantly; if this much data backs up the
|
|
6706
|
+
* client is dead or far too slow to keep up with the live stream.
|
|
6707
|
+
*/
|
|
6708
|
+
static MAX_CLIENT_BUFFERED_BYTES = 8 * 1024 * 1024;
|
|
6709
|
+
/**
|
|
6710
|
+
* Pure backpressure decision: should the client socket be disconnected
|
|
6711
|
+
* because its send buffer has grown past the hard cap? Extracted so the
|
|
6712
|
+
* decision can be unit-tested without a live socket.
|
|
6713
|
+
*/
|
|
6714
|
+
static shouldDisconnectForBackpressure(bufferedBytes) {
|
|
6715
|
+
return bufferedBytes > _BaichuanRtspServer.MAX_CLIENT_BUFFERED_BYTES;
|
|
6716
|
+
}
|
|
6679
6717
|
static isAdtsAacFrame(b) {
|
|
6680
6718
|
return b.length >= 2 && b[0] === 255 && (b[1] & 240) === 240;
|
|
6681
6719
|
}
|
|
@@ -7652,14 +7690,36 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter4 {
|
|
|
7652
7690
|
const rtspDebugLog = (message) => this.rtspDebugLog(message);
|
|
7653
7691
|
const sendInterleaved = (channel, msg) => {
|
|
7654
7692
|
if (!rtspSocket || rtspSocket.destroyed || !rtspSocket.writable)
|
|
7655
|
-
return
|
|
7693
|
+
return true;
|
|
7694
|
+
if (_BaichuanRtspServer.shouldDisconnectForBackpressure(
|
|
7695
|
+
rtspSocket.writableLength
|
|
7696
|
+
)) {
|
|
7697
|
+
this.logger.warn(
|
|
7698
|
+
`[rebroadcast] backpressure: ${Math.round(rtspSocket.writableLength / 1024)}KB buffered for client=${clientId} \u2014 disconnecting`
|
|
7699
|
+
);
|
|
7700
|
+
rtspSocket.destroy();
|
|
7701
|
+
return true;
|
|
7702
|
+
}
|
|
7656
7703
|
try {
|
|
7657
7704
|
return rtspSocket.write(frameRtpOverTcp(channel, msg));
|
|
7658
7705
|
} catch (error) {
|
|
7659
7706
|
if (error && typeof error === "object" && "code" in error && error.code === "EPIPE")
|
|
7660
|
-
return
|
|
7707
|
+
return true;
|
|
7661
7708
|
}
|
|
7662
|
-
return
|
|
7709
|
+
return true;
|
|
7710
|
+
};
|
|
7711
|
+
const awaitClientDrain = async () => {
|
|
7712
|
+
if (!rtspSocket || rtspSocket.destroyed) return;
|
|
7713
|
+
if (!rtspSocket.writableNeedDrain) return;
|
|
7714
|
+
await new Promise((resolve) => {
|
|
7715
|
+
const done = () => {
|
|
7716
|
+
rtspSocket.removeListener("drain", done);
|
|
7717
|
+
rtspSocket.removeListener("close", done);
|
|
7718
|
+
resolve();
|
|
7719
|
+
};
|
|
7720
|
+
rtspSocket.once("drain", done);
|
|
7721
|
+
rtspSocket.once("close", done);
|
|
7722
|
+
});
|
|
7663
7723
|
};
|
|
7664
7724
|
const getVideoChannel = () => resources?.track0RtpChannel ?? 0;
|
|
7665
7725
|
const getAudioChannel = () => resources?.track1RtpChannel ?? 2;
|
|
@@ -8238,6 +8298,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter4 {
|
|
|
8238
8298
|
resources.framesSent = (resources.framesSent ?? 0) + 1;
|
|
8239
8299
|
}
|
|
8240
8300
|
sendVideoAccessUnit(videoType, normalizedVideoData, true);
|
|
8301
|
+
await awaitClientDrain();
|
|
8241
8302
|
} else {
|
|
8242
8303
|
try {
|
|
8243
8304
|
if (stdin && !stdin.destroyed && !stdin.writableEnded && !stdin.writableFinished) {
|
|
@@ -14528,7 +14589,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
14528
14589
|
return;
|
|
14529
14590
|
}
|
|
14530
14591
|
entry.startInFlight = (async () => {
|
|
14531
|
-
const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-
|
|
14592
|
+
const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-6Y2JSBLN.js");
|
|
14532
14593
|
const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
|
|
14533
14594
|
const dedicated = await this.createDedicatedSession(sessionKey);
|
|
14534
14595
|
const stream = new BaichuanVideoStream2({
|
|
@@ -14679,9 +14740,45 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
14679
14740
|
const generalEntry = this.socketPool.get("general");
|
|
14680
14741
|
if (!generalEntry) return;
|
|
14681
14742
|
if (!generalEntry.client.isSocketConnected?.() || !generalEntry.client.loggedIn) {
|
|
14682
|
-
this.
|
|
14683
|
-
|
|
14743
|
+
if (!this.eventWatchdogSilenceResubscribeEnabled) {
|
|
14744
|
+
this.logger.debug?.(
|
|
14745
|
+
`[ReolinkBaichuanApi] event watchdog tick: skipping (connection not alive, battery/UDP \u2014 not forcing reconnect: connected=${generalEntry.client.isSocketConnected?.()} loggedIn=${generalEntry.client.loggedIn})`
|
|
14746
|
+
);
|
|
14747
|
+
return;
|
|
14748
|
+
}
|
|
14749
|
+
const now2 = Date.now();
|
|
14750
|
+
const backoffMs = Math.min(
|
|
14751
|
+
3e4 * Math.pow(2, this.simpleEventWatchdogRecoveryAttempts),
|
|
14752
|
+
this.simpleEventWatchdogSilenceThresholdMs
|
|
14753
|
+
);
|
|
14754
|
+
if (now2 - this.simpleEventWatchdogLastRecoveryAt < backoffMs) {
|
|
14755
|
+
this.logger.debug?.(
|
|
14756
|
+
`[ReolinkBaichuanApi] event watchdog tick: connection down, reconnect backoff active (connected=${generalEntry.client.isSocketConnected?.()} loggedIn=${generalEntry.client.loggedIn})`
|
|
14757
|
+
);
|
|
14758
|
+
return;
|
|
14759
|
+
}
|
|
14760
|
+
this.simpleEventWatchdogRecoveryAttempts++;
|
|
14761
|
+
this.simpleEventWatchdogLastRecoveryAt = now2;
|
|
14762
|
+
(this.logger.info ?? this.logger.log).call(
|
|
14763
|
+
this.logger,
|
|
14764
|
+
`[ReolinkBaichuanApi] event watchdog: control socket down, forcing full reconnect (attempt #${this.simpleEventWatchdogRecoveryAttempts})`,
|
|
14765
|
+
{ host: this.host }
|
|
14684
14766
|
);
|
|
14767
|
+
try {
|
|
14768
|
+
await this.ensureConnected();
|
|
14769
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
14770
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
14771
|
+
(this.logger.info ?? this.logger.log).call(
|
|
14772
|
+
this.logger,
|
|
14773
|
+
`[ReolinkBaichuanApi] event watchdog: full reconnect successful`
|
|
14774
|
+
);
|
|
14775
|
+
} catch (e) {
|
|
14776
|
+
(this.logger.debug ?? this.logger.log).call(
|
|
14777
|
+
this.logger,
|
|
14778
|
+
`[ReolinkBaichuanApi] event watchdog: full reconnect attempt #${this.simpleEventWatchdogRecoveryAttempts} failed`,
|
|
14779
|
+
formatErrorForLog(e)
|
|
14780
|
+
);
|
|
14781
|
+
}
|
|
14685
14782
|
return;
|
|
14686
14783
|
}
|
|
14687
14784
|
const now = Date.now();
|
|
@@ -21282,7 +21379,7 @@ ${xml}`
|
|
|
21282
21379
|
* @returns Test results for all stream types and profiles
|
|
21283
21380
|
*/
|
|
21284
21381
|
async testChannelStreams(channel, logger) {
|
|
21285
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
21382
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-G3S6L3FX.js");
|
|
21286
21383
|
return await testChannelStreams({
|
|
21287
21384
|
api: this,
|
|
21288
21385
|
channel: this.normalizeChannel(channel),
|
|
@@ -21298,7 +21395,7 @@ ${xml}`
|
|
|
21298
21395
|
* @returns Complete diagnostics for all channels and streams
|
|
21299
21396
|
*/
|
|
21300
21397
|
async collectMultifocalDiagnostics(logger) {
|
|
21301
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
21398
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-G3S6L3FX.js");
|
|
21302
21399
|
return await collectMultifocalDiagnostics({
|
|
21303
21400
|
api: this,
|
|
21304
21401
|
logger
|
|
@@ -26090,4 +26187,4 @@ export {
|
|
|
26090
26187
|
tcpReachabilityProbe,
|
|
26091
26188
|
autoDetectDeviceType
|
|
26092
26189
|
};
|
|
26093
|
-
//# sourceMappingURL=chunk-
|
|
26190
|
+
//# sourceMappingURL=chunk-OJ4YR45R.js.map
|