@apocaliss92/nodelink-js 0.4.7 → 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-GKLOJJ34.js → chunk-VBYF3BQX.js} +639 -341
- 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 +607 -311
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +793 -328
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -6
- package/dist/index.d.ts +238 -5
- package/dist/index.js +188 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-GKLOJJ34.js.map +0 -1
- /package/dist/{DiagnosticsTools-UMN4C7SY.js.map → DiagnosticsTools-HJDH4GPP.js.map} +0 -0
package/dist/cli/rtsp-server.cjs
CHANGED
|
@@ -1851,7 +1851,7 @@ var init_BaichuanVideoStream = __esm({
|
|
|
1851
1851
|
const allowMsgNum0Fallback = this.acceptAnyStreamType && frame.header.msgNum === 0;
|
|
1852
1852
|
if (!allowMsgNum0Fallback) {
|
|
1853
1853
|
const frameCount = this._msgNumMismatchCount = (this._msgNumMismatchCount || 0) + 1;
|
|
1854
|
-
if (frameCount <= 5) {
|
|
1854
|
+
if (frameCount <= 5 && this.client.getDebugConfig().general) {
|
|
1855
1855
|
this.logger?.log(
|
|
1856
1856
|
`[BaichuanVideoStream] Frame msgNum mismatch: received=${frame.header.msgNum}, expected=${this.activeMsgNum}, channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
|
|
1857
1857
|
);
|
|
@@ -1861,7 +1861,7 @@ var init_BaichuanVideoStream = __esm({
|
|
|
1861
1861
|
}
|
|
1862
1862
|
if (!this.acceptAnyStreamType && !this.expectedStreamTypes.has(frame.header.streamType)) {
|
|
1863
1863
|
const frameCount = this._streamTypeMismatchCount = (this._streamTypeMismatchCount || 0) + 1;
|
|
1864
|
-
if (frameCount <= 5) {
|
|
1864
|
+
if (frameCount <= 5 && this.client.getDebugConfig().general) {
|
|
1865
1865
|
this.logger?.log(
|
|
1866
1866
|
`[BaichuanVideoStream] Frame streamType mismatch: received=${frame.header.streamType}, expectedAny=[${[
|
|
1867
1867
|
...this.expectedStreamTypes
|
|
@@ -7815,19 +7815,34 @@ async function* createNativeStream(api, channel, profile, options) {
|
|
|
7815
7815
|
}
|
|
7816
7816
|
});
|
|
7817
7817
|
streamStarted = true;
|
|
7818
|
-
|
|
7818
|
+
const signal = options?.signal;
|
|
7819
|
+
while (!closed && !signal?.aborted) {
|
|
7819
7820
|
if (frameQueue.length > 0) {
|
|
7820
7821
|
const frame = frameQueue.shift();
|
|
7821
7822
|
yield frame;
|
|
7822
7823
|
} else {
|
|
7823
7824
|
await new Promise((resolve) => {
|
|
7824
7825
|
frameResolve = resolve;
|
|
7825
|
-
setTimeout(() => {
|
|
7826
|
+
const timer = setTimeout(() => {
|
|
7826
7827
|
if (frameResolve === resolve) {
|
|
7827
7828
|
frameResolve = null;
|
|
7828
7829
|
resolve();
|
|
7829
7830
|
}
|
|
7830
7831
|
}, 1e3);
|
|
7832
|
+
if (signal) {
|
|
7833
|
+
const onAbort = () => {
|
|
7834
|
+
clearTimeout(timer);
|
|
7835
|
+
if (frameResolve === resolve) frameResolve = null;
|
|
7836
|
+
resolve();
|
|
7837
|
+
};
|
|
7838
|
+
if (signal.aborted) {
|
|
7839
|
+
clearTimeout(timer);
|
|
7840
|
+
frameResolve = null;
|
|
7841
|
+
resolve();
|
|
7842
|
+
} else {
|
|
7843
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7831
7846
|
});
|
|
7832
7847
|
}
|
|
7833
7848
|
}
|
|
@@ -8000,13 +8015,14 @@ var NativeStreamFanout = class {
|
|
|
8000
8015
|
source = null;
|
|
8001
8016
|
running = false;
|
|
8002
8017
|
pumpPromise = null;
|
|
8018
|
+
abort = new AbortController();
|
|
8003
8019
|
constructor(opts) {
|
|
8004
8020
|
this.opts = opts;
|
|
8005
8021
|
}
|
|
8006
8022
|
start() {
|
|
8007
8023
|
if (this.running) return;
|
|
8008
8024
|
this.running = true;
|
|
8009
|
-
this.source = this.opts.createSource();
|
|
8025
|
+
this.source = this.opts.createSource(this.abort.signal);
|
|
8010
8026
|
this.pumpPromise = (async () => {
|
|
8011
8027
|
try {
|
|
8012
8028
|
for await (const frame of this.source) {
|
|
@@ -8052,6 +8068,7 @@ var NativeStreamFanout = class {
|
|
|
8052
8068
|
this.source = null;
|
|
8053
8069
|
for (const q of this.queues.values()) q.close();
|
|
8054
8070
|
this.queues.clear();
|
|
8071
|
+
this.abort.abort();
|
|
8055
8072
|
try {
|
|
8056
8073
|
await src?.return(void 0);
|
|
8057
8074
|
} catch {
|
|
@@ -8090,9 +8107,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8090
8107
|
requireAuth;
|
|
8091
8108
|
authNonces = /* @__PURE__ */ new Map();
|
|
8092
8109
|
// Track nonces per client
|
|
8093
|
-
AUTH_REALM
|
|
8110
|
+
AUTH_REALM;
|
|
8094
8111
|
NONCE_TIMEOUT_MS = 3e5;
|
|
8095
8112
|
// 5 minutes
|
|
8113
|
+
lazyMetadata;
|
|
8096
8114
|
// Client tracking
|
|
8097
8115
|
connectedClients = /* @__PURE__ */ new Set();
|
|
8098
8116
|
// Set of client IDs (IP:port)
|
|
@@ -8104,8 +8122,15 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8104
8122
|
// Track all client resources for cleanup
|
|
8105
8123
|
clientResources = /* @__PURE__ */ new Map();
|
|
8106
8124
|
isRtspDebugEnabled() {
|
|
8107
|
-
|
|
8108
|
-
|
|
8125
|
+
try {
|
|
8126
|
+
if (this.api.isClosed) {
|
|
8127
|
+
return envBool(process.env.BAICHUAN_DEBUG_RTSP, false);
|
|
8128
|
+
}
|
|
8129
|
+
const dbg = this.api.client.getDebugConfig();
|
|
8130
|
+
return dbg.debugRtsp || envBool(process.env.BAICHUAN_DEBUG_RTSP, false);
|
|
8131
|
+
} catch {
|
|
8132
|
+
return envBool(process.env.BAICHUAN_DEBUG_RTSP, false);
|
|
8133
|
+
}
|
|
8109
8134
|
}
|
|
8110
8135
|
rtspDebugLog(message) {
|
|
8111
8136
|
if (!this.isRtspDebugEnabled()) return;
|
|
@@ -8127,10 +8152,20 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8127
8152
|
// Shared native stream fan-out (single camera stream, multiple RTSP clients)
|
|
8128
8153
|
nativeFanout = null;
|
|
8129
8154
|
noClientAutoStopTimer;
|
|
8155
|
+
/** Fires if camera never sends frames after stream start (sleeping), even with clients connected. */
|
|
8156
|
+
noFrameDeadlineTimer;
|
|
8130
8157
|
/** After last RTSP client; 0 = never auto-stop native stream. */
|
|
8131
8158
|
nativeStreamIdleStopMs;
|
|
8132
8159
|
/** Primed-but-no-PLAY timeout; 0 = disabled. */
|
|
8133
8160
|
nativeStreamPrimeIdleStopMs;
|
|
8161
|
+
/**
|
|
8162
|
+
* Max time to wait for the first camera frame after stream start.
|
|
8163
|
+
* If no frames arrive within this window, the native stream is stopped
|
|
8164
|
+
* (camera is sleeping). Prevents the BaichuanVideoStream watchdog from
|
|
8165
|
+
* firing and waking the camera when no real viewer is watching.
|
|
8166
|
+
* 0 = disabled. Defaults to nativeStreamPrimeIdleStopMs * 2 when > 0.
|
|
8167
|
+
*/
|
|
8168
|
+
nativeStreamNoFrameDeadlineMs;
|
|
8134
8169
|
// Prebuffer: rolling ring of recent video frames for IDR-aligned fast startup.
|
|
8135
8170
|
// When a new client connects while the stream is already running it does not need
|
|
8136
8171
|
// to wait up to one full GOP interval for the next keyframe — we replay frames
|
|
@@ -8256,14 +8291,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8256
8291
|
this.logger = options.logger ?? console;
|
|
8257
8292
|
this.tcpRtpFraming = options.tcpRtpFraming ?? "rfc4571";
|
|
8258
8293
|
this.deviceId = options.deviceId;
|
|
8259
|
-
this.externalListener = options.externalListener ?? false;
|
|
8294
|
+
this.externalListener = (options.externalListener ?? false) || (options.muxMode ?? false);
|
|
8260
8295
|
this.nativeStreamIdleStopMs = options.nativeStreamIdleStopMs ?? 3e4;
|
|
8261
8296
|
this.nativeStreamPrimeIdleStopMs = options.nativeStreamPrimeIdleStopMs ?? (this.nativeStreamIdleStopMs > 0 ? 15e3 : 0);
|
|
8262
|
-
this.
|
|
8297
|
+
this.nativeStreamNoFrameDeadlineMs = this.nativeStreamPrimeIdleStopMs > 0 ? Math.min(this.nativeStreamPrimeIdleStopMs * 2, 3e4) : 0;
|
|
8298
|
+
this.authCredentials = (options.credentials ?? []).map((c) => ({
|
|
8299
|
+
username: c.username,
|
|
8300
|
+
...c.password !== void 0 ? { password: c.password } : {},
|
|
8301
|
+
...c.ha1 !== void 0 ? { ha1: c.ha1 } : {}
|
|
8302
|
+
}));
|
|
8263
8303
|
this.requireAuth = options.requireAuth ?? this.authCredentials.length > 0;
|
|
8304
|
+
this.AUTH_REALM = options.authRealm ?? "BaichuanRtspServer";
|
|
8305
|
+
this.lazyMetadata = options.lazyMetadata ?? false;
|
|
8264
8306
|
const transport = this.api.client.getTransport();
|
|
8265
8307
|
this.flow = createRtspFlow(transport, "H264");
|
|
8266
8308
|
}
|
|
8309
|
+
/** Number of currently connected RTSP clients. */
|
|
8310
|
+
get clientCount() {
|
|
8311
|
+
return this.connectedClients.size;
|
|
8312
|
+
}
|
|
8267
8313
|
// --- Authentication helpers ---
|
|
8268
8314
|
/**
|
|
8269
8315
|
* Generate a new nonce for Digest authentication
|
|
@@ -8324,9 +8370,16 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8324
8370
|
this.rtspDebugLog(`Auth failed: nonce mismatch for client ${clientId}`);
|
|
8325
8371
|
return false;
|
|
8326
8372
|
}
|
|
8373
|
+
if (realm !== this.AUTH_REALM) {
|
|
8374
|
+
this.rtspDebugLog(
|
|
8375
|
+
`Auth failed: realm mismatch (client="${realm}", server="${this.AUTH_REALM}")`
|
|
8376
|
+
);
|
|
8377
|
+
return false;
|
|
8378
|
+
}
|
|
8327
8379
|
for (const cred of this.authCredentials) {
|
|
8328
8380
|
if (username !== cred.username) continue;
|
|
8329
|
-
const ha1 = this.md5(`${cred.username}:${
|
|
8381
|
+
const ha1 = cred.ha1 ?? (cred.password !== void 0 ? this.md5(`${cred.username}:${this.AUTH_REALM}:${cred.password}`) : void 0);
|
|
8382
|
+
if (!ha1) continue;
|
|
8330
8383
|
const ha2 = this.md5(`${method}:${authUri || uri}`);
|
|
8331
8384
|
const expectedResponse = this.md5(`${ha1}:${nonce}:${ha2}`);
|
|
8332
8385
|
if (response === expectedResponse) {
|
|
@@ -8355,6 +8408,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8355
8408
|
this.noClientAutoStopTimer = void 0;
|
|
8356
8409
|
}
|
|
8357
8410
|
}
|
|
8411
|
+
clearNoFrameDeadlineTimer() {
|
|
8412
|
+
if (this.noFrameDeadlineTimer) {
|
|
8413
|
+
clearTimeout(this.noFrameDeadlineTimer);
|
|
8414
|
+
this.noFrameDeadlineTimer = void 0;
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8358
8417
|
setFlowVideoType(videoType, reason) {
|
|
8359
8418
|
if (this.flow.videoType === videoType) return;
|
|
8360
8419
|
const transport = this.api.client.getTransport();
|
|
@@ -8369,25 +8428,31 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8369
8428
|
if (this.active) {
|
|
8370
8429
|
throw new Error("RTSP server is already active");
|
|
8371
8430
|
}
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
if (stream) {
|
|
8376
|
-
this.streamMetadata = {
|
|
8377
|
-
frameRate: stream.frameRate || 25,
|
|
8378
|
-
width: stream.width,
|
|
8379
|
-
height: stream.height
|
|
8380
|
-
};
|
|
8381
|
-
const enc = String(stream.videoEncType ?? "").trim().toLowerCase();
|
|
8382
|
-
const metaVideoType = enc.includes("265") || enc.includes("hevc") ? "H265" : "H264";
|
|
8383
|
-
this.setFlowVideoType(metaVideoType, "metadata");
|
|
8384
|
-
}
|
|
8385
|
-
} catch (error) {
|
|
8386
|
-
this.logger.warn(
|
|
8387
|
-
`[BaichuanRtspServer] Could not get stream metadata: ${error}`
|
|
8431
|
+
if (this.lazyMetadata) {
|
|
8432
|
+
this.logger.info(
|
|
8433
|
+
`[BaichuanRtspServer] lazy metadata: skipping initial getStreamMetadata; will fetch on first DESCRIBE`
|
|
8388
8434
|
);
|
|
8389
|
-
|
|
8390
|
-
|
|
8435
|
+
} else {
|
|
8436
|
+
try {
|
|
8437
|
+
const metadata = await this.api.getStreamMetadata(this.channel);
|
|
8438
|
+
const stream = metadata.streams.find((s) => s.profile === this.profile);
|
|
8439
|
+
if (stream) {
|
|
8440
|
+
this.streamMetadata = {
|
|
8441
|
+
frameRate: stream.frameRate || 25,
|
|
8442
|
+
width: stream.width,
|
|
8443
|
+
height: stream.height
|
|
8444
|
+
};
|
|
8445
|
+
const enc = String(stream.videoEncType ?? "").trim().toLowerCase();
|
|
8446
|
+
const metaVideoType = enc.includes("265") || enc.includes("hevc") ? "H265" : "H264";
|
|
8447
|
+
this.setFlowVideoType(metaVideoType, "metadata");
|
|
8448
|
+
}
|
|
8449
|
+
} catch (error) {
|
|
8450
|
+
this.logger.warn(
|
|
8451
|
+
`[BaichuanRtspServer] Could not get stream metadata: ${error}`
|
|
8452
|
+
);
|
|
8453
|
+
this.streamMetadata = { frameRate: 25 };
|
|
8454
|
+
this.setFlowVideoType("H264", "metadata unavailable");
|
|
8455
|
+
}
|
|
8391
8456
|
}
|
|
8392
8457
|
if (!this.externalListener) {
|
|
8393
8458
|
this.clientConnectionServer = net.createServer((socket) => {
|
|
@@ -8428,6 +8493,30 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8428
8493
|
}
|
|
8429
8494
|
this.handleRtspConnection(socket, initialBuffer);
|
|
8430
8495
|
}
|
|
8496
|
+
/**
|
|
8497
|
+
* Inject an already-accepted client socket from a multiplexer
|
|
8498
|
+
* (e.g. `LocalRtspMux`) that owns the listening port.
|
|
8499
|
+
*
|
|
8500
|
+
* The mux reads the first RTSP request line to determine the target path,
|
|
8501
|
+
* then hands the socket over. Any bytes already consumed during routing
|
|
8502
|
+
* are replayed back onto the socket via `unshift()` so the RTSP parser in
|
|
8503
|
+
* `handleRtspConnection` sees the complete original request.
|
|
8504
|
+
*
|
|
8505
|
+
* @param socket - Client TCP socket, already accepted by the mux.
|
|
8506
|
+
* @param preReadData - Bytes the mux has already pulled off the socket
|
|
8507
|
+
* while parsing the request line. Replayed via `socket.unshift()`
|
|
8508
|
+
* before any further reads.
|
|
8509
|
+
*/
|
|
8510
|
+
injectSocket(socket, preReadData) {
|
|
8511
|
+
if (!this.active) {
|
|
8512
|
+
socket.end("RTSP/1.0 503 Service Unavailable\r\n\r\n");
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
if (preReadData && preReadData.length > 0) {
|
|
8516
|
+
socket.unshift(preReadData);
|
|
8517
|
+
}
|
|
8518
|
+
this.handleRtspConnection(socket);
|
|
8519
|
+
}
|
|
8431
8520
|
/**
|
|
8432
8521
|
* Handle RTSP connection from a client.
|
|
8433
8522
|
*/
|
|
@@ -8592,6 +8681,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8592
8681
|
Public: "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS"
|
|
8593
8682
|
});
|
|
8594
8683
|
} else if (method === "DESCRIBE") {
|
|
8684
|
+
if (!this.api.isClosed && !this.api.isReady && !this.nativeStreamActive) {
|
|
8685
|
+
void this.api.ensureConnected().catch(() => {
|
|
8686
|
+
});
|
|
8687
|
+
}
|
|
8595
8688
|
if (!this.flow.getFmtp().hasParamSets && this.connectedClients.size === 0) {
|
|
8596
8689
|
try {
|
|
8597
8690
|
if (!this.nativeStreamActive) {
|
|
@@ -8629,6 +8722,27 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8629
8722
|
}
|
|
8630
8723
|
}
|
|
8631
8724
|
}
|
|
8725
|
+
if (!this.hasAudio && this.firstAudioPromise) {
|
|
8726
|
+
const audioPrimingMs = this.api.client.getTransport() === "udp" ? 3e3 : 2e3;
|
|
8727
|
+
const audioPrimingStart = Date.now();
|
|
8728
|
+
try {
|
|
8729
|
+
await Promise.race([
|
|
8730
|
+
this.firstAudioPromise,
|
|
8731
|
+
new Promise((resolve) => setTimeout(resolve, audioPrimingMs))
|
|
8732
|
+
]);
|
|
8733
|
+
} catch {
|
|
8734
|
+
}
|
|
8735
|
+
const audioPrimingElapsed = Date.now() - audioPrimingStart;
|
|
8736
|
+
if (this.hasAudio) {
|
|
8737
|
+
this.logger.info(
|
|
8738
|
+
`[rebroadcast] DESCRIBE audio priming: AAC detected after ${audioPrimingElapsed}ms client=${clientId} path=${this.path}`
|
|
8739
|
+
);
|
|
8740
|
+
} else {
|
|
8741
|
+
this.logger.info(
|
|
8742
|
+
`[rebroadcast] DESCRIBE audio priming: no audio after ${audioPrimingElapsed}ms \u2014 SDP will be video-only client=${clientId} path=${this.path}`
|
|
8743
|
+
);
|
|
8744
|
+
}
|
|
8745
|
+
}
|
|
8632
8746
|
{
|
|
8633
8747
|
const { fmtp, hasParamSets } = this.flow.getFmtp();
|
|
8634
8748
|
const fmtpPreview = fmtp.length > 160 ? `${fmtp.slice(0, 160)}...` : fmtp;
|
|
@@ -8637,12 +8751,13 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8637
8751
|
);
|
|
8638
8752
|
}
|
|
8639
8753
|
const sdp = this.generateSdp();
|
|
8754
|
+
const contentHost = (socket.localAddress && socket.localAddress !== "0.0.0.0" && socket.localAddress !== "::" ? socket.localAddress.replace(/^::ffff:/, "") : null) ?? this.listenHost;
|
|
8640
8755
|
sendResponse(
|
|
8641
8756
|
200,
|
|
8642
8757
|
"OK",
|
|
8643
8758
|
{
|
|
8644
8759
|
"Content-Type": "application/sdp",
|
|
8645
|
-
"Content-Base": `rtsp://${
|
|
8760
|
+
"Content-Base": `rtsp://${contentHost}:${this.listenPort}${this.path}/`
|
|
8646
8761
|
},
|
|
8647
8762
|
sdp
|
|
8648
8763
|
);
|
|
@@ -8665,7 +8780,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8665
8780
|
this.emit("client", clientId);
|
|
8666
8781
|
this.clearNoClientAutoStopTimer();
|
|
8667
8782
|
if (this.connectedClients.size === 1 && !this.nativeStreamActive) {
|
|
8668
|
-
|
|
8783
|
+
void this.startNativeStream();
|
|
8669
8784
|
}
|
|
8670
8785
|
const transportMatch = requestText.match(/Transport:\s*([^\r\n]+)/i);
|
|
8671
8786
|
const transport = (transportMatch?.[1] ?? "").trim();
|
|
@@ -8799,12 +8914,23 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
8799
8914
|
}
|
|
8800
8915
|
}
|
|
8801
8916
|
};
|
|
8917
|
+
const runProcessBuffer = () => {
|
|
8918
|
+
processBuffer().catch((err) => {
|
|
8919
|
+
this.logger.debug(
|
|
8920
|
+
`[BaichuanRtspServer] processBuffer failed for ${clientId}: ${err?.message ?? err}`
|
|
8921
|
+
);
|
|
8922
|
+
try {
|
|
8923
|
+
socket.destroy();
|
|
8924
|
+
} catch {
|
|
8925
|
+
}
|
|
8926
|
+
});
|
|
8927
|
+
};
|
|
8802
8928
|
socket.on("data", (data) => {
|
|
8803
8929
|
buffer = Buffer.concat([buffer, data]);
|
|
8804
|
-
|
|
8930
|
+
runProcessBuffer();
|
|
8805
8931
|
});
|
|
8806
8932
|
if (buffer.includes("\r\n\r\n")) {
|
|
8807
|
-
|
|
8933
|
+
runProcessBuffer();
|
|
8808
8934
|
}
|
|
8809
8935
|
}
|
|
8810
8936
|
/**
|
|
@@ -9672,6 +9798,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9672
9798
|
if (this.nativeStreamActive) {
|
|
9673
9799
|
return;
|
|
9674
9800
|
}
|
|
9801
|
+
if (!this.api.isReady) {
|
|
9802
|
+
if (this.api.isClosed) {
|
|
9803
|
+
this.logger.warn?.(
|
|
9804
|
+
`[rebroadcast] API has been explicitly closed \u2014 stream cannot start profile=${this.profile}`
|
|
9805
|
+
);
|
|
9806
|
+
return;
|
|
9807
|
+
}
|
|
9808
|
+
try {
|
|
9809
|
+
this.logger.info?.(
|
|
9810
|
+
`[rebroadcast] API not ready (idle disconnect?), calling ensureConnected profile=${this.profile}`
|
|
9811
|
+
);
|
|
9812
|
+
await this.api.ensureConnected();
|
|
9813
|
+
} catch (e) {
|
|
9814
|
+
this.logger.warn?.(
|
|
9815
|
+
`[rebroadcast] ensureConnected failed, aborting stream start: ${e}`
|
|
9816
|
+
);
|
|
9817
|
+
return;
|
|
9818
|
+
}
|
|
9819
|
+
}
|
|
9675
9820
|
this.nativeStreamActive = true;
|
|
9676
9821
|
this.firstFrameReceived = false;
|
|
9677
9822
|
this.firstAudioDetected = false;
|
|
@@ -9706,13 +9851,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9706
9851
|
await this.flow.startKeepAlive(this.api);
|
|
9707
9852
|
this.nativeFanout = new NativeStreamFanout({
|
|
9708
9853
|
maxQueueItems: 200,
|
|
9709
|
-
createSource: () => createNativeStream(this.api, this.channel, this.profile, {
|
|
9854
|
+
createSource: (signal) => createNativeStream(this.api, this.channel, this.profile, {
|
|
9710
9855
|
variant: this.variant,
|
|
9711
|
-
...dedicatedClient ? { client: dedicatedClient } : {}
|
|
9856
|
+
...dedicatedClient ? { client: dedicatedClient } : {},
|
|
9857
|
+
signal
|
|
9712
9858
|
}),
|
|
9713
9859
|
onFrame: (frame) => {
|
|
9714
9860
|
if (frame.audio) {
|
|
9715
|
-
if (!this.hasAudio &&
|
|
9861
|
+
if (!this.hasAudio && _BaichuanRtspServer.isAdtsAacFrame(frame.data)) {
|
|
9716
9862
|
const info = _BaichuanRtspServer.parseAdtsSamplingInfo(frame.data);
|
|
9717
9863
|
if (info) {
|
|
9718
9864
|
this.hasAudio = true;
|
|
@@ -9761,6 +9907,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9761
9907
|
onEnd: () => {
|
|
9762
9908
|
if (!this.nativeStreamActive) return;
|
|
9763
9909
|
this.nativeStreamActive = false;
|
|
9910
|
+
this.clearNoFrameDeadlineTimer();
|
|
9911
|
+
const hadFrames = this.firstFrameReceived;
|
|
9764
9912
|
this.firstFrameReceived = false;
|
|
9765
9913
|
this.firstFramePromise = null;
|
|
9766
9914
|
this.firstFrameResolve = null;
|
|
@@ -9785,7 +9933,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9785
9933
|
} catch {
|
|
9786
9934
|
}
|
|
9787
9935
|
}
|
|
9788
|
-
if (this.connectedClients.size > 0) {
|
|
9936
|
+
if (this.connectedClients.size > 0 && hadFrames) {
|
|
9789
9937
|
this.logger.info(
|
|
9790
9938
|
`[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
|
|
9791
9939
|
);
|
|
@@ -9797,6 +9945,19 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9797
9945
|
}
|
|
9798
9946
|
});
|
|
9799
9947
|
this.nativeFanout.start();
|
|
9948
|
+
this.clearNoFrameDeadlineTimer();
|
|
9949
|
+
if (this.nativeStreamNoFrameDeadlineMs > 0) {
|
|
9950
|
+
this.noFrameDeadlineTimer = setTimeout(() => {
|
|
9951
|
+
this.noFrameDeadlineTimer = void 0;
|
|
9952
|
+
if (!this.firstFrameReceived && this.nativeStreamActive) {
|
|
9953
|
+
this.logger.info(
|
|
9954
|
+
`[rebroadcast] no frames within ${this.nativeStreamNoFrameDeadlineMs}ms \u2014 camera sleeping, stopping stream profile=${this.profile} channel=${this.channel}`
|
|
9955
|
+
);
|
|
9956
|
+
void this.stopNativeStream();
|
|
9957
|
+
}
|
|
9958
|
+
}, this.nativeStreamNoFrameDeadlineMs);
|
|
9959
|
+
this.noFrameDeadlineTimer?.unref?.();
|
|
9960
|
+
}
|
|
9800
9961
|
this.clearNoClientAutoStopTimer();
|
|
9801
9962
|
if (this.nativeStreamPrimeIdleStopMs > 0) {
|
|
9802
9963
|
this.noClientAutoStopTimer = setTimeout(() => {
|
|
@@ -9813,6 +9974,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9813
9974
|
markFirstFrameReceived() {
|
|
9814
9975
|
if (!this.firstFrameReceived && this.firstFrameResolve) {
|
|
9815
9976
|
this.firstFrameReceived = true;
|
|
9977
|
+
this.clearNoFrameDeadlineTimer();
|
|
9816
9978
|
this.rtspDebugLog(
|
|
9817
9979
|
`First frame received from camera for profile ${this.profile}`
|
|
9818
9980
|
);
|
|
@@ -9839,6 +10001,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events2.E
|
|
|
9839
10001
|
);
|
|
9840
10002
|
this.flow.stopKeepAlive();
|
|
9841
10003
|
this.clearNoClientAutoStopTimer();
|
|
10004
|
+
this.clearNoFrameDeadlineTimer();
|
|
9842
10005
|
this.nativeStreamActive = false;
|
|
9843
10006
|
this.firstFrameReceived = false;
|
|
9844
10007
|
this.firstFramePromise = null;
|
|
@@ -10055,149 +10218,17 @@ init_BcMediaAnnexBDecoder();
|
|
|
10055
10218
|
// src/baichuan/stream/MpegTsMuxer.ts
|
|
10056
10219
|
var TS_PACKET_SIZE = 188;
|
|
10057
10220
|
var TS_SYNC_BYTE = 71;
|
|
10058
|
-
var
|
|
10059
|
-
var
|
|
10060
|
-
var
|
|
10221
|
+
var TS_PAYLOAD_SIZE = TS_PACKET_SIZE - 4;
|
|
10222
|
+
var PID_PAT = 0;
|
|
10223
|
+
var PID_PMT = 4096;
|
|
10224
|
+
var PID_VIDEO = 256;
|
|
10225
|
+
var PID_AUDIO = 257;
|
|
10061
10226
|
var STREAM_TYPE_H264 = 27;
|
|
10062
10227
|
var STREAM_TYPE_H265 = 36;
|
|
10063
|
-
var
|
|
10064
|
-
var
|
|
10065
|
-
var
|
|
10066
|
-
|
|
10067
|
-
const packet = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
10068
|
-
packet[0] = TS_SYNC_BYTE;
|
|
10069
|
-
packet[1] = 64 | PAT_PID >> 8 & 31;
|
|
10070
|
-
packet[2] = PAT_PID & 255;
|
|
10071
|
-
packet[3] = 16 | patCc & 15;
|
|
10072
|
-
patCc = patCc + 1 & 15;
|
|
10073
|
-
packet[4] = 0;
|
|
10074
|
-
let idx = 5;
|
|
10075
|
-
packet[idx++] = 0;
|
|
10076
|
-
packet[idx++] = 176;
|
|
10077
|
-
packet[idx++] = 13;
|
|
10078
|
-
packet[idx++] = 0;
|
|
10079
|
-
packet[idx++] = 1;
|
|
10080
|
-
packet[idx++] = 193;
|
|
10081
|
-
packet[idx++] = 0;
|
|
10082
|
-
packet[idx++] = 0;
|
|
10083
|
-
packet[idx++] = 0;
|
|
10084
|
-
packet[idx++] = 1;
|
|
10085
|
-
packet[idx++] = 224 | PMT_PID >> 8 & 31;
|
|
10086
|
-
packet[idx++] = PMT_PID & 255;
|
|
10087
|
-
const crc = crc32Mpeg(packet.subarray(5, idx));
|
|
10088
|
-
packet.writeUInt32BE(crc, idx);
|
|
10089
|
-
return packet;
|
|
10090
|
-
}
|
|
10091
|
-
function createPmt(streamType) {
|
|
10092
|
-
const packet = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
10093
|
-
packet[0] = TS_SYNC_BYTE;
|
|
10094
|
-
packet[1] = 64 | PMT_PID >> 8 & 31;
|
|
10095
|
-
packet[2] = PMT_PID & 255;
|
|
10096
|
-
packet[3] = 16 | pmtCc & 15;
|
|
10097
|
-
pmtCc = pmtCc + 1 & 15;
|
|
10098
|
-
packet[4] = 0;
|
|
10099
|
-
let idx = 5;
|
|
10100
|
-
packet[idx++] = 2;
|
|
10101
|
-
packet[idx++] = 176;
|
|
10102
|
-
packet[idx++] = 18;
|
|
10103
|
-
packet[idx++] = 0;
|
|
10104
|
-
packet[idx++] = 1;
|
|
10105
|
-
packet[idx++] = 193;
|
|
10106
|
-
packet[idx++] = 0;
|
|
10107
|
-
packet[idx++] = 0;
|
|
10108
|
-
packet[idx++] = 224 | VIDEO_PID >> 8 & 31;
|
|
10109
|
-
packet[idx++] = VIDEO_PID & 255;
|
|
10110
|
-
packet[idx++] = 240;
|
|
10111
|
-
packet[idx++] = 0;
|
|
10112
|
-
packet[idx++] = streamType;
|
|
10113
|
-
packet[idx++] = 224 | VIDEO_PID >> 8 & 31;
|
|
10114
|
-
packet[idx++] = VIDEO_PID & 255;
|
|
10115
|
-
packet[idx++] = 240;
|
|
10116
|
-
packet[idx++] = 0;
|
|
10117
|
-
const crc = crc32Mpeg(packet.subarray(5, idx));
|
|
10118
|
-
packet.writeUInt32BE(crc, idx);
|
|
10119
|
-
return packet;
|
|
10120
|
-
}
|
|
10121
|
-
function createVideoPes(data, pts, isKeyframe) {
|
|
10122
|
-
const packets = [];
|
|
10123
|
-
const pts90k = Math.floor(pts * 9e4 / 1e6);
|
|
10124
|
-
const pesHeaderLen = 14;
|
|
10125
|
-
const pesHeader = Buffer.alloc(pesHeaderLen);
|
|
10126
|
-
let idx = 0;
|
|
10127
|
-
pesHeader[idx++] = 0;
|
|
10128
|
-
pesHeader[idx++] = 0;
|
|
10129
|
-
pesHeader[idx++] = 1;
|
|
10130
|
-
pesHeader[idx++] = 224;
|
|
10131
|
-
pesHeader[idx++] = 0;
|
|
10132
|
-
pesHeader[idx++] = 0;
|
|
10133
|
-
pesHeader[idx++] = 128;
|
|
10134
|
-
pesHeader[idx++] = 128;
|
|
10135
|
-
pesHeader[idx++] = 5;
|
|
10136
|
-
pesHeader[idx++] = 33 | pts90k >> 29 & 14;
|
|
10137
|
-
pesHeader[idx++] = pts90k >> 22 & 255;
|
|
10138
|
-
pesHeader[idx++] = 1 | pts90k >> 14 & 254;
|
|
10139
|
-
pesHeader[idx++] = pts90k >> 7 & 255;
|
|
10140
|
-
pesHeader[idx++] = 1 | pts90k << 1 & 254;
|
|
10141
|
-
const pesData = Buffer.concat([pesHeader, data]);
|
|
10142
|
-
let pesOffset = 0;
|
|
10143
|
-
let isFirst = true;
|
|
10144
|
-
while (pesOffset < pesData.length) {
|
|
10145
|
-
const packet = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
10146
|
-
let pktIdx = 0;
|
|
10147
|
-
packet[pktIdx++] = TS_SYNC_BYTE;
|
|
10148
|
-
packet[pktIdx++] = (isFirst ? 64 : 0) | VIDEO_PID >> 8 & 31;
|
|
10149
|
-
packet[pktIdx++] = VIDEO_PID & 255;
|
|
10150
|
-
const remaining = pesData.length - pesOffset;
|
|
10151
|
-
const maxPayload = TS_PACKET_SIZE - 4;
|
|
10152
|
-
if (remaining >= maxPayload) {
|
|
10153
|
-
packet[pktIdx++] = 16 | videoCc & 15;
|
|
10154
|
-
videoCc = videoCc + 1 & 15;
|
|
10155
|
-
pesData.copy(packet, pktIdx, pesOffset, pesOffset + maxPayload);
|
|
10156
|
-
pesOffset += maxPayload;
|
|
10157
|
-
} else {
|
|
10158
|
-
const adaptLen = maxPayload - remaining - 1;
|
|
10159
|
-
if (adaptLen < 0) {
|
|
10160
|
-
packet[pktIdx++] = 48 | videoCc & 15;
|
|
10161
|
-
videoCc = videoCc + 1 & 15;
|
|
10162
|
-
packet[pktIdx++] = TS_PACKET_SIZE - 4 - 1 - remaining;
|
|
10163
|
-
if (isFirst && isKeyframe) {
|
|
10164
|
-
packet[pktIdx++] = 64;
|
|
10165
|
-
for (let i = pktIdx; i < TS_PACKET_SIZE - remaining; i++) {
|
|
10166
|
-
packet[i] = 255;
|
|
10167
|
-
}
|
|
10168
|
-
} else {
|
|
10169
|
-
packet[pktIdx++] = 0;
|
|
10170
|
-
for (let i = pktIdx; i < TS_PACKET_SIZE - remaining; i++) {
|
|
10171
|
-
packet[i] = 255;
|
|
10172
|
-
}
|
|
10173
|
-
}
|
|
10174
|
-
pesData.copy(packet, TS_PACKET_SIZE - remaining, pesOffset);
|
|
10175
|
-
pesOffset += remaining;
|
|
10176
|
-
} else {
|
|
10177
|
-
packet[pktIdx++] = 48 | videoCc & 15;
|
|
10178
|
-
videoCc = videoCc + 1 & 15;
|
|
10179
|
-
if (adaptLen === 0) {
|
|
10180
|
-
packet[pktIdx++] = 0;
|
|
10181
|
-
} else {
|
|
10182
|
-
packet[pktIdx++] = adaptLen;
|
|
10183
|
-
if (isFirst && isKeyframe) {
|
|
10184
|
-
packet[pktIdx++] = 64;
|
|
10185
|
-
} else {
|
|
10186
|
-
packet[pktIdx++] = 0;
|
|
10187
|
-
}
|
|
10188
|
-
for (let i = 0; i < adaptLen - 1; i++) {
|
|
10189
|
-
packet[pktIdx++] = 255;
|
|
10190
|
-
}
|
|
10191
|
-
}
|
|
10192
|
-
pesData.copy(packet, pktIdx, pesOffset, pesOffset + remaining);
|
|
10193
|
-
pesOffset += remaining;
|
|
10194
|
-
}
|
|
10195
|
-
}
|
|
10196
|
-
packets.push(packet);
|
|
10197
|
-
isFirst = false;
|
|
10198
|
-
}
|
|
10199
|
-
return packets;
|
|
10200
|
-
}
|
|
10228
|
+
var STREAM_TYPE_AAC = 15;
|
|
10229
|
+
var PES_STREAM_ID_VIDEO = 224;
|
|
10230
|
+
var PES_STREAM_ID_AUDIO = 192;
|
|
10231
|
+
var PAT_PMT_INTERVAL = 40;
|
|
10201
10232
|
function crc32Mpeg(data) {
|
|
10202
10233
|
let crc = 4294967295;
|
|
10203
10234
|
for (let i = 0; i < data.length; i++) {
|
|
@@ -10212,45 +10243,218 @@ function crc32Mpeg(data) {
|
|
|
10212
10243
|
}
|
|
10213
10244
|
return crc >>> 0;
|
|
10214
10245
|
}
|
|
10246
|
+
function usToPts(us) {
|
|
10247
|
+
return Math.floor(us * 90 / 1e3) & 8589934591;
|
|
10248
|
+
}
|
|
10249
|
+
function encodePts(buf, offset, pts, prefix) {
|
|
10250
|
+
buf[offset + 0] = prefix << 4 | (pts >>> 30 & 7) << 1 | 1;
|
|
10251
|
+
buf[offset + 1] = pts >>> 22 & 255;
|
|
10252
|
+
buf[offset + 2] = (pts >>> 15 & 127) << 1 | 1;
|
|
10253
|
+
buf[offset + 3] = pts >>> 7 & 255;
|
|
10254
|
+
buf[offset + 4] = (pts & 127) << 1 | 1;
|
|
10255
|
+
}
|
|
10256
|
+
function writeTsHeader(buf, pid, pusi, cc, hasAdapt, hasPayload) {
|
|
10257
|
+
buf[0] = TS_SYNC_BYTE;
|
|
10258
|
+
buf[1] = (pusi ? 64 : 0) | pid >> 8 & 31;
|
|
10259
|
+
buf[2] = pid & 255;
|
|
10260
|
+
buf[3] = (hasAdapt ? 32 : 0) | (hasPayload ? 16 : 0) | cc & 15;
|
|
10261
|
+
}
|
|
10262
|
+
function pesToTsPackets(pesData, pid, ccRef, isKeyframe) {
|
|
10263
|
+
const totalPackets = Math.ceil(pesData.length / TS_PAYLOAD_SIZE);
|
|
10264
|
+
const out = Buffer.allocUnsafe(totalPackets * TS_PACKET_SIZE);
|
|
10265
|
+
let pesOffset = 0;
|
|
10266
|
+
let outOffset = 0;
|
|
10267
|
+
let isFirst = true;
|
|
10268
|
+
while (pesOffset < pesData.length) {
|
|
10269
|
+
const remaining = pesData.length - pesOffset;
|
|
10270
|
+
const packet = out.subarray(outOffset, outOffset + TS_PACKET_SIZE);
|
|
10271
|
+
outOffset += TS_PACKET_SIZE;
|
|
10272
|
+
if (remaining >= TS_PAYLOAD_SIZE) {
|
|
10273
|
+
writeTsHeader(packet, pid, isFirst, ccRef.cc, false, true);
|
|
10274
|
+
ccRef.cc = ccRef.cc + 1 & 15;
|
|
10275
|
+
pesData.copy(packet, 4, pesOffset, pesOffset + TS_PAYLOAD_SIZE);
|
|
10276
|
+
pesOffset += TS_PAYLOAD_SIZE;
|
|
10277
|
+
} else {
|
|
10278
|
+
const paddingNeeded = TS_PAYLOAD_SIZE - remaining;
|
|
10279
|
+
if (paddingNeeded === 1) {
|
|
10280
|
+
writeTsHeader(packet, pid, isFirst, ccRef.cc, true, true);
|
|
10281
|
+
ccRef.cc = ccRef.cc + 1 & 15;
|
|
10282
|
+
packet[4] = 0;
|
|
10283
|
+
pesData.copy(packet, 5, pesOffset, pesOffset + remaining);
|
|
10284
|
+
} else {
|
|
10285
|
+
writeTsHeader(packet, pid, isFirst, ccRef.cc, true, true);
|
|
10286
|
+
ccRef.cc = ccRef.cc + 1 & 15;
|
|
10287
|
+
const adaptLen = paddingNeeded - 1;
|
|
10288
|
+
packet[4] = adaptLen;
|
|
10289
|
+
packet[5] = isFirst && isKeyframe ? 64 : 0;
|
|
10290
|
+
packet.fill(255, 6, 4 + paddingNeeded);
|
|
10291
|
+
pesData.copy(packet, 4 + paddingNeeded, pesOffset, pesOffset + remaining);
|
|
10292
|
+
}
|
|
10293
|
+
pesOffset += remaining;
|
|
10294
|
+
}
|
|
10295
|
+
isFirst = false;
|
|
10296
|
+
}
|
|
10297
|
+
return out;
|
|
10298
|
+
}
|
|
10299
|
+
function buildPat(cc) {
|
|
10300
|
+
const pkt = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
10301
|
+
pkt[0] = TS_SYNC_BYTE;
|
|
10302
|
+
pkt[1] = 64 | PID_PAT >> 8 & 31;
|
|
10303
|
+
pkt[2] = PID_PAT & 255;
|
|
10304
|
+
pkt[3] = 16 | cc & 15;
|
|
10305
|
+
pkt[4] = 0;
|
|
10306
|
+
const sectionStart = 5;
|
|
10307
|
+
let i = sectionStart;
|
|
10308
|
+
pkt[i++] = 0;
|
|
10309
|
+
pkt[i++] = 176;
|
|
10310
|
+
pkt[i++] = 13;
|
|
10311
|
+
pkt[i++] = 0;
|
|
10312
|
+
pkt[i++] = 1;
|
|
10313
|
+
pkt[i++] = 193;
|
|
10314
|
+
pkt[i++] = 0;
|
|
10315
|
+
pkt[i++] = 0;
|
|
10316
|
+
pkt[i++] = 0;
|
|
10317
|
+
pkt[i++] = 1;
|
|
10318
|
+
pkt[i++] = 224 | PID_PMT >> 8 & 31;
|
|
10319
|
+
pkt[i++] = PID_PMT & 255;
|
|
10320
|
+
const crc = crc32Mpeg(pkt.subarray(sectionStart, i));
|
|
10321
|
+
pkt.writeUInt32BE(crc, i);
|
|
10322
|
+
return pkt;
|
|
10323
|
+
}
|
|
10324
|
+
function buildPmt(videoStreamType, includeAudio, cc) {
|
|
10325
|
+
const pkt = Buffer.alloc(TS_PACKET_SIZE, 255);
|
|
10326
|
+
pkt[0] = TS_SYNC_BYTE;
|
|
10327
|
+
pkt[1] = 64 | PID_PMT >> 8 & 31;
|
|
10328
|
+
pkt[2] = PID_PMT & 255;
|
|
10329
|
+
pkt[3] = 16 | cc & 15;
|
|
10330
|
+
pkt[4] = 0;
|
|
10331
|
+
const sectionStart = 5;
|
|
10332
|
+
let i = sectionStart;
|
|
10333
|
+
pkt[i++] = 2;
|
|
10334
|
+
pkt[i++] = 176;
|
|
10335
|
+
const sectionLenPos = i;
|
|
10336
|
+
i += 1;
|
|
10337
|
+
pkt[i++] = 0;
|
|
10338
|
+
pkt[i++] = 1;
|
|
10339
|
+
pkt[i++] = 193;
|
|
10340
|
+
pkt[i++] = 0;
|
|
10341
|
+
pkt[i++] = 0;
|
|
10342
|
+
pkt[i++] = 224 | PID_VIDEO >> 8 & 31;
|
|
10343
|
+
pkt[i++] = PID_VIDEO & 255;
|
|
10344
|
+
pkt[i++] = 240;
|
|
10345
|
+
pkt[i++] = 0;
|
|
10346
|
+
pkt[i++] = videoStreamType;
|
|
10347
|
+
pkt[i++] = 224 | PID_VIDEO >> 8 & 31;
|
|
10348
|
+
pkt[i++] = PID_VIDEO & 255;
|
|
10349
|
+
pkt[i++] = 240;
|
|
10350
|
+
pkt[i++] = 0;
|
|
10351
|
+
if (includeAudio) {
|
|
10352
|
+
pkt[i++] = STREAM_TYPE_AAC;
|
|
10353
|
+
pkt[i++] = 224 | PID_AUDIO >> 8 & 31;
|
|
10354
|
+
pkt[i++] = PID_AUDIO & 255;
|
|
10355
|
+
pkt[i++] = 240;
|
|
10356
|
+
pkt[i++] = 0;
|
|
10357
|
+
}
|
|
10358
|
+
const sectionLen = i - sectionStart - 3 + 4;
|
|
10359
|
+
pkt[sectionLenPos] = sectionLen;
|
|
10360
|
+
const crc = crc32Mpeg(pkt.subarray(sectionStart, i));
|
|
10361
|
+
pkt.writeUInt32BE(crc, i);
|
|
10362
|
+
return pkt;
|
|
10363
|
+
}
|
|
10364
|
+
function buildVideoPes(annexBData, ptsUs, isKeyframe) {
|
|
10365
|
+
const pts = usToPts(ptsUs);
|
|
10366
|
+
const pesHeader = Buffer.allocUnsafe(14);
|
|
10367
|
+
pesHeader[0] = 0;
|
|
10368
|
+
pesHeader[1] = 0;
|
|
10369
|
+
pesHeader[2] = 1;
|
|
10370
|
+
pesHeader[3] = PES_STREAM_ID_VIDEO;
|
|
10371
|
+
pesHeader[4] = 0;
|
|
10372
|
+
pesHeader[5] = 0;
|
|
10373
|
+
pesHeader[6] = 128 | (isKeyframe ? 4 : 0);
|
|
10374
|
+
pesHeader[7] = 128;
|
|
10375
|
+
pesHeader[8] = 5;
|
|
10376
|
+
encodePts(pesHeader, 9, pts, 2);
|
|
10377
|
+
return Buffer.concat([pesHeader, annexBData]);
|
|
10378
|
+
}
|
|
10379
|
+
function buildAudioPes(adtsData, ptsUs) {
|
|
10380
|
+
const pts = usToPts(ptsUs);
|
|
10381
|
+
const pesPayloadLen = 8 + adtsData.length;
|
|
10382
|
+
const pesHeader = Buffer.allocUnsafe(14);
|
|
10383
|
+
pesHeader[0] = 0;
|
|
10384
|
+
pesHeader[1] = 0;
|
|
10385
|
+
pesHeader[2] = 1;
|
|
10386
|
+
pesHeader[3] = PES_STREAM_ID_AUDIO;
|
|
10387
|
+
pesHeader[4] = pesPayloadLen >> 8 & 255;
|
|
10388
|
+
pesHeader[5] = pesPayloadLen & 255;
|
|
10389
|
+
pesHeader[6] = 128;
|
|
10390
|
+
pesHeader[7] = 128;
|
|
10391
|
+
pesHeader[8] = 5;
|
|
10392
|
+
encodePts(pesHeader, 9, pts, 2);
|
|
10393
|
+
return Buffer.concat([pesHeader, adtsData]);
|
|
10394
|
+
}
|
|
10215
10395
|
var MpegTsMuxer = class {
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10396
|
+
videoStreamType;
|
|
10397
|
+
includeAudio;
|
|
10398
|
+
// Per-instance continuity counters (4-bit, wrap at 16)
|
|
10399
|
+
patCc = 0;
|
|
10400
|
+
pmtCc = 0;
|
|
10401
|
+
videoCc = 0;
|
|
10402
|
+
audioCc = 0;
|
|
10403
|
+
framesSinceTableSend = 0;
|
|
10404
|
+
tablesSent = false;
|
|
10222
10405
|
constructor(options) {
|
|
10223
|
-
this.
|
|
10406
|
+
this.videoStreamType = options.videoType === "H265" ? STREAM_TYPE_H265 : STREAM_TYPE_H264;
|
|
10407
|
+
this.includeAudio = options.includeAudio ?? true;
|
|
10224
10408
|
}
|
|
10225
10409
|
/**
|
|
10226
|
-
*
|
|
10410
|
+
* Mux a video frame (Annex-B H.264 or H.265) into MPEG-TS packets.
|
|
10411
|
+
* PAT and PMT are emitted before keyframes and periodically.
|
|
10412
|
+
*
|
|
10413
|
+
* @param annexBData - Annex-B video data (with start codes)
|
|
10414
|
+
* @param ptsUs - Presentation timestamp in microseconds
|
|
10415
|
+
* @param isKeyframe - Whether this is an IDR / IRAP frame
|
|
10227
10416
|
*/
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10417
|
+
muxVideo(annexBData, ptsUs, isKeyframe) {
|
|
10418
|
+
const chunks = [];
|
|
10419
|
+
const needTables = !this.tablesSent || isKeyframe || this.framesSinceTableSend >= PAT_PMT_INTERVAL;
|
|
10420
|
+
if (needTables) {
|
|
10421
|
+
chunks.push(buildPat(this.patCc));
|
|
10422
|
+
this.patCc = this.patCc + 1 & 15;
|
|
10423
|
+
chunks.push(buildPmt(this.videoStreamType, this.includeAudio, this.pmtCc));
|
|
10424
|
+
this.pmtCc = this.pmtCc + 1 & 15;
|
|
10425
|
+
this.tablesSent = true;
|
|
10426
|
+
this.framesSinceTableSend = 0;
|
|
10427
|
+
}
|
|
10428
|
+
this.framesSinceTableSend++;
|
|
10429
|
+
const pes = buildVideoPes(annexBData, ptsUs, isKeyframe);
|
|
10430
|
+
const ccRef = { cc: this.videoCc };
|
|
10431
|
+
chunks.push(pesToTsPackets(pes, PID_VIDEO, ccRef, isKeyframe));
|
|
10432
|
+
this.videoCc = ccRef.cc;
|
|
10433
|
+
return Buffer.concat(chunks);
|
|
10434
|
+
}
|
|
10435
|
+
/**
|
|
10436
|
+
* Mux an audio frame (ADTS AAC) into MPEG-TS packets.
|
|
10437
|
+
* Returns an empty Buffer when includeAudio is false.
|
|
10235
10438
|
*
|
|
10236
|
-
* @param
|
|
10237
|
-
* @param
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
const
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
this.
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10439
|
+
* @param adtsData - Raw ADTS AAC frame (starting with 0xFF 0xF1/0xF9 syncword)
|
|
10440
|
+
* @param ptsUs - Presentation timestamp in microseconds
|
|
10441
|
+
*/
|
|
10442
|
+
muxAudio(adtsData, ptsUs) {
|
|
10443
|
+
if (!this.includeAudio || adtsData.length === 0) return Buffer.alloc(0);
|
|
10444
|
+
const pes = buildAudioPes(adtsData, ptsUs);
|
|
10445
|
+
const ccRef = { cc: this.audioCc };
|
|
10446
|
+
const result = pesToTsPackets(pes, PID_AUDIO, ccRef, false);
|
|
10447
|
+
this.audioCc = ccRef.cc;
|
|
10448
|
+
return result;
|
|
10449
|
+
}
|
|
10450
|
+
/** Reset all continuity counters and table state (e.g. after stream restart). */
|
|
10451
|
+
reset() {
|
|
10452
|
+
this.patCc = 0;
|
|
10453
|
+
this.pmtCc = 0;
|
|
10454
|
+
this.videoCc = 0;
|
|
10455
|
+
this.audioCc = 0;
|
|
10456
|
+
this.framesSinceTableSend = 0;
|
|
10457
|
+
this.tablesSent = false;
|
|
10254
10458
|
}
|
|
10255
10459
|
};
|
|
10256
10460
|
|
|
@@ -15526,6 +15730,53 @@ var getAiStateViaGetAiAlarm = async (params) => {
|
|
|
15526
15730
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr ?? "getAiState failed"));
|
|
15527
15731
|
};
|
|
15528
15732
|
|
|
15733
|
+
// src/reolink/baichuan/utils/sleepInference.ts
|
|
15734
|
+
function decideSleepInferenceTransition(input) {
|
|
15735
|
+
const { inferred, committed, pending, hysteresisPolls } = input;
|
|
15736
|
+
if (committed === void 0) {
|
|
15737
|
+
return {
|
|
15738
|
+
emit: inferred === "sleeping" ? "sleeping" : null,
|
|
15739
|
+
nextCommitted: inferred,
|
|
15740
|
+
nextPending: void 0
|
|
15741
|
+
};
|
|
15742
|
+
}
|
|
15743
|
+
if (inferred === committed) {
|
|
15744
|
+
return {
|
|
15745
|
+
emit: null,
|
|
15746
|
+
nextCommitted: committed,
|
|
15747
|
+
nextPending: void 0
|
|
15748
|
+
};
|
|
15749
|
+
}
|
|
15750
|
+
const effectivePolls = Math.max(1, hysteresisPolls);
|
|
15751
|
+
if (!pending || pending.state !== inferred) {
|
|
15752
|
+
if (effectivePolls <= 1) {
|
|
15753
|
+
return {
|
|
15754
|
+
emit: inferred === "sleeping" ? "sleeping" : "awake",
|
|
15755
|
+
nextCommitted: inferred,
|
|
15756
|
+
nextPending: void 0
|
|
15757
|
+
};
|
|
15758
|
+
}
|
|
15759
|
+
return {
|
|
15760
|
+
emit: null,
|
|
15761
|
+
nextCommitted: committed,
|
|
15762
|
+
nextPending: { state: inferred, count: 1 }
|
|
15763
|
+
};
|
|
15764
|
+
}
|
|
15765
|
+
const nextCount = pending.count + 1;
|
|
15766
|
+
if (nextCount < effectivePolls) {
|
|
15767
|
+
return {
|
|
15768
|
+
emit: null,
|
|
15769
|
+
nextCommitted: committed,
|
|
15770
|
+
nextPending: { state: inferred, count: nextCount }
|
|
15771
|
+
};
|
|
15772
|
+
}
|
|
15773
|
+
return {
|
|
15774
|
+
emit: inferred === "sleeping" ? "sleeping" : "awake",
|
|
15775
|
+
nextCommitted: inferred,
|
|
15776
|
+
nextPending: void 0
|
|
15777
|
+
};
|
|
15778
|
+
}
|
|
15779
|
+
|
|
15529
15780
|
// src/reolink/baichuan/utils/channelInfoPush.ts
|
|
15530
15781
|
init_xml();
|
|
15531
15782
|
var parseOptionalInt = (value) => {
|
|
@@ -17802,7 +18053,17 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17802
18053
|
statePollingInterval;
|
|
17803
18054
|
udpSleepInferenceInterval;
|
|
17804
18055
|
udpLastInferredSleepStateByChannel = /* @__PURE__ */ new Map();
|
|
18056
|
+
/**
|
|
18057
|
+
* Per-channel pending sleep-state candidate for hysteresis.
|
|
18058
|
+
* When the inference flips to a new state we require N consecutive polls
|
|
18059
|
+
* of that same state before committing it — this filters out transient
|
|
18060
|
+
* flapping caused by non-waking traffic drifting in/out of the 10 s
|
|
18061
|
+
* getSleepStatus() observation window during stream teardown.
|
|
18062
|
+
*/
|
|
18063
|
+
udpPendingSleepStateByChannel = /* @__PURE__ */ new Map();
|
|
17805
18064
|
udpSleepInferenceIntervalMs = 2e3;
|
|
18065
|
+
/** Consecutive inference polls required to commit a new sleeping/awake state. */
|
|
18066
|
+
udpSleepInferenceHysteresisPolls = 2;
|
|
17806
18067
|
lastMotionState;
|
|
17807
18068
|
lastAiState;
|
|
17808
18069
|
aiStatePollingDisabled = false;
|
|
@@ -18269,6 +18530,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18269
18530
|
*/
|
|
18270
18531
|
attachD2cDiscListener(client) {
|
|
18271
18532
|
client.on("d2c_disc", () => this.notifyD2cDisc());
|
|
18533
|
+
client.on("error", () => {
|
|
18534
|
+
});
|
|
18272
18535
|
}
|
|
18273
18536
|
/**
|
|
18274
18537
|
* Acquire a socket from the pool by tag.
|
|
@@ -18387,6 +18650,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18387
18650
|
const clientOpts = log ? { ...this.clientOptions, logger: log } : this.clientOptions;
|
|
18388
18651
|
const newClient = new BaichuanClient(clientOpts);
|
|
18389
18652
|
this.attachD2cDiscListener(newClient);
|
|
18653
|
+
newClient.on("error", (err) => {
|
|
18654
|
+
log?.debug?.(
|
|
18655
|
+
`[SocketPool] tag=${tag} client error: ${err?.message ?? err}`
|
|
18656
|
+
);
|
|
18657
|
+
});
|
|
18390
18658
|
await newClient.login();
|
|
18391
18659
|
const existingCooldown = this.socketPoolCooldowns.get(this.host);
|
|
18392
18660
|
if (existingCooldown) {
|
|
@@ -18902,6 +19170,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18902
19170
|
* Only counts sessions from our own IP address.
|
|
18903
19171
|
*/
|
|
18904
19172
|
async maybeRebootOnTooManySessions() {
|
|
19173
|
+
if (!this.client.isSocketConnected?.()) return;
|
|
18905
19174
|
const threshold = this.maxDedicatedSessionsBeforeReboot ?? 10;
|
|
18906
19175
|
if (this.sessionGuardRebootInFlight) return;
|
|
18907
19176
|
const cooldownMs = 10 * 6e4;
|
|
@@ -19343,6 +19612,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19343
19612
|
}
|
|
19344
19613
|
async renewSimpleEventSubscription() {
|
|
19345
19614
|
if (this.simpleEventListeners.size === 0) return;
|
|
19615
|
+
if (!this.client.isSocketConnected?.()) return;
|
|
19346
19616
|
if (this.simpleEventResubscribeInFlight)
|
|
19347
19617
|
return await this.simpleEventResubscribeInFlight;
|
|
19348
19618
|
this.simpleEventResubscribeInFlight = (async () => {
|
|
@@ -23110,23 +23380,32 @@ ${stderr}`)
|
|
|
23110
23380
|
return;
|
|
23111
23381
|
}
|
|
23112
23382
|
const channel = this.client.getConfiguredChannel?.() ?? 0;
|
|
23383
|
+
if (!this.client.isSocketConnected?.()) {
|
|
23384
|
+
this.udpPendingSleepStateByChannel.delete(channel);
|
|
23385
|
+
return;
|
|
23386
|
+
}
|
|
23113
23387
|
const status = this.getSleepStatus({ channel });
|
|
23114
23388
|
if (status.state === "unknown") return;
|
|
23115
|
-
const
|
|
23116
|
-
this.
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
23389
|
+
const committed = this.udpLastInferredSleepStateByChannel.get(channel);
|
|
23390
|
+
const pending = this.udpPendingSleepStateByChannel.get(channel);
|
|
23391
|
+
const decision = decideSleepInferenceTransition({
|
|
23392
|
+
inferred: status.state,
|
|
23393
|
+
committed,
|
|
23394
|
+
pending,
|
|
23395
|
+
hysteresisPolls: this.udpSleepInferenceHysteresisPolls
|
|
23396
|
+
});
|
|
23397
|
+
this.udpLastInferredSleepStateByChannel.set(
|
|
23398
|
+
channel,
|
|
23399
|
+
decision.nextCommitted
|
|
23400
|
+
);
|
|
23401
|
+
if (decision.nextPending === void 0) {
|
|
23402
|
+
this.udpPendingSleepStateByChannel.delete(channel);
|
|
23403
|
+
} else {
|
|
23404
|
+
this.udpPendingSleepStateByChannel.set(channel, decision.nextPending);
|
|
23126
23405
|
}
|
|
23127
|
-
if (
|
|
23406
|
+
if (decision.emit) {
|
|
23128
23407
|
this.dispatchSimpleEvent({
|
|
23129
|
-
type:
|
|
23408
|
+
type: decision.emit,
|
|
23130
23409
|
channel,
|
|
23131
23410
|
timestamp: Date.now()
|
|
23132
23411
|
});
|
|
@@ -23149,6 +23428,7 @@ ${stderr}`)
|
|
|
23149
23428
|
this.udpSleepInferenceInterval = void 0;
|
|
23150
23429
|
}
|
|
23151
23430
|
this.udpLastInferredSleepStateByChannel.clear();
|
|
23431
|
+
this.udpPendingSleepStateByChannel.clear();
|
|
23152
23432
|
}
|
|
23153
23433
|
/**
|
|
23154
23434
|
* GetEvents via Baichuan (legacy - use subscribeEvents for real-time events).
|
|
@@ -26747,8 +27027,8 @@ ${scheduleItems}
|
|
|
26747
27027
|
);
|
|
26748
27028
|
let args;
|
|
26749
27029
|
if (useMpegTsMuxer) {
|
|
26750
|
-
MpegTsMuxer
|
|
26751
|
-
tsMuxer
|
|
27030
|
+
tsMuxer = new MpegTsMuxer({ videoType, includeAudio: false });
|
|
27031
|
+
tsMuxer.reset();
|
|
26752
27032
|
args = [
|
|
26753
27033
|
"-hide_banner",
|
|
26754
27034
|
"-loglevel",
|
|
@@ -26912,7 +27192,7 @@ ${scheduleItems}
|
|
|
26912
27192
|
startFfmpeg(videoType);
|
|
26913
27193
|
frameCount++;
|
|
26914
27194
|
if (useMpegTsMuxer && tsMuxer) {
|
|
26915
|
-
const tsData = tsMuxer.
|
|
27195
|
+
const tsData = tsMuxer.muxVideo(data, microseconds, isKeyframe);
|
|
26916
27196
|
input.write(tsData);
|
|
26917
27197
|
} else {
|
|
26918
27198
|
if (videoType === "H264") input.write(H264_AUD);
|
|
@@ -27201,8 +27481,8 @@ ${scheduleItems}
|
|
|
27201
27481
|
logger?.log?.(
|
|
27202
27482
|
`[createRecordingReplayHlsSession] Starting ffmpeg HLS with videoType=${videoType}, transcode=${needsTranscode}, hlsTime=${hlsSegmentDuration}s, fileName=${params.fileName}`
|
|
27203
27483
|
);
|
|
27204
|
-
MpegTsMuxer
|
|
27205
|
-
tsMuxer
|
|
27484
|
+
tsMuxer = new MpegTsMuxer({ videoType, includeAudio: false });
|
|
27485
|
+
tsMuxer.reset();
|
|
27206
27486
|
const args = [
|
|
27207
27487
|
"-hide_banner",
|
|
27208
27488
|
"-loglevel",
|
|
@@ -27367,7 +27647,7 @@ ${scheduleItems}
|
|
|
27367
27647
|
startFfmpeg(videoType);
|
|
27368
27648
|
frameCount++;
|
|
27369
27649
|
if (tsMuxer) {
|
|
27370
|
-
const tsData = tsMuxer.
|
|
27650
|
+
const tsData = tsMuxer.muxVideo(data, microseconds, isKeyframe);
|
|
27371
27651
|
input.write(tsData);
|
|
27372
27652
|
}
|
|
27373
27653
|
if (frameCount === 1) {
|
|
@@ -28202,9 +28482,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28202
28482
|
const msg = fmtErr(e);
|
|
28203
28483
|
return msg.includes("Not running") || msg.includes("Baichuan UDP stream closed") || msg.includes("Baichuan socket closed") || msg.includes("ETIMEDOUT") || msg.toLowerCase().includes("timeout");
|
|
28204
28484
|
};
|
|
28205
|
-
const withRetries = async (label, max, op, shouldRetry) => {
|
|
28485
|
+
const withRetries = async (label, max, op, shouldRetry, isAborted) => {
|
|
28206
28486
|
let lastErr;
|
|
28207
28487
|
for (let attempt = 1; attempt <= max; attempt++) {
|
|
28488
|
+
if (isAborted?.()) throw new Error(`${label}: aborted (race won by another method)`);
|
|
28208
28489
|
try {
|
|
28209
28490
|
if (attempt > 1) {
|
|
28210
28491
|
logger?.log?.(`[AutoDetect] ${label}: retry ${attempt}/${max}...`);
|
|
@@ -28213,7 +28494,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28213
28494
|
} catch (e) {
|
|
28214
28495
|
lastErr = e;
|
|
28215
28496
|
const msg = fmtErr(e);
|
|
28216
|
-
const retryable = attempt < max && shouldRetry(e);
|
|
28497
|
+
const retryable = attempt < max && !isAborted?.() && shouldRetry(e);
|
|
28217
28498
|
logger?.log?.(
|
|
28218
28499
|
`[AutoDetect] ${label} attempt ${attempt}/${max} failed: ${msg}${retryable ? " (will retry)" : ""}`
|
|
28219
28500
|
);
|
|
@@ -28223,6 +28504,31 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28223
28504
|
}
|
|
28224
28505
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr ?? `${label} failed`));
|
|
28225
28506
|
};
|
|
28507
|
+
const runUdpMethodsParallel = async (methods, loginAndDetect, errorPrefix) => {
|
|
28508
|
+
let raceWon = false;
|
|
28509
|
+
const methodErrors = /* @__PURE__ */ new Map();
|
|
28510
|
+
try {
|
|
28511
|
+
return await Promise.any(
|
|
28512
|
+
methods.map(async (m) => {
|
|
28513
|
+
try {
|
|
28514
|
+
const result = await loginAndDetect(m, () => raceWon);
|
|
28515
|
+
raceWon = true;
|
|
28516
|
+
return result;
|
|
28517
|
+
} catch (e) {
|
|
28518
|
+
if (!raceWon) methodErrors.set(m, fmtErr(e));
|
|
28519
|
+
logger?.log?.(`[AutoDetect] UDP (${m}) failed: ${fmtErr(e)}`);
|
|
28520
|
+
throw e;
|
|
28521
|
+
}
|
|
28522
|
+
})
|
|
28523
|
+
);
|
|
28524
|
+
} catch (e) {
|
|
28525
|
+
if (e instanceof AggregateError) {
|
|
28526
|
+
const msgs = methods.map((m) => `${m}: ${methodErrors.get(m) ?? "unknown"}`);
|
|
28527
|
+
throw new Error(`${errorPrefix} ${msgs.join(" | ")}`);
|
|
28528
|
+
}
|
|
28529
|
+
throw e;
|
|
28530
|
+
}
|
|
28531
|
+
};
|
|
28226
28532
|
const effectiveUid = normalizeUid(uid);
|
|
28227
28533
|
logger?.log?.(`[AutoDetect] Pinging ${host}...`);
|
|
28228
28534
|
const isReachable = await pingHost(host);
|
|
@@ -28252,9 +28558,9 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28252
28558
|
normalizedUid = normalizedDiscovered;
|
|
28253
28559
|
}
|
|
28254
28560
|
const methodsToTry = inputs.udpDiscoveryMethod ? [inputs.udpDiscoveryMethod] : ["local-direct", "local-broadcast", "remote", "relay", "map"];
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
28561
|
+
return await runUdpMethodsParallel(
|
|
28562
|
+
methodsToTry,
|
|
28563
|
+
async (m, isAborted) => {
|
|
28258
28564
|
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
28259
28565
|
const udpApi = await withRetries(
|
|
28260
28566
|
`UDP(${m})`,
|
|
@@ -28277,11 +28583,14 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28277
28583
|
throw e;
|
|
28278
28584
|
}
|
|
28279
28585
|
},
|
|
28280
|
-
shouldRetryUdp
|
|
28586
|
+
shouldRetryUdp,
|
|
28587
|
+
isAborted
|
|
28281
28588
|
);
|
|
28282
|
-
const deviceInfo = await
|
|
28283
|
-
|
|
28284
|
-
|
|
28589
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
28590
|
+
udpApi.getInfo(),
|
|
28591
|
+
udpApi.getDeviceCapabilities(),
|
|
28592
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
28593
|
+
]);
|
|
28285
28594
|
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
28286
28595
|
const model = deviceInfo.type?.trim();
|
|
28287
28596
|
const normalizedModel = model ? model.trim() : void 0;
|
|
@@ -28320,14 +28629,8 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28320
28629
|
channelNum: 1,
|
|
28321
28630
|
api: udpApi
|
|
28322
28631
|
};
|
|
28323
|
-
}
|
|
28324
|
-
|
|
28325
|
-
udpErrors.push(`${m}: ${msg}`);
|
|
28326
|
-
logger?.log?.(`[AutoDetect] UDP (${m}) failed: ${msg}`);
|
|
28327
|
-
}
|
|
28328
|
-
}
|
|
28329
|
-
throw new Error(
|
|
28330
|
-
`Forced UDP autodetect failed for all methods. ${udpErrors.join(" | ")}`
|
|
28632
|
+
},
|
|
28633
|
+
"Forced UDP autodetect failed for all methods."
|
|
28331
28634
|
);
|
|
28332
28635
|
}
|
|
28333
28636
|
let tcpApi;
|
|
@@ -28380,54 +28683,57 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28380
28683
|
}
|
|
28381
28684
|
return void 0;
|
|
28382
28685
|
};
|
|
28383
|
-
const infoProbe = await
|
|
28384
|
-
|
|
28385
|
-
|
|
28686
|
+
const [infoProbe, supportProbe] = await Promise.all([
|
|
28687
|
+
runProbeVariants(
|
|
28688
|
+
"getInfo",
|
|
28689
|
+
[
|
|
28690
|
+
{
|
|
28691
|
+
variant: "cmd80 class=0x6414",
|
|
28692
|
+
op: () => api.getInfo(void 0, {
|
|
28693
|
+
timeoutMs: 2500,
|
|
28694
|
+
messageClass: BC_CLASS_MODERN_24
|
|
28695
|
+
})
|
|
28696
|
+
},
|
|
28697
|
+
{
|
|
28698
|
+
variant: "cmd80 class=0x6614",
|
|
28699
|
+
op: () => api.getInfo(void 0, {
|
|
28700
|
+
timeoutMs: 3e3,
|
|
28701
|
+
messageClass: BC_CLASS_MODERN_20
|
|
28702
|
+
})
|
|
28703
|
+
},
|
|
28704
|
+
{
|
|
28705
|
+
variant: "cmd318(ch0) class=0x6414",
|
|
28706
|
+
op: () => api.getInfo(0, {
|
|
28707
|
+
timeoutMs: 3e3,
|
|
28708
|
+
messageClass: BC_CLASS_MODERN_24
|
|
28709
|
+
})
|
|
28710
|
+
},
|
|
28711
|
+
{
|
|
28712
|
+
variant: "cmd318(ch0) class=0x6614",
|
|
28713
|
+
op: () => api.getInfo(0, {
|
|
28714
|
+
timeoutMs: 3500,
|
|
28715
|
+
messageClass: BC_CLASS_MODERN_20
|
|
28716
|
+
})
|
|
28717
|
+
}
|
|
28718
|
+
]
|
|
28719
|
+
),
|
|
28720
|
+
// Support probes (cmd 199). Some firmwares may not support it or are slow.
|
|
28721
|
+
runProbeVariants("getSupportInfo", [
|
|
28386
28722
|
{
|
|
28387
|
-
variant: "
|
|
28388
|
-
op: () => api.
|
|
28723
|
+
variant: "cmd199 class=0x6414",
|
|
28724
|
+
op: () => api.getSupportInfo({
|
|
28389
28725
|
timeoutMs: 2500,
|
|
28390
28726
|
messageClass: BC_CLASS_MODERN_24
|
|
28391
28727
|
})
|
|
28392
28728
|
},
|
|
28393
28729
|
{
|
|
28394
|
-
variant: "
|
|
28395
|
-
op: () => api.
|
|
28396
|
-
timeoutMs: 3e3,
|
|
28397
|
-
messageClass: BC_CLASS_MODERN_20
|
|
28398
|
-
})
|
|
28399
|
-
},
|
|
28400
|
-
{
|
|
28401
|
-
variant: "cmd318(ch0) class=0x6414",
|
|
28402
|
-
op: () => api.getInfo(0, {
|
|
28403
|
-
timeoutMs: 3e3,
|
|
28404
|
-
messageClass: BC_CLASS_MODERN_24
|
|
28405
|
-
})
|
|
28406
|
-
},
|
|
28407
|
-
{
|
|
28408
|
-
variant: "cmd318(ch0) class=0x6614",
|
|
28409
|
-
op: () => api.getInfo(0, {
|
|
28730
|
+
variant: "cmd199 class=0x6614",
|
|
28731
|
+
op: () => api.getSupportInfo({
|
|
28410
28732
|
timeoutMs: 3500,
|
|
28411
28733
|
messageClass: BC_CLASS_MODERN_20
|
|
28412
28734
|
})
|
|
28413
28735
|
}
|
|
28414
|
-
]
|
|
28415
|
-
);
|
|
28416
|
-
const supportProbe = await runProbeVariants("getSupportInfo", [
|
|
28417
|
-
{
|
|
28418
|
-
variant: "cmd199 class=0x6414",
|
|
28419
|
-
op: () => api.getSupportInfo({
|
|
28420
|
-
timeoutMs: 2500,
|
|
28421
|
-
messageClass: BC_CLASS_MODERN_24
|
|
28422
|
-
})
|
|
28423
|
-
},
|
|
28424
|
-
{
|
|
28425
|
-
variant: "cmd199 class=0x6614",
|
|
28426
|
-
op: () => api.getSupportInfo({
|
|
28427
|
-
timeoutMs: 3500,
|
|
28428
|
-
messageClass: BC_CLASS_MODERN_20
|
|
28429
|
-
})
|
|
28430
|
-
}
|
|
28736
|
+
])
|
|
28431
28737
|
]);
|
|
28432
28738
|
const deviceInfo = infoProbe?.value;
|
|
28433
28739
|
const support = supportProbe?.value;
|
|
@@ -28519,9 +28825,11 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28519
28825
|
}
|
|
28520
28826
|
try {
|
|
28521
28827
|
const detectOverUdpApi = async (udpApi, udpDiscoveryMethod) => {
|
|
28522
|
-
const deviceInfo = await
|
|
28523
|
-
|
|
28524
|
-
|
|
28828
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
28829
|
+
udpApi.getInfo(),
|
|
28830
|
+
udpApi.getDeviceCapabilities(),
|
|
28831
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
28832
|
+
]);
|
|
28525
28833
|
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
28526
28834
|
const model = deviceInfo.type?.trim();
|
|
28527
28835
|
const normalizedModel = model ? model.trim() : void 0;
|
|
@@ -28565,21 +28873,17 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28565
28873
|
};
|
|
28566
28874
|
};
|
|
28567
28875
|
const methodsToTry = ["local-direct", "local-broadcast", "remote", "relay", "map"];
|
|
28568
|
-
const
|
|
28569
|
-
|
|
28570
|
-
|
|
28876
|
+
const viableMethods = normalizedUid ? methodsToTry : methodsToTry.filter((m) => m === "local-direct" || m === "local-broadcast");
|
|
28877
|
+
return await runUdpMethodsParallel(
|
|
28878
|
+
viableMethods,
|
|
28879
|
+
async (m, isAborted) => {
|
|
28571
28880
|
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
28572
28881
|
const udpApi = await withRetries(
|
|
28573
28882
|
`UDP(${m})`,
|
|
28574
28883
|
maxRetries,
|
|
28575
28884
|
async (attempt) => {
|
|
28576
|
-
const apiInputs = {
|
|
28577
|
-
|
|
28578
|
-
udpDiscoveryMethod: m
|
|
28579
|
-
};
|
|
28580
|
-
if (normalizedUid) {
|
|
28581
|
-
apiInputs.uid = normalizedUid;
|
|
28582
|
-
}
|
|
28885
|
+
const apiInputs = { ...inputs, udpDiscoveryMethod: m };
|
|
28886
|
+
if (normalizedUid) apiInputs.uid = normalizedUid;
|
|
28583
28887
|
const api = createBaichuanApi(apiInputs, "udp");
|
|
28584
28888
|
try {
|
|
28585
28889
|
await api.login();
|
|
@@ -28594,20 +28898,12 @@ async function autoDetectDeviceType(inputs) {
|
|
|
28594
28898
|
throw e;
|
|
28595
28899
|
}
|
|
28596
28900
|
},
|
|
28597
|
-
shouldRetryUdp
|
|
28901
|
+
shouldRetryUdp,
|
|
28902
|
+
isAborted
|
|
28598
28903
|
);
|
|
28599
|
-
return
|
|
28600
|
-
}
|
|
28601
|
-
|
|
28602
|
-
udpErrors.push(`${m}: ${msg}`);
|
|
28603
|
-
try {
|
|
28604
|
-
} catch {
|
|
28605
|
-
}
|
|
28606
|
-
logger?.log?.(`[AutoDetect] UDP (${m}) failed: ${msg}`);
|
|
28607
|
-
}
|
|
28608
|
-
}
|
|
28609
|
-
throw new Error(
|
|
28610
|
-
`UDP discovery failed for all methods. ${udpErrors.join(" | ")}`
|
|
28904
|
+
return detectOverUdpApi(udpApi, m);
|
|
28905
|
+
},
|
|
28906
|
+
"UDP discovery failed for all methods."
|
|
28611
28907
|
);
|
|
28612
28908
|
} catch (udpError) {
|
|
28613
28909
|
logger?.log?.(
|