@apocaliss92/nodelink-js 0.1.17 → 0.1.20
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/README.md +41 -68
- package/dist/{DiagnosticsTools-6WEMO4L4.js → DiagnosticsTools-NUMCYEKQ.js} +2 -2
- package/dist/{chunk-ULSFEQSE.js → chunk-EHWVA3SG.js} +164 -17
- package/dist/chunk-EHWVA3SG.js.map +1 -0
- package/dist/{chunk-ZE7D7LI4.js → chunk-YPU7RAEY.js} +18 -12
- package/dist/{chunk-ZE7D7LI4.js.map → chunk-YPU7RAEY.js.map} +1 -1
- package/dist/cli/rtsp-server.cjs +177 -24
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +216 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -6
- package/dist/index.d.ts +52 -6
- package/dist/index.js +41 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ULSFEQSE.js.map +0 -1
- /package/dist/{DiagnosticsTools-6WEMO4L4.js.map → DiagnosticsTools-NUMCYEKQ.js.map} +0 -0
package/dist/cli/rtsp-server.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
BaichuanRtspServer,
|
|
4
4
|
ReolinkBaichuanApi,
|
|
5
5
|
autoDetectDeviceType
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-EHWVA3SG.js";
|
|
7
7
|
import {
|
|
8
8
|
__require
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-YPU7RAEY.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/rtsp-server.ts
|
|
12
12
|
function parseArgs() {
|
package/dist/index.cjs
CHANGED
|
@@ -1807,6 +1807,22 @@ function detectVideoCodecFromNal(data) {
|
|
|
1807
1807
|
if (nalStart < 0 || nalStart >= data.length) return null;
|
|
1808
1808
|
const nalByte = data[nalStart];
|
|
1809
1809
|
if (nalByte === void 0) return null;
|
|
1810
|
+
const forbiddenBit264 = nalByte >> 7 & 1;
|
|
1811
|
+
const h264Type = nalByte & 31;
|
|
1812
|
+
if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {
|
|
1813
|
+
if (h264Type === 7 || h264Type === 8) {
|
|
1814
|
+
return "H264";
|
|
1815
|
+
}
|
|
1816
|
+
if (h264Type === 5) {
|
|
1817
|
+
return "H264";
|
|
1818
|
+
}
|
|
1819
|
+
if (h264Type === 1) {
|
|
1820
|
+
const nalRefIdc = nalByte >> 5 & 3;
|
|
1821
|
+
if (nalRefIdc >= 1) {
|
|
1822
|
+
return "H264";
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1810
1826
|
if (nalStart + 1 < data.length) {
|
|
1811
1827
|
const nalByte2 = data[nalStart + 1];
|
|
1812
1828
|
if (nalByte2 !== void 0) {
|
|
@@ -1820,22 +1836,12 @@ function detectVideoCodecFromNal(data) {
|
|
|
1820
1836
|
if (hevcType === 19 || hevcType === 20 || hevcType === 21) {
|
|
1821
1837
|
return "H265";
|
|
1822
1838
|
}
|
|
1823
|
-
if (hevcType <=
|
|
1839
|
+
if (hevcType <= 1 && nalByte <= 3) {
|
|
1824
1840
|
return "H265";
|
|
1825
1841
|
}
|
|
1826
1842
|
}
|
|
1827
1843
|
}
|
|
1828
1844
|
}
|
|
1829
|
-
const forbiddenBit264 = nalByte >> 7 & 1;
|
|
1830
|
-
const h264Type = nalByte & 31;
|
|
1831
|
-
if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {
|
|
1832
|
-
if (h264Type === 7 || h264Type === 8) {
|
|
1833
|
-
return "H264";
|
|
1834
|
-
}
|
|
1835
|
-
if (h264Type === 5 || h264Type === 1) {
|
|
1836
|
-
return "H264";
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
1845
|
return null;
|
|
1840
1846
|
}
|
|
1841
1847
|
var ANNEXB_START_CODE_4B, BcMediaAnnexBDecoder;
|
|
@@ -16211,7 +16217,8 @@ var logDebugStreamBlock = (params) => {
|
|
|
16211
16217
|
const bitRateText = getXmlText(raw, "bitRate") ?? getXmlText(raw, "BitRate");
|
|
16212
16218
|
const videoEncTypeText = getXmlText(raw, "videoEncType") ?? getXmlText(raw, "VideoEncType");
|
|
16213
16219
|
const audioText = getXmlText(raw, "audio") ?? getXmlText(raw, "Audio");
|
|
16214
|
-
const
|
|
16220
|
+
const enableCheckRaw = raw.replace(/<smartH265[\s\S]*?<\/smartH265>/g, "");
|
|
16221
|
+
const enableText = getXmlText(enableCheckRaw, "enable") ?? getXmlText(enableCheckRaw, "Enable");
|
|
16215
16222
|
const width = Number(widthText ?? "0");
|
|
16216
16223
|
const height = Number(heightText ?? "0");
|
|
16217
16224
|
const frameRate = Number(frameText ?? "0");
|
|
@@ -16235,7 +16242,8 @@ var buildStream = (params) => {
|
|
|
16235
16242
|
const frameRate = Number(getXmlText(streamXml, "frame") ?? "0");
|
|
16236
16243
|
const bitRate = Number(getXmlText(streamXml, "bitRate") ?? "0");
|
|
16237
16244
|
const audio = Number(getXmlText(streamXml, "audio") ?? "0");
|
|
16238
|
-
const
|
|
16245
|
+
const enableXml = streamXml.replace(/<smartH265[\s\S]*?<\/smartH265>/g, "");
|
|
16246
|
+
const enabled = getXmlText(enableXml, "enable");
|
|
16239
16247
|
const isEnabled = isEnabledFromText(enabled);
|
|
16240
16248
|
if (!isEnabled || !isPlausibleStream({ width, height, frameRate, bitRate }))
|
|
16241
16249
|
return void 0;
|
|
@@ -16924,14 +16932,16 @@ var DUAL_LENS_SINGLE_MOTION_MODELS = /* @__PURE__ */ new Set([
|
|
|
16924
16932
|
"Reolink TrackMix",
|
|
16925
16933
|
"Reolink TrackMix PoE",
|
|
16926
16934
|
"Reolink TrackMix WiFi",
|
|
16927
|
-
"RLC-81MA"
|
|
16935
|
+
"RLC-81MA",
|
|
16936
|
+
"TrackFlex Floodlight WiFi"
|
|
16928
16937
|
]);
|
|
16929
16938
|
var DUAL_LENS_MODELS = /* @__PURE__ */ new Set([
|
|
16930
16939
|
...DUAL_LENS_DUAL_MOTION_MODELS,
|
|
16931
16940
|
...DUAL_LENS_SINGLE_MOTION_MODELS
|
|
16932
16941
|
]);
|
|
16933
16942
|
var isDualLenseModel = (model) => {
|
|
16934
|
-
|
|
16943
|
+
const lower = model.toLowerCase();
|
|
16944
|
+
return Array.from(DUAL_LENS_MODELS).some((m) => m.toLowerCase() === lower) || lower.includes("trackmix") || lower.includes("trackflex");
|
|
16935
16945
|
};
|
|
16936
16946
|
var NVR_HUB_EXACT_TYPES = ["NVR", "WIFI_NVR", "HOMEHUB"];
|
|
16937
16947
|
var NVR_HUB_MODEL_PATTERNS = [
|
|
@@ -17015,6 +17025,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17015
17025
|
* Set via setIsNvr() from the plugin or auto-detected via isNvrDevice().
|
|
17016
17026
|
*/
|
|
17017
17027
|
_isNvr;
|
|
17028
|
+
/**
|
|
17029
|
+
* Cached multi-focal detection result.
|
|
17030
|
+
* - true = dual-lens camera (e.g., TrackMix, TrackFlex) with multiple channels on a single device
|
|
17031
|
+
* - false = single-lens camera
|
|
17032
|
+
* Multi-focal cameras reject concurrent streaming TCP connections (response_code 430),
|
|
17033
|
+
* so all channels must multiplex on the same streaming socket.
|
|
17034
|
+
*/
|
|
17035
|
+
_isMultiFocal;
|
|
17018
17036
|
/** Maximum dedicated sessions allowed before triggering a reboot (default: 7). */
|
|
17019
17037
|
maxDedicatedSessionsBeforeReboot;
|
|
17020
17038
|
sessionGuardRebootInFlight;
|
|
@@ -17045,6 +17063,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17045
17063
|
simpleEventResubscribeTimer;
|
|
17046
17064
|
simpleEventResubscribeInFlight;
|
|
17047
17065
|
simpleEventResubscribeIntervalMs = 5 * 6e4;
|
|
17066
|
+
// Event watchdog: auto-recovery when events stop flowing or subscription fails
|
|
17067
|
+
simpleEventWatchdogTimer;
|
|
17068
|
+
simpleEventLastReceivedAt = 0;
|
|
17069
|
+
simpleEventWatchdogRecoveryAttempts = 0;
|
|
17070
|
+
simpleEventWatchdogLastRecoveryAt = 0;
|
|
17071
|
+
simpleEventWatchdogIntervalMs = 1e4;
|
|
17072
|
+
// check every 10s
|
|
17073
|
+
simpleEventWatchdogSilenceThresholdMs = 5 * 6e4;
|
|
17074
|
+
// 5 min without events
|
|
17048
17075
|
statePollingInterval;
|
|
17049
17076
|
udpSleepInferenceInterval;
|
|
17050
17077
|
udpLastInferredSleepStateByChannel = /* @__PURE__ */ new Map();
|
|
@@ -17429,14 +17456,16 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17429
17456
|
* Determine the socket tag for a given sessionKey.
|
|
17430
17457
|
* This implements the tag-based allocation strategy:
|
|
17431
17458
|
*
|
|
17432
|
-
* - "general" - commands, events
|
|
17433
|
-
* - "streaming:ch{N}" - main + sub for channel N (
|
|
17434
|
-
* - "streaming:ch{N}:ext" - ext for channel N (
|
|
17459
|
+
* - "general" - commands, events, ext on ch0
|
|
17460
|
+
* - "streaming:ch{N}" - main + sub for channel N (NVR/standalone single-lens)
|
|
17461
|
+
* - "streaming:ch{N}:ext" - ext for channel N (NVR/standalone, N>0)
|
|
17462
|
+
* - "streaming:a" - ch0 main + ch1 sub (multi-focal dedicated socket)
|
|
17463
|
+
* - "general" also carries ch1 main + ch0 sub for multi-focal (merged with commands/events)
|
|
17435
17464
|
* - "replay:deviceId:ch{N}" - dedicated per device+channel for replay
|
|
17436
17465
|
*
|
|
17437
|
-
*
|
|
17438
|
-
*
|
|
17439
|
-
*
|
|
17466
|
+
* Multi-focal cameras (TrackMix, TrackFlex, Duo) reject two main or two sub
|
|
17467
|
+
* streams on the same TCP connection (response_code 430). Cross-channel pairing
|
|
17468
|
+
* ensures each socket always has a valid M+S combination using only 2 TCP connections.
|
|
17440
17469
|
*
|
|
17441
17470
|
* @param sessionKey - The session key (e.g., "live:device:ch0:main", "replay:device:ch1:file")
|
|
17442
17471
|
* @returns The socket pool tag to use
|
|
@@ -17456,9 +17485,16 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17456
17485
|
if (channel === 0) {
|
|
17457
17486
|
return "general";
|
|
17458
17487
|
}
|
|
17459
|
-
|
|
17488
|
+
if (this._isMultiFocal) {
|
|
17489
|
+
return "general";
|
|
17490
|
+
}
|
|
17491
|
+
return this._isNvr ? `streaming:ch${channel}:ext` : `streaming:ch${channel}:ext`;
|
|
17460
17492
|
}
|
|
17461
|
-
|
|
17493
|
+
if (this._isMultiFocal) {
|
|
17494
|
+
const isSocketA = channel === 0 && profile === "main" || channel === 1 && profile === "sub";
|
|
17495
|
+
return isSocketA ? "streaming:a" : "general";
|
|
17496
|
+
}
|
|
17497
|
+
return this._isNvr ? `streaming:ch${channel}` : `streaming:ch${channel}`;
|
|
17462
17498
|
}
|
|
17463
17499
|
return "general";
|
|
17464
17500
|
}
|
|
@@ -17783,6 +17819,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17783
17819
|
async cleanupDedicatedClients() {
|
|
17784
17820
|
}
|
|
17785
17821
|
dispatchSimpleEvent(evt) {
|
|
17822
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
17823
|
+
if (this.simpleEventWatchdogRecoveryAttempts > 0) {
|
|
17824
|
+
(this.logger.info ?? this.logger.log).call(
|
|
17825
|
+
this.logger,
|
|
17826
|
+
`[ReolinkBaichuanApi] event watchdog: events flowing again after ${this.simpleEventWatchdogRecoveryAttempts} recovery attempt(s)`
|
|
17827
|
+
);
|
|
17828
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
17829
|
+
}
|
|
17786
17830
|
const debugCfg = this.client.getDebugConfig?.();
|
|
17787
17831
|
if (debugCfg) {
|
|
17788
17832
|
const sid = this.client.getSocketSessionId?.();
|
|
@@ -18331,11 +18375,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18331
18375
|
/**
|
|
18332
18376
|
* Subscribe to minimal high-level events.
|
|
18333
18377
|
* The API manages Baichuan subscribe/unsubscribe automatically.
|
|
18378
|
+
* Includes built-in watchdog: if no events arrive for 5 minutes while
|
|
18379
|
+
* the connection is alive, the subscription is automatically renewed.
|
|
18334
18380
|
*/
|
|
18335
18381
|
async onSimpleEvent(callback) {
|
|
18336
18382
|
this.simpleEventListeners.add(callback);
|
|
18337
18383
|
await this.ensureSimpleEventSubscribed();
|
|
18384
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
18338
18385
|
this.startSimpleEventResubscribeTimer();
|
|
18386
|
+
this.startSimpleEventWatchdog();
|
|
18339
18387
|
}
|
|
18340
18388
|
/**
|
|
18341
18389
|
* Remove one callback, or all callbacks if omitted.
|
|
@@ -18349,6 +18397,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18349
18397
|
}
|
|
18350
18398
|
if (this.simpleEventListeners.size === 0) {
|
|
18351
18399
|
this.stopSimpleEventResubscribeTimer();
|
|
18400
|
+
this.stopSimpleEventWatchdog();
|
|
18352
18401
|
this.stopUdpSleepInference();
|
|
18353
18402
|
await this.ensureSimpleEventUnsubscribed();
|
|
18354
18403
|
} else {
|
|
@@ -18372,6 +18421,97 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18372
18421
|
clearInterval(this.simpleEventResubscribeTimer);
|
|
18373
18422
|
this.simpleEventResubscribeTimer = void 0;
|
|
18374
18423
|
}
|
|
18424
|
+
/**
|
|
18425
|
+
* Event watchdog: monitors whether events are flowing and auto-recovers if they stop.
|
|
18426
|
+
*
|
|
18427
|
+
* Handles two failure modes:
|
|
18428
|
+
* 1. Subscription flag is true but no events arrive for 5+ minutes (device dropped subscription silently)
|
|
18429
|
+
* 2. Subscription flag is false because initial/retry subscribe failed, but connection is now alive
|
|
18430
|
+
*
|
|
18431
|
+
* Uses exponential backoff (30s → 60s → 120s → 240s → max 5min) to avoid hammering the device.
|
|
18432
|
+
*/
|
|
18433
|
+
startSimpleEventWatchdog() {
|
|
18434
|
+
if (this.simpleEventWatchdogTimer) return;
|
|
18435
|
+
if (this.simpleEventListeners.size === 0) return;
|
|
18436
|
+
this.simpleEventWatchdogTimer = setInterval(() => {
|
|
18437
|
+
void this.simpleEventWatchdogTick();
|
|
18438
|
+
}, this.simpleEventWatchdogIntervalMs);
|
|
18439
|
+
}
|
|
18440
|
+
stopSimpleEventWatchdog() {
|
|
18441
|
+
if (!this.simpleEventWatchdogTimer) return;
|
|
18442
|
+
clearInterval(this.simpleEventWatchdogTimer);
|
|
18443
|
+
this.simpleEventWatchdogTimer = void 0;
|
|
18444
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
18445
|
+
this.simpleEventWatchdogLastRecoveryAt = 0;
|
|
18446
|
+
this.simpleEventLastReceivedAt = 0;
|
|
18447
|
+
}
|
|
18448
|
+
async simpleEventWatchdogTick() {
|
|
18449
|
+
if (this.simpleEventListeners.size === 0) return;
|
|
18450
|
+
if (!this.client.isSocketConnected?.() || !this.client.loggedIn) return;
|
|
18451
|
+
const now = Date.now();
|
|
18452
|
+
if (this.simpleEventSubscribed && this.simpleEventLastReceivedAt > 0) {
|
|
18453
|
+
const silence = now - this.simpleEventLastReceivedAt;
|
|
18454
|
+
if (silence < this.simpleEventWatchdogSilenceThresholdMs) return;
|
|
18455
|
+
(this.logger.warn ?? this.logger.log).call(
|
|
18456
|
+
this.logger,
|
|
18457
|
+
`[ReolinkBaichuanApi] event watchdog: no events for ${Math.round(silence / 6e4)} min, forcing resubscribe`,
|
|
18458
|
+
{ host: this.host, silenceMs: silence }
|
|
18459
|
+
);
|
|
18460
|
+
try {
|
|
18461
|
+
this.simpleEventSubscribed = false;
|
|
18462
|
+
this.client.subscribed = false;
|
|
18463
|
+
await this.ensureSimpleEventSubscribed();
|
|
18464
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
18465
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
18466
|
+
(this.logger.info ?? this.logger.log).call(
|
|
18467
|
+
this.logger,
|
|
18468
|
+
`[ReolinkBaichuanApi] event watchdog: resubscribed successfully after silence`
|
|
18469
|
+
);
|
|
18470
|
+
} catch (e) {
|
|
18471
|
+
(this.logger.debug ?? this.logger.log).call(
|
|
18472
|
+
this.logger,
|
|
18473
|
+
`[ReolinkBaichuanApi] event watchdog: resubscribe after silence failed`,
|
|
18474
|
+
formatErrorForLog(e)
|
|
18475
|
+
);
|
|
18476
|
+
}
|
|
18477
|
+
return;
|
|
18478
|
+
}
|
|
18479
|
+
if (!this.simpleEventSubscribed) {
|
|
18480
|
+
const backoffMs = Math.min(
|
|
18481
|
+
3e4 * Math.pow(2, this.simpleEventWatchdogRecoveryAttempts),
|
|
18482
|
+
this.simpleEventWatchdogSilenceThresholdMs
|
|
18483
|
+
);
|
|
18484
|
+
if (now - this.simpleEventWatchdogLastRecoveryAt < backoffMs) return;
|
|
18485
|
+
this.simpleEventWatchdogRecoveryAttempts++;
|
|
18486
|
+
this.simpleEventWatchdogLastRecoveryAt = now;
|
|
18487
|
+
const nextBackoff = Math.min(
|
|
18488
|
+
3e4 * Math.pow(2, this.simpleEventWatchdogRecoveryAttempts),
|
|
18489
|
+
this.simpleEventWatchdogSilenceThresholdMs
|
|
18490
|
+
);
|
|
18491
|
+
(this.logger.info ?? this.logger.log).call(
|
|
18492
|
+
this.logger,
|
|
18493
|
+
`[ReolinkBaichuanApi] event watchdog: subscription inactive, attempting auto-recovery (attempt #${this.simpleEventWatchdogRecoveryAttempts}, next backoff ${Math.round(nextBackoff / 1e3)}s)`,
|
|
18494
|
+
{ host: this.host }
|
|
18495
|
+
);
|
|
18496
|
+
try {
|
|
18497
|
+
await this.ensureSimpleEventSubscribed();
|
|
18498
|
+
if (this.simpleEventSubscribed) {
|
|
18499
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
18500
|
+
(this.logger.info ?? this.logger.log).call(
|
|
18501
|
+
this.logger,
|
|
18502
|
+
`[ReolinkBaichuanApi] event watchdog: auto-recovery successful after ${this.simpleEventWatchdogRecoveryAttempts} attempt(s)`
|
|
18503
|
+
);
|
|
18504
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
18505
|
+
}
|
|
18506
|
+
} catch (e) {
|
|
18507
|
+
(this.logger.debug ?? this.logger.log).call(
|
|
18508
|
+
this.logger,
|
|
18509
|
+
`[ReolinkBaichuanApi] event watchdog: recovery attempt #${this.simpleEventWatchdogRecoveryAttempts} failed`,
|
|
18510
|
+
formatErrorForLog(e)
|
|
18511
|
+
);
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
}
|
|
18375
18515
|
async renewSimpleEventSubscription() {
|
|
18376
18516
|
if (this.simpleEventListeners.size === 0) return;
|
|
18377
18517
|
if (this.simpleEventResubscribeInFlight)
|
|
@@ -18510,6 +18650,17 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18510
18650
|
this._isNvr = isNvr;
|
|
18511
18651
|
this.logger.debug?.(`[ReolinkBaichuanApi] setIsNvr: ${isNvr}`);
|
|
18512
18652
|
}
|
|
18653
|
+
/**
|
|
18654
|
+
* Set the multi-focal flag explicitly.
|
|
18655
|
+
* Call this early (before streaming) to ensure correct socket pooling.
|
|
18656
|
+
* Multi-focal cameras (TrackMix, TrackFlex, Duo, etc.) reject concurrent
|
|
18657
|
+
* streaming TCP connections, so all channels must share a single streaming socket.
|
|
18658
|
+
* @param isMultiFocal - true if this is a dual-lens/multi-focal camera
|
|
18659
|
+
*/
|
|
18660
|
+
setIsMultiFocal(isMultiFocal) {
|
|
18661
|
+
this._isMultiFocal = isMultiFocal;
|
|
18662
|
+
this.logger.debug?.(`[ReolinkBaichuanApi] setIsMultiFocal: ${isMultiFocal}`);
|
|
18663
|
+
}
|
|
18513
18664
|
/**
|
|
18514
18665
|
* Enable or disable idle disconnect dynamically.
|
|
18515
18666
|
*
|
|
@@ -18560,6 +18711,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18560
18711
|
}
|
|
18561
18712
|
this.stopStatePolling();
|
|
18562
18713
|
this.stopUdpSleepInference();
|
|
18714
|
+
this.stopSimpleEventWatchdog();
|
|
18715
|
+
this.stopSimpleEventResubscribeTimer();
|
|
18563
18716
|
await this.cleanup();
|
|
18564
18717
|
await this.stopAllActiveStreams();
|
|
18565
18718
|
await this.cleanupSocketPool();
|
|
@@ -24011,7 +24164,7 @@ ${xml}`
|
|
|
24011
24164
|
});
|
|
24012
24165
|
model = typeof info.type === "string" ? info.type.toLowerCase() : "";
|
|
24013
24166
|
isMultiFocal = isDualLenseModel(model);
|
|
24014
|
-
isTrackMix = model.includes("trackmix");
|
|
24167
|
+
isTrackMix = model.includes("trackmix") || model.includes("trackflex");
|
|
24015
24168
|
} catch (e) {
|
|
24016
24169
|
logDebug(
|
|
24017
24170
|
"[ReolinkBaichuanApi] buildVideoStreamOptions: getInfo(type) failed",
|
|
@@ -28530,6 +28683,8 @@ var Rfc4571Muxer = class _Rfc4571Muxer {
|
|
|
28530
28683
|
videoUsWrapOffset = 0;
|
|
28531
28684
|
videoLastAbsUs;
|
|
28532
28685
|
videoAvgDeltaUs;
|
|
28686
|
+
videoFramesSinceAbsUsChange = 0;
|
|
28687
|
+
videoEstimatedFrameDeltaUs;
|
|
28533
28688
|
videoClockRate = 9e4;
|
|
28534
28689
|
fallbackVideoIncrement;
|
|
28535
28690
|
fallbackVideoDeltaUs;
|
|
@@ -28655,6 +28810,8 @@ var Rfc4571Muxer = class _Rfc4571Muxer {
|
|
|
28655
28810
|
this.videoUsWrapOffset = 0;
|
|
28656
28811
|
this.videoLastAbsUs = void 0;
|
|
28657
28812
|
this.videoAvgDeltaUs = void 0;
|
|
28813
|
+
this.videoFramesSinceAbsUsChange = 0;
|
|
28814
|
+
this.videoEstimatedFrameDeltaUs = void 0;
|
|
28658
28815
|
}
|
|
28659
28816
|
logVideoTiming(kind, message) {
|
|
28660
28817
|
const now = Date.now();
|
|
@@ -28725,20 +28882,44 @@ var Rfc4571Muxer = class _Rfc4571Muxer {
|
|
|
28725
28882
|
this.videoAvgDeltaUs = this.fallbackVideoDeltaUs;
|
|
28726
28883
|
return;
|
|
28727
28884
|
}
|
|
28728
|
-
const
|
|
28885
|
+
const lastAbsUs = this.videoLastAbsUs;
|
|
28886
|
+
const deltaUs = absUs - lastAbsUs;
|
|
28729
28887
|
this.videoLastAbsUs = absUs;
|
|
28730
|
-
const trusted = Number.isFinite(deltaUs) && deltaUs > 0 && deltaUs <= this.maxTrustedDeltaUs;
|
|
28731
28888
|
let effectiveDeltaUs;
|
|
28732
|
-
if (
|
|
28733
|
-
|
|
28734
|
-
|
|
28735
|
-
|
|
28889
|
+
if (!Number.isFinite(deltaUs) || deltaUs < 0) {
|
|
28890
|
+
this.logVideoTiming(
|
|
28891
|
+
"untrusted-delta",
|
|
28892
|
+
`discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${lastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? "n/a"}); using fallback`
|
|
28893
|
+
);
|
|
28894
|
+
this.videoFramesSinceAbsUsChange = 0;
|
|
28895
|
+
effectiveDeltaUs = this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;
|
|
28896
|
+
} else if (deltaUs === 0) {
|
|
28897
|
+
this.videoFramesSinceAbsUsChange++;
|
|
28898
|
+
effectiveDeltaUs = this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;
|
|
28899
|
+
} else if (deltaUs <= this.maxTrustedDeltaUs) {
|
|
28900
|
+
if (this.videoFramesSinceAbsUsChange > 0) {
|
|
28901
|
+
const framesCovered = this.videoFramesSinceAbsUsChange + 1;
|
|
28902
|
+
const perFrameDeltaUs = Math.max(
|
|
28903
|
+
1,
|
|
28904
|
+
Math.round(deltaUs / framesCovered)
|
|
28905
|
+
);
|
|
28906
|
+
this.videoEstimatedFrameDeltaUs = perFrameDeltaUs;
|
|
28907
|
+
this.videoFramesSinceAbsUsChange = 0;
|
|
28908
|
+
const prevAvg = this.videoAvgDeltaUs ?? perFrameDeltaUs;
|
|
28909
|
+
this.videoAvgDeltaUs = prevAvg + (perFrameDeltaUs - prevAvg) * this.emaAlpha;
|
|
28910
|
+
effectiveDeltaUs = perFrameDeltaUs;
|
|
28911
|
+
} else {
|
|
28912
|
+
const prevAvg = this.videoAvgDeltaUs ?? deltaUs;
|
|
28913
|
+
this.videoAvgDeltaUs = prevAvg + (deltaUs - prevAvg) * this.emaAlpha;
|
|
28914
|
+
effectiveDeltaUs = deltaUs;
|
|
28915
|
+
}
|
|
28736
28916
|
} else {
|
|
28737
28917
|
this.logVideoTiming(
|
|
28738
28918
|
"untrusted-delta",
|
|
28739
|
-
`discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${
|
|
28919
|
+
`discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${lastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? "n/a"} sameCount=${this.videoFramesSinceAbsUsChange}); using fallback`
|
|
28740
28920
|
);
|
|
28741
|
-
|
|
28921
|
+
this.videoFramesSinceAbsUsChange = 0;
|
|
28922
|
+
effectiveDeltaUs = this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;
|
|
28742
28923
|
}
|
|
28743
28924
|
const inc = Math.max(
|
|
28744
28925
|
1,
|
|
@@ -32241,6 +32422,9 @@ Error: ${err}`
|
|
|
32241
32422
|
}
|
|
32242
32423
|
const peerConnection = new RTCPeerConnection({
|
|
32243
32424
|
iceServers,
|
|
32425
|
+
icePortRange: this.options.icePortRange,
|
|
32426
|
+
iceAdditionalHostAddresses: this.options.iceAdditionalHostAddresses,
|
|
32427
|
+
iceTransportPolicy: this.options.iceTransportPolicy,
|
|
32244
32428
|
codecs: {
|
|
32245
32429
|
video: [
|
|
32246
32430
|
new RTCRtpCodecParameters({
|