@apocaliss92/nodelink-js 0.4.6 → 0.4.8
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-UMN4C7SY.js → DiagnosticsTools-HJDH4GPP.js} +2 -2
- package/dist/{chunk-F2Y5U3YP.js → chunk-VBYF3BQX.js} +730 -357
- package/dist/chunk-VBYF3BQX.js.map +1 -0
- package/dist/{chunk-TR3V5FTO.js → chunk-YKKQDUKU.js} +3 -3
- package/dist/{chunk-TR3V5FTO.js.map → chunk-YKKQDUKU.js.map} +1 -1
- package/dist/cli/rtsp-server.cjs +696 -325
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +946 -351
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -6
- package/dist/index.d.ts +283 -5
- package/dist/index.js +250 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-F2Y5U3YP.js.map +0 -1
- /package/dist/{DiagnosticsTools-UMN4C7SY.js.map → DiagnosticsTools-HJDH4GPP.js.map} +0 -0
|
@@ -144,7 +144,7 @@ import {
|
|
|
144
144
|
talkTraceLog,
|
|
145
145
|
traceLog,
|
|
146
146
|
xmlEscape
|
|
147
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-YKKQDUKU.js";
|
|
148
148
|
|
|
149
149
|
// src/protocol/framing.ts
|
|
150
150
|
function encodeHeader(h) {
|
|
@@ -5325,19 +5325,34 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
5325
5325
|
}
|
|
5326
5326
|
});
|
|
5327
5327
|
streamStarted = true;
|
|
5328
|
-
|
|
5328
|
+
const signal = options?.signal;
|
|
5329
|
+
while (!closed && !signal?.aborted) {
|
|
5329
5330
|
if (frameQueue.length > 0) {
|
|
5330
5331
|
const frame = frameQueue.shift();
|
|
5331
5332
|
yield frame;
|
|
5332
5333
|
} else {
|
|
5333
5334
|
await new Promise((resolve) => {
|
|
5334
5335
|
frameResolve = resolve;
|
|
5335
|
-
setTimeout(() => {
|
|
5336
|
+
const timer = setTimeout(() => {
|
|
5336
5337
|
if (frameResolve === resolve) {
|
|
5337
5338
|
frameResolve = null;
|
|
5338
5339
|
resolve();
|
|
5339
5340
|
}
|
|
5340
5341
|
}, 1e3);
|
|
5342
|
+
if (signal) {
|
|
5343
|
+
const onAbort = () => {
|
|
5344
|
+
clearTimeout(timer);
|
|
5345
|
+
if (frameResolve === resolve) frameResolve = null;
|
|
5346
|
+
resolve();
|
|
5347
|
+
};
|
|
5348
|
+
if (signal.aborted) {
|
|
5349
|
+
clearTimeout(timer);
|
|
5350
|
+
frameResolve = null;
|
|
5351
|
+
resolve();
|
|
5352
|
+
} else {
|
|
5353
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5341
5356
|
});
|
|
5342
5357
|
}
|
|
5343
5358
|
}
|
|
@@ -5513,13 +5528,14 @@ var NativeStreamFanout = class {
|
|
|
5513
5528
|
source = null;
|
|
5514
5529
|
running = false;
|
|
5515
5530
|
pumpPromise = null;
|
|
5531
|
+
abort = new AbortController();
|
|
5516
5532
|
constructor(opts) {
|
|
5517
5533
|
this.opts = opts;
|
|
5518
5534
|
}
|
|
5519
5535
|
start() {
|
|
5520
5536
|
if (this.running) return;
|
|
5521
5537
|
this.running = true;
|
|
5522
|
-
this.source = this.opts.createSource();
|
|
5538
|
+
this.source = this.opts.createSource(this.abort.signal);
|
|
5523
5539
|
this.pumpPromise = (async () => {
|
|
5524
5540
|
try {
|
|
5525
5541
|
for await (const frame of this.source) {
|
|
@@ -5565,6 +5581,7 @@ var NativeStreamFanout = class {
|
|
|
5565
5581
|
this.source = null;
|
|
5566
5582
|
for (const q of this.queues.values()) q.close();
|
|
5567
5583
|
this.queues.clear();
|
|
5584
|
+
this.abort.abort();
|
|
5568
5585
|
try {
|
|
5569
5586
|
await src?.return(void 0);
|
|
5570
5587
|
} catch {
|
|
@@ -5603,9 +5620,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5603
5620
|
requireAuth;
|
|
5604
5621
|
authNonces = /* @__PURE__ */ new Map();
|
|
5605
5622
|
// Track nonces per client
|
|
5606
|
-
AUTH_REALM
|
|
5623
|
+
AUTH_REALM;
|
|
5607
5624
|
NONCE_TIMEOUT_MS = 3e5;
|
|
5608
5625
|
// 5 minutes
|
|
5626
|
+
lazyMetadata;
|
|
5609
5627
|
// Client tracking
|
|
5610
5628
|
connectedClients = /* @__PURE__ */ new Set();
|
|
5611
5629
|
// Set of client IDs (IP:port)
|
|
@@ -5617,8 +5635,15 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5617
5635
|
// Track all client resources for cleanup
|
|
5618
5636
|
clientResources = /* @__PURE__ */ new Map();
|
|
5619
5637
|
isRtspDebugEnabled() {
|
|
5620
|
-
|
|
5621
|
-
|
|
5638
|
+
try {
|
|
5639
|
+
if (this.api.isClosed) {
|
|
5640
|
+
return envBool(process.env.BAICHUAN_DEBUG_RTSP, false);
|
|
5641
|
+
}
|
|
5642
|
+
const dbg = this.api.client.getDebugConfig();
|
|
5643
|
+
return dbg.debugRtsp || envBool(process.env.BAICHUAN_DEBUG_RTSP, false);
|
|
5644
|
+
} catch {
|
|
5645
|
+
return envBool(process.env.BAICHUAN_DEBUG_RTSP, false);
|
|
5646
|
+
}
|
|
5622
5647
|
}
|
|
5623
5648
|
rtspDebugLog(message) {
|
|
5624
5649
|
if (!this.isRtspDebugEnabled()) return;
|
|
@@ -5640,10 +5665,20 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5640
5665
|
// Shared native stream fan-out (single camera stream, multiple RTSP clients)
|
|
5641
5666
|
nativeFanout = null;
|
|
5642
5667
|
noClientAutoStopTimer;
|
|
5668
|
+
/** Fires if camera never sends frames after stream start (sleeping), even with clients connected. */
|
|
5669
|
+
noFrameDeadlineTimer;
|
|
5643
5670
|
/** After last RTSP client; 0 = never auto-stop native stream. */
|
|
5644
5671
|
nativeStreamIdleStopMs;
|
|
5645
5672
|
/** Primed-but-no-PLAY timeout; 0 = disabled. */
|
|
5646
5673
|
nativeStreamPrimeIdleStopMs;
|
|
5674
|
+
/**
|
|
5675
|
+
* Max time to wait for the first camera frame after stream start.
|
|
5676
|
+
* If no frames arrive within this window, the native stream is stopped
|
|
5677
|
+
* (camera is sleeping). Prevents the BaichuanVideoStream watchdog from
|
|
5678
|
+
* firing and waking the camera when no real viewer is watching.
|
|
5679
|
+
* 0 = disabled. Defaults to nativeStreamPrimeIdleStopMs * 2 when > 0.
|
|
5680
|
+
*/
|
|
5681
|
+
nativeStreamNoFrameDeadlineMs;
|
|
5647
5682
|
// Prebuffer: rolling ring of recent video frames for IDR-aligned fast startup.
|
|
5648
5683
|
// When a new client connects while the stream is already running it does not need
|
|
5649
5684
|
// to wait up to one full GOP interval for the next keyframe — we replay frames
|
|
@@ -5769,14 +5804,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5769
5804
|
this.logger = options.logger ?? console;
|
|
5770
5805
|
this.tcpRtpFraming = options.tcpRtpFraming ?? "rfc4571";
|
|
5771
5806
|
this.deviceId = options.deviceId;
|
|
5772
|
-
this.externalListener = options.externalListener ?? false;
|
|
5807
|
+
this.externalListener = (options.externalListener ?? false) || (options.muxMode ?? false);
|
|
5773
5808
|
this.nativeStreamIdleStopMs = options.nativeStreamIdleStopMs ?? 3e4;
|
|
5774
5809
|
this.nativeStreamPrimeIdleStopMs = options.nativeStreamPrimeIdleStopMs ?? (this.nativeStreamIdleStopMs > 0 ? 15e3 : 0);
|
|
5775
|
-
this.
|
|
5810
|
+
this.nativeStreamNoFrameDeadlineMs = this.nativeStreamPrimeIdleStopMs > 0 ? Math.min(this.nativeStreamPrimeIdleStopMs * 2, 3e4) : 0;
|
|
5811
|
+
this.authCredentials = (options.credentials ?? []).map((c) => ({
|
|
5812
|
+
username: c.username,
|
|
5813
|
+
...c.password !== void 0 ? { password: c.password } : {},
|
|
5814
|
+
...c.ha1 !== void 0 ? { ha1: c.ha1 } : {}
|
|
5815
|
+
}));
|
|
5776
5816
|
this.requireAuth = options.requireAuth ?? this.authCredentials.length > 0;
|
|
5817
|
+
this.AUTH_REALM = options.authRealm ?? "BaichuanRtspServer";
|
|
5818
|
+
this.lazyMetadata = options.lazyMetadata ?? false;
|
|
5777
5819
|
const transport = this.api.client.getTransport();
|
|
5778
5820
|
this.flow = createRtspFlow(transport, "H264");
|
|
5779
5821
|
}
|
|
5822
|
+
/** Number of currently connected RTSP clients. */
|
|
5823
|
+
get clientCount() {
|
|
5824
|
+
return this.connectedClients.size;
|
|
5825
|
+
}
|
|
5780
5826
|
// --- Authentication helpers ---
|
|
5781
5827
|
/**
|
|
5782
5828
|
* Generate a new nonce for Digest authentication
|
|
@@ -5837,9 +5883,16 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5837
5883
|
this.rtspDebugLog(`Auth failed: nonce mismatch for client ${clientId}`);
|
|
5838
5884
|
return false;
|
|
5839
5885
|
}
|
|
5886
|
+
if (realm !== this.AUTH_REALM) {
|
|
5887
|
+
this.rtspDebugLog(
|
|
5888
|
+
`Auth failed: realm mismatch (client="${realm}", server="${this.AUTH_REALM}")`
|
|
5889
|
+
);
|
|
5890
|
+
return false;
|
|
5891
|
+
}
|
|
5840
5892
|
for (const cred of this.authCredentials) {
|
|
5841
5893
|
if (username !== cred.username) continue;
|
|
5842
|
-
const ha1 = this.md5(`${cred.username}:${
|
|
5894
|
+
const ha1 = cred.ha1 ?? (cred.password !== void 0 ? this.md5(`${cred.username}:${this.AUTH_REALM}:${cred.password}`) : void 0);
|
|
5895
|
+
if (!ha1) continue;
|
|
5843
5896
|
const ha2 = this.md5(`${method}:${authUri || uri}`);
|
|
5844
5897
|
const expectedResponse = this.md5(`${ha1}:${nonce}:${ha2}`);
|
|
5845
5898
|
if (response === expectedResponse) {
|
|
@@ -5868,6 +5921,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5868
5921
|
this.noClientAutoStopTimer = void 0;
|
|
5869
5922
|
}
|
|
5870
5923
|
}
|
|
5924
|
+
clearNoFrameDeadlineTimer() {
|
|
5925
|
+
if (this.noFrameDeadlineTimer) {
|
|
5926
|
+
clearTimeout(this.noFrameDeadlineTimer);
|
|
5927
|
+
this.noFrameDeadlineTimer = void 0;
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5871
5930
|
setFlowVideoType(videoType, reason) {
|
|
5872
5931
|
if (this.flow.videoType === videoType) return;
|
|
5873
5932
|
const transport = this.api.client.getTransport();
|
|
@@ -5882,25 +5941,31 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5882
5941
|
if (this.active) {
|
|
5883
5942
|
throw new Error("RTSP server is already active");
|
|
5884
5943
|
}
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
if (stream) {
|
|
5889
|
-
this.streamMetadata = {
|
|
5890
|
-
frameRate: stream.frameRate || 25,
|
|
5891
|
-
width: stream.width,
|
|
5892
|
-
height: stream.height
|
|
5893
|
-
};
|
|
5894
|
-
const enc = String(stream.videoEncType ?? "").trim().toLowerCase();
|
|
5895
|
-
const metaVideoType = enc.includes("265") || enc.includes("hevc") ? "H265" : "H264";
|
|
5896
|
-
this.setFlowVideoType(metaVideoType, "metadata");
|
|
5897
|
-
}
|
|
5898
|
-
} catch (error) {
|
|
5899
|
-
this.logger.warn(
|
|
5900
|
-
`[BaichuanRtspServer] Could not get stream metadata: ${error}`
|
|
5944
|
+
if (this.lazyMetadata) {
|
|
5945
|
+
this.logger.info(
|
|
5946
|
+
`[BaichuanRtspServer] lazy metadata: skipping initial getStreamMetadata; will fetch on first DESCRIBE`
|
|
5901
5947
|
);
|
|
5902
|
-
|
|
5903
|
-
|
|
5948
|
+
} else {
|
|
5949
|
+
try {
|
|
5950
|
+
const metadata = await this.api.getStreamMetadata(this.channel);
|
|
5951
|
+
const stream = metadata.streams.find((s) => s.profile === this.profile);
|
|
5952
|
+
if (stream) {
|
|
5953
|
+
this.streamMetadata = {
|
|
5954
|
+
frameRate: stream.frameRate || 25,
|
|
5955
|
+
width: stream.width,
|
|
5956
|
+
height: stream.height
|
|
5957
|
+
};
|
|
5958
|
+
const enc = String(stream.videoEncType ?? "").trim().toLowerCase();
|
|
5959
|
+
const metaVideoType = enc.includes("265") || enc.includes("hevc") ? "H265" : "H264";
|
|
5960
|
+
this.setFlowVideoType(metaVideoType, "metadata");
|
|
5961
|
+
}
|
|
5962
|
+
} catch (error) {
|
|
5963
|
+
this.logger.warn(
|
|
5964
|
+
`[BaichuanRtspServer] Could not get stream metadata: ${error}`
|
|
5965
|
+
);
|
|
5966
|
+
this.streamMetadata = { frameRate: 25 };
|
|
5967
|
+
this.setFlowVideoType("H264", "metadata unavailable");
|
|
5968
|
+
}
|
|
5904
5969
|
}
|
|
5905
5970
|
if (!this.externalListener) {
|
|
5906
5971
|
this.clientConnectionServer = net2.createServer((socket) => {
|
|
@@ -5941,6 +6006,30 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
5941
6006
|
}
|
|
5942
6007
|
this.handleRtspConnection(socket, initialBuffer);
|
|
5943
6008
|
}
|
|
6009
|
+
/**
|
|
6010
|
+
* Inject an already-accepted client socket from a multiplexer
|
|
6011
|
+
* (e.g. `LocalRtspMux`) that owns the listening port.
|
|
6012
|
+
*
|
|
6013
|
+
* The mux reads the first RTSP request line to determine the target path,
|
|
6014
|
+
* then hands the socket over. Any bytes already consumed during routing
|
|
6015
|
+
* are replayed back onto the socket via `unshift()` so the RTSP parser in
|
|
6016
|
+
* `handleRtspConnection` sees the complete original request.
|
|
6017
|
+
*
|
|
6018
|
+
* @param socket - Client TCP socket, already accepted by the mux.
|
|
6019
|
+
* @param preReadData - Bytes the mux has already pulled off the socket
|
|
6020
|
+
* while parsing the request line. Replayed via `socket.unshift()`
|
|
6021
|
+
* before any further reads.
|
|
6022
|
+
*/
|
|
6023
|
+
injectSocket(socket, preReadData) {
|
|
6024
|
+
if (!this.active) {
|
|
6025
|
+
socket.end("RTSP/1.0 503 Service Unavailable\r\n\r\n");
|
|
6026
|
+
return;
|
|
6027
|
+
}
|
|
6028
|
+
if (preReadData && preReadData.length > 0) {
|
|
6029
|
+
socket.unshift(preReadData);
|
|
6030
|
+
}
|
|
6031
|
+
this.handleRtspConnection(socket);
|
|
6032
|
+
}
|
|
5944
6033
|
/**
|
|
5945
6034
|
* Handle RTSP connection from a client.
|
|
5946
6035
|
*/
|
|
@@ -6105,6 +6194,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6105
6194
|
Public: "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS"
|
|
6106
6195
|
});
|
|
6107
6196
|
} else if (method === "DESCRIBE") {
|
|
6197
|
+
if (!this.api.isClosed && !this.api.isReady && !this.nativeStreamActive) {
|
|
6198
|
+
void this.api.ensureConnected().catch(() => {
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6108
6201
|
if (!this.flow.getFmtp().hasParamSets && this.connectedClients.size === 0) {
|
|
6109
6202
|
try {
|
|
6110
6203
|
if (!this.nativeStreamActive) {
|
|
@@ -6142,6 +6235,27 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6142
6235
|
}
|
|
6143
6236
|
}
|
|
6144
6237
|
}
|
|
6238
|
+
if (!this.hasAudio && this.firstAudioPromise) {
|
|
6239
|
+
const audioPrimingMs = this.api.client.getTransport() === "udp" ? 3e3 : 2e3;
|
|
6240
|
+
const audioPrimingStart = Date.now();
|
|
6241
|
+
try {
|
|
6242
|
+
await Promise.race([
|
|
6243
|
+
this.firstAudioPromise,
|
|
6244
|
+
new Promise((resolve) => setTimeout(resolve, audioPrimingMs))
|
|
6245
|
+
]);
|
|
6246
|
+
} catch {
|
|
6247
|
+
}
|
|
6248
|
+
const audioPrimingElapsed = Date.now() - audioPrimingStart;
|
|
6249
|
+
if (this.hasAudio) {
|
|
6250
|
+
this.logger.info(
|
|
6251
|
+
`[rebroadcast] DESCRIBE audio priming: AAC detected after ${audioPrimingElapsed}ms client=${clientId} path=${this.path}`
|
|
6252
|
+
);
|
|
6253
|
+
} else {
|
|
6254
|
+
this.logger.info(
|
|
6255
|
+
`[rebroadcast] DESCRIBE audio priming: no audio after ${audioPrimingElapsed}ms \u2014 SDP will be video-only client=${clientId} path=${this.path}`
|
|
6256
|
+
);
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6145
6259
|
{
|
|
6146
6260
|
const { fmtp, hasParamSets } = this.flow.getFmtp();
|
|
6147
6261
|
const fmtpPreview = fmtp.length > 160 ? `${fmtp.slice(0, 160)}...` : fmtp;
|
|
@@ -6150,12 +6264,13 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6150
6264
|
);
|
|
6151
6265
|
}
|
|
6152
6266
|
const sdp = this.generateSdp();
|
|
6267
|
+
const contentHost = (socket.localAddress && socket.localAddress !== "0.0.0.0" && socket.localAddress !== "::" ? socket.localAddress.replace(/^::ffff:/, "") : null) ?? this.listenHost;
|
|
6153
6268
|
sendResponse(
|
|
6154
6269
|
200,
|
|
6155
6270
|
"OK",
|
|
6156
6271
|
{
|
|
6157
6272
|
"Content-Type": "application/sdp",
|
|
6158
|
-
"Content-Base": `rtsp://${
|
|
6273
|
+
"Content-Base": `rtsp://${contentHost}:${this.listenPort}${this.path}/`
|
|
6159
6274
|
},
|
|
6160
6275
|
sdp
|
|
6161
6276
|
);
|
|
@@ -6178,7 +6293,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6178
6293
|
this.emit("client", clientId);
|
|
6179
6294
|
this.clearNoClientAutoStopTimer();
|
|
6180
6295
|
if (this.connectedClients.size === 1 && !this.nativeStreamActive) {
|
|
6181
|
-
|
|
6296
|
+
void this.startNativeStream();
|
|
6182
6297
|
}
|
|
6183
6298
|
const transportMatch = requestText.match(/Transport:\s*([^\r\n]+)/i);
|
|
6184
6299
|
const transport = (transportMatch?.[1] ?? "").trim();
|
|
@@ -6312,12 +6427,23 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
6312
6427
|
}
|
|
6313
6428
|
}
|
|
6314
6429
|
};
|
|
6430
|
+
const runProcessBuffer = () => {
|
|
6431
|
+
processBuffer().catch((err) => {
|
|
6432
|
+
this.logger.debug(
|
|
6433
|
+
`[BaichuanRtspServer] processBuffer failed for ${clientId}: ${err?.message ?? err}`
|
|
6434
|
+
);
|
|
6435
|
+
try {
|
|
6436
|
+
socket.destroy();
|
|
6437
|
+
} catch {
|
|
6438
|
+
}
|
|
6439
|
+
});
|
|
6440
|
+
};
|
|
6315
6441
|
socket.on("data", (data) => {
|
|
6316
6442
|
buffer = Buffer.concat([buffer, data]);
|
|
6317
|
-
|
|
6443
|
+
runProcessBuffer();
|
|
6318
6444
|
});
|
|
6319
6445
|
if (buffer.includes("\r\n\r\n")) {
|
|
6320
|
-
|
|
6446
|
+
runProcessBuffer();
|
|
6321
6447
|
}
|
|
6322
6448
|
}
|
|
6323
6449
|
/**
|
|
@@ -7185,6 +7311,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7185
7311
|
if (this.nativeStreamActive) {
|
|
7186
7312
|
return;
|
|
7187
7313
|
}
|
|
7314
|
+
if (!this.api.isReady) {
|
|
7315
|
+
if (this.api.isClosed) {
|
|
7316
|
+
this.logger.warn?.(
|
|
7317
|
+
`[rebroadcast] API has been explicitly closed \u2014 stream cannot start profile=${this.profile}`
|
|
7318
|
+
);
|
|
7319
|
+
return;
|
|
7320
|
+
}
|
|
7321
|
+
try {
|
|
7322
|
+
this.logger.info?.(
|
|
7323
|
+
`[rebroadcast] API not ready (idle disconnect?), calling ensureConnected profile=${this.profile}`
|
|
7324
|
+
);
|
|
7325
|
+
await this.api.ensureConnected();
|
|
7326
|
+
} catch (e) {
|
|
7327
|
+
this.logger.warn?.(
|
|
7328
|
+
`[rebroadcast] ensureConnected failed, aborting stream start: ${e}`
|
|
7329
|
+
);
|
|
7330
|
+
return;
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7188
7333
|
this.nativeStreamActive = true;
|
|
7189
7334
|
this.firstFrameReceived = false;
|
|
7190
7335
|
this.firstAudioDetected = false;
|
|
@@ -7219,13 +7364,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7219
7364
|
await this.flow.startKeepAlive(this.api);
|
|
7220
7365
|
this.nativeFanout = new NativeStreamFanout({
|
|
7221
7366
|
maxQueueItems: 200,
|
|
7222
|
-
createSource: () => createNativeStream(this.api, this.channel, this.profile, {
|
|
7367
|
+
createSource: (signal) => createNativeStream(this.api, this.channel, this.profile, {
|
|
7223
7368
|
variant: this.variant,
|
|
7224
|
-
...dedicatedClient ? { client: dedicatedClient } : {}
|
|
7369
|
+
...dedicatedClient ? { client: dedicatedClient } : {},
|
|
7370
|
+
signal
|
|
7225
7371
|
}),
|
|
7226
7372
|
onFrame: (frame) => {
|
|
7227
7373
|
if (frame.audio) {
|
|
7228
|
-
if (!this.hasAudio &&
|
|
7374
|
+
if (!this.hasAudio && _BaichuanRtspServer.isAdtsAacFrame(frame.data)) {
|
|
7229
7375
|
const info = _BaichuanRtspServer.parseAdtsSamplingInfo(frame.data);
|
|
7230
7376
|
if (info) {
|
|
7231
7377
|
this.hasAudio = true;
|
|
@@ -7274,6 +7420,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7274
7420
|
onEnd: () => {
|
|
7275
7421
|
if (!this.nativeStreamActive) return;
|
|
7276
7422
|
this.nativeStreamActive = false;
|
|
7423
|
+
this.clearNoFrameDeadlineTimer();
|
|
7424
|
+
const hadFrames = this.firstFrameReceived;
|
|
7277
7425
|
this.firstFrameReceived = false;
|
|
7278
7426
|
this.firstFramePromise = null;
|
|
7279
7427
|
this.firstFrameResolve = null;
|
|
@@ -7298,7 +7446,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7298
7446
|
} catch {
|
|
7299
7447
|
}
|
|
7300
7448
|
}
|
|
7301
|
-
if (this.connectedClients.size > 0) {
|
|
7449
|
+
if (this.connectedClients.size > 0 && hadFrames) {
|
|
7302
7450
|
this.logger.info(
|
|
7303
7451
|
`[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
|
|
7304
7452
|
);
|
|
@@ -7310,6 +7458,19 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7310
7458
|
}
|
|
7311
7459
|
});
|
|
7312
7460
|
this.nativeFanout.start();
|
|
7461
|
+
this.clearNoFrameDeadlineTimer();
|
|
7462
|
+
if (this.nativeStreamNoFrameDeadlineMs > 0) {
|
|
7463
|
+
this.noFrameDeadlineTimer = setTimeout(() => {
|
|
7464
|
+
this.noFrameDeadlineTimer = void 0;
|
|
7465
|
+
if (!this.firstFrameReceived && this.nativeStreamActive) {
|
|
7466
|
+
this.logger.info(
|
|
7467
|
+
`[rebroadcast] no frames within ${this.nativeStreamNoFrameDeadlineMs}ms \u2014 camera sleeping, stopping stream profile=${this.profile} channel=${this.channel}`
|
|
7468
|
+
);
|
|
7469
|
+
void this.stopNativeStream();
|
|
7470
|
+
}
|
|
7471
|
+
}, this.nativeStreamNoFrameDeadlineMs);
|
|
7472
|
+
this.noFrameDeadlineTimer?.unref?.();
|
|
7473
|
+
}
|
|
7313
7474
|
this.clearNoClientAutoStopTimer();
|
|
7314
7475
|
if (this.nativeStreamPrimeIdleStopMs > 0) {
|
|
7315
7476
|
this.noClientAutoStopTimer = setTimeout(() => {
|
|
@@ -7326,6 +7487,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7326
7487
|
markFirstFrameReceived() {
|
|
7327
7488
|
if (!this.firstFrameReceived && this.firstFrameResolve) {
|
|
7328
7489
|
this.firstFrameReceived = true;
|
|
7490
|
+
this.clearNoFrameDeadlineTimer();
|
|
7329
7491
|
this.rtspDebugLog(
|
|
7330
7492
|
`First frame received from camera for profile ${this.profile}`
|
|
7331
7493
|
);
|
|
@@ -7352,6 +7514,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7352
7514
|
);
|
|
7353
7515
|
this.flow.stopKeepAlive();
|
|
7354
7516
|
this.clearNoClientAutoStopTimer();
|
|
7517
|
+
this.clearNoFrameDeadlineTimer();
|
|
7355
7518
|
this.nativeStreamActive = false;
|
|
7356
7519
|
this.firstFrameReceived = false;
|
|
7357
7520
|
this.firstFramePromise = null;
|
|
@@ -7561,6 +7724,249 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends EventEmitter3 {
|
|
|
7561
7724
|
}
|
|
7562
7725
|
};
|
|
7563
7726
|
|
|
7727
|
+
// src/baichuan/stream/MpegTsMuxer.ts
|
|
7728
|
+
var TS_PACKET_SIZE = 188;
|
|
7729
|
+
var TS_SYNC_BYTE = 71;
|
|
7730
|
+
var TS_PAYLOAD_SIZE = TS_PACKET_SIZE - 4;
|
|
7731
|
+
var PID_PAT = 0;
|
|
7732
|
+
var PID_PMT = 4096;
|
|
7733
|
+
var PID_VIDEO = 256;
|
|
7734
|
+
var PID_AUDIO = 257;
|
|
7735
|
+
var STREAM_TYPE_H264 = 27;
|
|
7736
|
+
var STREAM_TYPE_H265 = 36;
|
|
7737
|
+
var STREAM_TYPE_AAC = 15;
|
|
7738
|
+
var PES_STREAM_ID_VIDEO = 224;
|
|
7739
|
+
var PES_STREAM_ID_AUDIO = 192;
|
|
7740
|
+
var PAT_PMT_INTERVAL = 40;
|
|
7741
|
+
function crc32Mpeg(data) {
|
|
7742
|
+
let crc = 4294967295;
|
|
7743
|
+
for (let i = 0; i < data.length; i++) {
|
|
7744
|
+
crc ^= data[i] << 24;
|
|
7745
|
+
for (let j = 0; j < 8; j++) {
|
|
7746
|
+
if (crc & 2147483648) {
|
|
7747
|
+
crc = (crc << 1 ^ 79764919) >>> 0;
|
|
7748
|
+
} else {
|
|
7749
|
+
crc = crc << 1 >>> 0;
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7753
|
+
return crc >>> 0;
|
|
7754
|
+
}
|
|
7755
|
+
function usToPts(us) {
|
|
7756
|
+
return Math.floor(us * 90 / 1e3) & 8589934591;
|
|
7757
|
+
}
|
|
7758
|
+
function encodePts(buf, offset, pts, prefix) {
|
|
7759
|
+
buf[offset + 0] = prefix << 4 | (pts >>> 30 & 7) << 1 | 1;
|
|
7760
|
+
buf[offset + 1] = pts >>> 22 & 255;
|
|
7761
|
+
buf[offset + 2] = (pts >>> 15 & 127) << 1 | 1;
|
|
7762
|
+
buf[offset + 3] = pts >>> 7 & 255;
|
|
7763
|
+
buf[offset + 4] = (pts & 127) << 1 | 1;
|
|
7764
|
+
}
|
|
7765
|
+
function writeTsHeader(buf, pid, pusi, cc, hasAdapt, hasPayload) {
|
|
7766
|
+
buf[0] = TS_SYNC_BYTE;
|
|
7767
|
+
buf[1] = (pusi ? 64 : 0) | pid >> 8 & 31;
|
|
7768
|
+
buf[2] = pid & 255;
|
|
7769
|
+
buf[3] = (hasAdapt ? 32 : 0) | (hasPayload ? 16 : 0) | cc & 15;
|
|
7770
|
+
}
|
|
7771
|
+
function pesToTsPackets(pesData, pid, ccRef, isKeyframe) {
|
|
7772
|
+
const totalPackets = Math.ceil(pesData.length / TS_PAYLOAD_SIZE);
|
|
7773
|
+
const out = Buffer.allocUnsafe(totalPackets * TS_PACKET_SIZE);
|
|
7774
|
+
let pesOffset = 0;
|
|
7775
|
+
let outOffset = 0;
|
|
7776
|
+
let isFirst = true;
|
|
7777
|
+
while (pesOffset < pesData.length) {
|
|
7778
|
+
const remaining = pesData.length - pesOffset;
|
|
7779
|
+
const packet = out.subarray(outOffset, outOffset + TS_PACKET_SIZE);
|
|
7780
|
+
outOffset += TS_PACKET_SIZE;
|
|
7781
|
+
if (remaining >= TS_PAYLOAD_SIZE) {
|
|
7782
|
+
writeTsHeader(packet, pid, isFirst, ccRef.cc, false, true);
|
|
7783
|
+
ccRef.cc = ccRef.cc + 1 & 15;
|
|
7784
|
+
pesData.copy(packet, 4, pesOffset, pesOffset + TS_PAYLOAD_SIZE);
|
|
7785
|
+
pesOffset += TS_PAYLOAD_SIZE;
|
|
7786
|
+
} else {
|
|
7787
|
+
const paddingNeeded = TS_PAYLOAD_SIZE - remaining;
|
|
7788
|
+
if (paddingNeeded === 1) {
|
|
7789
|
+
writeTsHeader(packet, pid, isFirst, ccRef.cc, true, true);
|
|
7790
|
+
ccRef.cc = ccRef.cc + 1 & 15;
|
|
7791
|
+
packet[4] = 0;
|
|
7792
|
+
pesData.copy(packet, 5, pesOffset, pesOffset + remaining);
|
|
7793
|
+
} else {
|
|
7794
|
+
writeTsHeader(packet, pid, isFirst, ccRef.cc, true, true);
|
|
7795
|
+
ccRef.cc = ccRef.cc + 1 & 15;
|
|
7796
|
+
const adaptLen = paddingNeeded - 1;
|
|
7797
|
+
packet[4] = adaptLen;
|
|
7798
|
+
packet[5] = isFirst && isKeyframe ? 64 : 0;
|
|
7799
|
+
packet.fill(255, 6, 4 + paddingNeeded);
|
|
7800
|
+
pesData.copy(packet, 4 + paddingNeeded, pesOffset, pesOffset + remaining);
|
|
7801
|
+
}
|
|
7802
|
+
pesOffset += remaining;
|
|
7803
|
+
}
|
|
7804
|
+
isFirst = false;
|
|
7805
|
+
}
|
|
7806
|
+
return out;
|
|
7807
|
+
}
|
|
7808
|
+
function buildPat(cc) {
|
|
7809
|
+
const pkt = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
7810
|
+
pkt[0] = TS_SYNC_BYTE;
|
|
7811
|
+
pkt[1] = 64 | PID_PAT >> 8 & 31;
|
|
7812
|
+
pkt[2] = PID_PAT & 255;
|
|
7813
|
+
pkt[3] = 16 | cc & 15;
|
|
7814
|
+
pkt[4] = 0;
|
|
7815
|
+
const sectionStart = 5;
|
|
7816
|
+
let i = sectionStart;
|
|
7817
|
+
pkt[i++] = 0;
|
|
7818
|
+
pkt[i++] = 176;
|
|
7819
|
+
pkt[i++] = 13;
|
|
7820
|
+
pkt[i++] = 0;
|
|
7821
|
+
pkt[i++] = 1;
|
|
7822
|
+
pkt[i++] = 193;
|
|
7823
|
+
pkt[i++] = 0;
|
|
7824
|
+
pkt[i++] = 0;
|
|
7825
|
+
pkt[i++] = 0;
|
|
7826
|
+
pkt[i++] = 1;
|
|
7827
|
+
pkt[i++] = 224 | PID_PMT >> 8 & 31;
|
|
7828
|
+
pkt[i++] = PID_PMT & 255;
|
|
7829
|
+
const crc = crc32Mpeg(pkt.subarray(sectionStart, i));
|
|
7830
|
+
pkt.writeUInt32BE(crc, i);
|
|
7831
|
+
return pkt;
|
|
7832
|
+
}
|
|
7833
|
+
function buildPmt(videoStreamType, includeAudio, cc) {
|
|
7834
|
+
const pkt = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
7835
|
+
pkt[0] = TS_SYNC_BYTE;
|
|
7836
|
+
pkt[1] = 64 | PID_PMT >> 8 & 31;
|
|
7837
|
+
pkt[2] = PID_PMT & 255;
|
|
7838
|
+
pkt[3] = 16 | cc & 15;
|
|
7839
|
+
pkt[4] = 0;
|
|
7840
|
+
const sectionStart = 5;
|
|
7841
|
+
let i = sectionStart;
|
|
7842
|
+
pkt[i++] = 2;
|
|
7843
|
+
pkt[i++] = 176;
|
|
7844
|
+
const sectionLenPos = i;
|
|
7845
|
+
i += 1;
|
|
7846
|
+
pkt[i++] = 0;
|
|
7847
|
+
pkt[i++] = 1;
|
|
7848
|
+
pkt[i++] = 193;
|
|
7849
|
+
pkt[i++] = 0;
|
|
7850
|
+
pkt[i++] = 0;
|
|
7851
|
+
pkt[i++] = 224 | PID_VIDEO >> 8 & 31;
|
|
7852
|
+
pkt[i++] = PID_VIDEO & 255;
|
|
7853
|
+
pkt[i++] = 240;
|
|
7854
|
+
pkt[i++] = 0;
|
|
7855
|
+
pkt[i++] = videoStreamType;
|
|
7856
|
+
pkt[i++] = 224 | PID_VIDEO >> 8 & 31;
|
|
7857
|
+
pkt[i++] = PID_VIDEO & 255;
|
|
7858
|
+
pkt[i++] = 240;
|
|
7859
|
+
pkt[i++] = 0;
|
|
7860
|
+
if (includeAudio) {
|
|
7861
|
+
pkt[i++] = STREAM_TYPE_AAC;
|
|
7862
|
+
pkt[i++] = 224 | PID_AUDIO >> 8 & 31;
|
|
7863
|
+
pkt[i++] = PID_AUDIO & 255;
|
|
7864
|
+
pkt[i++] = 240;
|
|
7865
|
+
pkt[i++] = 0;
|
|
7866
|
+
}
|
|
7867
|
+
const sectionLen = i - sectionStart - 3 + 4;
|
|
7868
|
+
pkt[sectionLenPos] = sectionLen;
|
|
7869
|
+
const crc = crc32Mpeg(pkt.subarray(sectionStart, i));
|
|
7870
|
+
pkt.writeUInt32BE(crc, i);
|
|
7871
|
+
return pkt;
|
|
7872
|
+
}
|
|
7873
|
+
function buildVideoPes(annexBData, ptsUs, isKeyframe) {
|
|
7874
|
+
const pts = usToPts(ptsUs);
|
|
7875
|
+
const pesHeader = Buffer.allocUnsafe(14);
|
|
7876
|
+
pesHeader[0] = 0;
|
|
7877
|
+
pesHeader[1] = 0;
|
|
7878
|
+
pesHeader[2] = 1;
|
|
7879
|
+
pesHeader[3] = PES_STREAM_ID_VIDEO;
|
|
7880
|
+
pesHeader[4] = 0;
|
|
7881
|
+
pesHeader[5] = 0;
|
|
7882
|
+
pesHeader[6] = 128 | (isKeyframe ? 4 : 0);
|
|
7883
|
+
pesHeader[7] = 128;
|
|
7884
|
+
pesHeader[8] = 5;
|
|
7885
|
+
encodePts(pesHeader, 9, pts, 2);
|
|
7886
|
+
return Buffer.concat([pesHeader, annexBData]);
|
|
7887
|
+
}
|
|
7888
|
+
function buildAudioPes(adtsData, ptsUs) {
|
|
7889
|
+
const pts = usToPts(ptsUs);
|
|
7890
|
+
const pesPayloadLen = 8 + adtsData.length;
|
|
7891
|
+
const pesHeader = Buffer.allocUnsafe(14);
|
|
7892
|
+
pesHeader[0] = 0;
|
|
7893
|
+
pesHeader[1] = 0;
|
|
7894
|
+
pesHeader[2] = 1;
|
|
7895
|
+
pesHeader[3] = PES_STREAM_ID_AUDIO;
|
|
7896
|
+
pesHeader[4] = pesPayloadLen >> 8 & 255;
|
|
7897
|
+
pesHeader[5] = pesPayloadLen & 255;
|
|
7898
|
+
pesHeader[6] = 128;
|
|
7899
|
+
pesHeader[7] = 128;
|
|
7900
|
+
pesHeader[8] = 5;
|
|
7901
|
+
encodePts(pesHeader, 9, pts, 2);
|
|
7902
|
+
return Buffer.concat([pesHeader, adtsData]);
|
|
7903
|
+
}
|
|
7904
|
+
var MpegTsMuxer = class {
|
|
7905
|
+
videoStreamType;
|
|
7906
|
+
includeAudio;
|
|
7907
|
+
// Per-instance continuity counters (4-bit, wrap at 16)
|
|
7908
|
+
patCc = 0;
|
|
7909
|
+
pmtCc = 0;
|
|
7910
|
+
videoCc = 0;
|
|
7911
|
+
audioCc = 0;
|
|
7912
|
+
framesSinceTableSend = 0;
|
|
7913
|
+
tablesSent = false;
|
|
7914
|
+
constructor(options) {
|
|
7915
|
+
this.videoStreamType = options.videoType === "H265" ? STREAM_TYPE_H265 : STREAM_TYPE_H264;
|
|
7916
|
+
this.includeAudio = options.includeAudio ?? true;
|
|
7917
|
+
}
|
|
7918
|
+
/**
|
|
7919
|
+
* Mux a video frame (Annex-B H.264 or H.265) into MPEG-TS packets.
|
|
7920
|
+
* PAT and PMT are emitted before keyframes and periodically.
|
|
7921
|
+
*
|
|
7922
|
+
* @param annexBData - Annex-B video data (with start codes)
|
|
7923
|
+
* @param ptsUs - Presentation timestamp in microseconds
|
|
7924
|
+
* @param isKeyframe - Whether this is an IDR / IRAP frame
|
|
7925
|
+
*/
|
|
7926
|
+
muxVideo(annexBData, ptsUs, isKeyframe) {
|
|
7927
|
+
const chunks = [];
|
|
7928
|
+
const needTables = !this.tablesSent || isKeyframe || this.framesSinceTableSend >= PAT_PMT_INTERVAL;
|
|
7929
|
+
if (needTables) {
|
|
7930
|
+
chunks.push(buildPat(this.patCc));
|
|
7931
|
+
this.patCc = this.patCc + 1 & 15;
|
|
7932
|
+
chunks.push(buildPmt(this.videoStreamType, this.includeAudio, this.pmtCc));
|
|
7933
|
+
this.pmtCc = this.pmtCc + 1 & 15;
|
|
7934
|
+
this.tablesSent = true;
|
|
7935
|
+
this.framesSinceTableSend = 0;
|
|
7936
|
+
}
|
|
7937
|
+
this.framesSinceTableSend++;
|
|
7938
|
+
const pes = buildVideoPes(annexBData, ptsUs, isKeyframe);
|
|
7939
|
+
const ccRef = { cc: this.videoCc };
|
|
7940
|
+
chunks.push(pesToTsPackets(pes, PID_VIDEO, ccRef, isKeyframe));
|
|
7941
|
+
this.videoCc = ccRef.cc;
|
|
7942
|
+
return Buffer.concat(chunks);
|
|
7943
|
+
}
|
|
7944
|
+
/**
|
|
7945
|
+
* Mux an audio frame (ADTS AAC) into MPEG-TS packets.
|
|
7946
|
+
* Returns an empty Buffer when includeAudio is false.
|
|
7947
|
+
*
|
|
7948
|
+
* @param adtsData - Raw ADTS AAC frame (starting with 0xFF 0xF1/0xF9 syncword)
|
|
7949
|
+
* @param ptsUs - Presentation timestamp in microseconds
|
|
7950
|
+
*/
|
|
7951
|
+
muxAudio(adtsData, ptsUs) {
|
|
7952
|
+
if (!this.includeAudio || adtsData.length === 0) return Buffer.alloc(0);
|
|
7953
|
+
const pes = buildAudioPes(adtsData, ptsUs);
|
|
7954
|
+
const ccRef = { cc: this.audioCc };
|
|
7955
|
+
const result = pesToTsPackets(pes, PID_AUDIO, ccRef, false);
|
|
7956
|
+
this.audioCc = ccRef.cc;
|
|
7957
|
+
return result;
|
|
7958
|
+
}
|
|
7959
|
+
/** Reset all continuity counters and table state (e.g. after stream restart). */
|
|
7960
|
+
reset() {
|
|
7961
|
+
this.patCc = 0;
|
|
7962
|
+
this.pmtCc = 0;
|
|
7963
|
+
this.videoCc = 0;
|
|
7964
|
+
this.audioCc = 0;
|
|
7965
|
+
this.framesSinceTableSend = 0;
|
|
7966
|
+
this.tablesSent = false;
|
|
7967
|
+
}
|
|
7968
|
+
};
|
|
7969
|
+
|
|
7564
7970
|
// src/reolink/baichuan/capabilities.ts
|
|
7565
7971
|
function toNumberOrUndefined(value) {
|
|
7566
7972
|
if (value == null) return void 0;
|
|
@@ -7774,214 +8180,59 @@ function xmlIndicatesFloodlight(xml) {
|
|
|
7774
8180
|
return false;
|
|
7775
8181
|
}
|
|
7776
8182
|
|
|
8183
|
+
// src/reolink/baichuan/utils/sleepInference.ts
|
|
8184
|
+
function decideSleepInferenceTransition(input) {
|
|
8185
|
+
const { inferred, committed, pending, hysteresisPolls } = input;
|
|
8186
|
+
if (committed === void 0) {
|
|
8187
|
+
return {
|
|
8188
|
+
emit: inferred === "sleeping" ? "sleeping" : null,
|
|
8189
|
+
nextCommitted: inferred,
|
|
8190
|
+
nextPending: void 0
|
|
8191
|
+
};
|
|
8192
|
+
}
|
|
8193
|
+
if (inferred === committed) {
|
|
8194
|
+
return {
|
|
8195
|
+
emit: null,
|
|
8196
|
+
nextCommitted: committed,
|
|
8197
|
+
nextPending: void 0
|
|
8198
|
+
};
|
|
8199
|
+
}
|
|
8200
|
+
const effectivePolls = Math.max(1, hysteresisPolls);
|
|
8201
|
+
if (!pending || pending.state !== inferred) {
|
|
8202
|
+
if (effectivePolls <= 1) {
|
|
8203
|
+
return {
|
|
8204
|
+
emit: inferred === "sleeping" ? "sleeping" : "awake",
|
|
8205
|
+
nextCommitted: inferred,
|
|
8206
|
+
nextPending: void 0
|
|
8207
|
+
};
|
|
8208
|
+
}
|
|
8209
|
+
return {
|
|
8210
|
+
emit: null,
|
|
8211
|
+
nextCommitted: committed,
|
|
8212
|
+
nextPending: { state: inferred, count: 1 }
|
|
8213
|
+
};
|
|
8214
|
+
}
|
|
8215
|
+
const nextCount = pending.count + 1;
|
|
8216
|
+
if (nextCount < effectivePolls) {
|
|
8217
|
+
return {
|
|
8218
|
+
emit: null,
|
|
8219
|
+
nextCommitted: committed,
|
|
8220
|
+
nextPending: { state: inferred, count: nextCount }
|
|
8221
|
+
};
|
|
8222
|
+
}
|
|
8223
|
+
return {
|
|
8224
|
+
emit: inferred === "sleeping" ? "sleeping" : "awake",
|
|
8225
|
+
nextCommitted: inferred,
|
|
8226
|
+
nextPending: void 0
|
|
8227
|
+
};
|
|
8228
|
+
}
|
|
8229
|
+
|
|
7777
8230
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
7778
8231
|
import { spawn as spawn2 } from "child_process";
|
|
7779
8232
|
import { mkdir } from "fs/promises";
|
|
7780
8233
|
import { dirname } from "path";
|
|
7781
8234
|
import { PassThrough } from "stream";
|
|
7782
8235
|
|
|
7783
|
-
// src/baichuan/stream/MpegTsMuxer.ts
|
|
7784
|
-
var TS_PACKET_SIZE = 188;
|
|
7785
|
-
var TS_SYNC_BYTE = 71;
|
|
7786
|
-
var PAT_PID = 0;
|
|
7787
|
-
var PMT_PID = 4096;
|
|
7788
|
-
var VIDEO_PID = 256;
|
|
7789
|
-
var STREAM_TYPE_H264 = 27;
|
|
7790
|
-
var STREAM_TYPE_H265 = 36;
|
|
7791
|
-
var patCc = 0;
|
|
7792
|
-
var pmtCc = 0;
|
|
7793
|
-
var videoCc = 0;
|
|
7794
|
-
function createPat() {
|
|
7795
|
-
const packet = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
7796
|
-
packet[0] = TS_SYNC_BYTE;
|
|
7797
|
-
packet[1] = 64 | PAT_PID >> 8 & 31;
|
|
7798
|
-
packet[2] = PAT_PID & 255;
|
|
7799
|
-
packet[3] = 16 | patCc & 15;
|
|
7800
|
-
patCc = patCc + 1 & 15;
|
|
7801
|
-
packet[4] = 0;
|
|
7802
|
-
let idx = 5;
|
|
7803
|
-
packet[idx++] = 0;
|
|
7804
|
-
packet[idx++] = 176;
|
|
7805
|
-
packet[idx++] = 13;
|
|
7806
|
-
packet[idx++] = 0;
|
|
7807
|
-
packet[idx++] = 1;
|
|
7808
|
-
packet[idx++] = 193;
|
|
7809
|
-
packet[idx++] = 0;
|
|
7810
|
-
packet[idx++] = 0;
|
|
7811
|
-
packet[idx++] = 0;
|
|
7812
|
-
packet[idx++] = 1;
|
|
7813
|
-
packet[idx++] = 224 | PMT_PID >> 8 & 31;
|
|
7814
|
-
packet[idx++] = PMT_PID & 255;
|
|
7815
|
-
const crc = crc32Mpeg(packet.subarray(5, idx));
|
|
7816
|
-
packet.writeUInt32BE(crc, idx);
|
|
7817
|
-
return packet;
|
|
7818
|
-
}
|
|
7819
|
-
function createPmt(streamType) {
|
|
7820
|
-
const packet = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
7821
|
-
packet[0] = TS_SYNC_BYTE;
|
|
7822
|
-
packet[1] = 64 | PMT_PID >> 8 & 31;
|
|
7823
|
-
packet[2] = PMT_PID & 255;
|
|
7824
|
-
packet[3] = 16 | pmtCc & 15;
|
|
7825
|
-
pmtCc = pmtCc + 1 & 15;
|
|
7826
|
-
packet[4] = 0;
|
|
7827
|
-
let idx = 5;
|
|
7828
|
-
packet[idx++] = 2;
|
|
7829
|
-
packet[idx++] = 176;
|
|
7830
|
-
packet[idx++] = 18;
|
|
7831
|
-
packet[idx++] = 0;
|
|
7832
|
-
packet[idx++] = 1;
|
|
7833
|
-
packet[idx++] = 193;
|
|
7834
|
-
packet[idx++] = 0;
|
|
7835
|
-
packet[idx++] = 0;
|
|
7836
|
-
packet[idx++] = 224 | VIDEO_PID >> 8 & 31;
|
|
7837
|
-
packet[idx++] = VIDEO_PID & 255;
|
|
7838
|
-
packet[idx++] = 240;
|
|
7839
|
-
packet[idx++] = 0;
|
|
7840
|
-
packet[idx++] = streamType;
|
|
7841
|
-
packet[idx++] = 224 | VIDEO_PID >> 8 & 31;
|
|
7842
|
-
packet[idx++] = VIDEO_PID & 255;
|
|
7843
|
-
packet[idx++] = 240;
|
|
7844
|
-
packet[idx++] = 0;
|
|
7845
|
-
const crc = crc32Mpeg(packet.subarray(5, idx));
|
|
7846
|
-
packet.writeUInt32BE(crc, idx);
|
|
7847
|
-
return packet;
|
|
7848
|
-
}
|
|
7849
|
-
function createVideoPes(data, pts, isKeyframe) {
|
|
7850
|
-
const packets = [];
|
|
7851
|
-
const pts90k = Math.floor(pts * 9e4 / 1e6);
|
|
7852
|
-
const pesHeaderLen = 14;
|
|
7853
|
-
const pesHeader = Buffer.alloc(pesHeaderLen);
|
|
7854
|
-
let idx = 0;
|
|
7855
|
-
pesHeader[idx++] = 0;
|
|
7856
|
-
pesHeader[idx++] = 0;
|
|
7857
|
-
pesHeader[idx++] = 1;
|
|
7858
|
-
pesHeader[idx++] = 224;
|
|
7859
|
-
pesHeader[idx++] = 0;
|
|
7860
|
-
pesHeader[idx++] = 0;
|
|
7861
|
-
pesHeader[idx++] = 128;
|
|
7862
|
-
pesHeader[idx++] = 128;
|
|
7863
|
-
pesHeader[idx++] = 5;
|
|
7864
|
-
pesHeader[idx++] = 33 | pts90k >> 29 & 14;
|
|
7865
|
-
pesHeader[idx++] = pts90k >> 22 & 255;
|
|
7866
|
-
pesHeader[idx++] = 1 | pts90k >> 14 & 254;
|
|
7867
|
-
pesHeader[idx++] = pts90k >> 7 & 255;
|
|
7868
|
-
pesHeader[idx++] = 1 | pts90k << 1 & 254;
|
|
7869
|
-
const pesData = Buffer.concat([pesHeader, data]);
|
|
7870
|
-
let pesOffset = 0;
|
|
7871
|
-
let isFirst = true;
|
|
7872
|
-
while (pesOffset < pesData.length) {
|
|
7873
|
-
const packet = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
7874
|
-
let pktIdx = 0;
|
|
7875
|
-
packet[pktIdx++] = TS_SYNC_BYTE;
|
|
7876
|
-
packet[pktIdx++] = (isFirst ? 64 : 0) | VIDEO_PID >> 8 & 31;
|
|
7877
|
-
packet[pktIdx++] = VIDEO_PID & 255;
|
|
7878
|
-
const remaining = pesData.length - pesOffset;
|
|
7879
|
-
const maxPayload = TS_PACKET_SIZE - 4;
|
|
7880
|
-
if (remaining >= maxPayload) {
|
|
7881
|
-
packet[pktIdx++] = 16 | videoCc & 15;
|
|
7882
|
-
videoCc = videoCc + 1 & 15;
|
|
7883
|
-
pesData.copy(packet, pktIdx, pesOffset, pesOffset + maxPayload);
|
|
7884
|
-
pesOffset += maxPayload;
|
|
7885
|
-
} else {
|
|
7886
|
-
const adaptLen = maxPayload - remaining - 1;
|
|
7887
|
-
if (adaptLen < 0) {
|
|
7888
|
-
packet[pktIdx++] = 48 | videoCc & 15;
|
|
7889
|
-
videoCc = videoCc + 1 & 15;
|
|
7890
|
-
packet[pktIdx++] = TS_PACKET_SIZE - 4 - 1 - remaining;
|
|
7891
|
-
if (isFirst && isKeyframe) {
|
|
7892
|
-
packet[pktIdx++] = 64;
|
|
7893
|
-
for (let i = pktIdx; i < TS_PACKET_SIZE - remaining; i++) {
|
|
7894
|
-
packet[i] = 255;
|
|
7895
|
-
}
|
|
7896
|
-
} else {
|
|
7897
|
-
packet[pktIdx++] = 0;
|
|
7898
|
-
for (let i = pktIdx; i < TS_PACKET_SIZE - remaining; i++) {
|
|
7899
|
-
packet[i] = 255;
|
|
7900
|
-
}
|
|
7901
|
-
}
|
|
7902
|
-
pesData.copy(packet, TS_PACKET_SIZE - remaining, pesOffset);
|
|
7903
|
-
pesOffset += remaining;
|
|
7904
|
-
} else {
|
|
7905
|
-
packet[pktIdx++] = 48 | videoCc & 15;
|
|
7906
|
-
videoCc = videoCc + 1 & 15;
|
|
7907
|
-
if (adaptLen === 0) {
|
|
7908
|
-
packet[pktIdx++] = 0;
|
|
7909
|
-
} else {
|
|
7910
|
-
packet[pktIdx++] = adaptLen;
|
|
7911
|
-
if (isFirst && isKeyframe) {
|
|
7912
|
-
packet[pktIdx++] = 64;
|
|
7913
|
-
} else {
|
|
7914
|
-
packet[pktIdx++] = 0;
|
|
7915
|
-
}
|
|
7916
|
-
for (let i = 0; i < adaptLen - 1; i++) {
|
|
7917
|
-
packet[pktIdx++] = 255;
|
|
7918
|
-
}
|
|
7919
|
-
}
|
|
7920
|
-
pesData.copy(packet, pktIdx, pesOffset, pesOffset + remaining);
|
|
7921
|
-
pesOffset += remaining;
|
|
7922
|
-
}
|
|
7923
|
-
}
|
|
7924
|
-
packets.push(packet);
|
|
7925
|
-
isFirst = false;
|
|
7926
|
-
}
|
|
7927
|
-
return packets;
|
|
7928
|
-
}
|
|
7929
|
-
function crc32Mpeg(data) {
|
|
7930
|
-
let crc = 4294967295;
|
|
7931
|
-
for (let i = 0; i < data.length; i++) {
|
|
7932
|
-
crc ^= data[i] << 24;
|
|
7933
|
-
for (let j = 0; j < 8; j++) {
|
|
7934
|
-
if (crc & 2147483648) {
|
|
7935
|
-
crc = (crc << 1 ^ 79764919) >>> 0;
|
|
7936
|
-
} else {
|
|
7937
|
-
crc = crc << 1 >>> 0;
|
|
7938
|
-
}
|
|
7939
|
-
}
|
|
7940
|
-
}
|
|
7941
|
-
return crc >>> 0;
|
|
7942
|
-
}
|
|
7943
|
-
var MpegTsMuxer = class {
|
|
7944
|
-
streamType;
|
|
7945
|
-
patSent = false;
|
|
7946
|
-
pmtSent = false;
|
|
7947
|
-
patPmtInterval = 0;
|
|
7948
|
-
patPmtIntervalMax = 40;
|
|
7949
|
-
// Send PAT/PMT every ~40 frames
|
|
7950
|
-
constructor(options) {
|
|
7951
|
-
this.streamType = options.videoType === "H265" ? STREAM_TYPE_H265 : STREAM_TYPE_H264;
|
|
7952
|
-
}
|
|
7953
|
-
/**
|
|
7954
|
-
* Reset continuity counters (call when starting a new stream).
|
|
7955
|
-
*/
|
|
7956
|
-
static resetCounters() {
|
|
7957
|
-
patCc = 0;
|
|
7958
|
-
pmtCc = 0;
|
|
7959
|
-
videoCc = 0;
|
|
7960
|
-
}
|
|
7961
|
-
/**
|
|
7962
|
-
* Mux a video frame into MPEG-TS packets.
|
|
7963
|
-
*
|
|
7964
|
-
* @param data - Annex-B video data (with start codes)
|
|
7965
|
-
* @param microseconds - Frame timestamp in microseconds
|
|
7966
|
-
* @param isKeyframe - Whether this is a keyframe
|
|
7967
|
-
* @returns Buffer containing all TS packets for this frame
|
|
7968
|
-
*/
|
|
7969
|
-
mux(data, microseconds, isKeyframe) {
|
|
7970
|
-
const packets = [];
|
|
7971
|
-
if (!this.patSent || !this.pmtSent || isKeyframe || this.patPmtInterval >= this.patPmtIntervalMax) {
|
|
7972
|
-
packets.push(createPat());
|
|
7973
|
-
packets.push(createPmt(this.streamType));
|
|
7974
|
-
this.patSent = true;
|
|
7975
|
-
this.pmtSent = true;
|
|
7976
|
-
this.patPmtInterval = 0;
|
|
7977
|
-
}
|
|
7978
|
-
this.patPmtInterval++;
|
|
7979
|
-
const videoPackets = createVideoPes(data, microseconds, isKeyframe);
|
|
7980
|
-
packets.push(...videoPackets);
|
|
7981
|
-
return Buffer.concat(packets);
|
|
7982
|
-
}
|
|
7983
|
-
};
|
|
7984
|
-
|
|
7985
8236
|
// src/reolink/baichuan/utils/xml.ts
|
|
7986
8237
|
import { XMLParser } from "fast-xml-parser";
|
|
7987
8238
|
var parser = new XMLParser({
|
|
@@ -10130,6 +10381,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10130
10381
|
* - "replay:XXX" - dedicated per replay session
|
|
10131
10382
|
*/
|
|
10132
10383
|
socketPool = /* @__PURE__ */ new Map();
|
|
10384
|
+
/**
|
|
10385
|
+
* Consecutive stream-start (cmdId=3) timeout counter per socket tag.
|
|
10386
|
+
* When a streaming socket has N consecutive timeouts, the socket is force-closed
|
|
10387
|
+
* so the next attempt creates a fresh connection. Resets on success.
|
|
10388
|
+
*/
|
|
10389
|
+
consecutiveStreamTimeouts = /* @__PURE__ */ new Map();
|
|
10390
|
+
static MAX_CONSECUTIVE_STREAM_TIMEOUTS = 3;
|
|
10133
10391
|
/** BaichuanClientOptions to use when creating new sockets */
|
|
10134
10392
|
clientOptions;
|
|
10135
10393
|
/**
|
|
@@ -10284,14 +10542,20 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10284
10542
|
if (!xml) return;
|
|
10285
10543
|
const channel = frame.header.channelId;
|
|
10286
10544
|
const battery = this.parseBatteryInfoXml(xml, channel);
|
|
10287
|
-
if (battery.batteryPercent
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
});
|
|
10545
|
+
if (battery.batteryPercent === void 0 && battery.chargeStatus === void 0 && battery.adapterStatus === void 0) {
|
|
10546
|
+
return;
|
|
10547
|
+
}
|
|
10548
|
+
const key = `${battery.batteryPercent ?? ""}|${battery.chargeStatus ?? ""}|${battery.adapterStatus ?? ""}`;
|
|
10549
|
+
if (this.lastBatteryPushKey.get(channel) === key) {
|
|
10550
|
+
return;
|
|
10294
10551
|
}
|
|
10552
|
+
this.lastBatteryPushKey.set(channel, key);
|
|
10553
|
+
this.dispatchSimpleEvent({
|
|
10554
|
+
type: "battery",
|
|
10555
|
+
channel,
|
|
10556
|
+
timestamp: Date.now(),
|
|
10557
|
+
battery
|
|
10558
|
+
});
|
|
10295
10559
|
} catch (e) {
|
|
10296
10560
|
this.logger.debug?.(
|
|
10297
10561
|
"[ReolinkBaichuanApi] Error parsing battery push",
|
|
@@ -10424,7 +10688,17 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10424
10688
|
statePollingInterval;
|
|
10425
10689
|
udpSleepInferenceInterval;
|
|
10426
10690
|
udpLastInferredSleepStateByChannel = /* @__PURE__ */ new Map();
|
|
10691
|
+
/**
|
|
10692
|
+
* Per-channel pending sleep-state candidate for hysteresis.
|
|
10693
|
+
* When the inference flips to a new state we require N consecutive polls
|
|
10694
|
+
* of that same state before committing it — this filters out transient
|
|
10695
|
+
* flapping caused by non-waking traffic drifting in/out of the 10 s
|
|
10696
|
+
* getSleepStatus() observation window during stream teardown.
|
|
10697
|
+
*/
|
|
10698
|
+
udpPendingSleepStateByChannel = /* @__PURE__ */ new Map();
|
|
10427
10699
|
udpSleepInferenceIntervalMs = 2e3;
|
|
10700
|
+
/** Consecutive inference polls required to commit a new sleeping/awake state. */
|
|
10701
|
+
udpSleepInferenceHysteresisPolls = 2;
|
|
10428
10702
|
lastMotionState;
|
|
10429
10703
|
lastAiState;
|
|
10430
10704
|
aiStatePollingDisabled = false;
|
|
@@ -10457,6 +10731,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10457
10731
|
deviceCapabilitiesCache = /* @__PURE__ */ new Map();
|
|
10458
10732
|
static CAPABILITIES_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
10459
10733
|
// 5 minutes
|
|
10734
|
+
/**
|
|
10735
|
+
* Dedupe key for battery push events (cmd_id 252), per channel.
|
|
10736
|
+
* Cameras emit BatteryInfoList frequently while streaming (every few
|
|
10737
|
+
* seconds). We only forward an event when the meaningful fields change
|
|
10738
|
+
* (percent, chargeStatus, adapterStatus) to avoid flooding SSE/MQTT
|
|
10739
|
+
* consumers and the UI event log.
|
|
10740
|
+
*/
|
|
10741
|
+
lastBatteryPushKey = /* @__PURE__ */ new Map();
|
|
10460
10742
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
10461
10743
|
// SOCKET POOL CONSTANTS
|
|
10462
10744
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -10883,6 +11165,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10883
11165
|
*/
|
|
10884
11166
|
attachD2cDiscListener(client) {
|
|
10885
11167
|
client.on("d2c_disc", () => this.notifyD2cDisc());
|
|
11168
|
+
client.on("error", () => {
|
|
11169
|
+
});
|
|
10886
11170
|
}
|
|
10887
11171
|
/**
|
|
10888
11172
|
* Acquire a socket from the pool by tag.
|
|
@@ -11001,6 +11285,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11001
11285
|
const clientOpts = log ? { ...this.clientOptions, logger: log } : this.clientOptions;
|
|
11002
11286
|
const newClient = new BaichuanClient(clientOpts);
|
|
11003
11287
|
this.attachD2cDiscListener(newClient);
|
|
11288
|
+
newClient.on("error", (err) => {
|
|
11289
|
+
log?.debug?.(
|
|
11290
|
+
`[SocketPool] tag=${tag} client error: ${err?.message ?? err}`
|
|
11291
|
+
);
|
|
11292
|
+
});
|
|
11004
11293
|
await newClient.login();
|
|
11005
11294
|
const existingCooldown = this.socketPoolCooldowns.get(this.host);
|
|
11006
11295
|
if (existingCooldown) {
|
|
@@ -11516,6 +11805,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11516
11805
|
* Only counts sessions from our own IP address.
|
|
11517
11806
|
*/
|
|
11518
11807
|
async maybeRebootOnTooManySessions() {
|
|
11808
|
+
if (!this.client.isSocketConnected?.()) return;
|
|
11519
11809
|
const threshold = this.maxDedicatedSessionsBeforeReboot ?? 10;
|
|
11520
11810
|
if (this.sessionGuardRebootInFlight) return;
|
|
11521
11811
|
const cooldownMs = 10 * 6e4;
|
|
@@ -11957,6 +12247,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
11957
12247
|
}
|
|
11958
12248
|
async renewSimpleEventSubscription() {
|
|
11959
12249
|
if (this.simpleEventListeners.size === 0) return;
|
|
12250
|
+
if (!this.client.isSocketConnected?.()) return;
|
|
11960
12251
|
if (this.simpleEventResubscribeInFlight)
|
|
11961
12252
|
return await this.simpleEventResubscribeInFlight;
|
|
11962
12253
|
this.simpleEventResubscribeInFlight = (async () => {
|
|
@@ -15724,23 +16015,32 @@ ${stderr}`)
|
|
|
15724
16015
|
return;
|
|
15725
16016
|
}
|
|
15726
16017
|
const channel = this.client.getConfiguredChannel?.() ?? 0;
|
|
16018
|
+
if (!this.client.isSocketConnected?.()) {
|
|
16019
|
+
this.udpPendingSleepStateByChannel.delete(channel);
|
|
16020
|
+
return;
|
|
16021
|
+
}
|
|
15727
16022
|
const status = this.getSleepStatus({ channel });
|
|
15728
16023
|
if (status.state === "unknown") return;
|
|
15729
|
-
const
|
|
15730
|
-
this.
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
16024
|
+
const committed = this.udpLastInferredSleepStateByChannel.get(channel);
|
|
16025
|
+
const pending = this.udpPendingSleepStateByChannel.get(channel);
|
|
16026
|
+
const decision = decideSleepInferenceTransition({
|
|
16027
|
+
inferred: status.state,
|
|
16028
|
+
committed,
|
|
16029
|
+
pending,
|
|
16030
|
+
hysteresisPolls: this.udpSleepInferenceHysteresisPolls
|
|
16031
|
+
});
|
|
16032
|
+
this.udpLastInferredSleepStateByChannel.set(
|
|
16033
|
+
channel,
|
|
16034
|
+
decision.nextCommitted
|
|
16035
|
+
);
|
|
16036
|
+
if (decision.nextPending === void 0) {
|
|
16037
|
+
this.udpPendingSleepStateByChannel.delete(channel);
|
|
16038
|
+
} else {
|
|
16039
|
+
this.udpPendingSleepStateByChannel.set(channel, decision.nextPending);
|
|
15740
16040
|
}
|
|
15741
|
-
if (
|
|
16041
|
+
if (decision.emit) {
|
|
15742
16042
|
this.dispatchSimpleEvent({
|
|
15743
|
-
type:
|
|
16043
|
+
type: decision.emit,
|
|
15744
16044
|
channel,
|
|
15745
16045
|
timestamp: Date.now()
|
|
15746
16046
|
});
|
|
@@ -15763,6 +16063,7 @@ ${stderr}`)
|
|
|
15763
16063
|
this.udpSleepInferenceInterval = void 0;
|
|
15764
16064
|
}
|
|
15765
16065
|
this.udpLastInferredSleepStateByChannel.clear();
|
|
16066
|
+
this.udpPendingSleepStateByChannel.clear();
|
|
15766
16067
|
}
|
|
15767
16068
|
/**
|
|
15768
16069
|
* GetEvents via Baichuan (legacy - use subscribeEvents for real-time events).
|
|
@@ -15929,6 +16230,7 @@ ${stderr}`)
|
|
|
15929
16230
|
`${ch}:${profile}:${variant}`,
|
|
15930
16231
|
frame.header.msgNum
|
|
15931
16232
|
);
|
|
16233
|
+
this.resetStreamTimeoutCounter(targetClient);
|
|
15932
16234
|
return;
|
|
15933
16235
|
} catch (error) {
|
|
15934
16236
|
lastError = error;
|
|
@@ -15943,6 +16245,10 @@ ${stderr}`)
|
|
|
15943
16245
|
}
|
|
15944
16246
|
}
|
|
15945
16247
|
}
|
|
16248
|
+
const isTimeout = lastError instanceof Error && lastError.message?.includes("timeout");
|
|
16249
|
+
if (isTimeout) {
|
|
16250
|
+
this.trackStreamTimeout(targetClient);
|
|
16251
|
+
}
|
|
15946
16252
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
15947
16253
|
}
|
|
15948
16254
|
/**
|
|
@@ -16402,6 +16708,18 @@ ${stderr}`)
|
|
|
16402
16708
|
notifyD2cDisc() {
|
|
16403
16709
|
const now = Date.now();
|
|
16404
16710
|
this.lastD2cDiscAtMs = now;
|
|
16711
|
+
const streamingTags = Array.from(this.socketPool.keys()).filter(
|
|
16712
|
+
(tag) => tag.startsWith("streaming:")
|
|
16713
|
+
);
|
|
16714
|
+
if (streamingTags.length > 0) {
|
|
16715
|
+
this.logger?.log?.(
|
|
16716
|
+
`[D2C_DISC] Force-closing ${streamingTags.length} streaming socket(s): ${streamingTags.join(", ")}`
|
|
16717
|
+
);
|
|
16718
|
+
for (const tag of streamingTags) {
|
|
16719
|
+
this.forceClosePooledSocket(tag, this.logger).catch(() => {
|
|
16720
|
+
});
|
|
16721
|
+
}
|
|
16722
|
+
}
|
|
16405
16723
|
const immediateCooldownUntil = now + _ReolinkBaichuanApi.D2C_DISC_IMMEDIATE_COOLDOWN_MS;
|
|
16406
16724
|
const existing = this.socketPoolCooldowns.get(this.host);
|
|
16407
16725
|
if (!existing || existing.cooldownUntil < immediateCooldownUntil) {
|
|
@@ -16434,6 +16752,43 @@ ${stderr}`)
|
|
|
16434
16752
|
}
|
|
16435
16753
|
}
|
|
16436
16754
|
}
|
|
16755
|
+
/**
|
|
16756
|
+
* Find the socket pool tag for a given BaichuanClient instance.
|
|
16757
|
+
* Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
|
|
16758
|
+
*/
|
|
16759
|
+
findSocketTagForClient(client) {
|
|
16760
|
+
for (const [tag, entry] of this.socketPool) {
|
|
16761
|
+
if (entry.client === client) return tag;
|
|
16762
|
+
}
|
|
16763
|
+
return void 0;
|
|
16764
|
+
}
|
|
16765
|
+
/**
|
|
16766
|
+
* Reset the consecutive stream-start timeout counter for a streaming socket.
|
|
16767
|
+
* Called on successful stream start.
|
|
16768
|
+
*/
|
|
16769
|
+
resetStreamTimeoutCounter(client) {
|
|
16770
|
+
const tag = this.findSocketTagForClient(client);
|
|
16771
|
+
if (tag) this.consecutiveStreamTimeouts.delete(tag);
|
|
16772
|
+
}
|
|
16773
|
+
/**
|
|
16774
|
+
* Track a stream-start timeout on a streaming socket.
|
|
16775
|
+
* After MAX_CONSECUTIVE_STREAM_TIMEOUTS consecutive timeouts, force-close the
|
|
16776
|
+
* socket so the next attempt creates a fresh connection.
|
|
16777
|
+
*/
|
|
16778
|
+
trackStreamTimeout(client) {
|
|
16779
|
+
const tag = this.findSocketTagForClient(client);
|
|
16780
|
+
if (!tag || !tag.startsWith("streaming:")) return;
|
|
16781
|
+
const count = (this.consecutiveStreamTimeouts.get(tag) ?? 0) + 1;
|
|
16782
|
+
this.consecutiveStreamTimeouts.set(tag, count);
|
|
16783
|
+
if (count >= _ReolinkBaichuanApi.MAX_CONSECUTIVE_STREAM_TIMEOUTS) {
|
|
16784
|
+
this.logger?.warn?.(
|
|
16785
|
+
`[SocketPool] ${count} consecutive stream timeouts on tag=${tag}, force-closing socket`
|
|
16786
|
+
);
|
|
16787
|
+
this.consecutiveStreamTimeouts.delete(tag);
|
|
16788
|
+
this.forceClosePooledSocket(tag, this.logger).catch(() => {
|
|
16789
|
+
});
|
|
16790
|
+
}
|
|
16791
|
+
}
|
|
16437
16792
|
/**
|
|
16438
16793
|
* Best-effort sleeping inference for battery/BCUDP cameras.
|
|
16439
16794
|
*
|
|
@@ -18105,7 +18460,7 @@ ${xml}`
|
|
|
18105
18460
|
* @returns Test results for all stream types and profiles
|
|
18106
18461
|
*/
|
|
18107
18462
|
async testChannelStreams(channel, logger) {
|
|
18108
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
18463
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-HJDH4GPP.js");
|
|
18109
18464
|
return await testChannelStreams({
|
|
18110
18465
|
api: this,
|
|
18111
18466
|
channel: this.normalizeChannel(channel),
|
|
@@ -18121,7 +18476,7 @@ ${xml}`
|
|
|
18121
18476
|
* @returns Complete diagnostics for all channels and streams
|
|
18122
18477
|
*/
|
|
18123
18478
|
async collectMultifocalDiagnostics(logger) {
|
|
18124
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
18479
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-HJDH4GPP.js");
|
|
18125
18480
|
return await collectMultifocalDiagnostics({
|
|
18126
18481
|
api: this,
|
|
18127
18482
|
logger
|
|
@@ -19307,8 +19662,8 @@ ${scheduleItems}
|
|
|
19307
19662
|
);
|
|
19308
19663
|
let args;
|
|
19309
19664
|
if (useMpegTsMuxer) {
|
|
19310
|
-
MpegTsMuxer
|
|
19311
|
-
tsMuxer
|
|
19665
|
+
tsMuxer = new MpegTsMuxer({ videoType, includeAudio: false });
|
|
19666
|
+
tsMuxer.reset();
|
|
19312
19667
|
args = [
|
|
19313
19668
|
"-hide_banner",
|
|
19314
19669
|
"-loglevel",
|
|
@@ -19472,7 +19827,7 @@ ${scheduleItems}
|
|
|
19472
19827
|
startFfmpeg(videoType);
|
|
19473
19828
|
frameCount++;
|
|
19474
19829
|
if (useMpegTsMuxer && tsMuxer) {
|
|
19475
|
-
const tsData = tsMuxer.
|
|
19830
|
+
const tsData = tsMuxer.muxVideo(data, microseconds, isKeyframe);
|
|
19476
19831
|
input.write(tsData);
|
|
19477
19832
|
} else {
|
|
19478
19833
|
if (videoType === "H264") input.write(H264_AUD);
|
|
@@ -19761,8 +20116,8 @@ ${scheduleItems}
|
|
|
19761
20116
|
logger?.log?.(
|
|
19762
20117
|
`[createRecordingReplayHlsSession] Starting ffmpeg HLS with videoType=${videoType}, transcode=${needsTranscode}, hlsTime=${hlsSegmentDuration}s, fileName=${params.fileName}`
|
|
19763
20118
|
);
|
|
19764
|
-
MpegTsMuxer
|
|
19765
|
-
tsMuxer
|
|
20119
|
+
tsMuxer = new MpegTsMuxer({ videoType, includeAudio: false });
|
|
20120
|
+
tsMuxer.reset();
|
|
19766
20121
|
const args = [
|
|
19767
20122
|
"-hide_banner",
|
|
19768
20123
|
"-loglevel",
|
|
@@ -19927,7 +20282,7 @@ ${scheduleItems}
|
|
|
19927
20282
|
startFfmpeg(videoType);
|
|
19928
20283
|
frameCount++;
|
|
19929
20284
|
if (tsMuxer) {
|
|
19930
|
-
const tsData = tsMuxer.
|
|
20285
|
+
const tsData = tsMuxer.muxVideo(data, microseconds, isKeyframe);
|
|
19931
20286
|
input.write(tsData);
|
|
19932
20287
|
}
|
|
19933
20288
|
if (frameCount === 1) {
|
|
@@ -21248,16 +21603,16 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
|
21248
21603
|
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("EHOSTUNREACH") || message.includes("ENETUNREACH") || message.includes("socket hang up") || message.includes("TCP connection timeout") || message.includes("Baichuan socket closed") || message.includes("timeout waiting for nonce") || message.includes("expected encryption info") || message.includes("ECONNRESET") || message.includes("EPIPE");
|
|
21249
21604
|
}
|
|
21250
21605
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
21606
|
+
const { exec } = await import("child_process");
|
|
21607
|
+
const platform2 = process.platform;
|
|
21608
|
+
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
21609
|
+
// macOS: -W is in milliseconds (Linux: seconds)
|
|
21610
|
+
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
21611
|
+
) : (
|
|
21612
|
+
// Linux/BSD-ish: -W is in seconds on most distros
|
|
21613
|
+
`ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
|
|
21614
|
+
);
|
|
21251
21615
|
return new Promise((resolve) => {
|
|
21252
|
-
const { exec } = __require("child_process");
|
|
21253
|
-
const platform2 = process.platform;
|
|
21254
|
-
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
21255
|
-
// macOS: -W is in milliseconds (Linux: seconds)
|
|
21256
|
-
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
21257
|
-
) : (
|
|
21258
|
-
// Linux/BSD-ish: -W is in seconds on most distros
|
|
21259
|
-
`ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
|
|
21260
|
-
);
|
|
21261
21616
|
exec(pingCmd, (error) => {
|
|
21262
21617
|
resolve(!error);
|
|
21263
21618
|
});
|
|
@@ -21339,9 +21694,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21339
21694
|
const msg = fmtErr(e);
|
|
21340
21695
|
return msg.includes("Not running") || msg.includes("Baichuan UDP stream closed") || msg.includes("Baichuan socket closed") || msg.includes("ETIMEDOUT") || msg.toLowerCase().includes("timeout");
|
|
21341
21696
|
};
|
|
21342
|
-
const withRetries = async (label, max, op, shouldRetry) => {
|
|
21697
|
+
const withRetries = async (label, max, op, shouldRetry, isAborted) => {
|
|
21343
21698
|
let lastErr;
|
|
21344
21699
|
for (let attempt = 1; attempt <= max; attempt++) {
|
|
21700
|
+
if (isAborted?.()) throw new Error(`${label}: aborted (race won by another method)`);
|
|
21345
21701
|
try {
|
|
21346
21702
|
if (attempt > 1) {
|
|
21347
21703
|
logger?.log?.(`[AutoDetect] ${label}: retry ${attempt}/${max}...`);
|
|
@@ -21350,7 +21706,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21350
21706
|
} catch (e) {
|
|
21351
21707
|
lastErr = e;
|
|
21352
21708
|
const msg = fmtErr(e);
|
|
21353
|
-
const retryable = attempt < max && shouldRetry(e);
|
|
21709
|
+
const retryable = attempt < max && !isAborted?.() && shouldRetry(e);
|
|
21354
21710
|
logger?.log?.(
|
|
21355
21711
|
`[AutoDetect] ${label} attempt ${attempt}/${max} failed: ${msg}${retryable ? " (will retry)" : ""}`
|
|
21356
21712
|
);
|
|
@@ -21360,6 +21716,31 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21360
21716
|
}
|
|
21361
21717
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr ?? `${label} failed`));
|
|
21362
21718
|
};
|
|
21719
|
+
const runUdpMethodsParallel = async (methods, loginAndDetect, errorPrefix) => {
|
|
21720
|
+
let raceWon = false;
|
|
21721
|
+
const methodErrors = /* @__PURE__ */ new Map();
|
|
21722
|
+
try {
|
|
21723
|
+
return await Promise.any(
|
|
21724
|
+
methods.map(async (m) => {
|
|
21725
|
+
try {
|
|
21726
|
+
const result = await loginAndDetect(m, () => raceWon);
|
|
21727
|
+
raceWon = true;
|
|
21728
|
+
return result;
|
|
21729
|
+
} catch (e) {
|
|
21730
|
+
if (!raceWon) methodErrors.set(m, fmtErr(e));
|
|
21731
|
+
logger?.log?.(`[AutoDetect] UDP (${m}) failed: ${fmtErr(e)}`);
|
|
21732
|
+
throw e;
|
|
21733
|
+
}
|
|
21734
|
+
})
|
|
21735
|
+
);
|
|
21736
|
+
} catch (e) {
|
|
21737
|
+
if (e instanceof AggregateError) {
|
|
21738
|
+
const msgs = methods.map((m) => `${m}: ${methodErrors.get(m) ?? "unknown"}`);
|
|
21739
|
+
throw new Error(`${errorPrefix} ${msgs.join(" | ")}`);
|
|
21740
|
+
}
|
|
21741
|
+
throw e;
|
|
21742
|
+
}
|
|
21743
|
+
};
|
|
21363
21744
|
const effectiveUid = normalizeUid(uid);
|
|
21364
21745
|
logger?.log?.(`[AutoDetect] Pinging ${host}...`);
|
|
21365
21746
|
const isReachable = await pingHost(host);
|
|
@@ -21389,9 +21770,9 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21389
21770
|
normalizedUid = normalizedDiscovered;
|
|
21390
21771
|
}
|
|
21391
21772
|
const methodsToTry = inputs.udpDiscoveryMethod ? [inputs.udpDiscoveryMethod] : ["local-direct", "local-broadcast", "remote", "relay", "map"];
|
|
21392
|
-
|
|
21393
|
-
|
|
21394
|
-
|
|
21773
|
+
return await runUdpMethodsParallel(
|
|
21774
|
+
methodsToTry,
|
|
21775
|
+
async (m, isAborted) => {
|
|
21395
21776
|
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
21396
21777
|
const udpApi = await withRetries(
|
|
21397
21778
|
`UDP(${m})`,
|
|
@@ -21414,11 +21795,14 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21414
21795
|
throw e;
|
|
21415
21796
|
}
|
|
21416
21797
|
},
|
|
21417
|
-
shouldRetryUdp
|
|
21798
|
+
shouldRetryUdp,
|
|
21799
|
+
isAborted
|
|
21418
21800
|
);
|
|
21419
|
-
const deviceInfo = await
|
|
21420
|
-
|
|
21421
|
-
|
|
21801
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
21802
|
+
udpApi.getInfo(),
|
|
21803
|
+
udpApi.getDeviceCapabilities(),
|
|
21804
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
21805
|
+
]);
|
|
21422
21806
|
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
21423
21807
|
const model = deviceInfo.type?.trim();
|
|
21424
21808
|
const normalizedModel = model ? model.trim() : void 0;
|
|
@@ -21457,14 +21841,8 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21457
21841
|
channelNum: 1,
|
|
21458
21842
|
api: udpApi
|
|
21459
21843
|
};
|
|
21460
|
-
}
|
|
21461
|
-
|
|
21462
|
-
udpErrors.push(`${m}: ${msg}`);
|
|
21463
|
-
logger?.log?.(`[AutoDetect] UDP (${m}) failed: ${msg}`);
|
|
21464
|
-
}
|
|
21465
|
-
}
|
|
21466
|
-
throw new Error(
|
|
21467
|
-
`Forced UDP autodetect failed for all methods. ${udpErrors.join(" | ")}`
|
|
21844
|
+
},
|
|
21845
|
+
"Forced UDP autodetect failed for all methods."
|
|
21468
21846
|
);
|
|
21469
21847
|
}
|
|
21470
21848
|
let tcpApi;
|
|
@@ -21517,54 +21895,57 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21517
21895
|
}
|
|
21518
21896
|
return void 0;
|
|
21519
21897
|
};
|
|
21520
|
-
const infoProbe = await
|
|
21521
|
-
|
|
21522
|
-
|
|
21898
|
+
const [infoProbe, supportProbe] = await Promise.all([
|
|
21899
|
+
runProbeVariants(
|
|
21900
|
+
"getInfo",
|
|
21901
|
+
[
|
|
21902
|
+
{
|
|
21903
|
+
variant: "cmd80 class=0x6414",
|
|
21904
|
+
op: () => api.getInfo(void 0, {
|
|
21905
|
+
timeoutMs: 2500,
|
|
21906
|
+
messageClass: BC_CLASS_MODERN_24
|
|
21907
|
+
})
|
|
21908
|
+
},
|
|
21909
|
+
{
|
|
21910
|
+
variant: "cmd80 class=0x6614",
|
|
21911
|
+
op: () => api.getInfo(void 0, {
|
|
21912
|
+
timeoutMs: 3e3,
|
|
21913
|
+
messageClass: BC_CLASS_MODERN_20
|
|
21914
|
+
})
|
|
21915
|
+
},
|
|
21916
|
+
{
|
|
21917
|
+
variant: "cmd318(ch0) class=0x6414",
|
|
21918
|
+
op: () => api.getInfo(0, {
|
|
21919
|
+
timeoutMs: 3e3,
|
|
21920
|
+
messageClass: BC_CLASS_MODERN_24
|
|
21921
|
+
})
|
|
21922
|
+
},
|
|
21923
|
+
{
|
|
21924
|
+
variant: "cmd318(ch0) class=0x6614",
|
|
21925
|
+
op: () => api.getInfo(0, {
|
|
21926
|
+
timeoutMs: 3500,
|
|
21927
|
+
messageClass: BC_CLASS_MODERN_20
|
|
21928
|
+
})
|
|
21929
|
+
}
|
|
21930
|
+
]
|
|
21931
|
+
),
|
|
21932
|
+
// Support probes (cmd 199). Some firmwares may not support it or are slow.
|
|
21933
|
+
runProbeVariants("getSupportInfo", [
|
|
21523
21934
|
{
|
|
21524
|
-
variant: "
|
|
21525
|
-
op: () => api.
|
|
21935
|
+
variant: "cmd199 class=0x6414",
|
|
21936
|
+
op: () => api.getSupportInfo({
|
|
21526
21937
|
timeoutMs: 2500,
|
|
21527
21938
|
messageClass: BC_CLASS_MODERN_24
|
|
21528
21939
|
})
|
|
21529
21940
|
},
|
|
21530
21941
|
{
|
|
21531
|
-
variant: "
|
|
21532
|
-
op: () => api.
|
|
21533
|
-
timeoutMs: 3e3,
|
|
21534
|
-
messageClass: BC_CLASS_MODERN_20
|
|
21535
|
-
})
|
|
21536
|
-
},
|
|
21537
|
-
{
|
|
21538
|
-
variant: "cmd318(ch0) class=0x6414",
|
|
21539
|
-
op: () => api.getInfo(0, {
|
|
21540
|
-
timeoutMs: 3e3,
|
|
21541
|
-
messageClass: BC_CLASS_MODERN_24
|
|
21542
|
-
})
|
|
21543
|
-
},
|
|
21544
|
-
{
|
|
21545
|
-
variant: "cmd318(ch0) class=0x6614",
|
|
21546
|
-
op: () => api.getInfo(0, {
|
|
21942
|
+
variant: "cmd199 class=0x6614",
|
|
21943
|
+
op: () => api.getSupportInfo({
|
|
21547
21944
|
timeoutMs: 3500,
|
|
21548
21945
|
messageClass: BC_CLASS_MODERN_20
|
|
21549
21946
|
})
|
|
21550
21947
|
}
|
|
21551
|
-
]
|
|
21552
|
-
);
|
|
21553
|
-
const supportProbe = await runProbeVariants("getSupportInfo", [
|
|
21554
|
-
{
|
|
21555
|
-
variant: "cmd199 class=0x6414",
|
|
21556
|
-
op: () => api.getSupportInfo({
|
|
21557
|
-
timeoutMs: 2500,
|
|
21558
|
-
messageClass: BC_CLASS_MODERN_24
|
|
21559
|
-
})
|
|
21560
|
-
},
|
|
21561
|
-
{
|
|
21562
|
-
variant: "cmd199 class=0x6614",
|
|
21563
|
-
op: () => api.getSupportInfo({
|
|
21564
|
-
timeoutMs: 3500,
|
|
21565
|
-
messageClass: BC_CLASS_MODERN_20
|
|
21566
|
-
})
|
|
21567
|
-
}
|
|
21948
|
+
])
|
|
21568
21949
|
]);
|
|
21569
21950
|
const deviceInfo = infoProbe?.value;
|
|
21570
21951
|
const support = supportProbe?.value;
|
|
@@ -21656,9 +22037,11 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21656
22037
|
}
|
|
21657
22038
|
try {
|
|
21658
22039
|
const detectOverUdpApi = async (udpApi, udpDiscoveryMethod) => {
|
|
21659
|
-
const deviceInfo = await
|
|
21660
|
-
|
|
21661
|
-
|
|
22040
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
22041
|
+
udpApi.getInfo(),
|
|
22042
|
+
udpApi.getDeviceCapabilities(),
|
|
22043
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
22044
|
+
]);
|
|
21662
22045
|
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
21663
22046
|
const model = deviceInfo.type?.trim();
|
|
21664
22047
|
const normalizedModel = model ? model.trim() : void 0;
|
|
@@ -21702,21 +22085,17 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21702
22085
|
};
|
|
21703
22086
|
};
|
|
21704
22087
|
const methodsToTry = ["local-direct", "local-broadcast", "remote", "relay", "map"];
|
|
21705
|
-
const
|
|
21706
|
-
|
|
21707
|
-
|
|
22088
|
+
const viableMethods = normalizedUid ? methodsToTry : methodsToTry.filter((m) => m === "local-direct" || m === "local-broadcast");
|
|
22089
|
+
return await runUdpMethodsParallel(
|
|
22090
|
+
viableMethods,
|
|
22091
|
+
async (m, isAborted) => {
|
|
21708
22092
|
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
21709
22093
|
const udpApi = await withRetries(
|
|
21710
22094
|
`UDP(${m})`,
|
|
21711
22095
|
maxRetries,
|
|
21712
22096
|
async (attempt) => {
|
|
21713
|
-
const apiInputs = {
|
|
21714
|
-
|
|
21715
|
-
udpDiscoveryMethod: m
|
|
21716
|
-
};
|
|
21717
|
-
if (normalizedUid) {
|
|
21718
|
-
apiInputs.uid = normalizedUid;
|
|
21719
|
-
}
|
|
22097
|
+
const apiInputs = { ...inputs, udpDiscoveryMethod: m };
|
|
22098
|
+
if (normalizedUid) apiInputs.uid = normalizedUid;
|
|
21720
22099
|
const api = createBaichuanApi(apiInputs, "udp");
|
|
21721
22100
|
try {
|
|
21722
22101
|
await api.login();
|
|
@@ -21731,20 +22110,12 @@ async function autoDetectDeviceType(inputs) {
|
|
|
21731
22110
|
throw e;
|
|
21732
22111
|
}
|
|
21733
22112
|
},
|
|
21734
|
-
shouldRetryUdp
|
|
22113
|
+
shouldRetryUdp,
|
|
22114
|
+
isAborted
|
|
21735
22115
|
);
|
|
21736
|
-
return
|
|
21737
|
-
}
|
|
21738
|
-
|
|
21739
|
-
udpErrors.push(`${m}: ${msg}`);
|
|
21740
|
-
try {
|
|
21741
|
-
} catch {
|
|
21742
|
-
}
|
|
21743
|
-
logger?.log?.(`[AutoDetect] UDP (${m}) failed: ${msg}`);
|
|
21744
|
-
}
|
|
21745
|
-
}
|
|
21746
|
-
throw new Error(
|
|
21747
|
-
`UDP discovery failed for all methods. ${udpErrors.join(" | ")}`
|
|
22116
|
+
return detectOverUdpApi(udpApi, m);
|
|
22117
|
+
},
|
|
22118
|
+
"UDP discovery failed for all methods."
|
|
21748
22119
|
);
|
|
21749
22120
|
} catch (udpError) {
|
|
21750
22121
|
logger?.log?.(
|
|
@@ -21776,12 +22147,14 @@ export {
|
|
|
21776
22147
|
BaichuanEventEmitter,
|
|
21777
22148
|
createNativeStream,
|
|
21778
22149
|
BaichuanRtspServer,
|
|
22150
|
+
MpegTsMuxer,
|
|
21779
22151
|
flattenAbilitiesForChannel,
|
|
21780
22152
|
abilitiesHasAny,
|
|
21781
22153
|
parseSupportXml,
|
|
21782
22154
|
getSupportItemForChannel,
|
|
21783
22155
|
computeDeviceCapabilities,
|
|
21784
22156
|
xmlIndicatesFloodlight,
|
|
22157
|
+
decideSleepInferenceTransition,
|
|
21785
22158
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
|
21786
22159
|
DUAL_LENS_SINGLE_MOTION_MODELS,
|
|
21787
22160
|
DUAL_LENS_MODELS,
|
|
@@ -21803,4 +22176,4 @@ export {
|
|
|
21803
22176
|
isTcpFailureThatShouldFallbackToUdp,
|
|
21804
22177
|
autoDetectDeviceType
|
|
21805
22178
|
};
|
|
21806
|
-
//# sourceMappingURL=chunk-
|
|
22179
|
+
//# sourceMappingURL=chunk-VBYF3BQX.js.map
|