@apocaliss92/nodelink-js 0.1.20 → 0.2.2
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 +20 -1
- package/dist/{DiagnosticsTools-NUMCYEKQ.js → DiagnosticsTools-FNLGCOVA.js} +2 -2
- package/dist/{chunk-EHWVA3SG.js → chunk-MN7GUZT7.js} +981 -250
- package/dist/chunk-MN7GUZT7.js.map +1 -0
- package/dist/{chunk-YPU7RAEY.js → chunk-NLTB7GTA.js} +17 -1
- package/dist/{chunk-YPU7RAEY.js.map → chunk-NLTB7GTA.js.map} +1 -1
- package/dist/cli/rtsp-server.cjs +978 -247
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +1002 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +246 -16
- package/dist/index.d.ts +260 -15
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EHWVA3SG.js.map +0 -1
- /package/dist/{DiagnosticsTools-NUMCYEKQ.js.map → DiagnosticsTools-FNLGCOVA.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
34
34
|
function bcHeaderHasPayloadOffset(messageClass) {
|
|
35
35
|
return messageClass === BC_CLASS_MODERN_24 || messageClass === BC_CLASS_MODERN_24_ALT || messageClass === BC_CLASS_FILE_DOWNLOAD;
|
|
36
36
|
}
|
|
37
|
-
var BC_TCP_DEFAULT_PORT, BC_MAGIC, BC_MAGIC_REV, BC_XML_KEY, BC_AES_IV, BC_CLASS_LEGACY, BC_CLASS_MODERN_20, BC_CLASS_MODERN_24, BC_CLASS_MODERN_24_ALT, BC_CLASS_FILE_DOWNLOAD, BC_CMD_ID_LOGIN, BC_CMD_ID_LOGOUT, BC_CMD_ID_VIDEO, BC_CMD_ID_VIDEO_STOP, BC_CMD_ID_FILE_INFO_LIST_REPLAY, BC_CMD_ID_FILE_INFO_LIST_STOP, BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO, BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD, BC_CMD_ID_FILE_INFO_LIST_OPEN, BC_CMD_ID_FILE_INFO_LIST_GET, BC_CMD_ID_FILE_INFO_LIST_CLOSE, BC_CMD_ID_FIND_REC_VIDEO_OPEN, BC_CMD_ID_FIND_REC_VIDEO_GET, BC_CMD_ID_FIND_REC_VIDEO_CLOSE, BC_CMD_ID_COVER_PREVIEW, BC_CMD_ID_COVER_STANDALONE_458, BC_CMD_ID_COVER_STANDALONE_459, BC_CMD_ID_COVER_STANDALONE_460, BC_CMD_ID_COVER_STANDALONE_461, BC_CMD_ID_COVER_STANDALONE_462, BC_CMD_ID_COVER_RESPONSE, BC_CMD_ID_TALK_ABILITY, BC_CMD_ID_TALK_RESET, BC_CMD_ID_TALK_CONFIG, BC_CMD_ID_TALK, BC_CMD_ID_PTZ_CONTROL, BC_CMD_ID_PTZ_CONTROL_PRESET, BC_CMD_ID_GET_PTZ_PRESET, BC_CMD_ID_GET_PTZ_POSITION, BC_CMD_ID_GET_ZOOM_FOCUS, BC_CMD_ID_SET_ZOOM_FOCUS, BC_CMD_ID_GET_BATTERY_INFO_LIST, BC_CMD_ID_GET_BATTERY_INFO, BC_CMD_ID_UDP_KEEP_ALIVE, BC_CMD_ID_GET_PIR_INFO, BC_CMD_ID_SET_PIR_INFO, BC_CMD_ID_GET_MOTION_ALARM, BC_CMD_ID_SET_MOTION_ALARM, BC_CMD_ID_ALARM_EVENT_LIST, BC_CMD_ID_GET_AI_ALARM, BC_CMD_ID_SET_AI_ALARM, BC_CMD_ID_GET_AUDIO_ALARM, BC_CMD_ID_AUDIO_ALARM_PLAY, BC_CMD_ID_GET_WHITE_LED, BC_CMD_ID_SET_WHITE_LED_STATE, BC_CMD_ID_SET_WHITE_LED_TASK, BC_CMD_ID_FLOODLIGHT_STATUS_LIST, BC_CMD_ID_ABILITY_INFO, BC_CMD_ID_SUPPORT, BC_CMD_ID_PING, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_RECORD, BC_CMD_ID_GET_HDD_INFO_LIST, BC_CMD_ID_GET_WIFI_SIGNAL, BC_CMD_ID_GET_WIFI, BC_CMD_ID_GET_ONLINE_USER_LIST, BC_CMD_ID_GET_DAY_RECORDS, BC_CMD_ID_GET_STREAM_INFO_LIST, BC_CMD_ID_GET_LED_STATE, BC_CMD_ID_GET_EMAIL_TASK, BC_CMD_ID_GET_AUDIO_TASK, BC_CMD_ID_GET_AUDIO_CFG, BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_TIMELAPSE_CFG, BC_CMD_ID_GET_AI_DENOISE, BC_CMD_ID_GET_KIT_AP_CFG, BC_CMD_ID_GET_REC_ENC_CFG, BC_CMD_ID_GET_ACCESS_USER_LIST, BC_CMD_ID_GET_SLEEP_STATE, BC_CMD_ID_GET_VIDEO_INPUT, BC_CMD_ID_GET_SYSTEM_GENERAL, BC_CMD_ID_GET_SUPPORT, BC_CMD_ID_GET_AI_CFG, BC_CMD_ID_SET_AI_CFG, BC_CMD_ID_GET_SIREN_STATUS, BC_CMD_ID_SET_AUDIO_TASK, BC_CMD_ID_CMD_123, BC_CMD_ID_CMD_209, BC_CMD_ID_CMD_265, BC_CMD_ID_CMD_440, BC_CMD_ID_PUSH_VIDEO_INPUT, BC_CMD_ID_PUSH_SERIAL, BC_CMD_ID_PUSH_NET_INFO, BC_CMD_ID_PUSH_DINGDONG_LIST, BC_CMD_ID_PUSH_SLEEP_STATUS, BC_CMD_ID_PUSH_COORDINATE_POINT_LIST;
|
|
37
|
+
var BC_TCP_DEFAULT_PORT, BC_MAGIC, BC_MAGIC_REV, BC_XML_KEY, BC_AES_IV, BC_CLASS_LEGACY, BC_CLASS_MODERN_20, BC_CLASS_MODERN_24, BC_CLASS_MODERN_24_ALT, BC_CLASS_FILE_DOWNLOAD, BC_CMD_ID_LOGIN, BC_CMD_ID_LOGOUT, BC_CMD_ID_VIDEO, BC_CMD_ID_VIDEO_STOP, BC_CMD_ID_FILE_INFO_LIST_REPLAY, BC_CMD_ID_FILE_INFO_LIST_STOP, BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO, BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD, BC_CMD_ID_FILE_INFO_LIST_OPEN, BC_CMD_ID_FILE_INFO_LIST_GET, BC_CMD_ID_FILE_INFO_LIST_CLOSE, BC_CMD_ID_FIND_REC_VIDEO_OPEN, BC_CMD_ID_FIND_REC_VIDEO_GET, BC_CMD_ID_FIND_REC_VIDEO_CLOSE, BC_CMD_ID_COVER_PREVIEW, BC_CMD_ID_COVER_STANDALONE_458, BC_CMD_ID_COVER_STANDALONE_459, BC_CMD_ID_COVER_STANDALONE_460, BC_CMD_ID_COVER_STANDALONE_461, BC_CMD_ID_COVER_STANDALONE_462, BC_CMD_ID_COVER_RESPONSE, BC_CMD_ID_TALK_ABILITY, BC_CMD_ID_TALK_RESET, BC_CMD_ID_TALK_CONFIG, BC_CMD_ID_TALK, BC_CMD_ID_PTZ_CONTROL, BC_CMD_ID_PTZ_CONTROL_PRESET, BC_CMD_ID_GET_PTZ_PRESET, BC_CMD_ID_GET_PTZ_POSITION, BC_CMD_ID_GET_ZOOM_FOCUS, BC_CMD_ID_SET_ZOOM_FOCUS, BC_CMD_ID_GET_BATTERY_INFO_LIST, BC_CMD_ID_GET_BATTERY_INFO, BC_CMD_ID_UDP_KEEP_ALIVE, BC_CMD_ID_GET_PIR_INFO, BC_CMD_ID_SET_PIR_INFO, BC_CMD_ID_GET_MOTION_ALARM, BC_CMD_ID_SET_MOTION_ALARM, BC_CMD_ID_ALARM_EVENT_LIST, BC_CMD_ID_GET_AI_ALARM, BC_CMD_ID_SET_AI_ALARM, BC_CMD_ID_GET_AUDIO_ALARM, BC_CMD_ID_AUDIO_ALARM_PLAY, BC_CMD_ID_GET_WHITE_LED, BC_CMD_ID_SET_WHITE_LED_STATE, BC_CMD_ID_SET_WHITE_LED_TASK, BC_CMD_ID_FLOODLIGHT_STATUS_LIST, BC_CMD_ID_ABILITY_INFO, BC_CMD_ID_SUPPORT, BC_CMD_ID_PING, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_RECORD, BC_CMD_ID_GET_HDD_INFO_LIST, BC_CMD_ID_GET_WIFI_SIGNAL, BC_CMD_ID_GET_WIFI, BC_CMD_ID_GET_ONLINE_USER_LIST, BC_CMD_ID_GET_DAY_RECORDS, BC_CMD_ID_GET_STREAM_INFO_LIST, BC_CMD_ID_GET_LED_STATE, BC_CMD_ID_GET_EMAIL_TASK, BC_CMD_ID_GET_AUDIO_TASK, BC_CMD_ID_GET_AUDIO_CFG, BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_TIMELAPSE_CFG, BC_CMD_ID_GET_AI_DENOISE, BC_CMD_ID_GET_KIT_AP_CFG, BC_CMD_ID_GET_REC_ENC_CFG, BC_CMD_ID_GET_ACCESS_USER_LIST, BC_CMD_ID_GET_SLEEP_STATE, BC_CMD_ID_GET_VIDEO_INPUT, BC_CMD_ID_GET_SYSTEM_GENERAL, BC_CMD_ID_GET_SUPPORT, BC_CMD_ID_GET_AI_CFG, BC_CMD_ID_SET_AI_CFG, BC_CMD_ID_GET_SIREN_STATUS, BC_CMD_ID_SET_AUDIO_TASK, BC_CMD_ID_CMD_123, BC_CMD_ID_CMD_209, BC_CMD_ID_CMD_265, BC_CMD_ID_CMD_440, BC_CMD_ID_PUSH_VIDEO_INPUT, BC_CMD_ID_PUSH_SERIAL, BC_CMD_ID_PUSH_NET_INFO, BC_CMD_ID_PUSH_DINGDONG_LIST, BC_CMD_ID_PUSH_SLEEP_STATUS, BC_CMD_ID_PUSH_COORDINATE_POINT_LIST, BC_CMD_ID_DING_DONG_CTRL, BC_CMD_ID_GET_DING_DONG_LIST, BC_CMD_ID_DING_DONG_OPT, BC_CMD_ID_GET_DING_DONG_CFG, BC_CMD_ID_SET_DING_DONG_CFG, BC_CMD_ID_QUICK_REPLY_PLAY, BC_CMD_ID_GET_DING_DONG_SILENT, BC_CMD_ID_SET_DING_DONG_SILENT;
|
|
38
38
|
var init_constants = __esm({
|
|
39
39
|
"src/protocol/constants.ts"() {
|
|
40
40
|
"use strict";
|
|
@@ -147,6 +147,14 @@ var init_constants = __esm({
|
|
|
147
147
|
BC_CMD_ID_PUSH_DINGDONG_LIST = 484;
|
|
148
148
|
BC_CMD_ID_PUSH_SLEEP_STATUS = 623;
|
|
149
149
|
BC_CMD_ID_PUSH_COORDINATE_POINT_LIST = 723;
|
|
150
|
+
BC_CMD_ID_DING_DONG_CTRL = 483;
|
|
151
|
+
BC_CMD_ID_GET_DING_DONG_LIST = 484;
|
|
152
|
+
BC_CMD_ID_DING_DONG_OPT = 485;
|
|
153
|
+
BC_CMD_ID_GET_DING_DONG_CFG = 486;
|
|
154
|
+
BC_CMD_ID_SET_DING_DONG_CFG = 487;
|
|
155
|
+
BC_CMD_ID_QUICK_REPLY_PLAY = 349;
|
|
156
|
+
BC_CMD_ID_GET_DING_DONG_SILENT = 609;
|
|
157
|
+
BC_CMD_ID_SET_DING_DONG_SILENT = 610;
|
|
150
158
|
}
|
|
151
159
|
});
|
|
152
160
|
|
|
@@ -7331,6 +7339,8 @@ __export(index_exports, {
|
|
|
7331
7339
|
BC_CMD_ID_COVER_STANDALONE_460: () => BC_CMD_ID_COVER_STANDALONE_460,
|
|
7332
7340
|
BC_CMD_ID_COVER_STANDALONE_461: () => BC_CMD_ID_COVER_STANDALONE_461,
|
|
7333
7341
|
BC_CMD_ID_COVER_STANDALONE_462: () => BC_CMD_ID_COVER_STANDALONE_462,
|
|
7342
|
+
BC_CMD_ID_DING_DONG_CTRL: () => BC_CMD_ID_DING_DONG_CTRL,
|
|
7343
|
+
BC_CMD_ID_DING_DONG_OPT: () => BC_CMD_ID_DING_DONG_OPT,
|
|
7334
7344
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE: () => BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|
|
7335
7345
|
BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO: () => BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO,
|
|
7336
7346
|
BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD: () => BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD,
|
|
@@ -7354,6 +7364,9 @@ __export(index_exports, {
|
|
|
7354
7364
|
BC_CMD_ID_GET_BATTERY_INFO_LIST: () => BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
7355
7365
|
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD: () => BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
7356
7366
|
BC_CMD_ID_GET_DAY_RECORDS: () => BC_CMD_ID_GET_DAY_RECORDS,
|
|
7367
|
+
BC_CMD_ID_GET_DING_DONG_CFG: () => BC_CMD_ID_GET_DING_DONG_CFG,
|
|
7368
|
+
BC_CMD_ID_GET_DING_DONG_LIST: () => BC_CMD_ID_GET_DING_DONG_LIST,
|
|
7369
|
+
BC_CMD_ID_GET_DING_DONG_SILENT: () => BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
7357
7370
|
BC_CMD_ID_GET_EMAIL_TASK: () => BC_CMD_ID_GET_EMAIL_TASK,
|
|
7358
7371
|
BC_CMD_ID_GET_FTP_TASK: () => BC_CMD_ID_GET_FTP_TASK,
|
|
7359
7372
|
BC_CMD_ID_GET_HDD_INFO_LIST: () => BC_CMD_ID_GET_HDD_INFO_LIST,
|
|
@@ -7390,9 +7403,12 @@ __export(index_exports, {
|
|
|
7390
7403
|
BC_CMD_ID_PUSH_SERIAL: () => BC_CMD_ID_PUSH_SERIAL,
|
|
7391
7404
|
BC_CMD_ID_PUSH_SLEEP_STATUS: () => BC_CMD_ID_PUSH_SLEEP_STATUS,
|
|
7392
7405
|
BC_CMD_ID_PUSH_VIDEO_INPUT: () => BC_CMD_ID_PUSH_VIDEO_INPUT,
|
|
7406
|
+
BC_CMD_ID_QUICK_REPLY_PLAY: () => BC_CMD_ID_QUICK_REPLY_PLAY,
|
|
7393
7407
|
BC_CMD_ID_SET_AI_ALARM: () => BC_CMD_ID_SET_AI_ALARM,
|
|
7394
7408
|
BC_CMD_ID_SET_AI_CFG: () => BC_CMD_ID_SET_AI_CFG,
|
|
7395
7409
|
BC_CMD_ID_SET_AUDIO_TASK: () => BC_CMD_ID_SET_AUDIO_TASK,
|
|
7410
|
+
BC_CMD_ID_SET_DING_DONG_CFG: () => BC_CMD_ID_SET_DING_DONG_CFG,
|
|
7411
|
+
BC_CMD_ID_SET_DING_DONG_SILENT: () => BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
7396
7412
|
BC_CMD_ID_SET_MOTION_ALARM: () => BC_CMD_ID_SET_MOTION_ALARM,
|
|
7397
7413
|
BC_CMD_ID_SET_PIR_INFO: () => BC_CMD_ID_SET_PIR_INFO,
|
|
7398
7414
|
BC_CMD_ID_SET_WHITE_LED_STATE: () => BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
@@ -7509,6 +7525,7 @@ __export(index_exports, {
|
|
|
7509
7525
|
getGlobalLogger: () => getGlobalLogger,
|
|
7510
7526
|
getH265NalType: () => getH265NalType,
|
|
7511
7527
|
getMjpegContentType: () => getMjpegContentType,
|
|
7528
|
+
getSupportItemForChannel: () => getSupportItemForChannel,
|
|
7512
7529
|
getVideoStream: () => getVideoStream,
|
|
7513
7530
|
getVideoclipClientInfo: () => getVideoclipClientInfo,
|
|
7514
7531
|
getXmlText: () => getXmlText,
|
|
@@ -9232,6 +9249,13 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
9232
9249
|
* and may leave device-side sessions in a bad state.
|
|
9233
9250
|
*/
|
|
9234
9251
|
static coverPreviewQueueTail = /* @__PURE__ */ new Map();
|
|
9252
|
+
/**
|
|
9253
|
+
* Global CoverPreview backoff – increases on 400 rejection, resets on success.
|
|
9254
|
+
* Prevents flooding the camera when it's overwhelmed.
|
|
9255
|
+
*/
|
|
9256
|
+
static coverPreviewBackoffMs = /* @__PURE__ */ new Map();
|
|
9257
|
+
static COVER_PREVIEW_INITIAL_BACKOFF_MS = 1e3;
|
|
9258
|
+
static COVER_PREVIEW_MAX_BACKOFF_MS = 3e4;
|
|
9235
9259
|
opts;
|
|
9236
9260
|
debugCfg;
|
|
9237
9261
|
logger;
|
|
@@ -9379,7 +9403,7 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
9379
9403
|
if (!this.isIdleDisconnectEnabled()) return false;
|
|
9380
9404
|
if (!this.isSocketConnected()) return false;
|
|
9381
9405
|
if (this.pending.size > 0) return false;
|
|
9382
|
-
if (this.
|
|
9406
|
+
if (this.isDeviceStreamingActive()) return false;
|
|
9383
9407
|
if (this.permits.size > 0) return false;
|
|
9384
9408
|
return true;
|
|
9385
9409
|
}
|
|
@@ -9393,7 +9417,18 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
9393
9417
|
const delayMs = Math.max(0, timeoutMs - elapsedMs);
|
|
9394
9418
|
this.idleDisconnectTimer = setTimeout(() => {
|
|
9395
9419
|
try {
|
|
9396
|
-
if (!this.isIdleDisconnectEligibleNow())
|
|
9420
|
+
if (!this.isIdleDisconnectEligibleNow()) {
|
|
9421
|
+
this.logDebug("idle_disconnect_blocked", {
|
|
9422
|
+
reason: "not eligible",
|
|
9423
|
+
socketConnected: this.isSocketConnected(),
|
|
9424
|
+
pending: this.pending.size,
|
|
9425
|
+
deviceStreamingActive: this.isDeviceStreamingActive(),
|
|
9426
|
+
localVideoSubs: this.hasActiveVideoSubscriptionsInternal(),
|
|
9427
|
+
permits: this.permits.size,
|
|
9428
|
+
host: this.opts.host
|
|
9429
|
+
});
|
|
9430
|
+
return;
|
|
9431
|
+
}
|
|
9397
9432
|
if (this.lastUserActivityAtMs == null) return;
|
|
9398
9433
|
const elapsed2 = Date.now() - this.lastUserActivityAtMs;
|
|
9399
9434
|
if (elapsed2 < timeoutMs) {
|
|
@@ -9507,7 +9542,34 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
9507
9542
|
async withSerializedCoverPreview(fn) {
|
|
9508
9543
|
const key = this.getCoverPreviewQueueKey();
|
|
9509
9544
|
const prevTail = _BaichuanClient.coverPreviewQueueTail.get(key) ?? Promise.resolve();
|
|
9510
|
-
const run = prevTail.catch(() => void 0).then(
|
|
9545
|
+
const run = prevTail.catch(() => void 0).then(async () => {
|
|
9546
|
+
const backoffMs = _BaichuanClient.coverPreviewBackoffMs.get(key) ?? 0;
|
|
9547
|
+
if (backoffMs > 0) {
|
|
9548
|
+
this.logDebug("coverpreview_backoff_wait", { backoffMs });
|
|
9549
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
9550
|
+
}
|
|
9551
|
+
try {
|
|
9552
|
+
const result = await fn();
|
|
9553
|
+
_BaichuanClient.coverPreviewBackoffMs.delete(key);
|
|
9554
|
+
return result;
|
|
9555
|
+
} catch (e) {
|
|
9556
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
9557
|
+
const is400 = msg.includes("rejected") && (msg.includes("responseCode=400") || msg.includes("resp_code=400"));
|
|
9558
|
+
if (is400) {
|
|
9559
|
+
const current = _BaichuanClient.coverPreviewBackoffMs.get(key) ?? 0;
|
|
9560
|
+
const next = current === 0 ? _BaichuanClient.COVER_PREVIEW_INITIAL_BACKOFF_MS : Math.min(
|
|
9561
|
+
current * 2,
|
|
9562
|
+
_BaichuanClient.COVER_PREVIEW_MAX_BACKOFF_MS
|
|
9563
|
+
);
|
|
9564
|
+
_BaichuanClient.coverPreviewBackoffMs.set(key, next);
|
|
9565
|
+
this.logDebug("coverpreview_backoff_increased", {
|
|
9566
|
+
previous: current,
|
|
9567
|
+
next
|
|
9568
|
+
});
|
|
9569
|
+
}
|
|
9570
|
+
throw e;
|
|
9571
|
+
}
|
|
9572
|
+
});
|
|
9511
9573
|
const tail = run.then(
|
|
9512
9574
|
() => void 0,
|
|
9513
9575
|
() => void 0
|
|
@@ -11829,32 +11891,36 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
11829
11891
|
* Send CoverPreview command (cmd_id=298) to get an I-frame from a past recording.
|
|
11830
11892
|
* Similar to sendBinarySnapshot109 but handles the stream header + frame format
|
|
11831
11893
|
* instead of JPEG.
|
|
11894
|
+
*
|
|
11895
|
+
* Retry is minimal (2 attempts) – the global backoff in `withSerializedCoverPreview`
|
|
11896
|
+
* throttles subsequent requests when the camera is overwhelmed.
|
|
11897
|
+
* PCAP analysis shows the camera routinely rejects the first request with 400.
|
|
11832
11898
|
*/
|
|
11833
11899
|
async sendBinaryCoverPreview(params) {
|
|
11834
11900
|
return await this.withSerializedCoverPreview(async () => {
|
|
11835
|
-
const
|
|
11836
|
-
const
|
|
11901
|
+
const maxAttempts = 5;
|
|
11902
|
+
const retryDelay = 1500;
|
|
11837
11903
|
let lastError;
|
|
11838
|
-
for (let attempt = 0; attempt <
|
|
11904
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
11839
11905
|
try {
|
|
11840
11906
|
return await this._sendBinaryCoverPreviewOnce(params);
|
|
11841
11907
|
} catch (e) {
|
|
11842
11908
|
const msg = e instanceof Error ? e.message : String(e);
|
|
11843
11909
|
lastError = e instanceof Error ? e : new Error(msg);
|
|
11844
|
-
const
|
|
11845
|
-
if (
|
|
11910
|
+
const is400 = msg.includes("rejected") && (msg.includes("responseCode=400") || msg.includes("resp_code=400"));
|
|
11911
|
+
if (is400 && attempt < maxAttempts - 1) {
|
|
11846
11912
|
this.logDebug("coverpreview_retry_400", {
|
|
11847
11913
|
attempt: attempt + 1,
|
|
11848
|
-
|
|
11849
|
-
|
|
11914
|
+
maxAttempts,
|
|
11915
|
+
retryDelay
|
|
11850
11916
|
});
|
|
11851
|
-
await new Promise((
|
|
11917
|
+
await new Promise((r) => setTimeout(r, retryDelay));
|
|
11852
11918
|
continue;
|
|
11853
11919
|
}
|
|
11854
11920
|
throw lastError;
|
|
11855
11921
|
}
|
|
11856
11922
|
}
|
|
11857
|
-
throw lastError ?? new Error("CoverPreview failed after all
|
|
11923
|
+
throw lastError ?? new Error("CoverPreview failed after all attempts");
|
|
11858
11924
|
});
|
|
11859
11925
|
}
|
|
11860
11926
|
/**
|
|
@@ -12784,6 +12850,8 @@ var NativeStreamFanout = class {
|
|
|
12784
12850
|
} finally {
|
|
12785
12851
|
for (const q of this.queues.values()) q.close();
|
|
12786
12852
|
this.queues.clear();
|
|
12853
|
+
this.running = false;
|
|
12854
|
+
this.opts.onEnd?.();
|
|
12787
12855
|
}
|
|
12788
12856
|
})();
|
|
12789
12857
|
}
|
|
@@ -13109,7 +13177,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13109
13177
|
this.logger.warn(
|
|
13110
13178
|
`[BaichuanRtspServer] Could not get stream metadata: ${error}`
|
|
13111
13179
|
);
|
|
13112
|
-
this.streamMetadata = { frameRate: 25
|
|
13180
|
+
this.streamMetadata = { frameRate: 25 };
|
|
13113
13181
|
this.setFlowVideoType("H264", "metadata unavailable");
|
|
13114
13182
|
}
|
|
13115
13183
|
this.clientConnectionServer = net2.createServer((socket) => {
|
|
@@ -13141,7 +13209,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13141
13209
|
*/
|
|
13142
13210
|
handleRtspConnection(socket) {
|
|
13143
13211
|
const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
13144
|
-
|
|
13212
|
+
const connectTime = Date.now();
|
|
13213
|
+
this.logger.info(
|
|
13214
|
+
`[rebroadcast] client connected client=${clientId} path=${this.path} profile=${this.profile} channel=${this.channel}`
|
|
13215
|
+
);
|
|
13145
13216
|
let sessionId = "";
|
|
13146
13217
|
let buffer = Buffer.alloc(0);
|
|
13147
13218
|
let clientFfmpeg;
|
|
@@ -13149,6 +13220,12 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13149
13220
|
let clientUdpSocket = null;
|
|
13150
13221
|
let clientUdpSocketAudio = null;
|
|
13151
13222
|
const cleanup = () => {
|
|
13223
|
+
const sessionDurationMs = Date.now() - connectTime;
|
|
13224
|
+
const res = this.clientResources.get(clientId);
|
|
13225
|
+
const framesSent = res?.framesSent ?? 0;
|
|
13226
|
+
this.logger.info(
|
|
13227
|
+
`[rebroadcast] client disconnected client=${clientId} path=${this.path} profile=${this.profile} duration=${sessionDurationMs}ms frames=${framesSent}`
|
|
13228
|
+
);
|
|
13152
13229
|
this.removeClient(clientId);
|
|
13153
13230
|
this.authNonces.delete(clientId);
|
|
13154
13231
|
const resources = this.clientResources.get(clientId);
|
|
@@ -13290,7 +13367,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13290
13367
|
Public: "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS"
|
|
13291
13368
|
});
|
|
13292
13369
|
} else if (method === "DESCRIBE") {
|
|
13293
|
-
if (!this.
|
|
13370
|
+
if (!this.flow.getFmtp().hasParamSets && this.connectedClients.size === 0) {
|
|
13294
13371
|
try {
|
|
13295
13372
|
if (!this.nativeStreamActive) {
|
|
13296
13373
|
await this.startNativeStream();
|
|
@@ -13372,7 +13449,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13372
13449
|
seenFirstVideoKeyframe: false,
|
|
13373
13450
|
setupTrack0: false,
|
|
13374
13451
|
setupTrack1: false,
|
|
13375
|
-
isPlaying: false
|
|
13452
|
+
isPlaying: false,
|
|
13453
|
+
connectTime
|
|
13376
13454
|
});
|
|
13377
13455
|
} else {
|
|
13378
13456
|
existing.rtspSocket = socket;
|
|
@@ -13419,8 +13497,10 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13419
13497
|
if (resources) {
|
|
13420
13498
|
if (isTrack1) resources.setupTrack1 = true;
|
|
13421
13499
|
else resources.setupTrack0 = true;
|
|
13422
|
-
|
|
13423
|
-
|
|
13500
|
+
const transport2 = useTcpInterleaved ? "TCP/interleaved" : "UDP";
|
|
13501
|
+
const track = isTrack1 ? "track1(audio)" : "track0(video)";
|
|
13502
|
+
this.logger.info(
|
|
13503
|
+
`[rebroadcast] SETUP client=${clientId} ${track} transport=${transport2} session=${sessionId}`
|
|
13424
13504
|
);
|
|
13425
13505
|
}
|
|
13426
13506
|
}
|
|
@@ -13445,8 +13525,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13445
13525
|
const resources = this.clientResources.get(clientId);
|
|
13446
13526
|
if (resources) {
|
|
13447
13527
|
resources.isPlaying = true;
|
|
13448
|
-
|
|
13449
|
-
|
|
13528
|
+
const hasAudio = !!resources.setupTrack1;
|
|
13529
|
+
this.logger.info(
|
|
13530
|
+
`[rebroadcast] PLAY client=${clientId} path=${this.path} profile=${this.profile} channel=${this.channel} codec=${this.flow.sdpCodec} audio=${hasAudio} session=${sessionId}`
|
|
13450
13531
|
);
|
|
13451
13532
|
}
|
|
13452
13533
|
}
|
|
@@ -13455,6 +13536,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13455
13536
|
Range: "npt=0.000-"
|
|
13456
13537
|
});
|
|
13457
13538
|
} else if (method === "TEARDOWN") {
|
|
13539
|
+
this.logger.info(
|
|
13540
|
+
`[rebroadcast] TEARDOWN client=${clientId} session=${sessionId}`
|
|
13541
|
+
);
|
|
13458
13542
|
cleanup();
|
|
13459
13543
|
sendResponse(200, "OK", {
|
|
13460
13544
|
Session: sessionId
|
|
@@ -13549,7 +13633,7 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13549
13633
|
this.logger.warn(
|
|
13550
13634
|
`[BaichuanRtspServer] Could not fetch stream metadata: ${error}`
|
|
13551
13635
|
);
|
|
13552
|
-
streamMetadata = { frameRate: 25
|
|
13636
|
+
streamMetadata = { frameRate: 25 };
|
|
13553
13637
|
}
|
|
13554
13638
|
}
|
|
13555
13639
|
const ffmpegFormat = this.flow.ffmpegFormat;
|
|
@@ -14137,15 +14221,17 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14137
14221
|
`Sent ${frameCount} frames to client ${clientId} (frame size: ${frame.data.length} bytes)`
|
|
14138
14222
|
);
|
|
14139
14223
|
}
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
|
|
14224
|
+
if (!useDirectRtp) {
|
|
14225
|
+
const now = Date.now();
|
|
14226
|
+
const timeSinceLastFrame = now - lastFrameTime;
|
|
14227
|
+
const waitTime = targetFrameInterval - timeSinceLastFrame;
|
|
14228
|
+
if (waitTime > 0) {
|
|
14229
|
+
await new Promise(
|
|
14230
|
+
(resolve) => setTimeout(resolve, Math.min(waitTime, targetFrameInterval * 2))
|
|
14231
|
+
);
|
|
14232
|
+
}
|
|
14233
|
+
lastFrameTime = Date.now();
|
|
14147
14234
|
}
|
|
14148
|
-
lastFrameTime = Date.now();
|
|
14149
14235
|
if (useDirectRtp) {
|
|
14150
14236
|
const videoType = frame.videoType ?? this.flow.videoType;
|
|
14151
14237
|
const normalizedVideoData = videoType === "H264" ? convertToAnnexB(frame.data) : convertToAnnexB2(frame.data);
|
|
@@ -14218,6 +14304,11 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14218
14304
|
}
|
|
14219
14305
|
if (!firstVideoWriteLogged) {
|
|
14220
14306
|
firstVideoWriteLogged = true;
|
|
14307
|
+
const clientConnectTime = resources?.connectTime ?? Date.now();
|
|
14308
|
+
const ttffMs = Date.now() - clientConnectTime;
|
|
14309
|
+
this.logger.info(
|
|
14310
|
+
`[rebroadcast] first keyframe \u2192 client client=${clientId} codec=${videoType} ttff=${ttffMs}ms`
|
|
14311
|
+
);
|
|
14221
14312
|
if (rtspDebug) {
|
|
14222
14313
|
const headHex = frame.data.subarray(0, 16).toString("hex");
|
|
14223
14314
|
rtspDebugLog(
|
|
@@ -14225,6 +14316,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14225
14316
|
);
|
|
14226
14317
|
}
|
|
14227
14318
|
}
|
|
14319
|
+
if (resources) {
|
|
14320
|
+
resources.framesSent = (resources.framesSent ?? 0) + 1;
|
|
14321
|
+
}
|
|
14228
14322
|
sendVideoAccessUnit(videoType, normalizedVideoData, true);
|
|
14229
14323
|
} else {
|
|
14230
14324
|
try {
|
|
@@ -14309,8 +14403,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14309
14403
|
this.firstAudioPromise = new Promise((resolve) => {
|
|
14310
14404
|
this.firstAudioResolve = resolve;
|
|
14311
14405
|
});
|
|
14312
|
-
this.
|
|
14313
|
-
`
|
|
14406
|
+
this.logger.info(
|
|
14407
|
+
`[rebroadcast] native stream starting profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
14314
14408
|
);
|
|
14315
14409
|
await this.flow.startKeepAlive(this.api);
|
|
14316
14410
|
this.nativeFanout = new NativeStreamFanout({
|
|
@@ -14353,6 +14447,23 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14353
14447
|
this.logger.warn(
|
|
14354
14448
|
`[BaichuanRtspServer] Shared native stream error: ${error}`
|
|
14355
14449
|
);
|
|
14450
|
+
},
|
|
14451
|
+
onEnd: () => {
|
|
14452
|
+
if (!this.nativeStreamActive) return;
|
|
14453
|
+
this.nativeStreamActive = false;
|
|
14454
|
+
this.firstFrameReceived = false;
|
|
14455
|
+
this.firstFramePromise = null;
|
|
14456
|
+
this.firstFrameResolve = null;
|
|
14457
|
+
this.nativeFanout = null;
|
|
14458
|
+
this.logger.info(
|
|
14459
|
+
`[rebroadcast] native stream ended (camera sleeping or connection lost) profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
14460
|
+
);
|
|
14461
|
+
if (this.connectedClients.size > 0) {
|
|
14462
|
+
this.logger.info(
|
|
14463
|
+
`[rebroadcast] restarting native stream for ${this.connectedClients.size} active client(s)`
|
|
14464
|
+
);
|
|
14465
|
+
setImmediate(() => void this.startNativeStream());
|
|
14466
|
+
}
|
|
14356
14467
|
}
|
|
14357
14468
|
});
|
|
14358
14469
|
this.nativeFanout.start();
|
|
@@ -14391,7 +14502,9 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14391
14502
|
if (!this.nativeStreamActive) {
|
|
14392
14503
|
return;
|
|
14393
14504
|
}
|
|
14394
|
-
this.
|
|
14505
|
+
this.logger.info(
|
|
14506
|
+
`[rebroadcast] native stream stopping profile=${this.profile} channel=${this.channel} clients=${this.connectedClients.size}`
|
|
14507
|
+
);
|
|
14395
14508
|
this.flow.stopKeepAlive();
|
|
14396
14509
|
this.clearNoClientAutoStopTimer();
|
|
14397
14510
|
this.nativeStreamActive = false;
|
|
@@ -14425,9 +14538,6 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14425
14538
|
if (this.connectedClients.has(clientId)) {
|
|
14426
14539
|
this.connectedClients.delete(clientId);
|
|
14427
14540
|
this.emit("clientDisconnected", clientId);
|
|
14428
|
-
this.logger.info(
|
|
14429
|
-
`[BaichuanRtspServer] RTSP client disconnected: ${clientId}`
|
|
14430
|
-
);
|
|
14431
14541
|
if (this.connectedClients.size === 0) {
|
|
14432
14542
|
void this.stopNativeStream();
|
|
14433
14543
|
}
|
|
@@ -14902,10 +15012,12 @@ function parseSupportXml(xml) {
|
|
|
14902
15012
|
}
|
|
14903
15013
|
function getSupportItemForChannel(support, channel) {
|
|
14904
15014
|
if (!support?.items?.length) return void 0;
|
|
14905
|
-
const
|
|
15015
|
+
const candidates = support.items.filter((i) => i.chnID === channel);
|
|
15016
|
+
if (!candidates.length) return void 0;
|
|
15017
|
+
const score = (item) => {
|
|
14906
15018
|
const anyItem = item;
|
|
14907
|
-
let
|
|
14908
|
-
if (anyItem.name == null)
|
|
15019
|
+
let result = 0;
|
|
15020
|
+
if (anyItem.name == null) result += 100;
|
|
14909
15021
|
const capabilityKeys = [
|
|
14910
15022
|
"ptzType",
|
|
14911
15023
|
"ptzControl",
|
|
@@ -14917,20 +15029,17 @@ function getSupportItemForChannel(support, channel) {
|
|
|
14917
15029
|
"motion",
|
|
14918
15030
|
"encCtrl",
|
|
14919
15031
|
"newIspCfg",
|
|
14920
|
-
"remoteAbility"
|
|
15032
|
+
"remoteAbility",
|
|
15033
|
+
"aitype",
|
|
15034
|
+
"videoClip",
|
|
15035
|
+
"snap"
|
|
14921
15036
|
];
|
|
14922
15037
|
for (const k of capabilityKeys) {
|
|
14923
|
-
if (anyItem[k] !== void 0)
|
|
15038
|
+
if (anyItem[k] !== void 0) result += 3;
|
|
14924
15039
|
}
|
|
14925
|
-
|
|
14926
|
-
return score;
|
|
14927
|
-
};
|
|
14928
|
-
const pickBest = (chnId) => {
|
|
14929
|
-
const candidates = support.items.filter((i) => i.chnID === chnId);
|
|
14930
|
-
if (!candidates.length) return void 0;
|
|
14931
|
-
return candidates.slice().sort((a, b) => scoreSupportItem(b) - scoreSupportItem(a))[0];
|
|
15040
|
+
return result;
|
|
14932
15041
|
};
|
|
14933
|
-
return
|
|
15042
|
+
return candidates.sort((a, b) => score(b) - score(a))[0];
|
|
14934
15043
|
}
|
|
14935
15044
|
function computeDeviceCapabilities(params) {
|
|
14936
15045
|
const { channel } = params;
|
|
@@ -14962,6 +15071,7 @@ function computeDeviceCapabilities(params) {
|
|
|
14962
15071
|
flat,
|
|
14963
15072
|
/white\s*led|whiteLed|flood\s*light|floodlight/i
|
|
14964
15073
|
);
|
|
15074
|
+
const hasSirenFromSupport = supportItem ? isTruthyNumberLike(supportItem.audioVersion) : false;
|
|
14965
15075
|
const hasSirenFromAbilities = abilitiesHasAny(
|
|
14966
15076
|
flat,
|
|
14967
15077
|
/audio\s*alarm|audioAlarm|siren|pushAlarn|audioPlay/i
|
|
@@ -14974,6 +15084,9 @@ function computeDeviceCapabilities(params) {
|
|
|
14974
15084
|
const hasPirFromSupport = supportItem ? isTruthyNumberLike(supportItem.rfCfg) || isTruthyNumberLike(supportItem.newRfCfg) || isTruthyNumberLike(supportItem.rfVersion) || isTruthyNumberLike(supportItem.battery) : false;
|
|
14975
15085
|
const hasAutotrackingFromSupport = supportItem ? isTruthyNumberLike(supportItem.autoPt) || isTruthyNumberLike(supportItem.smartAI) : false;
|
|
14976
15086
|
const hasAutotrackingFromAbilities = abilitiesHasAny(flat, /smartTrack/i);
|
|
15087
|
+
const hasBattery = hasBatteryFromSupport || hasBatteryFromAbilities;
|
|
15088
|
+
const isDoorbell = isDoorbellFromSupport || isDoorbellFromModel;
|
|
15089
|
+
const hasWirelessChimeFromAbilities = abilitiesHasAny(flat, /dingDong|dingdong/i);
|
|
14977
15090
|
const hasPan = hasPanTiltFromSupport || hasPanTiltFromAbilities;
|
|
14978
15091
|
const hasTilt = hasPanTiltFromSupport || hasPanTiltFromAbilities;
|
|
14979
15092
|
const hasZoom = hasZoomFromSupport || hasZoomFromAbilities;
|
|
@@ -14989,14 +15102,15 @@ function computeDeviceCapabilities(params) {
|
|
|
14989
15102
|
hasZoom: finalHasZoom,
|
|
14990
15103
|
hasPresets: finalHasPresets,
|
|
14991
15104
|
hasPtz: ptzDisabledBySupport ? false : hasPtzFromSupport || finalHasPan || finalHasTilt || finalHasZoom || finalHasPresets,
|
|
14992
|
-
hasBattery
|
|
15105
|
+
hasBattery,
|
|
14993
15106
|
hasIntercom: hasIntercomFromSupport,
|
|
14994
|
-
hasSiren: hasSirenFromAbilities,
|
|
15107
|
+
hasSiren: hasSirenFromSupport || hasSirenFromAbilities,
|
|
14995
15108
|
// lightType >= 2 indicates controllable white LED / floodlight (1 = IR only)
|
|
14996
15109
|
hasFloodlight: Number.isFinite(lightType) ? lightType >= 2 : hasFloodlightFromAbilities,
|
|
14997
15110
|
hasPir: hasPirFromAbilities || hasPirFromSupport,
|
|
14998
|
-
isDoorbell
|
|
14999
|
-
hasAutotracking: hasAutotrackingFromSupport || hasAutotrackingFromAbilities
|
|
15111
|
+
isDoorbell,
|
|
15112
|
+
hasAutotracking: ptzDisabledBySupport ? false : hasAutotrackingFromSupport || hasAutotrackingFromAbilities,
|
|
15113
|
+
hasWirelessChime: isDoorbell || hasWirelessChimeFromAbilities
|
|
15000
15114
|
};
|
|
15001
15115
|
if (ptzMode !== void 0) result.ptzMode = ptzMode;
|
|
15002
15116
|
return result;
|
|
@@ -16661,6 +16775,162 @@ var discoverDeviceUidViaBaichuanGetP2p = async (params) => {
|
|
|
16661
16775
|
// src/reolink/baichuan/ReolinkBaichuanApi.ts
|
|
16662
16776
|
init_recordingFileName();
|
|
16663
16777
|
|
|
16778
|
+
// src/reolink/baichuan/utils/chime.ts
|
|
16779
|
+
init_xml();
|
|
16780
|
+
var buildDingDongGetParamsXml = (chimeId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16781
|
+
<body>
|
|
16782
|
+
<dingdongDeviceOpt version="1.1">
|
|
16783
|
+
<id>${chimeId}</id>
|
|
16784
|
+
<opt>getParam</opt>
|
|
16785
|
+
</dingdongDeviceOpt>
|
|
16786
|
+
</body>`;
|
|
16787
|
+
var buildDingDongSetParamsXml = (chimeId, params) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16788
|
+
<body>
|
|
16789
|
+
<dingdongDeviceOpt version="1.1">
|
|
16790
|
+
<opt>setParam</opt>
|
|
16791
|
+
<id>${chimeId}</id>
|
|
16792
|
+
${params.volLevel !== void 0 ? `<volLevel>${params.volLevel}</volLevel>` : ""}
|
|
16793
|
+
${params.ledState !== void 0 ? `<ledState>${params.ledState}</ledState>` : ""}
|
|
16794
|
+
${params.name !== void 0 ? `<name>${params.name}</name>` : ""}
|
|
16795
|
+
</dingdongDeviceOpt>
|
|
16796
|
+
</body>`;
|
|
16797
|
+
var buildDingDongRingXml = (chimeId, musicId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16798
|
+
<body>
|
|
16799
|
+
<dingdongDeviceOpt version="1.1">
|
|
16800
|
+
<id>${chimeId}</id>
|
|
16801
|
+
<opt>ringWithMusic</opt>
|
|
16802
|
+
<musicId>${musicId}</musicId>
|
|
16803
|
+
</dingdongDeviceOpt>
|
|
16804
|
+
</body>`;
|
|
16805
|
+
var buildSetDingDongCfgXml = (chimeId, eventType, state, musicId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16806
|
+
<body>
|
|
16807
|
+
<dingdongCfg version="1.1">
|
|
16808
|
+
<deviceCfg>
|
|
16809
|
+
<id>${chimeId}</id>
|
|
16810
|
+
<alarminCfg>
|
|
16811
|
+
<valid>${state}</valid>
|
|
16812
|
+
<musicId>${musicId}</musicId>
|
|
16813
|
+
<type>${eventType}</type>
|
|
16814
|
+
</alarminCfg>
|
|
16815
|
+
</deviceCfg>
|
|
16816
|
+
</dingdongCfg>
|
|
16817
|
+
</body>`;
|
|
16818
|
+
var buildGetDingDongCtrlXml = () => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16819
|
+
<body>
|
|
16820
|
+
<dingdongCtrl version="1.1">
|
|
16821
|
+
<opt>machineStateGet</opt>
|
|
16822
|
+
</dingdongCtrl>
|
|
16823
|
+
</body>`;
|
|
16824
|
+
var buildSetDingDongCtrlXml = (chimeType, enabled, time) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16825
|
+
<body>
|
|
16826
|
+
<dingdongCtrl version="1.1">
|
|
16827
|
+
<opt>machineStateSet</opt>
|
|
16828
|
+
<type>${chimeType}</type>
|
|
16829
|
+
<bopen>${enabled}</bopen>
|
|
16830
|
+
<bsave>1</bsave>
|
|
16831
|
+
<time>${time}</time>
|
|
16832
|
+
</dingdongCtrl>
|
|
16833
|
+
</body>`;
|
|
16834
|
+
var buildQuickReplyPlayXml = (channel, fileId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16835
|
+
<body>
|
|
16836
|
+
<audioFileInfo version="1.1">
|
|
16837
|
+
<channelId>${channel}</channelId>
|
|
16838
|
+
<id>${fileId}</id>
|
|
16839
|
+
<timeout>0</timeout>
|
|
16840
|
+
</audioFileInfo>
|
|
16841
|
+
</body>`;
|
|
16842
|
+
var parseDingDongListFromXml = (xml) => {
|
|
16843
|
+
const devices = [];
|
|
16844
|
+
const blocks = getXmlBlocks(xml, "dingdongDeviceInfo");
|
|
16845
|
+
for (const block of blocks) {
|
|
16846
|
+
const idText = getXmlText(block, "deviceId") ?? getXmlText(block, "id");
|
|
16847
|
+
const name = getXmlText(block, "deviceName") ?? getXmlText(block, "name") ?? "";
|
|
16848
|
+
const netStateText = getXmlText(block, "netState") ?? getXmlText(block, "netstate");
|
|
16849
|
+
if (idText === void 0) continue;
|
|
16850
|
+
const id = Number(idText);
|
|
16851
|
+
if (!Number.isFinite(id)) continue;
|
|
16852
|
+
devices.push({
|
|
16853
|
+
id,
|
|
16854
|
+
name,
|
|
16855
|
+
netState: netStateText !== void 0 ? Number(netStateText) : 0
|
|
16856
|
+
});
|
|
16857
|
+
}
|
|
16858
|
+
return devices;
|
|
16859
|
+
};
|
|
16860
|
+
var parseDingDongParamsFromXml = (xml) => {
|
|
16861
|
+
const name = getXmlText(xml, "name");
|
|
16862
|
+
const volLevelText = getXmlText(xml, "volLevel");
|
|
16863
|
+
const ledStateText = getXmlText(xml, "ledState");
|
|
16864
|
+
const result = {};
|
|
16865
|
+
if (name !== void 0) result.name = name;
|
|
16866
|
+
if (volLevelText !== void 0) {
|
|
16867
|
+
const n = Number(volLevelText);
|
|
16868
|
+
if (Number.isFinite(n)) result.volLevel = n;
|
|
16869
|
+
}
|
|
16870
|
+
if (ledStateText !== void 0) {
|
|
16871
|
+
const n = Number(ledStateText);
|
|
16872
|
+
if (Number.isFinite(n)) result.ledState = n;
|
|
16873
|
+
}
|
|
16874
|
+
return result;
|
|
16875
|
+
};
|
|
16876
|
+
var parseDingDongCfgFromXml = (xml) => {
|
|
16877
|
+
const configs = [];
|
|
16878
|
+
const deviceBlocks = getXmlBlocks(xml, "deviceCfg");
|
|
16879
|
+
for (const deviceBlock of deviceBlocks) {
|
|
16880
|
+
const idText = getXmlText(deviceBlock, "ringId") ?? getXmlText(deviceBlock, "id");
|
|
16881
|
+
if (idText === void 0) continue;
|
|
16882
|
+
const id = Number(idText);
|
|
16883
|
+
if (!Number.isFinite(id)) continue;
|
|
16884
|
+
const typeMap = {};
|
|
16885
|
+
const alarmBlocks = getXmlBlocks(deviceBlock, "alarminCfg");
|
|
16886
|
+
for (const alarmBlock of alarmBlocks) {
|
|
16887
|
+
const type = getXmlText(alarmBlock, "type");
|
|
16888
|
+
if (!type) continue;
|
|
16889
|
+
const validText = getXmlText(alarmBlock, "switch") ?? getXmlText(alarmBlock, "valid");
|
|
16890
|
+
const musicIdText = getXmlText(alarmBlock, "musicId");
|
|
16891
|
+
typeMap[type] = {
|
|
16892
|
+
valid: validText !== void 0 ? Number(validText) : 0,
|
|
16893
|
+
musicId: musicIdText !== void 0 ? Number(musicIdText) : 0
|
|
16894
|
+
};
|
|
16895
|
+
}
|
|
16896
|
+
configs.push({ id, type: typeMap });
|
|
16897
|
+
}
|
|
16898
|
+
return configs;
|
|
16899
|
+
};
|
|
16900
|
+
var parseHardwiredChimeFromXml = (xml) => {
|
|
16901
|
+
const type = getXmlText(xml, "type") ?? "";
|
|
16902
|
+
const bopenText = getXmlText(xml, "bopen") ?? getXmlText(xml, "enable");
|
|
16903
|
+
const timeText = getXmlText(xml, "time");
|
|
16904
|
+
return {
|
|
16905
|
+
type,
|
|
16906
|
+
enabled: bopenText === "1",
|
|
16907
|
+
time: timeText !== void 0 ? Number(timeText) : 0
|
|
16908
|
+
};
|
|
16909
|
+
};
|
|
16910
|
+
var buildGetDingDongSilentXml = (chimeId) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16911
|
+
<body>
|
|
16912
|
+
<dingdongSilentMode version="1.1">
|
|
16913
|
+
<id>${chimeId}</id>
|
|
16914
|
+
</dingdongSilentMode>
|
|
16915
|
+
</body>`;
|
|
16916
|
+
var buildSetDingDongSilentXml = (chimeId, time) => `<?xml version="1.0" encoding="UTF-8" ?>
|
|
16917
|
+
<body>
|
|
16918
|
+
<dingdongSilentMode version="1.1">
|
|
16919
|
+
<id>${chimeId}</id>
|
|
16920
|
+
<time>${time}</time>
|
|
16921
|
+
<type>63</type>
|
|
16922
|
+
</dingdongSilentMode>
|
|
16923
|
+
</body>`;
|
|
16924
|
+
var parseWirelessChimeSilentFromXml = (xml, chimeId) => {
|
|
16925
|
+
const timeText = getXmlText(xml, "time");
|
|
16926
|
+
const time = timeText !== void 0 ? Number(timeText) : 0;
|
|
16927
|
+
return {
|
|
16928
|
+
id: chimeId,
|
|
16929
|
+
time,
|
|
16930
|
+
active: time === 0
|
|
16931
|
+
};
|
|
16932
|
+
};
|
|
16933
|
+
|
|
16664
16934
|
// src/reolink/baichuan/utils/eventsGetEvents.ts
|
|
16665
16935
|
init_xml();
|
|
16666
16936
|
var parseAiTypeToken = (aiTypeRaw) => {
|
|
@@ -16973,6 +17243,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
16973
17243
|
host;
|
|
16974
17244
|
username;
|
|
16975
17245
|
password;
|
|
17246
|
+
/**
|
|
17247
|
+
* Set to `true` after `close()` is called.
|
|
17248
|
+
* Once closed, the API instance should not be reused.
|
|
17249
|
+
*/
|
|
17250
|
+
_closed = false;
|
|
16976
17251
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
16977
17252
|
// SOCKET POOL - Tag-based socket management
|
|
16978
17253
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -17002,10 +17277,194 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17002
17277
|
get client() {
|
|
17003
17278
|
const entry = this.socketPool.get("general");
|
|
17004
17279
|
if (!entry) {
|
|
17280
|
+
if (this._closed) {
|
|
17281
|
+
throw new Error(
|
|
17282
|
+
"[ReolinkBaichuanApi] API has been closed \u2014 create a new instance to reconnect"
|
|
17283
|
+
);
|
|
17284
|
+
}
|
|
17005
17285
|
throw new Error("[ReolinkBaichuanApi] General socket not initialized");
|
|
17006
17286
|
}
|
|
17007
17287
|
return entry.client;
|
|
17008
17288
|
}
|
|
17289
|
+
/**
|
|
17290
|
+
* `true` after `close()` has been called. A closed API should not be reused;
|
|
17291
|
+
* the consumer should create a new instance.
|
|
17292
|
+
*/
|
|
17293
|
+
get isClosed() {
|
|
17294
|
+
return this._closed;
|
|
17295
|
+
}
|
|
17296
|
+
/**
|
|
17297
|
+
* `true` when the API is usable: not closed, general socket exists, socket
|
|
17298
|
+
* is connected and the client is logged in.
|
|
17299
|
+
*
|
|
17300
|
+
* This is the recommended way for consumers to check whether the API is
|
|
17301
|
+
* still valid before issuing commands, instead of directly accessing
|
|
17302
|
+
* `api.client.isSocketConnected()` / `api.client.loggedIn` (which throws
|
|
17303
|
+
* if the socket pool was already destroyed).
|
|
17304
|
+
*/
|
|
17305
|
+
get isReady() {
|
|
17306
|
+
if (this._closed) return false;
|
|
17307
|
+
const entry = this.socketPool.get("general");
|
|
17308
|
+
if (!entry) return false;
|
|
17309
|
+
try {
|
|
17310
|
+
return entry.client.isSocketConnected() && entry.client.loggedIn;
|
|
17311
|
+
} catch {
|
|
17312
|
+
return false;
|
|
17313
|
+
}
|
|
17314
|
+
}
|
|
17315
|
+
/** Promise tracking an in-flight reconnection from `ensureConnected()`. */
|
|
17316
|
+
_ensureConnectedPromise;
|
|
17317
|
+
/**
|
|
17318
|
+
* Ensure the "general" socket is connected and logged in.
|
|
17319
|
+
* If the socket is disconnected or the pool entry was destroyed, a new
|
|
17320
|
+
* general socket is created, logged in, and all event/push/guard listeners
|
|
17321
|
+
* are re-attached automatically.
|
|
17322
|
+
*
|
|
17323
|
+
* This is a **no-op** when the API is already {@link isReady}.
|
|
17324
|
+
*
|
|
17325
|
+
* @throws If `close()` was called — the API is permanently closed and a new
|
|
17326
|
+
* instance must be created.
|
|
17327
|
+
*/
|
|
17328
|
+
async ensureConnected() {
|
|
17329
|
+
if (this._closed) {
|
|
17330
|
+
throw new Error(
|
|
17331
|
+
"[ReolinkBaichuanApi] API has been closed \u2014 create a new instance to reconnect"
|
|
17332
|
+
);
|
|
17333
|
+
}
|
|
17334
|
+
if (this.isReady) return;
|
|
17335
|
+
if (this._ensureConnectedPromise) {
|
|
17336
|
+
return this._ensureConnectedPromise;
|
|
17337
|
+
}
|
|
17338
|
+
this._ensureConnectedPromise = this.reconnectGeneralSocket();
|
|
17339
|
+
try {
|
|
17340
|
+
await this._ensureConnectedPromise;
|
|
17341
|
+
} finally {
|
|
17342
|
+
this._ensureConnectedPromise = void 0;
|
|
17343
|
+
}
|
|
17344
|
+
}
|
|
17345
|
+
/**
|
|
17346
|
+
* Internal: destroy the current general socket (if any), create a new one,
|
|
17347
|
+
* login, and re-attach all listeners.
|
|
17348
|
+
*/
|
|
17349
|
+
async reconnectGeneralSocket() {
|
|
17350
|
+
const oldEntry = this.socketPool.get("general");
|
|
17351
|
+
if (oldEntry) {
|
|
17352
|
+
oldEntry.client.removeAllListeners();
|
|
17353
|
+
if (oldEntry.idleCloseTimer) clearTimeout(oldEntry.idleCloseTimer);
|
|
17354
|
+
if (oldEntry.generalPermitRelease) {
|
|
17355
|
+
try {
|
|
17356
|
+
oldEntry.generalPermitRelease();
|
|
17357
|
+
} catch {
|
|
17358
|
+
}
|
|
17359
|
+
}
|
|
17360
|
+
this.socketPool.delete("general");
|
|
17361
|
+
try {
|
|
17362
|
+
await oldEntry.client.close({ reason: "reconnect", skipLogout: true });
|
|
17363
|
+
} catch {
|
|
17364
|
+
}
|
|
17365
|
+
}
|
|
17366
|
+
const newClient = new BaichuanClient(this.clientOptions);
|
|
17367
|
+
this.socketPool.set("general", {
|
|
17368
|
+
client: newClient,
|
|
17369
|
+
refCount: 1,
|
|
17370
|
+
// general socket is always "in use"
|
|
17371
|
+
createdAt: Date.now(),
|
|
17372
|
+
lastUsedAt: Date.now(),
|
|
17373
|
+
idleCloseTimer: void 0,
|
|
17374
|
+
generalPermitRelease: void 0
|
|
17375
|
+
});
|
|
17376
|
+
this.setupGeneralClientListeners();
|
|
17377
|
+
await this.client.login();
|
|
17378
|
+
this.logger.log?.(
|
|
17379
|
+
"[ReolinkBaichuanApi] General socket reconnected successfully"
|
|
17380
|
+
);
|
|
17381
|
+
if (this.simpleEventListeners.size > 0) {
|
|
17382
|
+
this.simpleEventSubscribed = false;
|
|
17383
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
17384
|
+
this.simpleEventWatchdogLastRecoveryAt = 0;
|
|
17385
|
+
try {
|
|
17386
|
+
await this.ensureSimpleEventSubscribed();
|
|
17387
|
+
this.simpleEventLastReceivedAt = Date.now();
|
|
17388
|
+
this.logger.log?.(
|
|
17389
|
+
`[ReolinkBaichuanApi] Events re-subscribed after reconnection (listeners=${this.simpleEventListeners.size})`
|
|
17390
|
+
);
|
|
17391
|
+
} catch (e) {
|
|
17392
|
+
(this.logger.debug ?? this.logger.log).call(
|
|
17393
|
+
this.logger,
|
|
17394
|
+
`[ReolinkBaichuanApi] Event re-subscribe after reconnection failed, watchdog will retry`,
|
|
17395
|
+
formatErrorForLog(e)
|
|
17396
|
+
);
|
|
17397
|
+
}
|
|
17398
|
+
}
|
|
17399
|
+
}
|
|
17400
|
+
/**
|
|
17401
|
+
* Attach event, push, channelInfo, and guard listeners to the current
|
|
17402
|
+
* "general" client. Called from the constructor and from
|
|
17403
|
+
* {@link reconnectGeneralSocket}.
|
|
17404
|
+
*/
|
|
17405
|
+
setupGeneralClientListeners() {
|
|
17406
|
+
const client = this.client;
|
|
17407
|
+
client.on("event", (event) => {
|
|
17408
|
+
const mapped = mapToSimpleEvent(event);
|
|
17409
|
+
if (!mapped) return;
|
|
17410
|
+
this.dispatchSimpleEvent(mapped);
|
|
17411
|
+
});
|
|
17412
|
+
client.on("channelInfo", (xml) => {
|
|
17413
|
+
try {
|
|
17414
|
+
this.parseAndStoreChannelInfo(xml);
|
|
17415
|
+
} catch (e) {
|
|
17416
|
+
this.logger.warn?.(
|
|
17417
|
+
"[ReolinkBaichuanApi] Error parsing channel info from push",
|
|
17418
|
+
formatErrorForLog(e)
|
|
17419
|
+
);
|
|
17420
|
+
}
|
|
17421
|
+
});
|
|
17422
|
+
client.on("push", (frame) => {
|
|
17423
|
+
const cmdId = frame.header.cmdId;
|
|
17424
|
+
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST) {
|
|
17425
|
+
return;
|
|
17426
|
+
}
|
|
17427
|
+
try {
|
|
17428
|
+
if (frame.body.length === 0) return;
|
|
17429
|
+
const xml = client.tryDecryptXml(
|
|
17430
|
+
frame.body,
|
|
17431
|
+
frame.header.channelId,
|
|
17432
|
+
client.enc
|
|
17433
|
+
);
|
|
17434
|
+
if (!xml || !xml.startsWith("<?xml")) return;
|
|
17435
|
+
this.parseAndStoreSettingsPush(cmdId, xml, frame.header.channelId);
|
|
17436
|
+
} catch (e) {
|
|
17437
|
+
this.logger.debug?.(
|
|
17438
|
+
"[ReolinkBaichuanApi] Error parsing settings push",
|
|
17439
|
+
formatErrorForLog(e)
|
|
17440
|
+
);
|
|
17441
|
+
}
|
|
17442
|
+
});
|
|
17443
|
+
if (this.rebootAfterDisconnectionsPerMinute > 0) {
|
|
17444
|
+
client.on("close", () => {
|
|
17445
|
+
try {
|
|
17446
|
+
void this.maybeRebootOnDisconnectStorm();
|
|
17447
|
+
} catch {
|
|
17448
|
+
}
|
|
17449
|
+
});
|
|
17450
|
+
}
|
|
17451
|
+
if (this.rebootAfterConsecutiveEconnreset > 0) {
|
|
17452
|
+
client.on("close", () => {
|
|
17453
|
+
try {
|
|
17454
|
+
void this.maybeRebootOnEconnresetStorm();
|
|
17455
|
+
} catch {
|
|
17456
|
+
}
|
|
17457
|
+
});
|
|
17458
|
+
}
|
|
17459
|
+
if (!this.sessionGuardIntervalTimer) {
|
|
17460
|
+
client.once("push", () => {
|
|
17461
|
+
void this.logActiveSessionsOnStartup();
|
|
17462
|
+
this.sessionGuardIntervalTimer = setInterval(() => {
|
|
17463
|
+
void this.maybeRebootOnTooManySessions();
|
|
17464
|
+
}, 6e4);
|
|
17465
|
+
});
|
|
17466
|
+
}
|
|
17467
|
+
}
|
|
17009
17468
|
/**
|
|
17010
17469
|
* Cached camera UID. May be initially undefined if not provided in the constructor.
|
|
17011
17470
|
* Will be lazily populated on demand when needed (e.g. for recordings).
|
|
@@ -17580,6 +18039,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17580
18039
|
`[SocketPool] Closing existing socket for tag=${tag} (recreating)`
|
|
17581
18040
|
);
|
|
17582
18041
|
this.socketPool.delete(tag);
|
|
18042
|
+
if (existing.generalPermitRelease) {
|
|
18043
|
+
try {
|
|
18044
|
+
existing.generalPermitRelease();
|
|
18045
|
+
} catch {
|
|
18046
|
+
}
|
|
18047
|
+
existing.generalPermitRelease = void 0;
|
|
18048
|
+
}
|
|
17583
18049
|
try {
|
|
17584
18050
|
await existing.client.close({
|
|
17585
18051
|
reason: "socket pool recreation",
|
|
@@ -17598,7 +18064,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17598
18064
|
refCount: 0,
|
|
17599
18065
|
createdAt: now,
|
|
17600
18066
|
lastUsedAt: now,
|
|
17601
|
-
idleCloseTimer: void 0
|
|
18067
|
+
idleCloseTimer: void 0,
|
|
18068
|
+
generalPermitRelease: void 0
|
|
17602
18069
|
};
|
|
17603
18070
|
entry.pendingPromise = (async () => {
|
|
17604
18071
|
try {
|
|
@@ -17616,6 +18083,19 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17616
18083
|
entry.lastUsedAt = Date.now();
|
|
17617
18084
|
delete entry.pendingPromise;
|
|
17618
18085
|
log?.log?.(`[SocketPool] Socket connected for tag=${tag}`);
|
|
18086
|
+
if (tag !== "general") {
|
|
18087
|
+
try {
|
|
18088
|
+
const generalEntry = this.socketPool.get("general");
|
|
18089
|
+
if (generalEntry?.client) {
|
|
18090
|
+
entry.generalPermitRelease = generalEntry.client.acquirePermit(
|
|
18091
|
+
0,
|
|
18092
|
+
// indefinite — released when the streaming socket closes
|
|
18093
|
+
`streaming-peer:${tag}`
|
|
18094
|
+
);
|
|
18095
|
+
}
|
|
18096
|
+
} catch {
|
|
18097
|
+
}
|
|
18098
|
+
}
|
|
17619
18099
|
void this.maybeRebootOnTooManySessions();
|
|
17620
18100
|
return newClient;
|
|
17621
18101
|
} catch (loginError) {
|
|
@@ -17684,6 +18164,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17684
18164
|
if (!current) return;
|
|
17685
18165
|
if (current.refCount > 0) return;
|
|
17686
18166
|
this.socketPool.delete(tag);
|
|
18167
|
+
if (current.generalPermitRelease) {
|
|
18168
|
+
try {
|
|
18169
|
+
current.generalPermitRelease();
|
|
18170
|
+
} catch {
|
|
18171
|
+
}
|
|
18172
|
+
current.generalPermitRelease = void 0;
|
|
18173
|
+
}
|
|
17687
18174
|
log?.log?.(`[SocketPool] Closing idle streaming socket for tag=${tag}`);
|
|
17688
18175
|
try {
|
|
17689
18176
|
await current.client.close({
|
|
@@ -17738,6 +18225,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17738
18225
|
clearTimeout(entry.idleCloseTimer);
|
|
17739
18226
|
entry.idleCloseTimer = void 0;
|
|
17740
18227
|
}
|
|
18228
|
+
if (entry.generalPermitRelease) {
|
|
18229
|
+
try {
|
|
18230
|
+
entry.generalPermitRelease();
|
|
18231
|
+
} catch {
|
|
18232
|
+
}
|
|
18233
|
+
entry.generalPermitRelease = void 0;
|
|
18234
|
+
}
|
|
17741
18235
|
log?.debug?.(`[SocketPool] Force-closing socket for tag=${tag}`);
|
|
17742
18236
|
this.socketPool.delete(tag);
|
|
17743
18237
|
try {
|
|
@@ -17763,6 +18257,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17763
18257
|
if (entry.idleCloseTimer) {
|
|
17764
18258
|
clearTimeout(entry.idleCloseTimer);
|
|
17765
18259
|
}
|
|
18260
|
+
if (entry.generalPermitRelease) {
|
|
18261
|
+
try {
|
|
18262
|
+
entry.generalPermitRelease();
|
|
18263
|
+
} catch {
|
|
18264
|
+
}
|
|
18265
|
+
entry.generalPermitRelease = void 0;
|
|
18266
|
+
}
|
|
17766
18267
|
this.logger?.debug?.(`[SocketPool] Cleanup: closing tag=${tag}`);
|
|
17767
18268
|
await entry.client.close({ reason: "API cleanup", skipLogout: true });
|
|
17768
18269
|
} catch {
|
|
@@ -17868,7 +18369,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17868
18369
|
password: opts.password,
|
|
17869
18370
|
...opts.logger ? { logger: opts.logger } : {},
|
|
17870
18371
|
...opts.debugOptions ? { debugOptions: opts.debugOptions } : {},
|
|
17871
|
-
...opts.uid ? { uid: opts.uid } : {}
|
|
18372
|
+
...opts.uid ? { uid: opts.uid } : {},
|
|
18373
|
+
...opts.transport ? { transport: opts.transport } : {},
|
|
18374
|
+
...opts.port !== void 0 ? { port: opts.port } : {},
|
|
18375
|
+
...opts.udpDiscoveryMethod ? { udpDiscoveryMethod: opts.udpDiscoveryMethod } : {},
|
|
18376
|
+
...opts.idleDisconnect !== void 0 ? { idleDisconnect: opts.idleDisconnect } : {},
|
|
18377
|
+
...opts.idleDisconnectTimeoutMs !== void 0 ? { idleDisconnectTimeoutMs: opts.idleDisconnectTimeoutMs } : {},
|
|
18378
|
+
...opts.channel !== void 0 ? { channel: opts.channel } : {}
|
|
17872
18379
|
};
|
|
17873
18380
|
const generalClient = new BaichuanClient(opts);
|
|
17874
18381
|
this.socketPool.set("general", {
|
|
@@ -17877,7 +18384,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17877
18384
|
// Always keep general socket "in use"
|
|
17878
18385
|
createdAt: Date.now(),
|
|
17879
18386
|
lastUsedAt: Date.now(),
|
|
17880
|
-
idleCloseTimer: void 0
|
|
18387
|
+
idleCloseTimer: void 0,
|
|
18388
|
+
generalPermitRelease: void 0
|
|
17881
18389
|
});
|
|
17882
18390
|
this.host = opts.host;
|
|
17883
18391
|
this.username = opts.username;
|
|
@@ -17897,42 +18405,6 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17897
18405
|
logger: this.logger,
|
|
17898
18406
|
debugConfig: generalClient.getDebugConfig?.()
|
|
17899
18407
|
});
|
|
17900
|
-
this.client.on("event", (event) => {
|
|
17901
|
-
const mapped = mapToSimpleEvent(event);
|
|
17902
|
-
if (!mapped) return;
|
|
17903
|
-
this.dispatchSimpleEvent(mapped);
|
|
17904
|
-
});
|
|
17905
|
-
this.client.on("channelInfo", (xml) => {
|
|
17906
|
-
try {
|
|
17907
|
-
this.parseAndStoreChannelInfo(xml);
|
|
17908
|
-
} catch (e) {
|
|
17909
|
-
this.logger.warn?.(
|
|
17910
|
-
"[ReolinkBaichuanApi] Error parsing channel info from push",
|
|
17911
|
-
formatErrorForLog(e)
|
|
17912
|
-
);
|
|
17913
|
-
}
|
|
17914
|
-
});
|
|
17915
|
-
this.client.on("push", (frame) => {
|
|
17916
|
-
const cmdId = frame.header.cmdId;
|
|
17917
|
-
if (cmdId !== BC_CMD_ID_PUSH_VIDEO_INPUT && cmdId !== BC_CMD_ID_PUSH_SERIAL && cmdId !== BC_CMD_ID_PUSH_NET_INFO && cmdId !== BC_CMD_ID_PUSH_DINGDONG_LIST && cmdId !== BC_CMD_ID_PUSH_SLEEP_STATUS && cmdId !== BC_CMD_ID_PUSH_COORDINATE_POINT_LIST) {
|
|
17918
|
-
return;
|
|
17919
|
-
}
|
|
17920
|
-
try {
|
|
17921
|
-
if (frame.body.length === 0) return;
|
|
17922
|
-
const xml = this.client.tryDecryptXml(
|
|
17923
|
-
frame.body,
|
|
17924
|
-
frame.header.channelId,
|
|
17925
|
-
this.client.enc
|
|
17926
|
-
);
|
|
17927
|
-
if (!xml || !xml.startsWith("<?xml")) return;
|
|
17928
|
-
this.parseAndStoreSettingsPush(cmdId, xml, frame.header.channelId);
|
|
17929
|
-
} catch (e) {
|
|
17930
|
-
this.logger.debug?.(
|
|
17931
|
-
"[ReolinkBaichuanApi] Error parsing settings push",
|
|
17932
|
-
formatErrorForLog(e)
|
|
17933
|
-
);
|
|
17934
|
-
}
|
|
17935
|
-
});
|
|
17936
18408
|
const maxSessions = opts.maxDedicatedSessionsBeforeReboot;
|
|
17937
18409
|
if (typeof maxSessions === "number" && Number.isFinite(maxSessions) && maxSessions > 0) {
|
|
17938
18410
|
this.maxDedicatedSessionsBeforeReboot = Math.floor(maxSessions);
|
|
@@ -17941,32 +18413,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17941
18413
|
if (typeof disconnectThreshold === "number" && Number.isFinite(disconnectThreshold)) {
|
|
17942
18414
|
this.rebootAfterDisconnectionsPerMinute = Math.floor(disconnectThreshold);
|
|
17943
18415
|
}
|
|
17944
|
-
if (this.rebootAfterDisconnectionsPerMinute > 0) {
|
|
17945
|
-
this.client.on("close", () => {
|
|
17946
|
-
try {
|
|
17947
|
-
void this.maybeRebootOnDisconnectStorm();
|
|
17948
|
-
} catch {
|
|
17949
|
-
}
|
|
17950
|
-
});
|
|
17951
|
-
}
|
|
17952
18416
|
const econnresetThreshold = opts.rebootAfterConsecutiveEconnreset;
|
|
17953
18417
|
if (typeof econnresetThreshold === "number" && Number.isFinite(econnresetThreshold)) {
|
|
17954
18418
|
this.rebootAfterConsecutiveEconnreset = Math.floor(econnresetThreshold);
|
|
17955
18419
|
}
|
|
17956
|
-
|
|
17957
|
-
this.client.on("close", () => {
|
|
17958
|
-
try {
|
|
17959
|
-
void this.maybeRebootOnEconnresetStorm();
|
|
17960
|
-
} catch {
|
|
17961
|
-
}
|
|
17962
|
-
});
|
|
17963
|
-
}
|
|
17964
|
-
this.client.once("push", () => {
|
|
17965
|
-
void this.logActiveSessionsOnStartup();
|
|
17966
|
-
this.sessionGuardIntervalTimer = setInterval(() => {
|
|
17967
|
-
void this.maybeRebootOnTooManySessions();
|
|
17968
|
-
}, 6e4);
|
|
17969
|
-
});
|
|
18420
|
+
this.setupGeneralClientListeners();
|
|
17970
18421
|
}
|
|
17971
18422
|
/**
|
|
17972
18423
|
* CGI forward: fetch RTSP URL for a channel via `GetRtspUrl`.
|
|
@@ -18380,7 +18831,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18380
18831
|
*/
|
|
18381
18832
|
async onSimpleEvent(callback) {
|
|
18382
18833
|
this.simpleEventListeners.add(callback);
|
|
18383
|
-
|
|
18834
|
+
this.logger.debug?.(
|
|
18835
|
+
`[ReolinkBaichuanApi] onSimpleEvent: registering listener (total=${this.simpleEventListeners.size})`
|
|
18836
|
+
);
|
|
18837
|
+
try {
|
|
18838
|
+
await this.ensureSimpleEventSubscribed();
|
|
18839
|
+
this.logger.debug?.(
|
|
18840
|
+
`[ReolinkBaichuanApi] onSimpleEvent: initial subscribe succeeded, simpleEventSubscribed=${this.simpleEventSubscribed}`
|
|
18841
|
+
);
|
|
18842
|
+
} catch (e) {
|
|
18843
|
+
(this.logger.debug ?? this.logger.log).call(
|
|
18844
|
+
this.logger,
|
|
18845
|
+
`[ReolinkBaichuanApi] onSimpleEvent: initial subscribe failed, simpleEventSubscribed=${this.simpleEventSubscribed}, watchdog will retry`,
|
|
18846
|
+
formatErrorForLog(e)
|
|
18847
|
+
);
|
|
18848
|
+
}
|
|
18384
18849
|
this.simpleEventLastReceivedAt = Date.now();
|
|
18385
18850
|
this.startSimpleEventResubscribeTimer();
|
|
18386
18851
|
this.startSimpleEventWatchdog();
|
|
@@ -18401,11 +18866,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18401
18866
|
this.stopUdpSleepInference();
|
|
18402
18867
|
await this.ensureSimpleEventUnsubscribed();
|
|
18403
18868
|
} else {
|
|
18404
|
-
const
|
|
18405
|
-
if (
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
|
|
18869
|
+
const generalEntry = this.socketPool.get("general");
|
|
18870
|
+
if (generalEntry) {
|
|
18871
|
+
const isUdp = generalEntry.client.getTransport?.() === "udp";
|
|
18872
|
+
if (isUdp) {
|
|
18873
|
+
this.startUdpSleepInference();
|
|
18874
|
+
} else if (generalEntry.client.isStatePollingEnabled?.()) {
|
|
18875
|
+
this.startStatePolling();
|
|
18876
|
+
}
|
|
18409
18877
|
}
|
|
18410
18878
|
}
|
|
18411
18879
|
}
|
|
@@ -18447,8 +18915,19 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18447
18915
|
}
|
|
18448
18916
|
async simpleEventWatchdogTick() {
|
|
18449
18917
|
if (this.simpleEventListeners.size === 0) return;
|
|
18450
|
-
|
|
18918
|
+
const generalEntry = this.socketPool.get("general");
|
|
18919
|
+
if (!generalEntry) return;
|
|
18920
|
+
if (!generalEntry.client.isSocketConnected?.() || !generalEntry.client.loggedIn) {
|
|
18921
|
+
this.logger.debug?.(
|
|
18922
|
+
`[ReolinkBaichuanApi] event watchdog tick: skipping (connection not alive: connected=${generalEntry.client.isSocketConnected?.()} loggedIn=${generalEntry.client.loggedIn})`
|
|
18923
|
+
);
|
|
18924
|
+
return;
|
|
18925
|
+
}
|
|
18451
18926
|
const now = Date.now();
|
|
18927
|
+
const sinceLastEvent = this.simpleEventLastReceivedAt > 0 ? now - this.simpleEventLastReceivedAt : -1;
|
|
18928
|
+
this.logger.debug?.(
|
|
18929
|
+
`[ReolinkBaichuanApi] event watchdog tick: subscribed=${this.simpleEventSubscribed} clientSubscribed=${generalEntry.client.subscribed} lastEventAgoMs=${sinceLastEvent} recoveryAttempts=${this.simpleEventWatchdogRecoveryAttempts} listeners=${this.simpleEventListeners.size}`
|
|
18930
|
+
);
|
|
18452
18931
|
if (this.simpleEventSubscribed && this.simpleEventLastReceivedAt > 0) {
|
|
18453
18932
|
const silence = now - this.simpleEventLastReceivedAt;
|
|
18454
18933
|
if (silence < this.simpleEventWatchdogSilenceThresholdMs) return;
|
|
@@ -18459,7 +18938,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18459
18938
|
);
|
|
18460
18939
|
try {
|
|
18461
18940
|
this.simpleEventSubscribed = false;
|
|
18462
|
-
|
|
18941
|
+
generalEntry.client.subscribed = false;
|
|
18463
18942
|
await this.ensureSimpleEventSubscribed();
|
|
18464
18943
|
this.simpleEventLastReceivedAt = Date.now();
|
|
18465
18944
|
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
@@ -18477,6 +18956,31 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18477
18956
|
return;
|
|
18478
18957
|
}
|
|
18479
18958
|
if (!this.simpleEventSubscribed) {
|
|
18959
|
+
if (this.simpleEventLastReceivedAt > 0) {
|
|
18960
|
+
const sinceLastEvent2 = now - this.simpleEventLastReceivedAt;
|
|
18961
|
+
if (sinceLastEvent2 < this.simpleEventWatchdogSilenceThresholdMs) {
|
|
18962
|
+
this.simpleEventSubscribed = true;
|
|
18963
|
+
this.logger.debug?.(
|
|
18964
|
+
`[ReolinkBaichuanApi] event watchdog: events flowing (lastEventAgo=${Math.round(sinceLastEvent2 / 1e3)}s) despite simpleEventSubscribed=false, marking subscription as active (recoveryAttempts=${this.simpleEventWatchdogRecoveryAttempts})`
|
|
18965
|
+
);
|
|
18966
|
+
if (this.simpleEventWatchdogRecoveryAttempts > 0) {
|
|
18967
|
+
(this.logger.info ?? this.logger.log).call(
|
|
18968
|
+
this.logger,
|
|
18969
|
+
`[ReolinkBaichuanApi] event watchdog: events flowing despite failed subscribe, marking subscription active`
|
|
18970
|
+
);
|
|
18971
|
+
this.simpleEventWatchdogRecoveryAttempts = 0;
|
|
18972
|
+
}
|
|
18973
|
+
return;
|
|
18974
|
+
} else {
|
|
18975
|
+
this.logger.debug?.(
|
|
18976
|
+
`[ReolinkBaichuanApi] event watchdog: events stale (lastEventAgo=${Math.round(sinceLastEvent2 / 1e3)}s, threshold=${Math.round(this.simpleEventWatchdogSilenceThresholdMs / 1e3)}s), proceeding with recovery`
|
|
18977
|
+
);
|
|
18978
|
+
}
|
|
18979
|
+
} else {
|
|
18980
|
+
this.logger.debug?.(
|
|
18981
|
+
`[ReolinkBaichuanApi] event watchdog: no events ever received (simpleEventLastReceivedAt=0), proceeding with recovery`
|
|
18982
|
+
);
|
|
18983
|
+
}
|
|
18480
18984
|
const backoffMs = Math.min(
|
|
18481
18985
|
3e4 * Math.pow(2, this.simpleEventWatchdogRecoveryAttempts),
|
|
18482
18986
|
this.simpleEventWatchdogSilenceThresholdMs
|
|
@@ -18540,20 +19044,51 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18540
19044
|
return await this.simpleEventResubscribeInFlight;
|
|
18541
19045
|
}
|
|
18542
19046
|
async ensureSimpleEventSubscribed() {
|
|
18543
|
-
if (this.simpleEventListeners.size === 0)
|
|
18544
|
-
|
|
18545
|
-
|
|
19047
|
+
if (this.simpleEventListeners.size === 0) {
|
|
19048
|
+
this.logger.debug?.(
|
|
19049
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: no listeners, skipping`
|
|
19050
|
+
);
|
|
19051
|
+
return;
|
|
19052
|
+
}
|
|
19053
|
+
if (this.simpleEventSubscribed) {
|
|
19054
|
+
this.logger.debug?.(
|
|
19055
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: already subscribed, skipping`
|
|
19056
|
+
);
|
|
19057
|
+
return;
|
|
19058
|
+
}
|
|
19059
|
+
if (this.simpleEventSubscribeInFlight) {
|
|
19060
|
+
this.logger.debug?.(
|
|
19061
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: subscribe already in-flight, awaiting`
|
|
19062
|
+
);
|
|
18546
19063
|
return await this.simpleEventSubscribeInFlight;
|
|
19064
|
+
}
|
|
19065
|
+
this.logger.debug?.(
|
|
19066
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: starting subscribe (clientSubscribed=${this.socketPool.get("general")?.client.subscribed})`
|
|
19067
|
+
);
|
|
18547
19068
|
this.simpleEventSubscribeInFlight = (async () => {
|
|
18548
|
-
|
|
19069
|
+
const entry = this.socketPool.get("general");
|
|
19070
|
+
if (!entry) {
|
|
19071
|
+
this.logger.debug?.(
|
|
19072
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: no general socket, bailing out`
|
|
19073
|
+
);
|
|
19074
|
+
return;
|
|
19075
|
+
}
|
|
19076
|
+
if (!entry.client.subscribed) {
|
|
19077
|
+
this.logger.debug?.(
|
|
19078
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: client.subscribed=false, calling subscribeEvents()`
|
|
19079
|
+
);
|
|
18549
19080
|
await this.subscribeEvents();
|
|
19081
|
+
} else {
|
|
19082
|
+
this.logger.debug?.(
|
|
19083
|
+
`[ReolinkBaichuanApi] ensureSimpleEventSubscribed: client already subscribed, skipping subscribeEvents()`
|
|
19084
|
+
);
|
|
18550
19085
|
}
|
|
18551
19086
|
this.simpleEventSubscribed = true;
|
|
18552
|
-
const isUdp =
|
|
19087
|
+
const isUdp = entry.client.getTransport?.() === "udp";
|
|
18553
19088
|
if (isUdp) {
|
|
18554
19089
|
this.startUdpSleepInference();
|
|
18555
|
-
} else if (
|
|
18556
|
-
const channel =
|
|
19090
|
+
} else if (entry.client.isStatePollingEnabled?.()) {
|
|
19091
|
+
const channel = entry.client.getConfiguredChannel?.() ?? 0;
|
|
18557
19092
|
await this.checkAndDispatchCurrentState(channel);
|
|
18558
19093
|
this.startStatePolling();
|
|
18559
19094
|
}
|
|
@@ -18563,7 +19098,15 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18563
19098
|
return await this.simpleEventSubscribeInFlight;
|
|
18564
19099
|
}
|
|
18565
19100
|
async ensureSimpleEventUnsubscribed() {
|
|
18566
|
-
|
|
19101
|
+
const generalEntry = this.socketPool.get("general");
|
|
19102
|
+
if (!generalEntry) {
|
|
19103
|
+
this.simpleEventSubscribed = false;
|
|
19104
|
+
this.stopSimpleEventResubscribeTimer();
|
|
19105
|
+
this.stopStatePolling();
|
|
19106
|
+
this.stopUdpSleepInference();
|
|
19107
|
+
return;
|
|
19108
|
+
}
|
|
19109
|
+
if (!this.simpleEventSubscribed && !generalEntry.client.subscribed) return;
|
|
18567
19110
|
if (this.simpleEventUnsubscribeInFlight)
|
|
18568
19111
|
return await this.simpleEventUnsubscribeInFlight;
|
|
18569
19112
|
if (this.simpleEventSubscribeInFlight) {
|
|
@@ -18705,6 +19248,8 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18705
19248
|
);
|
|
18706
19249
|
}
|
|
18707
19250
|
async close(options) {
|
|
19251
|
+
if (this._closed) return;
|
|
19252
|
+
this._closed = true;
|
|
18708
19253
|
if (this.sessionGuardIntervalTimer) {
|
|
18709
19254
|
clearInterval(this.sessionGuardIntervalTimer);
|
|
18710
19255
|
this.sessionGuardIntervalTimer = void 0;
|
|
@@ -18767,7 +19312,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18767
19312
|
}
|
|
18768
19313
|
async handleSendXml400(params, frame, retry) {
|
|
18769
19314
|
const emptyBody = frame.body.length === 0;
|
|
18770
|
-
const emptyBody400Msg = "Baichuan request failed (responseCode 400, empty body). Possible causes:
|
|
19315
|
+
const emptyBody400Msg = "Baichuan request failed (responseCode 400, empty body). Possible causes: expired session, invalid username/password, or unsupported command on NVR/Hub.";
|
|
18771
19316
|
if (this.isSendXmlFailFast400(params, frame.body.length)) {
|
|
18772
19317
|
throw new Error(emptyBody400Msg);
|
|
18773
19318
|
}
|
|
@@ -19283,11 +19828,50 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19283
19828
|
* Minimal per-channel inventory for NVR-connected devices.
|
|
19284
19829
|
*
|
|
19285
19830
|
* Intended to be fast: avoids AI/abilities and returns only the common identity + battery hints.
|
|
19831
|
+
*
|
|
19832
|
+
* @param options.source - Data source for the channel list (default: `"cgi"`):
|
|
19833
|
+
* - `"cgi"`: Uses HTTP `GetChannelstatus` — returns the channel list immediately,
|
|
19834
|
+
* no dependency on async push messages. Recommended for first-call discovery.
|
|
19835
|
+
* - `"baichuan"`: Uses the cmd_id 145 push cache populated when the NVR sends channel
|
|
19836
|
+
* info after login + event subscription. This push is *asynchronous*: if it has not
|
|
19837
|
+
* arrived yet, the result will have zero channels. Callers must retry (nvr.ts does this
|
|
19838
|
+
* with a 1-second loop). Note: explicitly requesting cmd_id 145 is not supported.
|
|
19286
19839
|
*/
|
|
19287
19840
|
async getNvrChannelsSummary(options) {
|
|
19288
|
-
const source = options?.source ?? "
|
|
19289
|
-
|
|
19290
|
-
const
|
|
19841
|
+
const source = options?.source ?? "cgi";
|
|
19842
|
+
let channels;
|
|
19843
|
+
const cgiStatusByChannel = /* @__PURE__ */ new Map();
|
|
19844
|
+
if (options?.channels?.length) {
|
|
19845
|
+
channels = options.channels.map((c) => Number(c)).filter((n) => Number.isFinite(n));
|
|
19846
|
+
} else if (source === "cgi") {
|
|
19847
|
+
try {
|
|
19848
|
+
const { channels: cgiChannels, channelsResponse } = await this.cgiApi.getChannels();
|
|
19849
|
+
const status = channelsResponse?.[0]?.value?.status ?? [];
|
|
19850
|
+
for (const s of status) {
|
|
19851
|
+
const ch = Number(s?.channel);
|
|
19852
|
+
if (!Number.isFinite(ch)) continue;
|
|
19853
|
+
cgiStatusByChannel.set(ch, {
|
|
19854
|
+
...s.name != null ? { name: s.name } : {},
|
|
19855
|
+
...s.uid != null ? { uid: s.uid } : {},
|
|
19856
|
+
sleeping: s.sleep === 1
|
|
19857
|
+
});
|
|
19858
|
+
}
|
|
19859
|
+
channels = cgiChannels;
|
|
19860
|
+
this.logger.debug?.(
|
|
19861
|
+
`[ReolinkBaichuanApi] getNvrChannelsSummary: CGI found ${channels.length} channel(s): [${channels.join(", ")}]`
|
|
19862
|
+
);
|
|
19863
|
+
} catch (e) {
|
|
19864
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
19865
|
+
this.logger.warn?.(
|
|
19866
|
+
`[ReolinkBaichuanApi] getNvrChannelsSummary: CGI GetChannelstatus failed (${msg}), returning empty`
|
|
19867
|
+
);
|
|
19868
|
+
channels = [];
|
|
19869
|
+
}
|
|
19870
|
+
} else {
|
|
19871
|
+
const pushInfo2 = this.getChannelInfoFromPushCache();
|
|
19872
|
+
channels = Array.from(pushInfo2.keys()).map((c) => Number(c)).filter((n) => Number.isFinite(n));
|
|
19873
|
+
}
|
|
19874
|
+
channels = channels.sort((a, b) => a - b);
|
|
19291
19875
|
const support = await this.getSupportInfo().catch(() => {
|
|
19292
19876
|
this.logger.error?.(
|
|
19293
19877
|
"[ReolinkBaichuanApi] getNvrChannelsSummary: failed to get support info"
|
|
@@ -19317,7 +19901,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19317
19901
|
);
|
|
19318
19902
|
}
|
|
19319
19903
|
}
|
|
19320
|
-
const cacheKey =
|
|
19904
|
+
const cacheKey = `${source}:${channels.join(",")}`;
|
|
19321
19905
|
const cached = this.nvrChannelsSummaryCache.get(cacheKey);
|
|
19322
19906
|
if (cached) {
|
|
19323
19907
|
return {
|
|
@@ -19338,8 +19922,10 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19338
19922
|
} catch {
|
|
19339
19923
|
}
|
|
19340
19924
|
}
|
|
19925
|
+
const pushInfo = this.getChannelInfoFromPushCache();
|
|
19341
19926
|
const devices = channels.map((channel) => {
|
|
19342
|
-
const
|
|
19927
|
+
const pushCached = pushInfo.get(channel);
|
|
19928
|
+
const cgiStatus = cgiStatusByChannel.get(channel);
|
|
19343
19929
|
const info = infoPerChannel.get(channel);
|
|
19344
19930
|
const networkInfo = networkInfoPerChannel.get(channel);
|
|
19345
19931
|
const isBattery = isBatteryByChannel.get(channel) ?? false;
|
|
@@ -19347,6 +19933,9 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19347
19933
|
const isDoorbell = (isDoorbellByChannel.get(channel) ?? false) || /doorbell/i.test(model);
|
|
19348
19934
|
const normalizedModel = model ? model.trim() : void 0;
|
|
19349
19935
|
const isMultifocal = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
19936
|
+
const name = pushCached?.name || cgiStatus?.name || "";
|
|
19937
|
+
const uid = pushCached?.uid || cgiStatus?.uid || "";
|
|
19938
|
+
const sleeping = pushCached?.sleeping ?? cgiStatus?.sleeping;
|
|
19350
19939
|
return {
|
|
19351
19940
|
channel,
|
|
19352
19941
|
isBattery,
|
|
@@ -19356,19 +19945,19 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
19356
19945
|
...networkInfo?.ip ? { ip: networkInfo.ip } : {},
|
|
19357
19946
|
...networkInfo?.mac ? { mac: networkInfo.mac } : {},
|
|
19358
19947
|
...networkInfo?.activeLink ? { activeLink: networkInfo.activeLink } : {},
|
|
19359
|
-
...
|
|
19360
|
-
...
|
|
19361
|
-
...
|
|
19362
|
-
...typeof
|
|
19363
|
-
...
|
|
19364
|
-
...
|
|
19365
|
-
...
|
|
19366
|
-
...typeof
|
|
19367
|
-
...typeof
|
|
19368
|
-
...typeof
|
|
19369
|
-
...typeof
|
|
19370
|
-
...
|
|
19371
|
-
...typeof
|
|
19948
|
+
...name ? { name } : {},
|
|
19949
|
+
...uid ? { uid } : {},
|
|
19950
|
+
...pushCached?.state ? { state: pushCached.state } : {},
|
|
19951
|
+
...typeof pushCached?.index === "number" ? { index: pushCached.index } : {},
|
|
19952
|
+
...pushCached?.streamSupport?.length ? { streamSupport: pushCached.streamSupport } : {},
|
|
19953
|
+
...pushCached?.wifiState ? { wifiState: pushCached.wifiState } : {},
|
|
19954
|
+
...pushCached?.networkSegment ? { networkSegment: pushCached.networkSegment } : {},
|
|
19955
|
+
...typeof pushCached?.changed === "boolean" ? { changed: pushCached.changed } : {},
|
|
19956
|
+
...typeof pushCached?.abilityChanged === "boolean" ? { abilityChanged: pushCached.abilityChanged } : {},
|
|
19957
|
+
...typeof pushCached?.online === "boolean" ? { online: pushCached.online } : {},
|
|
19958
|
+
...typeof sleeping === "boolean" ? { sleeping } : {},
|
|
19959
|
+
...pushCached?.loginState ? { loginState: pushCached.loginState } : {},
|
|
19960
|
+
...typeof pushCached?.updatedAtMs === "number" ? { updatedAtMs: pushCached.updatedAtMs } : {}
|
|
19372
19961
|
};
|
|
19373
19962
|
});
|
|
19374
19963
|
const result = { channels, devices };
|
|
@@ -20548,6 +21137,11 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
20548
21137
|
this._processVideoclipThumbnailQueue();
|
|
20549
21138
|
}
|
|
20550
21139
|
}
|
|
21140
|
+
if (this.videoclipThumbnailQueue.length >= 50) {
|
|
21141
|
+
throw new Error(
|
|
21142
|
+
`Thumbnail queue full (${this.videoclipThumbnailQueue.length}/50) \u2013 request rejected to protect camera stability`
|
|
21143
|
+
);
|
|
21144
|
+
}
|
|
20551
21145
|
return new Promise((resolve, reject) => {
|
|
20552
21146
|
this.videoclipThumbnailQueue.push({ params, resolve, reject });
|
|
20553
21147
|
});
|
|
@@ -20648,10 +21242,7 @@ ${xml}`);
|
|
|
20648
21242
|
messageClass: BC_CLASS_MODERN_24,
|
|
20649
21243
|
streamType: 0,
|
|
20650
21244
|
payloadXml: xml,
|
|
20651
|
-
timeoutMs
|
|
20652
|
-
// Retry parameters - camera often rejects first few requests
|
|
20653
|
-
maxRetries: 8,
|
|
20654
|
-
retryDelayMs: 1500
|
|
21245
|
+
timeoutMs
|
|
20655
21246
|
});
|
|
20656
21247
|
trace(`CoverPreview succeeded`);
|
|
20657
21248
|
} catch (e) {
|
|
@@ -22044,8 +22635,10 @@ ${stderr}`)
|
|
|
22044
22635
|
* Unsubscribe from events.
|
|
22045
22636
|
*/
|
|
22046
22637
|
async unsubscribeEvents() {
|
|
22047
|
-
this.
|
|
22048
|
-
|
|
22638
|
+
const generalEntry = this.socketPool.get("general");
|
|
22639
|
+
if (!generalEntry) return;
|
|
22640
|
+
generalEntry.client.subscribed = false;
|
|
22641
|
+
generalEntry.client.refreshKeepAlive?.();
|
|
22049
22642
|
}
|
|
22050
22643
|
/**
|
|
22051
22644
|
* Check current motion and AI state and dispatch events if state changed.
|
|
@@ -23632,13 +24225,12 @@ ${xml}`
|
|
|
23632
24225
|
]);
|
|
23633
24226
|
const support = supportResult.status === "fulfilled" ? supportResult.value : void 0;
|
|
23634
24227
|
const abilities = abilitiesResult.status === "fulfilled" ? abilitiesResult.value : void 0;
|
|
23635
|
-
const supportItem =
|
|
23636
|
-
const capabilities =
|
|
23637
|
-
ch,
|
|
23638
|
-
|
|
23639
|
-
|
|
23640
|
-
|
|
23641
|
-
);
|
|
24228
|
+
const supportItem = getSupportItemForChannel(support, ch);
|
|
24229
|
+
const capabilities = computeDeviceCapabilities({
|
|
24230
|
+
channel: ch,
|
|
24231
|
+
...support != null && { support },
|
|
24232
|
+
...abilities != null && { abilities }
|
|
24233
|
+
});
|
|
23642
24234
|
const item = supportItem;
|
|
23643
24235
|
const lightType = item?.lightType;
|
|
23644
24236
|
const ledCtrl = item?.ledCtrl;
|
|
@@ -23654,6 +24246,25 @@ ${xml}`
|
|
|
23654
24246
|
});
|
|
23655
24247
|
capabilities.hasFloodlight = probed;
|
|
23656
24248
|
}
|
|
24249
|
+
let dingDongListIds;
|
|
24250
|
+
let dingDongCfgIds;
|
|
24251
|
+
let wirelessChimeError;
|
|
24252
|
+
if (capabilities.hasWirelessChime) {
|
|
24253
|
+
try {
|
|
24254
|
+
const list = await this.getDingDongList(ch);
|
|
24255
|
+
dingDongListIds = list.map((d) => d.id);
|
|
24256
|
+
const first = list[0];
|
|
24257
|
+
const fromList = first !== void 0 && first.id >= 0;
|
|
24258
|
+
if (!fromList) {
|
|
24259
|
+
const configs = await this.getDingDongCfg(ch);
|
|
24260
|
+
dingDongCfgIds = configs.map((c) => c.id);
|
|
24261
|
+
capabilities.hasWirelessChime = configs.some((c) => c.id >= 0);
|
|
24262
|
+
}
|
|
24263
|
+
} catch (e) {
|
|
24264
|
+
capabilities.hasWirelessChime = false;
|
|
24265
|
+
wirelessChimeError = e instanceof Error ? e.message : String(e);
|
|
24266
|
+
}
|
|
24267
|
+
}
|
|
23657
24268
|
const features = this.parseFeaturesFromSupport(support);
|
|
23658
24269
|
const objects = await this.getAiDetectTypes(ch, { timeoutMs: 1500 });
|
|
23659
24270
|
const autotrackingProbed = await this.probeAutotrackingSupport(ch, {
|
|
@@ -23690,7 +24301,10 @@ ${xml}`
|
|
|
23690
24301
|
...abilities && {
|
|
23691
24302
|
abilityMergedKeyCount: Object.keys(abilities).length
|
|
23692
24303
|
},
|
|
23693
|
-
...support?.items && { supportItemCount: support.items.length }
|
|
24304
|
+
...support?.items && { supportItemCount: support.items.length },
|
|
24305
|
+
...dingDongListIds !== void 0 && { dingDongListIds },
|
|
24306
|
+
...dingDongCfgIds !== void 0 && { dingDongCfgIds },
|
|
24307
|
+
...wirelessChimeError !== void 0 && { wirelessChimeError }
|
|
23694
24308
|
};
|
|
23695
24309
|
const result = {
|
|
23696
24310
|
capabilities,
|
|
@@ -23717,90 +24331,6 @@ ${xml}`
|
|
|
23717
24331
|
this.deviceCapabilitiesCache.clear();
|
|
23718
24332
|
}
|
|
23719
24333
|
}
|
|
23720
|
-
/**
|
|
23721
|
-
* Pick the best SupportItem for a channel.
|
|
23722
|
-
* Prefers items without a name (capability items) over named items (googleHome, amazonAlexa).
|
|
23723
|
-
*/
|
|
23724
|
-
pickBestSupportItem(support, channel) {
|
|
23725
|
-
if (!support?.items?.length) return void 0;
|
|
23726
|
-
const candidates = support.items.filter((i) => i.chnID === channel);
|
|
23727
|
-
if (!candidates.length) return void 0;
|
|
23728
|
-
const score = (item) => {
|
|
23729
|
-
const anyItem = item;
|
|
23730
|
-
let result = 0;
|
|
23731
|
-
if (anyItem.name == null) result += 100;
|
|
23732
|
-
const capabilityKeys = [
|
|
23733
|
-
"ptzType",
|
|
23734
|
-
"ptzControl",
|
|
23735
|
-
"ptzPreset",
|
|
23736
|
-
"ledCtrl",
|
|
23737
|
-
"lightType",
|
|
23738
|
-
"battery",
|
|
23739
|
-
"audioVersion",
|
|
23740
|
-
"motion",
|
|
23741
|
-
"encCtrl",
|
|
23742
|
-
"newIspCfg",
|
|
23743
|
-
"remoteAbility",
|
|
23744
|
-
"aitype",
|
|
23745
|
-
"videoClip",
|
|
23746
|
-
"snap"
|
|
23747
|
-
];
|
|
23748
|
-
for (const k of capabilityKeys) {
|
|
23749
|
-
if (anyItem[k] !== void 0) result += 3;
|
|
23750
|
-
}
|
|
23751
|
-
return result;
|
|
23752
|
-
};
|
|
23753
|
-
return candidates.sort((a, b) => score(b) - score(a))[0];
|
|
23754
|
-
}
|
|
23755
|
-
/**
|
|
23756
|
-
* Parse device capabilities from SupportInfo.
|
|
23757
|
-
* Uses SupportInfo as the single source of truth with AbilityInfo as fallback.
|
|
23758
|
-
*/
|
|
23759
|
-
parseCapabilitiesFromSupport(channel, supportItem, support, abilities) {
|
|
23760
|
-
const truthy = (v) => {
|
|
23761
|
-
if (typeof v === "number") return v > 0;
|
|
23762
|
-
if (typeof v === "string") {
|
|
23763
|
-
const n = Number(v);
|
|
23764
|
-
return Number.isFinite(n) ? n > 0 : v.length > 0 && v !== "0";
|
|
23765
|
-
}
|
|
23766
|
-
return Boolean(v);
|
|
23767
|
-
};
|
|
23768
|
-
const item = supportItem;
|
|
23769
|
-
const ptzMode = support?.ptzMode?.toLowerCase();
|
|
23770
|
-
const ptzType = item ? truthy(item.ptzType) : false;
|
|
23771
|
-
const ptzControl = item ? truthy(item.ptzControl) : false;
|
|
23772
|
-
const hasPtzFromItem = ptzType || ptzControl;
|
|
23773
|
-
const hasPtzFromMode = ptzMode ? ptzMode !== "none" && ptzMode !== "0" : false;
|
|
23774
|
-
const hasPanTilt = ptzMode ? ptzMode.includes("pt") || ptzMode === "ptz" : hasPtzFromItem;
|
|
23775
|
-
const hasZoom = ptzMode ? ptzMode.includes("z") : hasPtzFromItem;
|
|
23776
|
-
const hasPresets = item ? truthy(item.ptzPreset) : false;
|
|
23777
|
-
const hasBattery = item ? truthy(item.battery) : false;
|
|
23778
|
-
const hasSiren = item ? truthy(item.audioVersion) : false;
|
|
23779
|
-
const lightType = item?.lightType;
|
|
23780
|
-
const hasFloodlight = typeof lightType === "number" ? lightType >= 2 : false;
|
|
23781
|
-
const hasPir = item ? truthy(item.rfCfg) || truthy(item.newRfCfg) || truthy(item.rfVersion) : false;
|
|
23782
|
-
const isDoorbell = item ? truthy(item.doorbellVersion) : false;
|
|
23783
|
-
const hasIntercom = truthy(support?.audioTalk) || (item ? truthy(item.ipcAudioTalk) : false);
|
|
23784
|
-
return {
|
|
23785
|
-
channel,
|
|
23786
|
-
...ptzMode && { ptzMode },
|
|
23787
|
-
hasPan: hasPanTilt,
|
|
23788
|
-
hasTilt: hasPanTilt,
|
|
23789
|
-
hasZoom,
|
|
23790
|
-
hasPresets,
|
|
23791
|
-
hasPtz: hasPtzFromItem || hasPtzFromMode || hasPanTilt || hasZoom,
|
|
23792
|
-
hasBattery,
|
|
23793
|
-
hasIntercom,
|
|
23794
|
-
hasSiren,
|
|
23795
|
-
hasFloodlight,
|
|
23796
|
-
hasPir,
|
|
23797
|
-
isDoorbell,
|
|
23798
|
-
// Autotracking: explicit flags only (autoPt or smartAI)
|
|
23799
|
-
// Note: the heuristic (ptzControl && aitype) was too aggressive and caused false positives
|
|
23800
|
-
// on cameras that have PTZ and AI detection but NOT autotracking capability.
|
|
23801
|
-
hasAutotracking: item ? truthy(item.autoPt) || truthy(item.smartAI) : false
|
|
23802
|
-
};
|
|
23803
|
-
}
|
|
23804
24334
|
/**
|
|
23805
24335
|
* Parse support features from SupportInfo.
|
|
23806
24336
|
*/
|
|
@@ -26673,6 +27203,216 @@ ${scheduleItems}
|
|
|
26673
27203
|
const channel = 0;
|
|
26674
27204
|
return await this.getSnapshot(channel);
|
|
26675
27205
|
}
|
|
27206
|
+
// --------------------
|
|
27207
|
+
// Chime / DingDong APIs
|
|
27208
|
+
// --------------------
|
|
27209
|
+
/**
|
|
27210
|
+
* Get the list of paired wireless chime devices.
|
|
27211
|
+
* cmd_id: 484 (GetDingDongList)
|
|
27212
|
+
*
|
|
27213
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27214
|
+
* @returns Array of paired chime devices
|
|
27215
|
+
*/
|
|
27216
|
+
async getDingDongList(channel) {
|
|
27217
|
+
const ch = this.normalizeChannel(channel);
|
|
27218
|
+
const xml = await this.sendXml({
|
|
27219
|
+
cmdId: BC_CMD_ID_GET_DING_DONG_LIST,
|
|
27220
|
+
channel: ch
|
|
27221
|
+
});
|
|
27222
|
+
return parseDingDongListFromXml(xml);
|
|
27223
|
+
}
|
|
27224
|
+
/**
|
|
27225
|
+
* Get parameters (name, volume, LED state) for a specific wireless chime.
|
|
27226
|
+
* cmd_id: 485 (DingDongOpt, option getParam)
|
|
27227
|
+
*
|
|
27228
|
+
* @param chimeId - The chime device ID
|
|
27229
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27230
|
+
* @returns Chime parameters
|
|
27231
|
+
*/
|
|
27232
|
+
async getDingDongParams(chimeId, channel) {
|
|
27233
|
+
const ch = this.normalizeChannel(channel);
|
|
27234
|
+
const payloadXml = buildDingDongGetParamsXml(chimeId);
|
|
27235
|
+
const xml = await this.sendXml({
|
|
27236
|
+
cmdId: BC_CMD_ID_DING_DONG_OPT,
|
|
27237
|
+
channel: ch,
|
|
27238
|
+
payloadXml
|
|
27239
|
+
});
|
|
27240
|
+
return parseDingDongParamsFromXml(xml);
|
|
27241
|
+
}
|
|
27242
|
+
/**
|
|
27243
|
+
* Set parameters (name, volume, LED state) for a specific wireless chime.
|
|
27244
|
+
* cmd_id: 485 (DingDongOpt, option setParam)
|
|
27245
|
+
*
|
|
27246
|
+
* @param chimeId - The chime device ID
|
|
27247
|
+
* @param params - Parameters to set (volLevel, ledState, name)
|
|
27248
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27249
|
+
*/
|
|
27250
|
+
async setDingDongParams(chimeId, params, channel) {
|
|
27251
|
+
const ch = this.normalizeChannel(channel);
|
|
27252
|
+
const payloadXml = buildDingDongSetParamsXml(chimeId, params);
|
|
27253
|
+
await this.sendXml({
|
|
27254
|
+
cmdId: BC_CMD_ID_DING_DONG_OPT,
|
|
27255
|
+
channel: ch,
|
|
27256
|
+
payloadXml
|
|
27257
|
+
});
|
|
27258
|
+
}
|
|
27259
|
+
/**
|
|
27260
|
+
* Trigger a wireless chime to ring with a specific ringtone.
|
|
27261
|
+
* cmd_id: 485 (DingDongOpt, option ringWithMusic)
|
|
27262
|
+
*
|
|
27263
|
+
* @param chimeId - The chime device ID
|
|
27264
|
+
* @param musicId - The ringtone/music ID to play
|
|
27265
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27266
|
+
*/
|
|
27267
|
+
async ringDingDong(chimeId, musicId, channel) {
|
|
27268
|
+
const ch = this.normalizeChannel(channel);
|
|
27269
|
+
const payloadXml = buildDingDongRingXml(chimeId, musicId);
|
|
27270
|
+
await this.sendXml({
|
|
27271
|
+
cmdId: BC_CMD_ID_DING_DONG_OPT,
|
|
27272
|
+
channel: ch,
|
|
27273
|
+
payloadXml
|
|
27274
|
+
});
|
|
27275
|
+
}
|
|
27276
|
+
/**
|
|
27277
|
+
* Get the per-event alarm configuration for paired wireless chimes.
|
|
27278
|
+
* cmd_id: 486 (GetDingDongCfg)
|
|
27279
|
+
*
|
|
27280
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27281
|
+
* @returns Array of chime configurations (one per paired chime)
|
|
27282
|
+
*/
|
|
27283
|
+
async getDingDongCfg(channel) {
|
|
27284
|
+
const ch = this.normalizeChannel(channel);
|
|
27285
|
+
const xml = await this.sendXml({
|
|
27286
|
+
cmdId: BC_CMD_ID_GET_DING_DONG_CFG,
|
|
27287
|
+
channel: ch
|
|
27288
|
+
});
|
|
27289
|
+
return parseDingDongCfgFromXml(xml);
|
|
27290
|
+
}
|
|
27291
|
+
/**
|
|
27292
|
+
* Set the per-event alarm configuration for a specific wireless chime.
|
|
27293
|
+
* cmd_id: 487 (SetDingDongCfg)
|
|
27294
|
+
*
|
|
27295
|
+
* @param chimeId - The chime ring/device ID
|
|
27296
|
+
* @param eventType - Event type string (e.g. "doorbell", "package", "people")
|
|
27297
|
+
* @param state - 0 = disabled, 1 = enabled
|
|
27298
|
+
* @param musicId - Ringtone ID to use for this event type
|
|
27299
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27300
|
+
*/
|
|
27301
|
+
async setDingDongCfg(chimeId, eventType, state, musicId, channel) {
|
|
27302
|
+
const ch = this.normalizeChannel(channel);
|
|
27303
|
+
const payloadXml = buildSetDingDongCfgXml(chimeId, eventType, state, musicId);
|
|
27304
|
+
await this.sendXml({
|
|
27305
|
+
cmdId: BC_CMD_ID_SET_DING_DONG_CFG,
|
|
27306
|
+
channel: ch,
|
|
27307
|
+
payloadXml
|
|
27308
|
+
});
|
|
27309
|
+
}
|
|
27310
|
+
/** Cache of last known hardwired chime state per channel, used to avoid re-fetching on every set. */
|
|
27311
|
+
_hardwiredChimeCache = /* @__PURE__ */ new Map();
|
|
27312
|
+
/**
|
|
27313
|
+
* Get the hardwired (wired-in) chime state.
|
|
27314
|
+
* cmd_id: 483 (GetDingDongCtrl)
|
|
27315
|
+
*
|
|
27316
|
+
* Note: calling this may briefly trigger the physical chime to rattle.
|
|
27317
|
+
*
|
|
27318
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27319
|
+
* @returns Hardwired chime state (type, enabled, time)
|
|
27320
|
+
*/
|
|
27321
|
+
async getHardwiredChime(channel) {
|
|
27322
|
+
const ch = this.normalizeChannel(channel);
|
|
27323
|
+
const payloadXml = buildGetDingDongCtrlXml();
|
|
27324
|
+
const xml = await this.sendXml({
|
|
27325
|
+
cmdId: BC_CMD_ID_DING_DONG_CTRL,
|
|
27326
|
+
channel: ch,
|
|
27327
|
+
payloadXml
|
|
27328
|
+
});
|
|
27329
|
+
const state = parseHardwiredChimeFromXml(xml);
|
|
27330
|
+
this._hardwiredChimeCache.set(ch, state);
|
|
27331
|
+
return state;
|
|
27332
|
+
}
|
|
27333
|
+
/**
|
|
27334
|
+
* Set the hardwired (wired-in) chime state.
|
|
27335
|
+
* cmd_id: 483 (SetDingDongCtrl)
|
|
27336
|
+
*
|
|
27337
|
+
* Uses the cached state from a previous getHardwiredChime call to fill in
|
|
27338
|
+
* missing type/time fields, avoiding a double round-trip on every set.
|
|
27339
|
+
* Falls back to fetching if no cache is available.
|
|
27340
|
+
*
|
|
27341
|
+
* @param params - Chime configuration (type, enabled, time)
|
|
27342
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27343
|
+
*/
|
|
27344
|
+
async setHardwiredChime(params, channel) {
|
|
27345
|
+
const ch = this.normalizeChannel(channel);
|
|
27346
|
+
let current = this._hardwiredChimeCache.get(ch);
|
|
27347
|
+
if (!current) {
|
|
27348
|
+
current = await this.getHardwiredChime(ch);
|
|
27349
|
+
}
|
|
27350
|
+
const chimeType = params.type ?? current.type;
|
|
27351
|
+
const enabled = params.enabled ? 1 : 0;
|
|
27352
|
+
const time = params.time ?? current.time;
|
|
27353
|
+
const payloadXml = buildSetDingDongCtrlXml(chimeType, enabled, time);
|
|
27354
|
+
const xml = await this.sendXml({
|
|
27355
|
+
cmdId: BC_CMD_ID_DING_DONG_CTRL,
|
|
27356
|
+
channel: ch,
|
|
27357
|
+
payloadXml
|
|
27358
|
+
});
|
|
27359
|
+
const newState = parseHardwiredChimeFromXml(xml);
|
|
27360
|
+
this._hardwiredChimeCache.set(ch, newState);
|
|
27361
|
+
return newState;
|
|
27362
|
+
}
|
|
27363
|
+
/**
|
|
27364
|
+
* Play an audio file on the doorbell / chime device.
|
|
27365
|
+
* cmd_id: 349 (QuickReplyPlay)
|
|
27366
|
+
*
|
|
27367
|
+
* @param fileId - The audio file ID to play
|
|
27368
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27369
|
+
*/
|
|
27370
|
+
async quickReplyPlay(fileId, channel) {
|
|
27371
|
+
const ch = this.normalizeChannel(channel);
|
|
27372
|
+
const payloadXml = buildQuickReplyPlayXml(ch, fileId);
|
|
27373
|
+
await this.sendXml({
|
|
27374
|
+
cmdId: BC_CMD_ID_QUICK_REPLY_PLAY,
|
|
27375
|
+
channel: ch,
|
|
27376
|
+
payloadXml
|
|
27377
|
+
});
|
|
27378
|
+
}
|
|
27379
|
+
/**
|
|
27380
|
+
* Get the silent mode state of a paired wireless chime.
|
|
27381
|
+
* cmd_id: 609 (GetDingDongSilent)
|
|
27382
|
+
*
|
|
27383
|
+
* @param chimeId - The wireless chime device ID (from getDingDongList)
|
|
27384
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27385
|
+
* @returns Wireless chime silent state (time=0 means active/not silenced)
|
|
27386
|
+
*/
|
|
27387
|
+
async getDingDongSilent(chimeId, channel) {
|
|
27388
|
+
const ch = this.normalizeChannel(channel);
|
|
27389
|
+
const payloadXml = buildGetDingDongSilentXml(chimeId);
|
|
27390
|
+
const xml = await this.sendXml({
|
|
27391
|
+
cmdId: BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
27392
|
+
channel: ch,
|
|
27393
|
+
payloadXml
|
|
27394
|
+
});
|
|
27395
|
+
return parseWirelessChimeSilentFromXml(xml, chimeId);
|
|
27396
|
+
}
|
|
27397
|
+
/**
|
|
27398
|
+
* Set the silent mode of a paired wireless chime.
|
|
27399
|
+
* cmd_id: 610 (SetDingDongSilent)
|
|
27400
|
+
*
|
|
27401
|
+
* @param chimeId - The wireless chime device ID (from getDingDongList)
|
|
27402
|
+
* @param time - Silence duration in seconds. 0 = not silenced (chime active), >0 = silenced for this many seconds.
|
|
27403
|
+
* @param channel - Channel number (0-based, default 0)
|
|
27404
|
+
* @returns Updated wireless chime silent state
|
|
27405
|
+
*/
|
|
27406
|
+
async setDingDongSilent(chimeId, time, channel) {
|
|
27407
|
+
const ch = this.normalizeChannel(channel);
|
|
27408
|
+
const payloadXml = buildSetDingDongSilentXml(chimeId, time);
|
|
27409
|
+
const xml = await this.sendXml({
|
|
27410
|
+
cmdId: BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
27411
|
+
channel: ch,
|
|
27412
|
+
payloadXml
|
|
27413
|
+
});
|
|
27414
|
+
return parseWirelessChimeSilentFromXml(xml, chimeId);
|
|
27415
|
+
}
|
|
26676
27416
|
};
|
|
26677
27417
|
|
|
26678
27418
|
// src/reolink/baichuan/HlsSessionManager.ts
|
|
@@ -27025,8 +27765,12 @@ function detectIosClient(userAgent) {
|
|
|
27025
27765
|
return {
|
|
27026
27766
|
isIos,
|
|
27027
27767
|
isIosInstalledApp,
|
|
27028
|
-
// iOS
|
|
27029
|
-
|
|
27768
|
+
// ALL iOS clients need HLS for reliable video clip playback.
|
|
27769
|
+
// iOS AVFoundation requires Content-Length + Range support for regular MP4,
|
|
27770
|
+
// but generating the full MP4 upfront takes too long (camera download + transcode).
|
|
27771
|
+
// HLS delivers segments progressively (~3-5s to first frame vs 25+ seconds).
|
|
27772
|
+
// Safari, AVPlayer, and InstalledApp all support HLS natively.
|
|
27773
|
+
needsHls: isIos
|
|
27030
27774
|
};
|
|
27031
27775
|
}
|
|
27032
27776
|
function buildHlsRedirectUrl(originalUrl) {
|
|
@@ -34545,6 +35289,8 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
34545
35289
|
BC_CMD_ID_COVER_STANDALONE_460,
|
|
34546
35290
|
BC_CMD_ID_COVER_STANDALONE_461,
|
|
34547
35291
|
BC_CMD_ID_COVER_STANDALONE_462,
|
|
35292
|
+
BC_CMD_ID_DING_DONG_CTRL,
|
|
35293
|
+
BC_CMD_ID_DING_DONG_OPT,
|
|
34548
35294
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|
|
34549
35295
|
BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO,
|
|
34550
35296
|
BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD,
|
|
@@ -34568,6 +35314,9 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
34568
35314
|
BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
34569
35315
|
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
34570
35316
|
BC_CMD_ID_GET_DAY_RECORDS,
|
|
35317
|
+
BC_CMD_ID_GET_DING_DONG_CFG,
|
|
35318
|
+
BC_CMD_ID_GET_DING_DONG_LIST,
|
|
35319
|
+
BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
34571
35320
|
BC_CMD_ID_GET_EMAIL_TASK,
|
|
34572
35321
|
BC_CMD_ID_GET_FTP_TASK,
|
|
34573
35322
|
BC_CMD_ID_GET_HDD_INFO_LIST,
|
|
@@ -34604,9 +35353,12 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
34604
35353
|
BC_CMD_ID_PUSH_SERIAL,
|
|
34605
35354
|
BC_CMD_ID_PUSH_SLEEP_STATUS,
|
|
34606
35355
|
BC_CMD_ID_PUSH_VIDEO_INPUT,
|
|
35356
|
+
BC_CMD_ID_QUICK_REPLY_PLAY,
|
|
34607
35357
|
BC_CMD_ID_SET_AI_ALARM,
|
|
34608
35358
|
BC_CMD_ID_SET_AI_CFG,
|
|
34609
35359
|
BC_CMD_ID_SET_AUDIO_TASK,
|
|
35360
|
+
BC_CMD_ID_SET_DING_DONG_CFG,
|
|
35361
|
+
BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
34610
35362
|
BC_CMD_ID_SET_MOTION_ALARM,
|
|
34611
35363
|
BC_CMD_ID_SET_PIR_INFO,
|
|
34612
35364
|
BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
@@ -34723,6 +35475,7 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
34723
35475
|
getGlobalLogger,
|
|
34724
35476
|
getH265NalType,
|
|
34725
35477
|
getMjpegContentType,
|
|
35478
|
+
getSupportItemForChannel,
|
|
34726
35479
|
getVideoStream,
|
|
34727
35480
|
getVideoclipClientInfo,
|
|
34728
35481
|
getXmlText,
|