@apocaliss92/nodelink-js 0.4.10 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BaichuanVideoStream-PHQG4A2L.js +7 -0
- package/dist/{DiagnosticsTools-RNIDFEJK.js → DiagnosticsTools-HGJGVQXZ.js} +3 -2
- package/dist/DiagnosticsTools-HGJGVQXZ.js.map +1 -0
- package/dist/{chunk-EDLMKBG2.js → chunk-IJG45AOT.js} +151 -2733
- package/dist/chunk-IJG45AOT.js.map +1 -0
- package/dist/{chunk-HGQ53FB3.js → chunk-ND73IJIB.js} +765 -45
- package/dist/chunk-ND73IJIB.js.map +1 -0
- package/dist/chunk-W2ANCJVM.js +2703 -0
- package/dist/chunk-W2ANCJVM.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +834 -10
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -2
- package/dist/cli/rtsp-server.js.map +1 -1
- package/dist/index.cjs +1322 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +708 -142
- package/dist/index.d.ts +601 -20
- package/dist/index.js +527 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-EDLMKBG2.js.map +0 -1
- package/dist/chunk-HGQ53FB3.js.map +0 -1
- /package/dist/{DiagnosticsTools-RNIDFEJK.js.map → BaichuanVideoStream-PHQG4A2L.js.map} +0 -0
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReolinkCgiApi,
|
|
3
|
+
ReolinkHttpClient,
|
|
4
|
+
applyStreamPatch,
|
|
5
|
+
applyXmlTagPatch,
|
|
6
|
+
buildAbilityInfoExtensionXml,
|
|
7
|
+
buildBinaryExtensionXml,
|
|
8
|
+
buildChannelExtensionXml,
|
|
9
|
+
buildFloodlightManualXml,
|
|
10
|
+
buildLoginXml,
|
|
11
|
+
buildLogoutXml,
|
|
12
|
+
buildPreviewStopXml,
|
|
13
|
+
buildPreviewStopXmlV11,
|
|
14
|
+
buildPreviewXml,
|
|
15
|
+
buildPreviewXmlV11,
|
|
16
|
+
buildPtzControlXml,
|
|
17
|
+
buildPtzPresetXml,
|
|
18
|
+
buildPtzPresetXmlV2,
|
|
19
|
+
buildRtspUrl,
|
|
20
|
+
buildSirenManualXml,
|
|
21
|
+
buildSirenTimesXml,
|
|
22
|
+
buildStartZoomFocusXml,
|
|
23
|
+
collectNvrDiagnostics,
|
|
24
|
+
ensureXmlHeader,
|
|
25
|
+
getXmlText,
|
|
26
|
+
normalizeDayNightMode,
|
|
27
|
+
normalizeOpenClose,
|
|
28
|
+
parseRecordingFileName,
|
|
29
|
+
patchNestedTag,
|
|
30
|
+
runAllDiagnosticsConsecutively,
|
|
31
|
+
runMultifocalDiagnosticsConsecutively,
|
|
32
|
+
xmlEscape
|
|
33
|
+
} from "./chunk-IJG45AOT.js";
|
|
1
34
|
import {
|
|
2
35
|
BC_CLASS_FILE_DOWNLOAD,
|
|
3
36
|
BC_CLASS_LEGACY,
|
|
@@ -61,6 +94,7 @@ import {
|
|
|
61
94
|
BC_CMD_ID_GET_SUPPORT,
|
|
62
95
|
BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
63
96
|
BC_CMD_ID_GET_TIMELAPSE_CFG,
|
|
97
|
+
BC_CMD_ID_GET_VERSION_INFO,
|
|
64
98
|
BC_CMD_ID_GET_VIDEO_INPUT,
|
|
65
99
|
BC_CMD_ID_GET_WHITE_LED,
|
|
66
100
|
BC_CMD_ID_GET_WIFI,
|
|
@@ -89,6 +123,7 @@ import {
|
|
|
89
123
|
BC_CMD_ID_SET_ENC,
|
|
90
124
|
BC_CMD_ID_SET_LED_STATE,
|
|
91
125
|
BC_CMD_ID_SET_MOTION_ALARM,
|
|
126
|
+
BC_CMD_ID_SET_OSD_DATETIME,
|
|
92
127
|
BC_CMD_ID_SET_PIR_INFO,
|
|
93
128
|
BC_CMD_ID_SET_PRIVACY_MASK,
|
|
94
129
|
BC_CMD_ID_SET_VIDEO_INPUT,
|
|
@@ -108,60 +143,29 @@ import {
|
|
|
108
143
|
BC_TCP_DEFAULT_PORT,
|
|
109
144
|
BaichuanVideoStream,
|
|
110
145
|
BcMediaAnnexBDecoder,
|
|
111
|
-
ReolinkCgiApi,
|
|
112
|
-
ReolinkHttpClient,
|
|
113
146
|
__require,
|
|
114
147
|
aesDecrypt,
|
|
115
148
|
aesEncrypt,
|
|
116
|
-
applyStreamPatch,
|
|
117
|
-
applyXmlTagPatch,
|
|
118
149
|
bcDecrypt,
|
|
119
150
|
bcEncrypt,
|
|
120
151
|
bcHeaderHasPayloadOffset,
|
|
121
|
-
buildAbilityInfoExtensionXml,
|
|
122
|
-
buildBinaryExtensionXml,
|
|
123
|
-
buildChannelExtensionXml,
|
|
124
|
-
buildFloodlightManualXml,
|
|
125
|
-
buildLoginXml,
|
|
126
|
-
buildLogoutXml,
|
|
127
|
-
buildPreviewStopXml,
|
|
128
|
-
buildPreviewStopXmlV11,
|
|
129
|
-
buildPreviewXml,
|
|
130
|
-
buildPreviewXmlV11,
|
|
131
|
-
buildPtzControlXml,
|
|
132
|
-
buildPtzPresetXml,
|
|
133
|
-
buildPtzPresetXmlV2,
|
|
134
|
-
buildRtspUrl,
|
|
135
|
-
buildSirenManualXml,
|
|
136
|
-
buildSirenTimesXml,
|
|
137
|
-
buildStartZoomFocusXml,
|
|
138
|
-
collectNvrDiagnostics,
|
|
139
152
|
convertToAnnexB,
|
|
140
153
|
convertToAnnexB2,
|
|
141
154
|
debugLog,
|
|
142
155
|
deriveAesKey,
|
|
143
|
-
ensureXmlHeader,
|
|
144
156
|
eventTraceLog,
|
|
145
157
|
extractPpsFromAnnexB,
|
|
146
158
|
extractSpsFromAnnexB,
|
|
147
159
|
extractVpsFromAnnexB,
|
|
148
|
-
getXmlText,
|
|
149
160
|
isH265Irap,
|
|
150
161
|
md5StrModern,
|
|
151
|
-
normalizeDayNightMode,
|
|
152
162
|
normalizeDebugOptions,
|
|
153
|
-
normalizeOpenClose,
|
|
154
|
-
parseRecordingFileName,
|
|
155
|
-
patchNestedTag,
|
|
156
163
|
recordingsTraceLog,
|
|
157
|
-
runAllDiagnosticsConsecutively,
|
|
158
|
-
runMultifocalDiagnosticsConsecutively,
|
|
159
164
|
splitAnnexBToNalPayloads,
|
|
160
165
|
splitAnnexBToNalPayloads2,
|
|
161
166
|
talkTraceLog,
|
|
162
|
-
traceLog
|
|
163
|
-
|
|
164
|
-
} from "./chunk-EDLMKBG2.js";
|
|
167
|
+
traceLog
|
|
168
|
+
} from "./chunk-W2ANCJVM.js";
|
|
165
169
|
|
|
166
170
|
// src/protocol/framing.ts
|
|
167
171
|
function encodeHeader(h) {
|
|
@@ -1870,6 +1874,23 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
1870
1874
|
* even if the current client instance is idle/disconnected.
|
|
1871
1875
|
*/
|
|
1872
1876
|
static streamingRegistry = /* @__PURE__ */ new Map();
|
|
1877
|
+
/**
|
|
1878
|
+
* Per-device set of live BaichuanClient instances.
|
|
1879
|
+
*
|
|
1880
|
+
* Why: when a streaming client unsubscribes (e.g. RTSP grace timer expires
|
|
1881
|
+
* and SocketPool tears the streaming socket down), the global streaming
|
|
1882
|
+
* registry decrements but the GENERAL client of the same device has no
|
|
1883
|
+
* way of knowing — its idle-disconnect timer was last evaluated while
|
|
1884
|
+
* `isDeviceStreamingActive()` was still true (because the streaming socket
|
|
1885
|
+
* was still alive) and wasn't rescheduled. Without this registry the
|
|
1886
|
+
* general socket stays connected, the 60-second session-guard timer keeps
|
|
1887
|
+
* sending getOnlineUserList() to the camera, and a battery camera ends up
|
|
1888
|
+
* waking up every minute (issue #18).
|
|
1889
|
+
*
|
|
1890
|
+
* On streamingRegistry decrement-to-zero we walk this set and kick every
|
|
1891
|
+
* sibling's idle-disconnect timer so it can re-evaluate eligibility.
|
|
1892
|
+
*/
|
|
1893
|
+
static deviceClients = /* @__PURE__ */ new Map();
|
|
1873
1894
|
/**
|
|
1874
1895
|
* Per-host D2C_DISC backoff state that persists across client instance recreation.
|
|
1875
1896
|
*
|
|
@@ -1984,6 +2005,29 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
1984
2005
|
// AlarmEventList (cmdId=33) can be very chatty (often sent every second).
|
|
1985
2006
|
// Track last per-channel alarm state so we only emit on transitions.
|
|
1986
2007
|
alarmEventState = /* @__PURE__ */ new Map();
|
|
2008
|
+
/** Whether this instance is currently in BaichuanClient.deviceClients. */
|
|
2009
|
+
registeredInDeviceClients = false;
|
|
2010
|
+
registerInDeviceClients() {
|
|
2011
|
+
if (this.registeredInDeviceClients) return;
|
|
2012
|
+
const key = this.getDeviceRegistryKey();
|
|
2013
|
+
let set = _BaichuanClient.deviceClients.get(key);
|
|
2014
|
+
if (!set) {
|
|
2015
|
+
set = /* @__PURE__ */ new Set();
|
|
2016
|
+
_BaichuanClient.deviceClients.set(key, set);
|
|
2017
|
+
}
|
|
2018
|
+
set.add(this);
|
|
2019
|
+
this.registeredInDeviceClients = true;
|
|
2020
|
+
}
|
|
2021
|
+
unregisterFromDeviceClients() {
|
|
2022
|
+
if (!this.registeredInDeviceClients) return;
|
|
2023
|
+
const key = this.getDeviceRegistryKey();
|
|
2024
|
+
const set = _BaichuanClient.deviceClients.get(key);
|
|
2025
|
+
if (set) {
|
|
2026
|
+
set.delete(this);
|
|
2027
|
+
if (set.size === 0) _BaichuanClient.deviceClients.delete(key);
|
|
2028
|
+
}
|
|
2029
|
+
this.registeredInDeviceClients = false;
|
|
2030
|
+
}
|
|
1987
2031
|
constructor(options) {
|
|
1988
2032
|
super();
|
|
1989
2033
|
this.opts = options;
|
|
@@ -1998,6 +2042,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
1998
2042
|
code: err?.code
|
|
1999
2043
|
});
|
|
2000
2044
|
});
|
|
2045
|
+
this.registerInDeviceClients();
|
|
2001
2046
|
}
|
|
2002
2047
|
newSocketSessionId(transport) {
|
|
2003
2048
|
const short = randomUUID().split("-")[0] ?? randomUUID().slice(0, 8);
|
|
@@ -2254,6 +2299,18 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
2254
2299
|
activeStreamClients: nextCount
|
|
2255
2300
|
});
|
|
2256
2301
|
this.contributesToGlobalStreamingRegistry = shouldContribute;
|
|
2302
|
+
if (!shouldContribute && nextCount === 0) {
|
|
2303
|
+
const siblings = _BaichuanClient.deviceClients.get(key);
|
|
2304
|
+
if (siblings) {
|
|
2305
|
+
for (const sib of siblings) {
|
|
2306
|
+
if (sib === this) continue;
|
|
2307
|
+
try {
|
|
2308
|
+
sib.kickIdleDisconnectTimer();
|
|
2309
|
+
} catch {
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2257
2314
|
}
|
|
2258
2315
|
/**
|
|
2259
2316
|
* True if the device should be considered "awake" due to active streaming.
|
|
@@ -2718,6 +2775,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
2718
2775
|
`transport=tcp host=${this.opts.host} port=${port}${sid ? ` sid=${sid}` : ""}${remote ? ` remote=${remote}` : ""}${peer ? ` peer=${peer}` : ""}`
|
|
2719
2776
|
);
|
|
2720
2777
|
this.logSocketState("tcp_connected");
|
|
2778
|
+
this.registerInDeviceClients();
|
|
2721
2779
|
this.startKeepAlive();
|
|
2722
2780
|
this.kickIdleDisconnectTimer();
|
|
2723
2781
|
}
|
|
@@ -3034,6 +3092,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
3034
3092
|
this.logDebug("udp_close_error", e);
|
|
3035
3093
|
}
|
|
3036
3094
|
}
|
|
3095
|
+
this.unregisterFromDeviceClients();
|
|
3037
3096
|
}
|
|
3038
3097
|
handleFrame(frame) {
|
|
3039
3098
|
const now = Date.now();
|
|
@@ -8335,6 +8394,31 @@ var parseAbilityInfoXml = (xml) => {
|
|
|
8335
8394
|
return abilities;
|
|
8336
8395
|
};
|
|
8337
8396
|
|
|
8397
|
+
// src/reolink/baichuan/utils/versionInfo.ts
|
|
8398
|
+
function parseVersionInfo(xml) {
|
|
8399
|
+
const out = {};
|
|
8400
|
+
const set = (key) => {
|
|
8401
|
+
const v = getXmlText(xml, key);
|
|
8402
|
+
if (v !== void 0) out[key] = v;
|
|
8403
|
+
};
|
|
8404
|
+
set("name");
|
|
8405
|
+
set("type");
|
|
8406
|
+
set("serialNumber");
|
|
8407
|
+
set("buildDay");
|
|
8408
|
+
set("hardwareVersion");
|
|
8409
|
+
set("cfgVersion");
|
|
8410
|
+
set("firmwareVersion");
|
|
8411
|
+
set("detail");
|
|
8412
|
+
set("IEClient");
|
|
8413
|
+
set("cc3200Version");
|
|
8414
|
+
set("spVersion");
|
|
8415
|
+
set("pakSuffix");
|
|
8416
|
+
set("itemNo");
|
|
8417
|
+
set("aiVersion");
|
|
8418
|
+
set("helpVersion");
|
|
8419
|
+
return out;
|
|
8420
|
+
}
|
|
8421
|
+
|
|
8338
8422
|
// src/reolink/baichuan/utils/logging.ts
|
|
8339
8423
|
var formatErrorForLog = (e) => {
|
|
8340
8424
|
if (e instanceof Error) {
|
|
@@ -8589,6 +8673,204 @@ var buildChannelPushDataLogSnapshot = (channelPushData) => {
|
|
|
8589
8673
|
return { result: resultObj, storedChannels: Object.keys(resultObj) };
|
|
8590
8674
|
};
|
|
8591
8675
|
|
|
8676
|
+
// src/reolink/baichuan/utils/detection.ts
|
|
8677
|
+
import * as lz4 from "lz4js";
|
|
8678
|
+
|
|
8679
|
+
// src/reolink/baichuan/utils/aiClassMap.ts
|
|
8680
|
+
function type1ToLabel(type1) {
|
|
8681
|
+
switch (type1) {
|
|
8682
|
+
case 1:
|
|
8683
|
+
return "people";
|
|
8684
|
+
case 2:
|
|
8685
|
+
return "vehicle";
|
|
8686
|
+
case 3:
|
|
8687
|
+
return "animal";
|
|
8688
|
+
case 11259375:
|
|
8689
|
+
return "face";
|
|
8690
|
+
default:
|
|
8691
|
+
return "unknown";
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
|
|
8695
|
+
// src/reolink/baichuan/utils/detection.ts
|
|
8696
|
+
var MARKER_LENGTH = 8;
|
|
8697
|
+
var IFRAME_PREFIX_LENGTH = 8;
|
|
8698
|
+
var COUNTER_OFFSET = 8;
|
|
8699
|
+
var BASELINE_SIZE = 128;
|
|
8700
|
+
var FRAME_SIZE_TLV = Buffer.from([3, 4, 0]);
|
|
8701
|
+
var LZ4F_MAGIC = Buffer.from([4, 34, 77, 24]);
|
|
8702
|
+
var CONFIDENCE_DIVISOR = 100;
|
|
8703
|
+
var DEFAULT_AI_FRAME_WIDTH = 896;
|
|
8704
|
+
var DEFAULT_AI_FRAME_HEIGHT = 480;
|
|
8705
|
+
var LZ4_DECOMPRESS_MAX = 256 * 1024;
|
|
8706
|
+
function walkBoxes(buf, start, end, type1, type2, out) {
|
|
8707
|
+
let pos = start;
|
|
8708
|
+
while (pos + 3 <= end) {
|
|
8709
|
+
const t = buf[pos];
|
|
8710
|
+
if (t === 0) return;
|
|
8711
|
+
const length = buf[pos + 1] | buf[pos + 2] << 8;
|
|
8712
|
+
const recordEnd = pos + 3 + length;
|
|
8713
|
+
if (recordEnd > end) return;
|
|
8714
|
+
const isBoxType4 = t === 4 && (length === 10 || length === 13 || length === 14);
|
|
8715
|
+
const isBoxType2 = t === 2 && length === 10;
|
|
8716
|
+
if ((isBoxType4 || isBoxType2) && type1 !== 0 && type2 !== 0) {
|
|
8717
|
+
const x1 = buf.readUInt16LE(pos + 3);
|
|
8718
|
+
const y1 = buf.readUInt16LE(pos + 5);
|
|
8719
|
+
const x2 = buf.readUInt16LE(pos + 7);
|
|
8720
|
+
const y2 = buf.readUInt16LE(pos + 9);
|
|
8721
|
+
const conf = buf.readUInt16LE(pos + 11);
|
|
8722
|
+
if (x2 > x1 && y2 > y1) {
|
|
8723
|
+
out.push({ x1, y1, x2, y2, conf, label: type1ToLabel(type1) });
|
|
8724
|
+
}
|
|
8725
|
+
pos = recordEnd;
|
|
8726
|
+
continue;
|
|
8727
|
+
}
|
|
8728
|
+
if (type1 === 255 && type2 === 2 && t === 2 && length >= LZ4F_MAGIC.length && buf[pos + 3] === LZ4F_MAGIC[0] && buf[pos + 4] === LZ4F_MAGIC[1] && buf[pos + 5] === LZ4F_MAGIC[2] && buf[pos + 6] === LZ4F_MAGIC[3]) {
|
|
8729
|
+
try {
|
|
8730
|
+
const decompressed = lz4.decompress(
|
|
8731
|
+
buf.subarray(pos + 3, recordEnd),
|
|
8732
|
+
LZ4_DECOMPRESS_MAX
|
|
8733
|
+
);
|
|
8734
|
+
const decBuf = Buffer.from(decompressed);
|
|
8735
|
+
walkBoxes(decBuf, 0, decBuf.length, 0, 0, out);
|
|
8736
|
+
} catch {
|
|
8737
|
+
}
|
|
8738
|
+
pos = recordEnd;
|
|
8739
|
+
continue;
|
|
8740
|
+
}
|
|
8741
|
+
if (length > 0) {
|
|
8742
|
+
let nextT1 = type1;
|
|
8743
|
+
let nextT2 = type2;
|
|
8744
|
+
if (type1 === 0) nextT1 = t;
|
|
8745
|
+
else if (type2 === 0) nextT2 = t;
|
|
8746
|
+
walkBoxes(buf, pos + 3, recordEnd, nextT1, nextT2, out);
|
|
8747
|
+
}
|
|
8748
|
+
pos = recordEnd;
|
|
8749
|
+
}
|
|
8750
|
+
}
|
|
8751
|
+
function decodeDetectionHeader(raw, frameType) {
|
|
8752
|
+
const markerOffset = frameType === "Iframe" ? IFRAME_PREFIX_LENGTH : 0;
|
|
8753
|
+
const blockLength = raw.length - markerOffset;
|
|
8754
|
+
const empty = {
|
|
8755
|
+
state: "invalid-marker",
|
|
8756
|
+
markerOffset,
|
|
8757
|
+
blockLength,
|
|
8758
|
+
boxes: []
|
|
8759
|
+
};
|
|
8760
|
+
if (blockLength < MARKER_LENGTH) return empty;
|
|
8761
|
+
if (!hasStandardMarker(raw, markerOffset)) return empty;
|
|
8762
|
+
if (blockLength < COUNTER_OFFSET + 4) return empty;
|
|
8763
|
+
const counter = raw.readUInt32LE(markerOffset + COUNTER_OFFSET);
|
|
8764
|
+
const rawBoxes = [];
|
|
8765
|
+
walkBoxes(raw, markerOffset, raw.length, 0, 0, rawBoxes);
|
|
8766
|
+
let aiFrameWidth = DEFAULT_AI_FRAME_WIDTH;
|
|
8767
|
+
let aiFrameHeight = DEFAULT_AI_FRAME_HEIGHT;
|
|
8768
|
+
let frameSizeFound = false;
|
|
8769
|
+
const searchStart = markerOffset + MARKER_LENGTH;
|
|
8770
|
+
for (let i = searchStart; i + 7 <= raw.length; i++) {
|
|
8771
|
+
if (raw[i] === FRAME_SIZE_TLV[0] && raw[i + 1] === FRAME_SIZE_TLV[1] && raw[i + 2] === FRAME_SIZE_TLV[2]) {
|
|
8772
|
+
const w = raw.readUInt16LE(i + 3);
|
|
8773
|
+
const h = raw.readUInt16LE(i + 5);
|
|
8774
|
+
if (w >= 64 && w <= 8192 && h >= 64 && h <= 8192) {
|
|
8775
|
+
aiFrameWidth = w;
|
|
8776
|
+
aiFrameHeight = h;
|
|
8777
|
+
frameSizeFound = true;
|
|
8778
|
+
break;
|
|
8779
|
+
}
|
|
8780
|
+
}
|
|
8781
|
+
}
|
|
8782
|
+
const specificity = {
|
|
8783
|
+
face: 4,
|
|
8784
|
+
animal: 3,
|
|
8785
|
+
people: 2,
|
|
8786
|
+
vehicle: 1,
|
|
8787
|
+
unknown: 0
|
|
8788
|
+
};
|
|
8789
|
+
const dedup = /* @__PURE__ */ new Map();
|
|
8790
|
+
for (const rb of rawBoxes) {
|
|
8791
|
+
if (rb.x2 > aiFrameWidth || rb.y2 > aiFrameHeight) continue;
|
|
8792
|
+
const key = `${rb.x1}_${rb.y1}_${rb.x2}_${rb.y2}`;
|
|
8793
|
+
const prev = dedup.get(key);
|
|
8794
|
+
if (!prev || (specificity[rb.label] ?? 0) > (specificity[prev.label] ?? 0)) {
|
|
8795
|
+
dedup.set(key, rb);
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
const boxes = [];
|
|
8799
|
+
for (const rb of dedup.values()) {
|
|
8800
|
+
boxes.push({
|
|
8801
|
+
x: rb.x1 / aiFrameWidth,
|
|
8802
|
+
y: rb.y1 / aiFrameHeight,
|
|
8803
|
+
width: (rb.x2 - rb.x1) / aiFrameWidth,
|
|
8804
|
+
height: (rb.y2 - rb.y1) / aiFrameHeight,
|
|
8805
|
+
...rb.conf > 0 && rb.conf <= 100 ? { confidence: rb.conf / CONFIDENCE_DIVISOR } : {},
|
|
8806
|
+
...rb.label !== "unknown" ? { label: rb.label } : {}
|
|
8807
|
+
});
|
|
8808
|
+
}
|
|
8809
|
+
let state;
|
|
8810
|
+
if (boxes.length > 0) state = "overlay-decoded";
|
|
8811
|
+
else if (blockLength === BASELINE_SIZE) state = "no-overlay";
|
|
8812
|
+
else state = "overlay-undecoded";
|
|
8813
|
+
return {
|
|
8814
|
+
state,
|
|
8815
|
+
markerOffset,
|
|
8816
|
+
blockLength,
|
|
8817
|
+
counter,
|
|
8818
|
+
...frameSizeFound ? { aiFrameWidth, aiFrameHeight } : {},
|
|
8819
|
+
boxes
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8822
|
+
function hasStandardMarker(raw, offset) {
|
|
8823
|
+
if (raw.length < offset + MARKER_LENGTH) return false;
|
|
8824
|
+
return raw[offset] === 255 && raw[offset + 2] === 0 && raw[offset + 3] === 1 && raw[offset + 4] === 11 && raw[offset + 5] === 0 && raw[offset + 6] === 1 && raw[offset + 7] === 8;
|
|
8825
|
+
}
|
|
8826
|
+
|
|
8827
|
+
// src/reolink/baichuan/utils/encOptions.ts
|
|
8828
|
+
function buildEncOptions(list, channel) {
|
|
8829
|
+
const result = { channel };
|
|
8830
|
+
const main = aggregateByType(list, "mainStream");
|
|
8831
|
+
const sub = aggregateByType(list, "subStream");
|
|
8832
|
+
const third = aggregateByType(list, "thirdStream");
|
|
8833
|
+
if (main) result.mainStream = main;
|
|
8834
|
+
if (sub) result.subStream = sub;
|
|
8835
|
+
if (third) result.thirdStream = third;
|
|
8836
|
+
return result;
|
|
8837
|
+
}
|
|
8838
|
+
function aggregateByType(list, type) {
|
|
8839
|
+
const seen = /* @__PURE__ */ new Map();
|
|
8840
|
+
for (const stream of list.streams) {
|
|
8841
|
+
for (const eb of stream.encodeTables) {
|
|
8842
|
+
if (eb.type !== type) continue;
|
|
8843
|
+
const mapped = mapEncodeTable(eb);
|
|
8844
|
+
if (!mapped) continue;
|
|
8845
|
+
const key = `${mapped.width}x${mapped.height}`;
|
|
8846
|
+
if (!seen.has(key)) seen.set(key, mapped);
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
if (seen.size === 0) return void 0;
|
|
8850
|
+
return {
|
|
8851
|
+
type,
|
|
8852
|
+
resolutions: [...seen.values()],
|
|
8853
|
+
encoderTypes: ["vbr", "cbr"],
|
|
8854
|
+
encoderProfiles: ["high", "main", "baseline"]
|
|
8855
|
+
};
|
|
8856
|
+
}
|
|
8857
|
+
function mapEncodeTable(eb) {
|
|
8858
|
+
if (eb.width == null || eb.height == null) return void 0;
|
|
8859
|
+
const videoEncTypes = (eb.videoEncTypeList ?? (eb.videoEncType != null ? [eb.videoEncType] : [])).map(
|
|
8860
|
+
(t) => t === 0 ? "h264" : t === 1 ? "h265" : void 0
|
|
8861
|
+
).filter((t) => t !== void 0);
|
|
8862
|
+
return {
|
|
8863
|
+
width: eb.width,
|
|
8864
|
+
height: eb.height,
|
|
8865
|
+
videoEncTypes,
|
|
8866
|
+
...eb.defaultFramerate != null ? { defaultFramerate: eb.defaultFramerate } : {},
|
|
8867
|
+
...eb.defaultBitrate != null ? { defaultBitrate: eb.defaultBitrate } : {},
|
|
8868
|
+
...eb.defaultGop != null ? { defaultGop: eb.defaultGop } : {},
|
|
8869
|
+
framerateOptions: eb.framerateTable ?? [],
|
|
8870
|
+
bitrateOptions: eb.bitrateTable ?? []
|
|
8871
|
+
};
|
|
8872
|
+
}
|
|
8873
|
+
|
|
8592
8874
|
// src/reolink/baichuan/utils/events.ts
|
|
8593
8875
|
var mapToSimpleEvent = (event) => {
|
|
8594
8876
|
const timestamp = event.timestamp ?? Date.now();
|
|
@@ -10688,6 +10970,21 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
10688
10970
|
sessionGuardIntervalTimer;
|
|
10689
10971
|
simpleEventListeners = /* @__PURE__ */ new Set();
|
|
10690
10972
|
simpleEventSubscribed = false;
|
|
10973
|
+
// Detection events are sourced from BcMedia additionalHeader on active video
|
|
10974
|
+
// streams. Unlike simpleEvent, no Baichuan subscribe command is needed — the
|
|
10975
|
+
// data flows whenever a stream is open. Active streams register themselves via
|
|
10976
|
+
// _registerVideoStreamForDetection (called from BaichuanVideoStream.start).
|
|
10977
|
+
detectionEventListeners = /* @__PURE__ */ new Set();
|
|
10978
|
+
detectionEventStreamHooks = /* @__PURE__ */ new Map();
|
|
10979
|
+
// Auto-managed substream for `onObjectDetections` listeners. Reference-counted
|
|
10980
|
+
// by the listener set: the substream is opened on the first listener and torn
|
|
10981
|
+
// down with the last one. Mirrors `onSimpleEvent`'s subscribe/unsubscribe
|
|
10982
|
+
// lifecycle so a caller never has to manage a video stream just to read AI
|
|
10983
|
+
// detections.
|
|
10984
|
+
objectDetectionListeners = /* @__PURE__ */ new Set();
|
|
10985
|
+
objectDetectionStream;
|
|
10986
|
+
objectDetectionStreamStartInFlight;
|
|
10987
|
+
objectDetectionInternalListener;
|
|
10691
10988
|
simpleEventSubscribeInFlight;
|
|
10692
10989
|
simpleEventUnsubscribeInFlight;
|
|
10693
10990
|
simpleEventResubscribeTimer;
|
|
@@ -12123,6 +12420,205 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12123
12420
|
}
|
|
12124
12421
|
}
|
|
12125
12422
|
}
|
|
12423
|
+
/**
|
|
12424
|
+
* Subscribe to per-frame detection events sourced from the BcMedia
|
|
12425
|
+
* `additionalHeader` block on active video streams.
|
|
12426
|
+
*
|
|
12427
|
+
* Mirrors {@link onSimpleEvent} but is fed by the streaming side-channel:
|
|
12428
|
+
* one event fires for every I-frame / P-frame that carries an overlay block.
|
|
12429
|
+
* Coordinates are reported in normalized [0, 1] fractions of the source
|
|
12430
|
+
* frame, so the same box renders correctly on mainStream, subStream, and
|
|
12431
|
+
* externStream.
|
|
12432
|
+
*
|
|
12433
|
+
* Unlike `onSimpleEvent`, no Baichuan subscribe command is involved — events
|
|
12434
|
+
* only flow while a video stream is open. The library hooks every
|
|
12435
|
+
* `BaichuanVideoStream` created via this API for the listener's lifetime.
|
|
12436
|
+
*/
|
|
12437
|
+
onDetection(callback) {
|
|
12438
|
+
this.detectionEventListeners.add(callback);
|
|
12439
|
+
}
|
|
12440
|
+
/**
|
|
12441
|
+
* Remove a single detection callback, or all of them if `callback` is omitted.
|
|
12442
|
+
*/
|
|
12443
|
+
offDetection(callback) {
|
|
12444
|
+
if (callback) {
|
|
12445
|
+
this.detectionEventListeners.delete(callback);
|
|
12446
|
+
} else {
|
|
12447
|
+
this.detectionEventListeners.clear();
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12450
|
+
/**
|
|
12451
|
+
* Subscribe to AI object detections (people / vehicle / animal / face boxes
|
|
12452
|
+
* with class label and confidence) without managing a video stream yourself.
|
|
12453
|
+
*
|
|
12454
|
+
* Mirrors {@link onSimpleEvent} end-to-end: the API opens a dedicated
|
|
12455
|
+
* substream behind the scenes on the first listener, forwards every box-bearing
|
|
12456
|
+
* `additionalHeader` to your callback, and tears the stream down when the last
|
|
12457
|
+
* listener unsubscribes. The substream is the lightest profile (typically
|
|
12458
|
+
* 640×360) so the additional bandwidth/CPU overhead is minimal.
|
|
12459
|
+
*
|
|
12460
|
+
* Each event carries normalized `[0, 1]` box coordinates, a class label, and
|
|
12461
|
+
* a confidence score — render-ready without further conversion.
|
|
12462
|
+
*/
|
|
12463
|
+
async onObjectDetections(callback) {
|
|
12464
|
+
this.objectDetectionListeners.add(callback);
|
|
12465
|
+
this.logger.debug?.(
|
|
12466
|
+
`[ReolinkBaichuanApi] onObjectDetections: registering listener (total=${this.objectDetectionListeners.size})`
|
|
12467
|
+
);
|
|
12468
|
+
await this.ensureObjectDetectionStream();
|
|
12469
|
+
}
|
|
12470
|
+
/**
|
|
12471
|
+
* Remove one detection callback, or all of them if `callback` is omitted.
|
|
12472
|
+
* When the last listener is removed the auto-managed substream is closed.
|
|
12473
|
+
*/
|
|
12474
|
+
async offObjectDetections(callback) {
|
|
12475
|
+
if (callback) {
|
|
12476
|
+
this.objectDetectionListeners.delete(callback);
|
|
12477
|
+
} else {
|
|
12478
|
+
this.objectDetectionListeners.clear();
|
|
12479
|
+
}
|
|
12480
|
+
if (this.objectDetectionListeners.size === 0) {
|
|
12481
|
+
await this.tearDownObjectDetectionStream();
|
|
12482
|
+
}
|
|
12483
|
+
}
|
|
12484
|
+
async ensureObjectDetectionStream() {
|
|
12485
|
+
if (this.objectDetectionStream) return;
|
|
12486
|
+
if (this.objectDetectionStreamStartInFlight) {
|
|
12487
|
+
await this.objectDetectionStreamStartInFlight;
|
|
12488
|
+
return;
|
|
12489
|
+
}
|
|
12490
|
+
this.objectDetectionStreamStartInFlight = (async () => {
|
|
12491
|
+
const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-PHQG4A2L.js");
|
|
12492
|
+
const sessionKey = `live:object-detections:ch0:sub`;
|
|
12493
|
+
const dedicated = await this.createDedicatedSession(sessionKey);
|
|
12494
|
+
const stream = new BaichuanVideoStream2({
|
|
12495
|
+
client: dedicated.client,
|
|
12496
|
+
api: this,
|
|
12497
|
+
channel: 0,
|
|
12498
|
+
profile: "sub",
|
|
12499
|
+
logger: this.logger
|
|
12500
|
+
});
|
|
12501
|
+
this.objectDetectionInternalListener = (event) => {
|
|
12502
|
+
for (const cb of this.objectDetectionListeners) {
|
|
12503
|
+
try {
|
|
12504
|
+
void Promise.resolve(cb(event)).catch((e) => {
|
|
12505
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12506
|
+
this.logger,
|
|
12507
|
+
"[ReolinkBaichuanApi] onObjectDetections handler error",
|
|
12508
|
+
formatErrorForLog(e)
|
|
12509
|
+
);
|
|
12510
|
+
});
|
|
12511
|
+
} catch (e) {
|
|
12512
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12513
|
+
this.logger,
|
|
12514
|
+
"[ReolinkBaichuanApi] onObjectDetections handler error",
|
|
12515
|
+
formatErrorForLog(e)
|
|
12516
|
+
);
|
|
12517
|
+
}
|
|
12518
|
+
}
|
|
12519
|
+
};
|
|
12520
|
+
this.detectionEventListeners.add(this.objectDetectionInternalListener);
|
|
12521
|
+
try {
|
|
12522
|
+
await stream.start();
|
|
12523
|
+
} catch (e) {
|
|
12524
|
+
if (this.objectDetectionInternalListener) {
|
|
12525
|
+
this.detectionEventListeners.delete(
|
|
12526
|
+
this.objectDetectionInternalListener
|
|
12527
|
+
);
|
|
12528
|
+
this.objectDetectionInternalListener = void 0;
|
|
12529
|
+
}
|
|
12530
|
+
await dedicated.release().catch(() => {
|
|
12531
|
+
});
|
|
12532
|
+
throw e;
|
|
12533
|
+
}
|
|
12534
|
+
this.objectDetectionStream = {
|
|
12535
|
+
stop: () => stream.stop(),
|
|
12536
|
+
release: () => dedicated.release()
|
|
12537
|
+
};
|
|
12538
|
+
this.logger.debug?.(
|
|
12539
|
+
`[ReolinkBaichuanApi] onObjectDetections: substream started (key=${sessionKey})`
|
|
12540
|
+
);
|
|
12541
|
+
})();
|
|
12542
|
+
try {
|
|
12543
|
+
await this.objectDetectionStreamStartInFlight;
|
|
12544
|
+
} finally {
|
|
12545
|
+
this.objectDetectionStreamStartInFlight = void 0;
|
|
12546
|
+
}
|
|
12547
|
+
}
|
|
12548
|
+
async tearDownObjectDetectionStream() {
|
|
12549
|
+
const handle = this.objectDetectionStream;
|
|
12550
|
+
this.objectDetectionStream = void 0;
|
|
12551
|
+
if (this.objectDetectionInternalListener) {
|
|
12552
|
+
this.detectionEventListeners.delete(this.objectDetectionInternalListener);
|
|
12553
|
+
this.objectDetectionInternalListener = void 0;
|
|
12554
|
+
}
|
|
12555
|
+
if (!handle) return;
|
|
12556
|
+
try {
|
|
12557
|
+
await handle.stop();
|
|
12558
|
+
} catch (e) {
|
|
12559
|
+
this.logger.debug?.(
|
|
12560
|
+
`[ReolinkBaichuanApi] onObjectDetections: stream stop error: ${formatErrorForLog(e)}`
|
|
12561
|
+
);
|
|
12562
|
+
}
|
|
12563
|
+
try {
|
|
12564
|
+
await handle.release();
|
|
12565
|
+
} catch (e) {
|
|
12566
|
+
this.logger.debug?.(
|
|
12567
|
+
`[ReolinkBaichuanApi] onObjectDetections: session release error: ${formatErrorForLog(e)}`
|
|
12568
|
+
);
|
|
12569
|
+
}
|
|
12570
|
+
this.logger.debug?.(
|
|
12571
|
+
`[ReolinkBaichuanApi] onObjectDetections: substream torn down`
|
|
12572
|
+
);
|
|
12573
|
+
}
|
|
12574
|
+
/**
|
|
12575
|
+
* Internal: invoked by BaichuanVideoStream when it starts so the API can hook
|
|
12576
|
+
* its `additionalHeader` event. Returns a teardown function the stream calls
|
|
12577
|
+
* on stop. Not intended for direct use by consumers.
|
|
12578
|
+
*/
|
|
12579
|
+
_registerVideoStreamForDetection(stream, context) {
|
|
12580
|
+
const listener = (info) => {
|
|
12581
|
+
if (this.detectionEventListeners.size === 0) return;
|
|
12582
|
+
const decoded = decodeDetectionHeader(info.raw, info.frameType);
|
|
12583
|
+
const event = {
|
|
12584
|
+
channel: context.channel,
|
|
12585
|
+
microseconds: info.microseconds,
|
|
12586
|
+
profile: context.profile,
|
|
12587
|
+
boxes: decoded.boxes,
|
|
12588
|
+
...info.frameWidth !== void 0 ? { frameWidth: info.frameWidth } : {},
|
|
12589
|
+
...info.frameHeight !== void 0 ? { frameHeight: info.frameHeight } : {},
|
|
12590
|
+
decodeState: decoded.state,
|
|
12591
|
+
rawHeader: info.raw
|
|
12592
|
+
};
|
|
12593
|
+
this.dispatchDetectionEvent(event);
|
|
12594
|
+
};
|
|
12595
|
+
stream.on("additionalHeader", listener);
|
|
12596
|
+
const teardown = () => {
|
|
12597
|
+
stream.off("additionalHeader", listener);
|
|
12598
|
+
this.detectionEventStreamHooks.delete(stream);
|
|
12599
|
+
};
|
|
12600
|
+
this.detectionEventStreamHooks.set(stream, teardown);
|
|
12601
|
+
return teardown;
|
|
12602
|
+
}
|
|
12603
|
+
dispatchDetectionEvent(evt) {
|
|
12604
|
+
for (const cb of this.detectionEventListeners) {
|
|
12605
|
+
try {
|
|
12606
|
+
void Promise.resolve(cb(evt)).catch((e) => {
|
|
12607
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12608
|
+
this.logger,
|
|
12609
|
+
"[ReolinkBaichuanApi] onDetection handler error",
|
|
12610
|
+
formatErrorForLog(e)
|
|
12611
|
+
);
|
|
12612
|
+
});
|
|
12613
|
+
} catch (e) {
|
|
12614
|
+
(this.logger.warn ?? this.logger.error).call(
|
|
12615
|
+
this.logger,
|
|
12616
|
+
"[ReolinkBaichuanApi] onDetection handler error",
|
|
12617
|
+
formatErrorForLog(e)
|
|
12618
|
+
);
|
|
12619
|
+
}
|
|
12620
|
+
}
|
|
12621
|
+
}
|
|
12126
12622
|
startSimpleEventResubscribeTimer() {
|
|
12127
12623
|
if (this.simpleEventResubscribeTimer) return;
|
|
12128
12624
|
if (this.simpleEventListeners.size === 0) return;
|
|
@@ -12505,6 +13001,9 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12505
13001
|
this.stopUdpSleepInference();
|
|
12506
13002
|
this.stopSimpleEventWatchdog();
|
|
12507
13003
|
this.stopSimpleEventResubscribeTimer();
|
|
13004
|
+
this.objectDetectionListeners.clear();
|
|
13005
|
+
await this.tearDownObjectDetectionStream().catch(() => {
|
|
13006
|
+
});
|
|
12508
13007
|
await this.cleanup();
|
|
12509
13008
|
await this.stopAllActiveStreams();
|
|
12510
13009
|
await this.cleanupSocketPool();
|
|
@@ -12852,6 +13351,53 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
12852
13351
|
const xml = `<?xml version="1.0" encoding="UTF-8" ?><body><${tag} version="1.1"><enable>${params.enable ? 1 : 0}</enable></${tag}></body>`;
|
|
12853
13352
|
await this.sendXml({ cmdId: 36, payloadXml: xml });
|
|
12854
13353
|
}
|
|
13354
|
+
/**
|
|
13355
|
+
* Full port-config setter (cmd_id 36). Patches one or more of the six
|
|
13356
|
+
* service ports the camera serves — Server (Baichuan), HTTP, HTTPS,
|
|
13357
|
+
* RTSP, RTMP, ONVIF. Each entry takes an optional `port` (number) and
|
|
13358
|
+
* `enable` (boolean); fields the caller doesn't pass are left alone.
|
|
13359
|
+
*
|
|
13360
|
+
* Sends one block per port that has any field set, then issues a
|
|
13361
|
+
* single cmd_36 with the merged body. The camera accepts multiple
|
|
13362
|
+
* `<XxxPort>` siblings in the same payload.
|
|
13363
|
+
*
|
|
13364
|
+
* Wire format observed on E1 Zoom:
|
|
13365
|
+
*
|
|
13366
|
+
* <body>
|
|
13367
|
+
* <RtspPort version="1.1">
|
|
13368
|
+
* <rtspPort>554</rtspPort>
|
|
13369
|
+
* <enable>1</enable>
|
|
13370
|
+
* </RtspPort>
|
|
13371
|
+
* <HttpsPort version="1.1">
|
|
13372
|
+
* <enable>0</enable>
|
|
13373
|
+
* </HttpsPort>
|
|
13374
|
+
* ...
|
|
13375
|
+
* </body>
|
|
13376
|
+
*/
|
|
13377
|
+
async setPortConfig(patch) {
|
|
13378
|
+
const blocks = [];
|
|
13379
|
+
const append = (tag, portField, cfg) => {
|
|
13380
|
+
if (!cfg) return;
|
|
13381
|
+
if (cfg.port === void 0 && cfg.enable === void 0) return;
|
|
13382
|
+
const inner = [];
|
|
13383
|
+
if (cfg.port !== void 0) {
|
|
13384
|
+
inner.push(`<${portField}>${cfg.port}</${portField}>`);
|
|
13385
|
+
}
|
|
13386
|
+
if (cfg.enable !== void 0) {
|
|
13387
|
+
inner.push(`<enable>${cfg.enable ? 1 : 0}</enable>`);
|
|
13388
|
+
}
|
|
13389
|
+
blocks.push(`<${tag} version="1.1">${inner.join("")}</${tag}>`);
|
|
13390
|
+
};
|
|
13391
|
+
append("ServerPort", "serverPort", patch.server);
|
|
13392
|
+
append("HttpPort", "httpPort", patch.http);
|
|
13393
|
+
append("HttpsPort", "httpsPort", patch.https);
|
|
13394
|
+
append("RtspPort", "rtspPort", patch.rtsp);
|
|
13395
|
+
append("RtmpPort", "rtmpPort", patch.rtmp);
|
|
13396
|
+
append("OnvifPort", "onvifPort", patch.onvif);
|
|
13397
|
+
if (blocks.length === 0) return;
|
|
13398
|
+
const xml = `<?xml version="1.0" encoding="UTF-8" ?><body>${blocks.join("")}</body>`;
|
|
13399
|
+
await this.sendXml({ cmdId: 36, payloadXml: xml });
|
|
13400
|
+
}
|
|
12855
13401
|
/** GetDevInfo via Baichuan: host cmd_id 80, channel cmd_id 318 */
|
|
12856
13402
|
async getInfo(channel, options) {
|
|
12857
13403
|
const req = { cmdId: channel == null ? 80 : 318 };
|
|
@@ -16536,6 +17082,27 @@ ${stderr}`)
|
|
|
16536
17082
|
);
|
|
16537
17083
|
}
|
|
16538
17084
|
}
|
|
17085
|
+
async gotoPtzPreset(arg1, arg2) {
|
|
17086
|
+
const ch = arg2 === void 0 ? this.normalizeChannel(void 0) : this.normalizeChannel(arg1);
|
|
17087
|
+
const presetId = arg2 === void 0 ? arg1 : arg2;
|
|
17088
|
+
const channelId = ch;
|
|
17089
|
+
const payloadXml = buildPtzPresetXmlV2(channelId, presetId, "toPos");
|
|
17090
|
+
const extensionXml = buildChannelExtensionXml(channelId);
|
|
17091
|
+
const frame = await this.client.sendFrame({
|
|
17092
|
+
cmdId: BC_CMD_ID_PTZ_CONTROL_PRESET,
|
|
17093
|
+
channel: ch,
|
|
17094
|
+
channelIdOverride: channelId,
|
|
17095
|
+
extensionXml,
|
|
17096
|
+
payloadXml,
|
|
17097
|
+
messageClass: BC_CLASS_MODERN_24,
|
|
17098
|
+
streamType: 0
|
|
17099
|
+
});
|
|
17100
|
+
if (frame.header.responseCode !== 200) {
|
|
17101
|
+
throw new Error(
|
|
17102
|
+
`PTZ goto preset rejected (response_code ${frame.header.responseCode})`
|
|
17103
|
+
);
|
|
17104
|
+
}
|
|
17105
|
+
}
|
|
16539
17106
|
async deletePtzPreset(arg1, arg2) {
|
|
16540
17107
|
const ch = arg2 === void 0 ? this.normalizeChannel(void 0) : this.normalizeChannel(arg1);
|
|
16541
17108
|
const presetId = arg2 === void 0 ? arg1 : arg2;
|
|
@@ -17187,22 +17754,62 @@ ${stderr}`)
|
|
|
17187
17754
|
const channel = typeof arg1 === "number" ? arg1 : arg3;
|
|
17188
17755
|
const enabled = typeof arg1 === "number" ? arg2 : arg1;
|
|
17189
17756
|
const sensitivity = typeof arg1 === "number" ? arg3 : arg2;
|
|
17190
|
-
|
|
17757
|
+
return await this.setMotionAlarmFull({
|
|
17758
|
+
...channel !== void 0 ? { channel } : {},
|
|
17759
|
+
enabled,
|
|
17760
|
+
...sensitivity !== void 0 ? { sensitivity } : {}
|
|
17761
|
+
});
|
|
17762
|
+
}
|
|
17763
|
+
/**
|
|
17764
|
+
* Set motion alarm with full control, including the detection-zone grid.
|
|
17765
|
+
*
|
|
17766
|
+
* Wire format observed on E1 Zoom (cmd_id=47 SetMdAlarm body):
|
|
17767
|
+
*
|
|
17768
|
+
* <MD version="1.1">
|
|
17769
|
+
* <channelId>0</channelId>
|
|
17770
|
+
* <enable>1</enable>
|
|
17771
|
+
* <usepir>0</usepir>
|
|
17772
|
+
* <width>60</width> <height>33</height>
|
|
17773
|
+
* <scope>
|
|
17774
|
+
* <columns>96</columns> <rows>64</rows>
|
|
17775
|
+
* <valueTable>{base64 6144-bit bitmap}</valueTable>
|
|
17776
|
+
* </scope>
|
|
17777
|
+
* ... other camera-specific fields ...
|
|
17778
|
+
* </MD>
|
|
17779
|
+
*
|
|
17780
|
+
* We do a read-modify-write of the GET response so any camera-specific
|
|
17781
|
+
* extension fields are preserved untouched. Pass `valueTable` to update
|
|
17782
|
+
* the detection zone — see `encodeMotionScopeBitmap` for the bitmap layout.
|
|
17783
|
+
*
|
|
17784
|
+
* @param channel - 0-based channel
|
|
17785
|
+
* @param enabled - toggle motion detection on/off (optional)
|
|
17786
|
+
* @param sensitivity - 0-50, higher = more sensitive (optional)
|
|
17787
|
+
* @param valueTable - base64-encoded grid bitmap; size must match
|
|
17788
|
+
* `<scope><columns>×<rows></scope>` from the GET (optional)
|
|
17789
|
+
*/
|
|
17790
|
+
async setMotionAlarmFull(opts) {
|
|
17791
|
+
const ch = this.normalizeChannel(opts.channel);
|
|
17191
17792
|
const currentXml = await this.sendXml({
|
|
17192
17793
|
cmdId: BC_CMD_ID_GET_MOTION_ALARM,
|
|
17193
17794
|
channel: ch
|
|
17194
17795
|
});
|
|
17195
17796
|
let modifiedXml = currentXml;
|
|
17196
|
-
if (enabled !== void 0) {
|
|
17797
|
+
if (opts.enabled !== void 0) {
|
|
17197
17798
|
modifiedXml = modifiedXml.replace(
|
|
17198
17799
|
/<enable>[^<]*<\/enable>/,
|
|
17199
|
-
`<enable>${enabled ? "1" : "0"}</enable>`
|
|
17800
|
+
`<enable>${opts.enabled ? "1" : "0"}</enable>`
|
|
17200
17801
|
);
|
|
17201
17802
|
}
|
|
17202
|
-
if (sensitivity !== void 0) {
|
|
17803
|
+
if (opts.sensitivity !== void 0) {
|
|
17203
17804
|
modifiedXml = modifiedXml.replace(
|
|
17204
17805
|
/<sensitivityDefault>[^<]*<\/sensitivityDefault>/,
|
|
17205
|
-
`<sensitivityDefault>${sensitivity}</sensitivityDefault>`
|
|
17806
|
+
`<sensitivityDefault>${opts.sensitivity}</sensitivityDefault>`
|
|
17807
|
+
);
|
|
17808
|
+
}
|
|
17809
|
+
if (opts.valueTable !== void 0) {
|
|
17810
|
+
modifiedXml = modifiedXml.replace(
|
|
17811
|
+
/<valueTable>[^<]*<\/valueTable>/,
|
|
17812
|
+
`<valueTable>${opts.valueTable}</valueTable>`
|
|
17206
17813
|
);
|
|
17207
17814
|
}
|
|
17208
17815
|
await this.sendXml({
|
|
@@ -18477,7 +19084,7 @@ ${xml}`
|
|
|
18477
19084
|
* @returns Test results for all stream types and profiles
|
|
18478
19085
|
*/
|
|
18479
19086
|
async testChannelStreams(channel, logger) {
|
|
18480
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
19087
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-HGJGVQXZ.js");
|
|
18481
19088
|
return await testChannelStreams({
|
|
18482
19089
|
api: this,
|
|
18483
19090
|
channel: this.normalizeChannel(channel),
|
|
@@ -18493,7 +19100,7 @@ ${xml}`
|
|
|
18493
19100
|
* @returns Complete diagnostics for all channels and streams
|
|
18494
19101
|
*/
|
|
18495
19102
|
async collectMultifocalDiagnostics(logger) {
|
|
18496
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
19103
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-HGJGVQXZ.js");
|
|
18497
19104
|
return await collectMultifocalDiagnostics({
|
|
18498
19105
|
api: this,
|
|
18499
19106
|
logger
|
|
@@ -18546,12 +19153,24 @@ ${xml}`
|
|
|
18546
19153
|
}
|
|
18547
19154
|
/**
|
|
18548
19155
|
* SetEnc via Baichuan (cmdId=57). Read-modify-write — preserves
|
|
18549
|
-
* unspecified fields. Mirrors reolink_aio's `SetEnc
|
|
19156
|
+
* unspecified fields. Mirrors reolink_aio's `SetEnc` plus the additional
|
|
19157
|
+
* `width`/`height`/`encoderType`/`encoderProfile`/`gop`/`thirdStream`
|
|
19158
|
+
* fields observed in the official mobile app (see `pcap/resolution.pcapng`).
|
|
19159
|
+
*
|
|
19160
|
+
* Field meaning per stream:
|
|
19161
|
+
* - `audio` — 0/1 toggle
|
|
19162
|
+
* - `width`/`height` — resolution in pixels. Must be one of the
|
|
19163
|
+
* resolutions returned by {@link getStreamInfoList}.
|
|
19164
|
+
* - `bitRate` — kbps. Must match the table from `getStreamInfoList`.
|
|
19165
|
+
* - `frameRate` — fps. Must match the table from `getStreamInfoList`.
|
|
19166
|
+
* - `videoEncType` — `"h264"` or `"h265"`
|
|
19167
|
+
* - `encoderType` — `"vbr"` or `"cbr"`
|
|
19168
|
+
* - `encoderProfile` — `"high"`, `"main"`, or `"baseline"`
|
|
19169
|
+
* - `gop` — keyframe interval in seconds (sets `<gop><cur>`)
|
|
18550
19170
|
*
|
|
18551
19171
|
* @param channel - Channel number (0-based)
|
|
18552
|
-
* @param patch - Fields to update
|
|
18553
|
-
*
|
|
18554
|
-
* to change.
|
|
19172
|
+
* @param patch - Fields to update. Pass only the fields you want to change;
|
|
19173
|
+
* everything else is preserved from the device's current configuration.
|
|
18555
19174
|
*/
|
|
18556
19175
|
async setEnc(channel, patch, options) {
|
|
18557
19176
|
const ch = this.normalizeChannel(channel);
|
|
@@ -18568,6 +19187,7 @@ ${xml}`
|
|
|
18568
19187
|
}
|
|
18569
19188
|
xml = applyStreamPatch(xml, "mainStream", patch.mainStream);
|
|
18570
19189
|
xml = applyStreamPatch(xml, "subStream", patch.subStream);
|
|
19190
|
+
xml = applyStreamPatch(xml, "thirdStream", patch.thirdStream);
|
|
18571
19191
|
await this.sendXml({
|
|
18572
19192
|
cmdId: BC_CMD_ID_SET_ENC,
|
|
18573
19193
|
channel: ch,
|
|
@@ -19175,6 +19795,71 @@ ${xml}`
|
|
|
19175
19795
|
`PCAP-derived settings GET failed for cmdId=${params.cmdId}: ${String(lastErr)}`
|
|
19176
19796
|
);
|
|
19177
19797
|
}
|
|
19798
|
+
/**
|
|
19799
|
+
* Update the OSD timestamp + channel-name overlay via cmd_id=45
|
|
19800
|
+
* (SetOsdDatetime). The schema is the same `<body><OsdDatetime>` +
|
|
19801
|
+
* `<OsdChannelName>` block returned by `getOsdDatetime` — we
|
|
19802
|
+
* read-modify-write so any extension fields the camera sent are
|
|
19803
|
+
* preserved.
|
|
19804
|
+
*
|
|
19805
|
+
* Position is in **camera pixel coordinates** (e.g. (1,1) for top-left,
|
|
19806
|
+
* not preset strings). Set `enable=0` to hide the overlay; the camera
|
|
19807
|
+
* keeps the stored position so re-enabling later restores it.
|
|
19808
|
+
*/
|
|
19809
|
+
async setOsdDatetime(channel, patch, options) {
|
|
19810
|
+
const ch = this.normalizeChannel(channel);
|
|
19811
|
+
const timeoutOpts = options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {};
|
|
19812
|
+
let xml = await this.sendPcapDerivedSettingsGetXml({
|
|
19813
|
+
cmdId: BC_CMD_ID_GET_OSD_DATETIME,
|
|
19814
|
+
channel: ch,
|
|
19815
|
+
...timeoutOpts
|
|
19816
|
+
});
|
|
19817
|
+
const patchBlock = (block, fields) => {
|
|
19818
|
+
const start = xml.indexOf(`<${block}`);
|
|
19819
|
+
if (start < 0) return;
|
|
19820
|
+
const end = xml.indexOf(`</${block}>`, start);
|
|
19821
|
+
if (end < 0) return;
|
|
19822
|
+
let body = xml.slice(start, end);
|
|
19823
|
+
for (const [tag, value] of Object.entries(fields)) {
|
|
19824
|
+
if (value === void 0) continue;
|
|
19825
|
+
const raw = typeof value === "boolean" ? value ? "1" : "0" : String(value);
|
|
19826
|
+
const escaped = raw.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
19827
|
+
if (body.includes(`<${tag}>`)) {
|
|
19828
|
+
body = body.replace(
|
|
19829
|
+
new RegExp(`<${tag}>[^<]*<\\/${tag}>`),
|
|
19830
|
+
`<${tag}>${escaped}</${tag}>`
|
|
19831
|
+
);
|
|
19832
|
+
} else {
|
|
19833
|
+
body += `<${tag}>${escaped}</${tag}>`;
|
|
19834
|
+
}
|
|
19835
|
+
}
|
|
19836
|
+
xml = xml.slice(0, start) + body + xml.slice(end);
|
|
19837
|
+
};
|
|
19838
|
+
if (patch.datetime) {
|
|
19839
|
+
patchBlock("OsdDatetime", {
|
|
19840
|
+
enable: patch.datetime.enable,
|
|
19841
|
+
topLeftX: patch.datetime.topLeftX,
|
|
19842
|
+
topLeftY: patch.datetime.topLeftY,
|
|
19843
|
+
language: patch.datetime.language
|
|
19844
|
+
});
|
|
19845
|
+
}
|
|
19846
|
+
if (patch.channelName) {
|
|
19847
|
+
patchBlock("OsdChannelName", {
|
|
19848
|
+
name: patch.channelName.name,
|
|
19849
|
+
enable: patch.channelName.enable,
|
|
19850
|
+
topLeftX: patch.channelName.topLeftX,
|
|
19851
|
+
topLeftY: patch.channelName.topLeftY,
|
|
19852
|
+
enWatermark: patch.channelName.enWatermark,
|
|
19853
|
+
enBgcolor: patch.channelName.enBgcolor
|
|
19854
|
+
});
|
|
19855
|
+
}
|
|
19856
|
+
await this.sendXml({
|
|
19857
|
+
cmdId: BC_CMD_ID_SET_OSD_DATETIME,
|
|
19858
|
+
channel: ch,
|
|
19859
|
+
payloadXml: ensureXmlHeader(xml),
|
|
19860
|
+
...timeoutOpts
|
|
19861
|
+
});
|
|
19862
|
+
}
|
|
19178
19863
|
async getOsdDatetime(channel, options) {
|
|
19179
19864
|
const rawXml = await this.sendPcapDerivedSettingsGetXml({
|
|
19180
19865
|
cmdId: BC_CMD_ID_GET_OSD_DATETIME,
|
|
@@ -19367,6 +20052,41 @@ ${xml}`
|
|
|
19367
20052
|
});
|
|
19368
20053
|
return { streams };
|
|
19369
20054
|
}
|
|
20055
|
+
/**
|
|
20056
|
+
* Return the set of values `setEnc` will accept on each stream of `channel`.
|
|
20057
|
+
* Aggregates `getStreamInfoList` (cmd_146) into a UI-friendly shape:
|
|
20058
|
+
* per-stream resolutions with their allowed codecs/framerates/bitrates plus
|
|
20059
|
+
* the enumerated encoder modes/profiles Reolink exposes.
|
|
20060
|
+
*
|
|
20061
|
+
* Useful for populating selectors and validating user input before calling
|
|
20062
|
+
* `setEnc` — picking an unsupported combination causes the camera to reject
|
|
20063
|
+
* the SET_ENC command (responseCode != 200).
|
|
20064
|
+
*/
|
|
20065
|
+
async getEncOptions(channel, options) {
|
|
20066
|
+
const list = await this.getStreamInfoList(channel, options);
|
|
20067
|
+
return buildEncOptions(list, channel);
|
|
20068
|
+
}
|
|
20069
|
+
/**
|
|
20070
|
+
* Read the camera's `<VersionInfo>` block (cmd_id=80). Returns the
|
|
20071
|
+
* friendly name, model code (e.g. `"E1 Zoom"`), serial number, firmware
|
|
20072
|
+
* version, hardware revision, build day, AI model bundle version, etc.
|
|
20073
|
+
*
|
|
20074
|
+
* This is the same info the Reolink mobile app shows in "About this
|
|
20075
|
+
* device" — distinct from `getSystemGeneral` (cmd_104) which carries
|
|
20076
|
+
* time/locale.
|
|
20077
|
+
*
|
|
20078
|
+
* No channel parameter: this command is device-global on NVRs/Hubs and
|
|
20079
|
+
* camera-global on standalone cameras. Pass an explicit channel via the
|
|
20080
|
+
* underlying `sendXml` only if a specific firmware demands it (none we've
|
|
20081
|
+
* tested do).
|
|
20082
|
+
*/
|
|
20083
|
+
async getVersionInfo(options) {
|
|
20084
|
+
const xml = await this.sendXml({
|
|
20085
|
+
cmdId: BC_CMD_ID_GET_VERSION_INFO,
|
|
20086
|
+
...options?.timeoutMs != null ? { timeoutMs: options.timeoutMs } : {}
|
|
20087
|
+
});
|
|
20088
|
+
return parseVersionInfo(xml);
|
|
20089
|
+
}
|
|
19370
20090
|
async getLedState(channel, options) {
|
|
19371
20091
|
const rawXml = await this.sendPcapDerivedSettingsGetXml({
|
|
19372
20092
|
cmdId: BC_CMD_ID_GET_LED_STATE,
|
|
@@ -22560,4 +23280,4 @@ export {
|
|
|
22560
23280
|
isTcpFailureThatShouldFallbackToUdp,
|
|
22561
23281
|
autoDetectDeviceType
|
|
22562
23282
|
};
|
|
22563
|
-
//# sourceMappingURL=chunk-
|
|
23283
|
+
//# sourceMappingURL=chunk-ND73IJIB.js.map
|