@apocaliss92/nodelink-js 0.4.5 → 0.4.7
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/{DiagnosticsTools-55PR4WFD.js → DiagnosticsTools-UMN4C7SY.js} +2 -2
- package/dist/{chunk-WDFKIHM5.js → chunk-GKLOJJ34.js} +177 -29
- package/dist/chunk-GKLOJJ34.js.map +1 -0
- package/dist/{chunk-DEOMUWBN.js → chunk-TR3V5FTO.js} +15 -1
- package/dist/chunk-TR3V5FTO.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +187 -25
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +278 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +93 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DEOMUWBN.js.map +0 -1
- package/dist/chunk-WDFKIHM5.js.map +0 -1
- /package/dist/{DiagnosticsTools-55PR4WFD.js.map → DiagnosticsTools-UMN4C7SY.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -2434,6 +2434,7 @@ declare class BcUdpStream extends EventEmitter<{
|
|
|
2434
2434
|
private resendTimer;
|
|
2435
2435
|
private hbTimer;
|
|
2436
2436
|
private discoveryTid;
|
|
2437
|
+
private discoveryTimers;
|
|
2437
2438
|
private acceptSent;
|
|
2438
2439
|
private lastAcceptAtMs;
|
|
2439
2440
|
private ackScheduled;
|
|
@@ -3655,6 +3656,15 @@ declare class BaichuanVideoStream extends EventEmitter<{
|
|
|
3655
3656
|
private lastPpsH265;
|
|
3656
3657
|
private lastPrependedParamSetsH265;
|
|
3657
3658
|
private aesStreamDecryptor;
|
|
3659
|
+
/**
|
|
3660
|
+
* Pending startup error stashed when emitSafeError is called before any
|
|
3661
|
+
* "error" listener is registered (e.g. camera returns 400 during start()).
|
|
3662
|
+
* The rfc4571-server's waitForKeyframe can consume this immediately instead
|
|
3663
|
+
* of waiting for the full keyframe timeout.
|
|
3664
|
+
*/
|
|
3665
|
+
private _pendingStartupError;
|
|
3666
|
+
/** Consume and clear any pending startup error. */
|
|
3667
|
+
consumePendingStartupError(): Error | undefined;
|
|
3658
3668
|
private emitSafeError;
|
|
3659
3669
|
private lastMediaAtMs;
|
|
3660
3670
|
private watchdogTimer;
|
|
@@ -4229,6 +4239,13 @@ declare class ReolinkBaichuanApi {
|
|
|
4229
4239
|
* - "replay:XXX" - dedicated per replay session
|
|
4230
4240
|
*/
|
|
4231
4241
|
private readonly socketPool;
|
|
4242
|
+
/**
|
|
4243
|
+
* Consecutive stream-start (cmdId=3) timeout counter per socket tag.
|
|
4244
|
+
* When a streaming socket has N consecutive timeouts, the socket is force-closed
|
|
4245
|
+
* so the next attempt creates a fresh connection. Resets on success.
|
|
4246
|
+
*/
|
|
4247
|
+
private readonly consecutiveStreamTimeouts;
|
|
4248
|
+
private static readonly MAX_CONSECUTIVE_STREAM_TIMEOUTS;
|
|
4232
4249
|
/** BaichuanClientOptions to use when creating new sockets */
|
|
4233
4250
|
private readonly clientOptions;
|
|
4234
4251
|
/**
|
|
@@ -4373,6 +4390,14 @@ declare class ReolinkBaichuanApi {
|
|
|
4373
4390
|
*/
|
|
4374
4391
|
private readonly deviceCapabilitiesCache;
|
|
4375
4392
|
private static readonly CAPABILITIES_CACHE_TTL_MS;
|
|
4393
|
+
/**
|
|
4394
|
+
* Dedupe key for battery push events (cmd_id 252), per channel.
|
|
4395
|
+
* Cameras emit BatteryInfoList frequently while streaming (every few
|
|
4396
|
+
* seconds). We only forward an event when the meaningful fields change
|
|
4397
|
+
* (percent, chargeStatus, adapterStatus) to avoid flooding SSE/MQTT
|
|
4398
|
+
* consumers and the UI event log.
|
|
4399
|
+
*/
|
|
4400
|
+
private readonly lastBatteryPushKey;
|
|
4376
4401
|
/** Keep replay/streaming sockets warm briefly to reduce clip switch latency. */
|
|
4377
4402
|
private static readonly SOCKET_POOL_KEEPALIVE_MS;
|
|
4378
4403
|
/**
|
|
@@ -4505,6 +4530,18 @@ declare class ReolinkBaichuanApi {
|
|
|
4505
4530
|
* which indicates subStream (e.g., RecS03_, RecS_).
|
|
4506
4531
|
*/
|
|
4507
4532
|
private determineStreamTypeFromFileName;
|
|
4533
|
+
/**
|
|
4534
|
+
* Stream profiles that the device explicitly rejected (response_code 400).
|
|
4535
|
+
* Keyed by `"ch:profile"` (e.g. `"0:ext"`). Once a profile is in this set
|
|
4536
|
+
* it is excluded from `buildVideoStreamOptions()` results and no further
|
|
4537
|
+
* start attempts are made until the API instance is recreated.
|
|
4538
|
+
*/
|
|
4539
|
+
private readonly _rejectedStreamProfiles;
|
|
4540
|
+
/**
|
|
4541
|
+
* Check whether a stream profile was rejected by the device at runtime
|
|
4542
|
+
* (e.g. ext returned response_code 400).
|
|
4543
|
+
*/
|
|
4544
|
+
isStreamProfileRejected(channel: number, profile: StreamProfile): boolean;
|
|
4508
4545
|
/**
|
|
4509
4546
|
* Cache for buildVideoStreamOptions.
|
|
4510
4547
|
*
|
|
@@ -5700,6 +5737,22 @@ declare class ReolinkBaichuanApi {
|
|
|
5700
5737
|
* 2. **Storm**: ≥3 D2C_DISCs within 60 s triggers extended cooldown (120 s).
|
|
5701
5738
|
*/
|
|
5702
5739
|
private notifyD2cDisc;
|
|
5740
|
+
/**
|
|
5741
|
+
* Find the socket pool tag for a given BaichuanClient instance.
|
|
5742
|
+
* Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
|
|
5743
|
+
*/
|
|
5744
|
+
private findSocketTagForClient;
|
|
5745
|
+
/**
|
|
5746
|
+
* Reset the consecutive stream-start timeout counter for a streaming socket.
|
|
5747
|
+
* Called on successful stream start.
|
|
5748
|
+
*/
|
|
5749
|
+
private resetStreamTimeoutCounter;
|
|
5750
|
+
/**
|
|
5751
|
+
* Track a stream-start timeout on a streaming socket.
|
|
5752
|
+
* After MAX_CONSECUTIVE_STREAM_TIMEOUTS consecutive timeouts, force-close the
|
|
5753
|
+
* socket so the next attempt creates a fresh connection.
|
|
5754
|
+
*/
|
|
5755
|
+
private trackStreamTimeout;
|
|
5703
5756
|
/**
|
|
5704
5757
|
* Best-effort sleeping inference for battery/BCUDP cameras.
|
|
5705
5758
|
*
|
|
@@ -8024,6 +8077,13 @@ interface Go2rtcTcpServerOptions {
|
|
|
8024
8077
|
prebufferMs?: number;
|
|
8025
8078
|
/** Maximum write buffer per client before dropping the connection (default: 100 MB). */
|
|
8026
8079
|
maxBufferBytes?: number;
|
|
8080
|
+
/**
|
|
8081
|
+
* Stream inactivity timeout in ms. If no frames arrive from the native
|
|
8082
|
+
* stream for this duration, the stream is considered dead and will be
|
|
8083
|
+
* torn down + restarted (similar to go2rtc's per-packet read deadline).
|
|
8084
|
+
* Default: 15 000 (15s). Set to 0 to disable.
|
|
8085
|
+
*/
|
|
8086
|
+
streamTimeoutMs?: number;
|
|
8027
8087
|
/**
|
|
8028
8088
|
* When true, the native camera stream is started immediately on start()
|
|
8029
8089
|
* rather than waiting for the first TCP client. This ensures frames are
|
|
@@ -8053,6 +8113,7 @@ declare class Go2rtcTcpServer extends EventEmitter<{
|
|
|
8053
8113
|
private readonly gracePeriodMs;
|
|
8054
8114
|
private readonly prebufferMaxMs;
|
|
8055
8115
|
private readonly maxBufferBytes;
|
|
8116
|
+
private readonly streamTimeoutMs;
|
|
8056
8117
|
private readonly prestartStream;
|
|
8057
8118
|
private active;
|
|
8058
8119
|
private server;
|
|
@@ -8064,6 +8125,10 @@ declare class Go2rtcTcpServer extends EventEmitter<{
|
|
|
8064
8125
|
private connectedClients;
|
|
8065
8126
|
private clientSockets;
|
|
8066
8127
|
private stopGraceTimer;
|
|
8128
|
+
private lastFrameAt;
|
|
8129
|
+
private streamHealthTimer;
|
|
8130
|
+
private totalFramesReceived;
|
|
8131
|
+
private totalVideoFramesWritten;
|
|
8067
8132
|
private prebuffer;
|
|
8068
8133
|
constructor(options: Go2rtcTcpServerOptions);
|
|
8069
8134
|
/** Start listening. Resolves once the TCP server is bound. */
|
|
@@ -8090,6 +8155,8 @@ declare class Go2rtcTcpServer extends EventEmitter<{
|
|
|
8090
8155
|
private static splitAnnexBNals;
|
|
8091
8156
|
private startNativeStream;
|
|
8092
8157
|
private stopNativeStream;
|
|
8158
|
+
private startStreamHealthMonitor;
|
|
8159
|
+
private stopStreamHealthMonitor;
|
|
8093
8160
|
private removeClient;
|
|
8094
8161
|
private scheduleStop;
|
|
8095
8162
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1647,6 +1647,15 @@ export declare class BaichuanVideoStream extends EventEmitter<{
|
|
|
1647
1647
|
private lastPpsH265;
|
|
1648
1648
|
private lastPrependedParamSetsH265;
|
|
1649
1649
|
private aesStreamDecryptor;
|
|
1650
|
+
/**
|
|
1651
|
+
* Pending startup error stashed when emitSafeError is called before any
|
|
1652
|
+
* "error" listener is registered (e.g. camera returns 400 during start()).
|
|
1653
|
+
* The rfc4571-server's waitForKeyframe can consume this immediately instead
|
|
1654
|
+
* of waiting for the full keyframe timeout.
|
|
1655
|
+
*/
|
|
1656
|
+
private _pendingStartupError;
|
|
1657
|
+
/** Consume and clear any pending startup error. */
|
|
1658
|
+
consumePendingStartupError(): Error | undefined;
|
|
1650
1659
|
private emitSafeError;
|
|
1651
1660
|
private lastMediaAtMs;
|
|
1652
1661
|
private watchdogTimer;
|
|
@@ -2337,6 +2346,7 @@ export declare class BcUdpStream extends EventEmitter<{
|
|
|
2337
2346
|
private resendTimer;
|
|
2338
2347
|
private hbTimer;
|
|
2339
2348
|
private discoveryTid;
|
|
2349
|
+
private discoveryTimers;
|
|
2340
2350
|
private acceptSent;
|
|
2341
2351
|
private lastAcceptAtMs;
|
|
2342
2352
|
private ackScheduled;
|
|
@@ -4190,6 +4200,7 @@ export declare class Go2rtcTcpServer extends EventEmitter<{
|
|
|
4190
4200
|
private readonly gracePeriodMs;
|
|
4191
4201
|
private readonly prebufferMaxMs;
|
|
4192
4202
|
private readonly maxBufferBytes;
|
|
4203
|
+
private readonly streamTimeoutMs;
|
|
4193
4204
|
private readonly prestartStream;
|
|
4194
4205
|
private active;
|
|
4195
4206
|
private server;
|
|
@@ -4201,6 +4212,10 @@ export declare class Go2rtcTcpServer extends EventEmitter<{
|
|
|
4201
4212
|
private connectedClients;
|
|
4202
4213
|
private clientSockets;
|
|
4203
4214
|
private stopGraceTimer;
|
|
4215
|
+
private lastFrameAt;
|
|
4216
|
+
private streamHealthTimer;
|
|
4217
|
+
private totalFramesReceived;
|
|
4218
|
+
private totalVideoFramesWritten;
|
|
4204
4219
|
private prebuffer;
|
|
4205
4220
|
constructor(options: Go2rtcTcpServerOptions);
|
|
4206
4221
|
/** Start listening. Resolves once the TCP server is bound. */
|
|
@@ -4227,6 +4242,8 @@ export declare class Go2rtcTcpServer extends EventEmitter<{
|
|
|
4227
4242
|
private static splitAnnexBNals;
|
|
4228
4243
|
private startNativeStream;
|
|
4229
4244
|
private stopNativeStream;
|
|
4245
|
+
private startStreamHealthMonitor;
|
|
4246
|
+
private stopStreamHealthMonitor;
|
|
4230
4247
|
private removeClient;
|
|
4231
4248
|
private scheduleStop;
|
|
4232
4249
|
}
|
|
@@ -4258,6 +4275,13 @@ export declare interface Go2rtcTcpServerOptions {
|
|
|
4258
4275
|
prebufferMs?: number;
|
|
4259
4276
|
/** Maximum write buffer per client before dropping the connection (default: 100 MB). */
|
|
4260
4277
|
maxBufferBytes?: number;
|
|
4278
|
+
/**
|
|
4279
|
+
* Stream inactivity timeout in ms. If no frames arrive from the native
|
|
4280
|
+
* stream for this duration, the stream is considered dead and will be
|
|
4281
|
+
* torn down + restarted (similar to go2rtc's per-packet read deadline).
|
|
4282
|
+
* Default: 15 000 (15s). Set to 0 to disable.
|
|
4283
|
+
*/
|
|
4284
|
+
streamTimeoutMs?: number;
|
|
4261
4285
|
/**
|
|
4262
4286
|
* When true, the native camera stream is started immediately on start()
|
|
4263
4287
|
* rather than waiting for the first TCP client. This ensures frames are
|
|
@@ -5072,6 +5096,13 @@ export declare class ReolinkBaichuanApi {
|
|
|
5072
5096
|
* - "replay:XXX" - dedicated per replay session
|
|
5073
5097
|
*/
|
|
5074
5098
|
private readonly socketPool;
|
|
5099
|
+
/**
|
|
5100
|
+
* Consecutive stream-start (cmdId=3) timeout counter per socket tag.
|
|
5101
|
+
* When a streaming socket has N consecutive timeouts, the socket is force-closed
|
|
5102
|
+
* so the next attempt creates a fresh connection. Resets on success.
|
|
5103
|
+
*/
|
|
5104
|
+
private readonly consecutiveStreamTimeouts;
|
|
5105
|
+
private static readonly MAX_CONSECUTIVE_STREAM_TIMEOUTS;
|
|
5075
5106
|
/** BaichuanClientOptions to use when creating new sockets */
|
|
5076
5107
|
private readonly clientOptions;
|
|
5077
5108
|
/**
|
|
@@ -5216,6 +5247,14 @@ export declare class ReolinkBaichuanApi {
|
|
|
5216
5247
|
*/
|
|
5217
5248
|
private readonly deviceCapabilitiesCache;
|
|
5218
5249
|
private static readonly CAPABILITIES_CACHE_TTL_MS;
|
|
5250
|
+
/**
|
|
5251
|
+
* Dedupe key for battery push events (cmd_id 252), per channel.
|
|
5252
|
+
* Cameras emit BatteryInfoList frequently while streaming (every few
|
|
5253
|
+
* seconds). We only forward an event when the meaningful fields change
|
|
5254
|
+
* (percent, chargeStatus, adapterStatus) to avoid flooding SSE/MQTT
|
|
5255
|
+
* consumers and the UI event log.
|
|
5256
|
+
*/
|
|
5257
|
+
private readonly lastBatteryPushKey;
|
|
5219
5258
|
/** Keep replay/streaming sockets warm briefly to reduce clip switch latency. */
|
|
5220
5259
|
private static readonly SOCKET_POOL_KEEPALIVE_MS;
|
|
5221
5260
|
/**
|
|
@@ -5348,6 +5387,18 @@ export declare class ReolinkBaichuanApi {
|
|
|
5348
5387
|
* which indicates subStream (e.g., RecS03_, RecS_).
|
|
5349
5388
|
*/
|
|
5350
5389
|
private determineStreamTypeFromFileName;
|
|
5390
|
+
/**
|
|
5391
|
+
* Stream profiles that the device explicitly rejected (response_code 400).
|
|
5392
|
+
* Keyed by `"ch:profile"` (e.g. `"0:ext"`). Once a profile is in this set
|
|
5393
|
+
* it is excluded from `buildVideoStreamOptions()` results and no further
|
|
5394
|
+
* start attempts are made until the API instance is recreated.
|
|
5395
|
+
*/
|
|
5396
|
+
private readonly _rejectedStreamProfiles;
|
|
5397
|
+
/**
|
|
5398
|
+
* Check whether a stream profile was rejected by the device at runtime
|
|
5399
|
+
* (e.g. ext returned response_code 400).
|
|
5400
|
+
*/
|
|
5401
|
+
isStreamProfileRejected(channel: number, profile: StreamProfile): boolean;
|
|
5351
5402
|
/**
|
|
5352
5403
|
* Cache for buildVideoStreamOptions.
|
|
5353
5404
|
*
|
|
@@ -6543,6 +6594,22 @@ export declare class ReolinkBaichuanApi {
|
|
|
6543
6594
|
* 2. **Storm**: ≥3 D2C_DISCs within 60 s triggers extended cooldown (120 s).
|
|
6544
6595
|
*/
|
|
6545
6596
|
private notifyD2cDisc;
|
|
6597
|
+
/**
|
|
6598
|
+
* Find the socket pool tag for a given BaichuanClient instance.
|
|
6599
|
+
* Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
|
|
6600
|
+
*/
|
|
6601
|
+
private findSocketTagForClient;
|
|
6602
|
+
/**
|
|
6603
|
+
* Reset the consecutive stream-start timeout counter for a streaming socket.
|
|
6604
|
+
* Called on successful stream start.
|
|
6605
|
+
*/
|
|
6606
|
+
private resetStreamTimeoutCounter;
|
|
6607
|
+
/**
|
|
6608
|
+
* Track a stream-start timeout on a streaming socket.
|
|
6609
|
+
* After MAX_CONSECUTIVE_STREAM_TIMEOUTS consecutive timeouts, force-close the
|
|
6610
|
+
* socket so the next attempt creates a fresh connection.
|
|
6611
|
+
*/
|
|
6612
|
+
private trackStreamTimeout;
|
|
6546
6613
|
/**
|
|
6547
6614
|
* Best-effort sleeping inference for battery/BCUDP cameras.
|
|
6548
6615
|
*
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
parseSupportXml,
|
|
44
44
|
setGlobalLogger,
|
|
45
45
|
xmlIndicatesFloodlight
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-GKLOJJ34.js";
|
|
47
47
|
import {
|
|
48
48
|
AesStreamDecryptor,
|
|
49
49
|
BC_AES_IV,
|
|
@@ -223,7 +223,7 @@ import {
|
|
|
223
223
|
testChannelStreams,
|
|
224
224
|
xmlEscape,
|
|
225
225
|
zipDirectory
|
|
226
|
-
} from "./chunk-
|
|
226
|
+
} from "./chunk-TR3V5FTO.js";
|
|
227
227
|
|
|
228
228
|
// src/reolink/baichuan/HlsSessionManager.ts
|
|
229
229
|
var withTimeout = async (p, ms, label) => {
|
|
@@ -3466,6 +3466,11 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
3466
3466
|
"videoAccessUnit",
|
|
3467
3467
|
onAu
|
|
3468
3468
|
);
|
|
3469
|
+
const pendingErr = videoStream.consumePendingStartupError?.();
|
|
3470
|
+
if (pendingErr) {
|
|
3471
|
+
cleanup();
|
|
3472
|
+
reject(pendingErr);
|
|
3473
|
+
}
|
|
3469
3474
|
});
|
|
3470
3475
|
}
|
|
3471
3476
|
};
|
|
@@ -3477,24 +3482,32 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
3477
3482
|
await videoStream.stop();
|
|
3478
3483
|
} catch {
|
|
3479
3484
|
}
|
|
3480
|
-
if (
|
|
3481
|
-
|
|
3482
|
-
|
|
3485
|
+
if (dedicatedSession) {
|
|
3486
|
+
try {
|
|
3487
|
+
await dedicatedSession.release();
|
|
3488
|
+
} catch {
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
if (!dedicatedSession) {
|
|
3492
|
+
if (closeApiOnTeardown) {
|
|
3493
|
+
await Promise.allSettled(
|
|
3494
|
+
Array.from(apisToClose).map(async (a) => {
|
|
3495
|
+
try {
|
|
3496
|
+
await a.close();
|
|
3497
|
+
} catch {
|
|
3498
|
+
}
|
|
3499
|
+
})
|
|
3500
|
+
);
|
|
3501
|
+
} else {
|
|
3502
|
+
const graceMs = isComposite ? 5e3 : 0;
|
|
3503
|
+
for (const a of Array.from(apisToClose)) {
|
|
3483
3504
|
try {
|
|
3484
|
-
|
|
3505
|
+
a?.client?.requestIdleDisconnectSoon?.(
|
|
3506
|
+
"rfc4571_teardown",
|
|
3507
|
+
graceMs
|
|
3508
|
+
);
|
|
3485
3509
|
} catch {
|
|
3486
3510
|
}
|
|
3487
|
-
})
|
|
3488
|
-
);
|
|
3489
|
-
} else {
|
|
3490
|
-
const graceMs = isComposite ? 5e3 : 0;
|
|
3491
|
-
for (const a of Array.from(apisToClose)) {
|
|
3492
|
-
try {
|
|
3493
|
-
a?.client?.requestIdleDisconnectSoon?.(
|
|
3494
|
-
"rfc4571_teardown",
|
|
3495
|
-
graceMs
|
|
3496
|
-
);
|
|
3497
|
-
} catch {
|
|
3498
3511
|
}
|
|
3499
3512
|
}
|
|
3500
3513
|
}
|
|
@@ -3750,7 +3763,7 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
3750
3763
|
} catch {
|
|
3751
3764
|
}
|
|
3752
3765
|
}
|
|
3753
|
-
if (closeApiOnTeardown) {
|
|
3766
|
+
if (closeApiOnTeardown && !dedicatedSession) {
|
|
3754
3767
|
await Promise.allSettled(
|
|
3755
3768
|
Array.from(apisToClose).map(async (a) => {
|
|
3756
3769
|
try {
|
|
@@ -4712,6 +4725,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4712
4725
|
gracePeriodMs;
|
|
4713
4726
|
prebufferMaxMs;
|
|
4714
4727
|
maxBufferBytes;
|
|
4728
|
+
streamTimeoutMs;
|
|
4715
4729
|
prestartStream;
|
|
4716
4730
|
active = false;
|
|
4717
4731
|
server;
|
|
@@ -4725,6 +4739,11 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4725
4739
|
connectedClients = /* @__PURE__ */ new Set();
|
|
4726
4740
|
clientSockets = /* @__PURE__ */ new Map();
|
|
4727
4741
|
stopGraceTimer;
|
|
4742
|
+
// Stream health monitoring
|
|
4743
|
+
lastFrameAt = 0;
|
|
4744
|
+
streamHealthTimer;
|
|
4745
|
+
totalFramesReceived = 0;
|
|
4746
|
+
totalVideoFramesWritten = 0;
|
|
4728
4747
|
// Prebuffer
|
|
4729
4748
|
prebuffer = [];
|
|
4730
4749
|
constructor(options) {
|
|
@@ -4740,6 +4759,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4740
4759
|
this.gracePeriodMs = options.gracePeriodMs ?? 3e4;
|
|
4741
4760
|
this.prebufferMaxMs = options.prebufferMs ?? 3e3;
|
|
4742
4761
|
this.maxBufferBytes = options.maxBufferBytes ?? 1e8;
|
|
4762
|
+
this.streamTimeoutMs = options.streamTimeoutMs ?? 15e3;
|
|
4743
4763
|
this.prestartStream = options.prestartStream ?? true;
|
|
4744
4764
|
}
|
|
4745
4765
|
// -----------------------------------------------------------------------
|
|
@@ -4778,6 +4798,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4778
4798
|
if (!this.active) return;
|
|
4779
4799
|
this.active = false;
|
|
4780
4800
|
clearTimeout(this.stopGraceTimer);
|
|
4801
|
+
this.stopStreamHealthMonitor();
|
|
4781
4802
|
for (const [id, sock] of this.clientSockets) {
|
|
4782
4803
|
sock.destroy();
|
|
4783
4804
|
this.connectedClients.delete(id);
|
|
@@ -4831,12 +4852,12 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4831
4852
|
`[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`
|
|
4832
4853
|
);
|
|
4833
4854
|
});
|
|
4834
|
-
const cleanup = () => {
|
|
4835
|
-
this.removeClient(clientId);
|
|
4855
|
+
const cleanup = (reason) => {
|
|
4856
|
+
this.removeClient(clientId, reason);
|
|
4836
4857
|
socket.destroy();
|
|
4837
4858
|
};
|
|
4838
|
-
socket.on("error", cleanup);
|
|
4839
|
-
socket.on("close", cleanup);
|
|
4859
|
+
socket.on("error", (err) => cleanup(`error: ${err.message}`));
|
|
4860
|
+
socket.on("close", (hadError) => cleanup(hadError ? "close (with error)" : "close (clean)"));
|
|
4840
4861
|
}
|
|
4841
4862
|
async feedClient(clientId, socket) {
|
|
4842
4863
|
const fanoutDeadline = Date.now() + 3e4;
|
|
@@ -4897,6 +4918,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
4897
4918
|
}
|
|
4898
4919
|
socket.write(annexB);
|
|
4899
4920
|
liveVideoWritten++;
|
|
4921
|
+
this.totalVideoFramesWritten++;
|
|
4900
4922
|
if (Date.now() - lastLogAt > 1e4) {
|
|
4901
4923
|
this.logger.info?.(
|
|
4902
4924
|
`[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`
|
|
@@ -5023,6 +5045,8 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5023
5045
|
...dedicatedClient ? { client: dedicatedClient } : {}
|
|
5024
5046
|
}),
|
|
5025
5047
|
onFrame: (frame) => {
|
|
5048
|
+
this.lastFrameAt = Date.now();
|
|
5049
|
+
this.totalFramesReceived++;
|
|
5026
5050
|
if (!frame.audio && (frame.videoType === "H264" || frame.videoType === "H265")) {
|
|
5027
5051
|
this.detectedVideoType = frame.videoType;
|
|
5028
5052
|
}
|
|
@@ -5049,6 +5073,12 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5049
5073
|
if (!this.nativeStreamActive) return;
|
|
5050
5074
|
this.nativeStreamActive = false;
|
|
5051
5075
|
this.nativeFanout = null;
|
|
5076
|
+
this.stopStreamHealthMonitor();
|
|
5077
|
+
const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
|
|
5078
|
+
const diagnosis = silenceMs > this.streamTimeoutMs ? "camera stopped sending frames" : silenceMs >= 0 ? "stream source closed" : "no frames were ever received";
|
|
5079
|
+
this.logger.warn?.(
|
|
5080
|
+
`[Go2rtcTcpServer] native stream ended diagnosis="${diagnosis}" lastFrame=${silenceMs >= 0 ? `${(silenceMs / 1e3).toFixed(1)}s ago` : "never"} totalRx=${this.totalFramesReceived} clients=${this.connectedClients.size}`
|
|
5081
|
+
);
|
|
5052
5082
|
if (this.dedicatedSessionRelease) {
|
|
5053
5083
|
this.dedicatedSessionRelease().catch(() => {
|
|
5054
5084
|
});
|
|
@@ -5056,16 +5086,18 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5056
5086
|
}
|
|
5057
5087
|
if (this.active && (this.connectedClients.size > 0 || this.prestartStream)) {
|
|
5058
5088
|
this.logger.info?.(
|
|
5059
|
-
`[Go2rtcTcpServer] native stream
|
|
5089
|
+
`[Go2rtcTcpServer] restarting native stream (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`
|
|
5060
5090
|
);
|
|
5061
5091
|
this.startNativeStream();
|
|
5062
5092
|
}
|
|
5063
5093
|
}
|
|
5064
5094
|
});
|
|
5065
5095
|
this.nativeFanout.start();
|
|
5096
|
+
this.startStreamHealthMonitor();
|
|
5066
5097
|
}
|
|
5067
5098
|
async stopNativeStream() {
|
|
5068
5099
|
this.nativeStreamActive = false;
|
|
5100
|
+
this.stopStreamHealthMonitor();
|
|
5069
5101
|
const fanout = this.nativeFanout;
|
|
5070
5102
|
this.nativeFanout = null;
|
|
5071
5103
|
if (fanout) {
|
|
@@ -5079,14 +5111,50 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
|
|
|
5079
5111
|
}
|
|
5080
5112
|
}
|
|
5081
5113
|
// -----------------------------------------------------------------------
|
|
5114
|
+
// Stream health monitoring
|
|
5115
|
+
// -----------------------------------------------------------------------
|
|
5116
|
+
startStreamHealthMonitor() {
|
|
5117
|
+
this.stopStreamHealthMonitor();
|
|
5118
|
+
if (this.streamTimeoutMs <= 0) return;
|
|
5119
|
+
this.lastFrameAt = Date.now();
|
|
5120
|
+
this.streamHealthTimer = setInterval(() => {
|
|
5121
|
+
if (!this.nativeStreamActive || !this.active) {
|
|
5122
|
+
this.stopStreamHealthMonitor();
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
const silenceMs = Date.now() - this.lastFrameAt;
|
|
5126
|
+
if (silenceMs > this.streamTimeoutMs) {
|
|
5127
|
+
this.logger.warn?.(
|
|
5128
|
+
`[Go2rtcTcpServer] stream inactivity timeout: no frames for ${(silenceMs / 1e3).toFixed(1)}s (threshold=${this.streamTimeoutMs}ms), totalReceived=${this.totalFramesReceived} clients=${this.connectedClients.size} \u2014 forcing stream restart`
|
|
5129
|
+
);
|
|
5130
|
+
this.stopStreamHealthMonitor();
|
|
5131
|
+
const fanout = this.nativeFanout;
|
|
5132
|
+
if (fanout) {
|
|
5133
|
+
this.nativeStreamActive = false;
|
|
5134
|
+
this.nativeFanout = null;
|
|
5135
|
+
fanout.stop().catch(() => {
|
|
5136
|
+
});
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
}, Math.min(this.streamTimeoutMs / 2, 5e3));
|
|
5140
|
+
}
|
|
5141
|
+
stopStreamHealthMonitor() {
|
|
5142
|
+
if (this.streamHealthTimer) {
|
|
5143
|
+
clearInterval(this.streamHealthTimer);
|
|
5144
|
+
this.streamHealthTimer = void 0;
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
// -----------------------------------------------------------------------
|
|
5082
5148
|
// Client lifecycle
|
|
5083
5149
|
// -----------------------------------------------------------------------
|
|
5084
|
-
removeClient(clientId) {
|
|
5150
|
+
removeClient(clientId, reason) {
|
|
5085
5151
|
if (!this.connectedClients.has(clientId)) return;
|
|
5086
5152
|
this.connectedClients.delete(clientId);
|
|
5087
5153
|
this.clientSockets.delete(clientId);
|
|
5154
|
+
const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
|
|
5155
|
+
const silenceInfo = silenceMs >= 0 ? ` lastFrame=${(silenceMs / 1e3).toFixed(1)}s ago` : "";
|
|
5088
5156
|
this.logger.info?.(
|
|
5089
|
-
`[Go2rtcTcpServer] client disconnected id=${clientId} remaining=${this.connectedClients.size}`
|
|
5157
|
+
`[Go2rtcTcpServer] client disconnected id=${clientId} reason=${reason ?? "unknown"} remaining=${this.connectedClients.size} totalRx=${this.totalFramesReceived} totalTx=${this.totalVideoFramesWritten}${silenceInfo}`
|
|
5090
5158
|
);
|
|
5091
5159
|
this.emit("clientDisconnected", clientId);
|
|
5092
5160
|
if (this.connectedClients.size === 0 && !this.prestartStream) {
|