@apocaliss92/nodelink-js 0.5.1 → 0.6.0
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-OCLOM452.js +7 -0
- package/dist/{DiagnosticsTools-7BIWJDZS.js → DiagnosticsTools-K4MF2VXZ.js} +3 -3
- package/dist/{chunk-OJQLZETO.js → chunk-7HSTETZR.js} +409 -108
- package/dist/chunk-7HSTETZR.js.map +1 -0
- package/dist/{chunk-GVWJGQPT.js → chunk-MZUSWKF3.js} +5 -1
- package/dist/chunk-MZUSWKF3.js.map +1 -0
- package/dist/{chunk-VOPEOB4H.js → chunk-XDVBNZGR.js} +2 -2
- package/dist/cli/rtsp-server.cjs +403 -102
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -3
- package/dist/index.cjs +482 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -1
- package/dist/index.d.ts +142 -0
- package/dist/index.js +79 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/BaichuanVideoStream-NTIGPHYJ.js +0 -7
- package/dist/chunk-GVWJGQPT.js.map +0 -1
- package/dist/chunk-OJQLZETO.js.map +0 -1
- /package/dist/{BaichuanVideoStream-NTIGPHYJ.js.map → BaichuanVideoStream-OCLOM452.js.map} +0 -0
- /package/dist/{DiagnosticsTools-7BIWJDZS.js.map → DiagnosticsTools-K4MF2VXZ.js.map} +0 -0
- /package/dist/{chunk-VOPEOB4H.js.map → chunk-XDVBNZGR.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_GET_SNAPSHOT, BC_CMD_ID_GET_UID, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_SET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_VERSION_INFO, 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_SET_VIDEO_INPUT, BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_ENC, BC_CMD_ID_SET_ENC, BC_CMD_ID_GET_PRIVACY_MASK, BC_CMD_ID_SET_PRIVACY_MASK, BC_CMD_ID_SET_AI_DENOISE, BC_CMD_ID_SET_LED_STATE, BC_CMD_ID_SET_AUDIO_CFG, BC_CMD_ID_SET_RECORD, BC_CMD_ID_SET_RECORD_CFG, BC_CMD_ID_SET_EMAIL_TASK, BC_CMD_ID_GET_PUSH_TASK, BC_CMD_ID_SET_PUSH_TASK, BC_CMD_ID_GET_AUTO_FOCUS, BC_CMD_ID_SET_AUTO_FOCUS, BC_CMD_ID_GET_EMAIL, BC_CMD_ID_SET_EMAIL, BC_CMD_ID_TEST_EMAIL, BC_CMD_ID_GET_NTP, BC_CMD_ID_SET_NTP, BC_CMD_ID_SET_SYSTEM_GENERAL, BC_CMD_ID_GET_DST, BC_CMD_ID_SET_DST, BC_CMD_ID_GET_AUTO_REBOOT, BC_CMD_ID_SET_AUTO_REBOOT, 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;
|
|
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_DEVICE_DETECT_CANDIDATES, BC_CMD_ID_DEVICE_DETECT, BC_CMD_ID_GET_SNAPSHOT, BC_CMD_ID_GET_UID, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_SET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_VERSION_INFO, 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_SET_VIDEO_INPUT, BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_ENC, BC_CMD_ID_SET_ENC, BC_CMD_ID_GET_PRIVACY_MASK, BC_CMD_ID_SET_PRIVACY_MASK, BC_CMD_ID_SET_AI_DENOISE, BC_CMD_ID_SET_LED_STATE, BC_CMD_ID_SET_AUDIO_CFG, BC_CMD_ID_SET_RECORD, BC_CMD_ID_SET_RECORD_CFG, BC_CMD_ID_SET_EMAIL_TASK, BC_CMD_ID_GET_PUSH_TASK, BC_CMD_ID_SET_PUSH_TASK, BC_CMD_ID_GET_AUTO_FOCUS, BC_CMD_ID_SET_AUTO_FOCUS, BC_CMD_ID_GET_EMAIL, BC_CMD_ID_SET_EMAIL, BC_CMD_ID_TEST_EMAIL, BC_CMD_ID_GET_NTP, BC_CMD_ID_SET_NTP, BC_CMD_ID_SET_SYSTEM_GENERAL, BC_CMD_ID_GET_DST, BC_CMD_ID_SET_DST, BC_CMD_ID_GET_AUTO_REBOOT, BC_CMD_ID_SET_AUTO_REBOOT, 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";
|
|
@@ -107,6 +107,8 @@ var init_constants = __esm({
|
|
|
107
107
|
BC_CMD_ID_ABILITY_INFO = 151;
|
|
108
108
|
BC_CMD_ID_SUPPORT = 199;
|
|
109
109
|
BC_CMD_ID_PING = 93;
|
|
110
|
+
BC_CMD_ID_DEVICE_DETECT_CANDIDATES = [31, 132, 150];
|
|
111
|
+
BC_CMD_ID_DEVICE_DETECT = null;
|
|
110
112
|
BC_CMD_ID_GET_SNAPSHOT = 109;
|
|
111
113
|
BC_CMD_ID_GET_UID = 114;
|
|
112
114
|
BC_CMD_ID_CHANNEL_INFO_ALL = 145;
|
|
@@ -8256,6 +8258,8 @@ __export(index_exports, {
|
|
|
8256
8258
|
BC_CMD_ID_COVER_STANDALONE_460: () => BC_CMD_ID_COVER_STANDALONE_460,
|
|
8257
8259
|
BC_CMD_ID_COVER_STANDALONE_461: () => BC_CMD_ID_COVER_STANDALONE_461,
|
|
8258
8260
|
BC_CMD_ID_COVER_STANDALONE_462: () => BC_CMD_ID_COVER_STANDALONE_462,
|
|
8261
|
+
BC_CMD_ID_DEVICE_DETECT: () => BC_CMD_ID_DEVICE_DETECT,
|
|
8262
|
+
BC_CMD_ID_DEVICE_DETECT_CANDIDATES: () => BC_CMD_ID_DEVICE_DETECT_CANDIDATES,
|
|
8259
8263
|
BC_CMD_ID_DING_DONG_CTRL: () => BC_CMD_ID_DING_DONG_CTRL,
|
|
8260
8264
|
BC_CMD_ID_DING_DONG_OPT: () => BC_CMD_ID_DING_DONG_OPT,
|
|
8261
8265
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE: () => BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|
|
@@ -9017,6 +9021,143 @@ function parseD2cHb(xml) {
|
|
|
9017
9021
|
return { cid: Number(cid), did: Number(did) };
|
|
9018
9022
|
}
|
|
9019
9023
|
|
|
9024
|
+
// src/cloud/server-binding.ts
|
|
9025
|
+
var REOLINK_API_V2_BASE = "https://apis.reolink.com/v2";
|
|
9026
|
+
var POSITIVE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9027
|
+
var NEGATIVE_TTL_MS = 30 * 1e3;
|
|
9028
|
+
var cache = /* @__PURE__ */ new Map();
|
|
9029
|
+
function readCache(uid, now) {
|
|
9030
|
+
const e = cache.get(uid);
|
|
9031
|
+
if (!e) return void 0;
|
|
9032
|
+
if (now >= e.expires) {
|
|
9033
|
+
cache.delete(uid);
|
|
9034
|
+
return void 0;
|
|
9035
|
+
}
|
|
9036
|
+
return e;
|
|
9037
|
+
}
|
|
9038
|
+
async function getServerBinding(uid, options = {}) {
|
|
9039
|
+
if (!uid || typeof uid !== "string") return void 0;
|
|
9040
|
+
const now = Date.now();
|
|
9041
|
+
const cached = readCache(uid, now);
|
|
9042
|
+
if (cached?.kind === "ok") return cached.response;
|
|
9043
|
+
if (cached?.kind === "err") return void 0;
|
|
9044
|
+
const language = options.language ?? "en";
|
|
9045
|
+
const baseUrl = options.baseUrl ?? REOLINK_API_V2_BASE;
|
|
9046
|
+
const timeoutMs = options.timeoutMs ?? 4e3;
|
|
9047
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
9048
|
+
const logger = options.logger;
|
|
9049
|
+
if (typeof fetchImpl !== "function") {
|
|
9050
|
+
logger?.debug?.(
|
|
9051
|
+
`[server-binding] global fetch unavailable; skipping cloud lookup`
|
|
9052
|
+
);
|
|
9053
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9054
|
+
return void 0;
|
|
9055
|
+
}
|
|
9056
|
+
const url = `${baseUrl}/devices/${encodeURIComponent(uid)}/server-binding?language=${encodeURIComponent(language)}`;
|
|
9057
|
+
const controller = new AbortController();
|
|
9058
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
9059
|
+
try {
|
|
9060
|
+
const res = await fetchImpl(url, {
|
|
9061
|
+
method: "GET",
|
|
9062
|
+
signal: controller.signal,
|
|
9063
|
+
headers: { Accept: "application/json" }
|
|
9064
|
+
});
|
|
9065
|
+
if (!res.ok) {
|
|
9066
|
+
logger?.debug?.(
|
|
9067
|
+
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText}`
|
|
9068
|
+
);
|
|
9069
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9070
|
+
return void 0;
|
|
9071
|
+
}
|
|
9072
|
+
const json = await res.json();
|
|
9073
|
+
const parsed = parseServerBindingResponse(json);
|
|
9074
|
+
if (!parsed) {
|
|
9075
|
+
logger?.debug?.(
|
|
9076
|
+
`[server-binding] ${uid}: response shape did not match expectations`
|
|
9077
|
+
);
|
|
9078
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9079
|
+
return void 0;
|
|
9080
|
+
}
|
|
9081
|
+
cache.set(uid, {
|
|
9082
|
+
kind: "ok",
|
|
9083
|
+
response: parsed,
|
|
9084
|
+
expires: now + POSITIVE_TTL_MS
|
|
9085
|
+
});
|
|
9086
|
+
const pick = parsed.availableZones.find(
|
|
9087
|
+
(z) => z.status === "active" && z.services.p2p?.server
|
|
9088
|
+
);
|
|
9089
|
+
const hint = pick?.services.p2p?.server ?? parsed.availableZones[0]?.services.p2p?.server;
|
|
9090
|
+
logger?.log?.(
|
|
9091
|
+
`[server-binding] ${uid}: ${parsed.availableZones.length} zone(s)${hint ? `, p2p hint=${hint}` : ""}`
|
|
9092
|
+
);
|
|
9093
|
+
return parsed;
|
|
9094
|
+
} catch (e) {
|
|
9095
|
+
logger?.debug?.(
|
|
9096
|
+
`[server-binding] ${uid}: ${e?.message ?? String(e)}`
|
|
9097
|
+
);
|
|
9098
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9099
|
+
return void 0;
|
|
9100
|
+
} finally {
|
|
9101
|
+
clearTimeout(timer);
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
function pickP2pHostFromBinding(response) {
|
|
9105
|
+
if (!response) return void 0;
|
|
9106
|
+
const zones = response.availableZones;
|
|
9107
|
+
if (!zones || zones.length === 0) return void 0;
|
|
9108
|
+
const active = zones.find(
|
|
9109
|
+
(z) => z.status === "active" && z.services.p2p?.server
|
|
9110
|
+
);
|
|
9111
|
+
if (active?.services.p2p?.server) return active.services.p2p.server;
|
|
9112
|
+
const def = zones.find(
|
|
9113
|
+
(z) => z.status === "default" && z.services.p2p?.server
|
|
9114
|
+
);
|
|
9115
|
+
if (def?.services.p2p?.server) return def.services.p2p.server;
|
|
9116
|
+
const any = zones.find((z) => z.services.p2p?.server);
|
|
9117
|
+
return any?.services.p2p?.server;
|
|
9118
|
+
}
|
|
9119
|
+
function isString(v) {
|
|
9120
|
+
return typeof v === "string";
|
|
9121
|
+
}
|
|
9122
|
+
function parseServerBindingResponse(raw) {
|
|
9123
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
9124
|
+
const rawZones = raw.availableZones;
|
|
9125
|
+
if (!Array.isArray(rawZones)) return void 0;
|
|
9126
|
+
const zones = [];
|
|
9127
|
+
for (const r of rawZones) {
|
|
9128
|
+
if (!r || typeof r !== "object") continue;
|
|
9129
|
+
const rec = r;
|
|
9130
|
+
const id = rec.id;
|
|
9131
|
+
const name = rec.name;
|
|
9132
|
+
const status = rec.status;
|
|
9133
|
+
if (!isString(id) || !isString(name) || !isString(status)) continue;
|
|
9134
|
+
const servicesRaw = rec.services;
|
|
9135
|
+
const services = {};
|
|
9136
|
+
if (servicesRaw && typeof servicesRaw === "object") {
|
|
9137
|
+
const s = servicesRaw;
|
|
9138
|
+
for (const key of ["p2p", "cloud", "roms_ota", "alarm_push"]) {
|
|
9139
|
+
const v = s[key];
|
|
9140
|
+
if (v && typeof v === "object") {
|
|
9141
|
+
const server = v.server;
|
|
9142
|
+
if (isString(server) && server.length > 0) {
|
|
9143
|
+
services[key] = { server };
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
const locationsRaw = rec.locations;
|
|
9149
|
+
const locations = Array.isArray(locationsRaw) && locationsRaw.every(isString) ? locationsRaw : void 0;
|
|
9150
|
+
zones.push({
|
|
9151
|
+
id,
|
|
9152
|
+
name,
|
|
9153
|
+
status,
|
|
9154
|
+
services,
|
|
9155
|
+
...locations ? { locations } : {}
|
|
9156
|
+
});
|
|
9157
|
+
}
|
|
9158
|
+
return { availableZones: zones };
|
|
9159
|
+
}
|
|
9160
|
+
|
|
9020
9161
|
// src/bcudp/BcUdpStream.ts
|
|
9021
9162
|
var AckLatency = class {
|
|
9022
9163
|
currentValues = [];
|
|
@@ -9098,6 +9239,16 @@ var P2P_MAX_WAIT_MS = 15e3;
|
|
|
9098
9239
|
var P2P_RESEND_WAIT_MS = 500;
|
|
9099
9240
|
var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
9100
9241
|
opts;
|
|
9242
|
+
/**
|
|
9243
|
+
* Optional info-level logger for diagnostic milestones — set via
|
|
9244
|
+
* {@link BcUdpStream.setLogger} by `BaichuanClient` so the lib's
|
|
9245
|
+
* standard logger sink sees BCUDP / P2P progress (DNS resolutions,
|
|
9246
|
+
* outgoing UDP probes, timeouts with elapsed times) without the user
|
|
9247
|
+
* having to opt into the per-packet `debug` event firehose. Kept
|
|
9248
|
+
* separate from `emit('debug', ...)` because that channel is intended
|
|
9249
|
+
* for the per-packet debug trace and is gated by debugOptions.
|
|
9250
|
+
*/
|
|
9251
|
+
discoveryLogger;
|
|
9101
9252
|
sock;
|
|
9102
9253
|
remote;
|
|
9103
9254
|
mtu;
|
|
@@ -9141,6 +9292,17 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9141
9292
|
this.mtu = BCUDP_DEFAULT_MTU;
|
|
9142
9293
|
}
|
|
9143
9294
|
/** True if the underlying UDP socket is open and the remote peer is known. */
|
|
9295
|
+
/**
|
|
9296
|
+
* Attach an info-level logger for high-signal diagnostic milestones
|
|
9297
|
+
* (DNS resolution, outgoing UDP probe sends, P2P UID lookup wins/losses,
|
|
9298
|
+
* BCUDP local discovery timeouts). The lib's `BaichuanClient` calls
|
|
9299
|
+
* this immediately after constructing the stream so consumers get
|
|
9300
|
+
* actionable progress logs without enabling the per-packet debug trace.
|
|
9301
|
+
* Safe to call repeatedly; only the most recent logger is used.
|
|
9302
|
+
*/
|
|
9303
|
+
setLogger(logger) {
|
|
9304
|
+
this.discoveryLogger = logger;
|
|
9305
|
+
}
|
|
9144
9306
|
isConnected() {
|
|
9145
9307
|
return !!this.sock && !!this.remote && this.cameraId != null;
|
|
9146
9308
|
}
|
|
@@ -9258,20 +9420,60 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9258
9420
|
this.remote = { host: connected.rhost, port: connected.rport };
|
|
9259
9421
|
}
|
|
9260
9422
|
async p2pUidLookup(sock, uid) {
|
|
9423
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[P2P] ${msg}`);
|
|
9424
|
+
const shortUid = uid.length > 7 ? `${uid.slice(0, 5)}\u2026${uid.slice(-2)}` : uid;
|
|
9425
|
+
const t0 = Date.now();
|
|
9426
|
+
const hostnamesToTry = [];
|
|
9427
|
+
const binding = await getServerBinding(uid, {
|
|
9428
|
+
...this.discoveryLogger ? { logger: this.discoveryLogger } : {}
|
|
9429
|
+
}).catch(() => void 0);
|
|
9430
|
+
const hintedHost = pickP2pHostFromBinding(binding);
|
|
9431
|
+
if (hintedHost) {
|
|
9432
|
+
hostnamesToTry.push(hintedHost);
|
|
9433
|
+
log(
|
|
9434
|
+
`UID=${shortUid} cloud server-binding \u2192 hint=${hintedHost} (will try first)`
|
|
9435
|
+
);
|
|
9436
|
+
} else {
|
|
9437
|
+
log(
|
|
9438
|
+
`UID=${shortUid} cloud server-binding \u2192 no hint (apis.reolink.com unreachable / no zone match) \u2192 sweeping ${P2P_RELAY_HOSTNAMES.length} fallback hostnames`
|
|
9439
|
+
);
|
|
9440
|
+
}
|
|
9441
|
+
for (const host of P2P_RELAY_HOSTNAMES) {
|
|
9442
|
+
if (!hostnamesToTry.includes(host)) hostnamesToTry.push(host);
|
|
9443
|
+
}
|
|
9261
9444
|
const resolved = [];
|
|
9262
9445
|
const sinkholed = [];
|
|
9263
|
-
for (const host of
|
|
9446
|
+
for (const host of hostnamesToTry) {
|
|
9264
9447
|
try {
|
|
9265
9448
|
const answers = await import_promises.default.lookup(host, { family: 4, all: true });
|
|
9449
|
+
let publicCount = 0;
|
|
9450
|
+
let sinkCount = 0;
|
|
9266
9451
|
for (const a of answers) {
|
|
9267
9452
|
if (!a.address) continue;
|
|
9268
9453
|
if (isUnroutableForP2P(a.address)) {
|
|
9269
9454
|
sinkholed.push({ host, ip: a.address });
|
|
9455
|
+
sinkCount++;
|
|
9270
9456
|
continue;
|
|
9271
9457
|
}
|
|
9272
|
-
if (!resolved.includes(a.address))
|
|
9458
|
+
if (!resolved.includes(a.address)) {
|
|
9459
|
+
resolved.push(a.address);
|
|
9460
|
+
publicCount++;
|
|
9461
|
+
}
|
|
9273
9462
|
}
|
|
9274
|
-
|
|
9463
|
+
if (sinkCount > 0 && publicCount === 0) {
|
|
9464
|
+
log(
|
|
9465
|
+
`DNS ${host} \u2192 sinkhole (${sinkholed[sinkholed.length - 1]?.ip}) \u2014 DNS filter / /etc/hosts override`
|
|
9466
|
+
);
|
|
9467
|
+
} else if (publicCount > 0) {
|
|
9468
|
+
if (host === hintedHost) {
|
|
9469
|
+
log(`DNS ${host} \u2192 ${answers.find((a) => !isUnroutableForP2P(a.address))?.address} \u2713`);
|
|
9470
|
+
}
|
|
9471
|
+
}
|
|
9472
|
+
} catch (e) {
|
|
9473
|
+
log(`DNS ${host} \u2192 ENOTFOUND/timeout (${e?.code ?? "?"})`);
|
|
9474
|
+
}
|
|
9475
|
+
if (hintedHost && host === hintedHost && resolved.length > 0 && sinkholed.length === 0) {
|
|
9476
|
+
break;
|
|
9275
9477
|
}
|
|
9276
9478
|
}
|
|
9277
9479
|
if (resolved.length === 0) {
|
|
@@ -9285,11 +9487,22 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9285
9487
|
"P2P UID lookup failed: no p2p.reolink.com addresses resolved (DNS failure)"
|
|
9286
9488
|
);
|
|
9287
9489
|
}
|
|
9490
|
+
log(
|
|
9491
|
+
`Resolved ${resolved.length} P2P relay IP(s) (${resolved.slice(0, 3).join(", ")}${resolved.length > 3 ? "\u2026" : ""}). Sending C2M_Q probes (3s budget each, ${P2P_MAX_WAIT_MS}ms total)`
|
|
9492
|
+
);
|
|
9288
9493
|
const start = Date.now();
|
|
9289
9494
|
let lastErr;
|
|
9495
|
+
let attemptsMade = 0;
|
|
9290
9496
|
for (const ip of resolved) {
|
|
9291
9497
|
const remaining = P2P_MAX_WAIT_MS - (Date.now() - start);
|
|
9292
|
-
if (remaining <= 0)
|
|
9498
|
+
if (remaining <= 0) {
|
|
9499
|
+
log(
|
|
9500
|
+
`Aborting after ${attemptsMade} attempt(s) \u2014 total budget ${P2P_MAX_WAIT_MS}ms exhausted`
|
|
9501
|
+
);
|
|
9502
|
+
break;
|
|
9503
|
+
}
|
|
9504
|
+
attemptsMade++;
|
|
9505
|
+
const probeStart = Date.now();
|
|
9293
9506
|
try {
|
|
9294
9507
|
const res = await this.p2pUidLookupOne(
|
|
9295
9508
|
sock,
|
|
@@ -9297,11 +9510,20 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9297
9510
|
{ host: ip, port: P2P_LOOKUP_PORT },
|
|
9298
9511
|
Math.min(remaining, 3e3)
|
|
9299
9512
|
);
|
|
9513
|
+
log(
|
|
9514
|
+
`${ip}:${P2P_LOOKUP_PORT} replied in ${Date.now() - probeStart}ms \u2713 (total ${Date.now() - t0}ms)`
|
|
9515
|
+
);
|
|
9300
9516
|
return res;
|
|
9301
9517
|
} catch (e) {
|
|
9518
|
+
const ms = Date.now() - probeStart;
|
|
9519
|
+
const msg = e?.message ?? String(e);
|
|
9520
|
+
log(`${ip}:${P2P_LOOKUP_PORT} no reply after ${ms}ms (${msg})`);
|
|
9302
9521
|
lastErr = e instanceof Error ? e : new Error(String(e));
|
|
9303
9522
|
}
|
|
9304
9523
|
}
|
|
9524
|
+
log(
|
|
9525
|
+
`Exhausted all ${attemptsMade} relay candidate(s) after ${Date.now() - t0}ms \u2014 UID lookup failed`
|
|
9526
|
+
);
|
|
9305
9527
|
throw lastErr ?? new Error("P2P UID lookup failed");
|
|
9306
9528
|
}
|
|
9307
9529
|
async p2pUidLookupOne(sock, uid, dest, timeoutMs) {
|
|
@@ -9640,13 +9862,23 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9640
9862
|
const directHost = (this.opts.directHost ?? "").trim();
|
|
9641
9863
|
const localMode = opts?.localMode ?? "local-broadcast";
|
|
9642
9864
|
const directFirstWindowMs = localMode === "local-direct" && directHost ? 3e3 : 0;
|
|
9643
|
-
const discoveryTimeout =
|
|
9865
|
+
const discoveryTimeout = typeof this.opts.localDiscoveryTimeoutMs === "number" && this.opts.localDiscoveryTimeoutMs > 0 ? this.opts.localDiscoveryTimeoutMs : 15e3;
|
|
9644
9866
|
const retryInterval = 500;
|
|
9645
9867
|
const startMs = Date.now();
|
|
9646
9868
|
sock.setBroadcast(true);
|
|
9647
9869
|
const addr = sock.address();
|
|
9648
9870
|
const localPort = typeof addr === "string" ? 0 : addr.port;
|
|
9649
9871
|
const cid = Math.floor(Math.random() * 2147483647) | 0 || 82e3;
|
|
9872
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[BCUDP] ${msg}`);
|
|
9873
|
+
const shortUid = this.opts.uid.length > 7 ? `${this.opts.uid.slice(0, 5)}\u2026${this.opts.uid.slice(-2)}` : this.opts.uid;
|
|
9874
|
+
log(
|
|
9875
|
+
`local discovery: mode=${localMode} uid=${shortUid} ports=[${ports.join(", ")}] broadcasts=[${broadcastHosts.join(", ")}]${directHost ? ` direct=${directHost}` : ""} localBindPort=${localPort} timeout=${discoveryTimeout}ms`
|
|
9876
|
+
);
|
|
9877
|
+
let bytesSent = 0;
|
|
9878
|
+
let pktsRecv = 0;
|
|
9879
|
+
sock.on("message", () => {
|
|
9880
|
+
pktsRecv++;
|
|
9881
|
+
});
|
|
9650
9882
|
const xml = buildC2dC({
|
|
9651
9883
|
uid: this.opts.uid,
|
|
9652
9884
|
clientPort: localPort,
|
|
@@ -9657,6 +9889,9 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9657
9889
|
const timeout = setTimeout(() => {
|
|
9658
9890
|
if (retryTimer) clearInterval(retryTimer);
|
|
9659
9891
|
sock.off("message", onMsg);
|
|
9892
|
+
log(
|
|
9893
|
+
`local discovery timeout after ${discoveryTimeout}ms \u2014 sent=${bytesSent}B replies=${pktsRecv} (camera likely sleeping / off-LAN / firewall dropping replies)`
|
|
9894
|
+
);
|
|
9660
9895
|
reject(
|
|
9661
9896
|
new Error(
|
|
9662
9897
|
`BCUDP discovery timeout after ${discoveryTimeout}ms (camera may be sleeping or unreachable)`
|
|
@@ -9850,6 +10085,7 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9850
10085
|
for (const port of ports) {
|
|
9851
10086
|
try {
|
|
9852
10087
|
sock.send(packet, port, host);
|
|
10088
|
+
bytesSent += packet.length;
|
|
9853
10089
|
retryCount++;
|
|
9854
10090
|
this.emit("debug", "discovery_send", { retryCount, host, port });
|
|
9855
10091
|
} catch {
|
|
@@ -11389,6 +11625,7 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
11389
11625
|
sock.on("debug", (event, data) => {
|
|
11390
11626
|
this.logDebug(`udp_${event}`, data);
|
|
11391
11627
|
});
|
|
11628
|
+
sock.setLogger(this.logger);
|
|
11392
11629
|
await sock.connect();
|
|
11393
11630
|
const shortUid = this.opts.uid ? this.opts.uid.substring(0, 5) : "";
|
|
11394
11631
|
const udpDiscoveryMethod = this.opts.udpDiscoveryMethod ?? "local-direct";
|
|
@@ -34928,10 +35165,18 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
34928
35165
|
}
|
|
34929
35166
|
}
|
|
34930
35167
|
async startFfmpegCompositionFromRtspUrls(mainWidth, mainHeight, pipWidth, pipHeight, position, widerRtspUrl, teleRtspUrl, rtspTransport) {
|
|
35168
|
+
const videoEncoder = this.options.videoEncoder ?? "libx264";
|
|
35169
|
+
const isX264 = videoEncoder === "libx264";
|
|
35170
|
+
const encoderPreset = this.options.encoderPreset ?? "ultrafast";
|
|
35171
|
+
const crf = this.options.crf ?? 23;
|
|
35172
|
+
const gopSeconds = this.options.gopSeconds ?? 1;
|
|
35173
|
+
const assumedFps = 30;
|
|
35174
|
+
const gopFrames = Math.max(1, Math.round(gopSeconds * assumedFps));
|
|
34931
35175
|
const ffmpegArgs = [
|
|
34932
35176
|
"-hide_banner",
|
|
34933
35177
|
"-loglevel",
|
|
34934
35178
|
"error",
|
|
35179
|
+
...this.options.extraGlobalArgs ?? [],
|
|
34935
35180
|
"-fflags",
|
|
34936
35181
|
"+genpts",
|
|
34937
35182
|
// Input 0: wider
|
|
@@ -34952,27 +35197,33 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
34952
35197
|
// Output: always H.264 Annex-B
|
|
34953
35198
|
"-an",
|
|
34954
35199
|
"-c:v",
|
|
34955
|
-
|
|
35200
|
+
videoEncoder,
|
|
34956
35201
|
"-g",
|
|
34957
|
-
|
|
35202
|
+
String(gopFrames),
|
|
34958
35203
|
"-keyint_min",
|
|
34959
|
-
|
|
35204
|
+
String(gopFrames),
|
|
34960
35205
|
"-sc_threshold",
|
|
34961
35206
|
"0",
|
|
34962
|
-
|
|
34963
|
-
|
|
34964
|
-
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
34968
|
-
|
|
34969
|
-
|
|
35207
|
+
...isX264 ? [
|
|
35208
|
+
"-x264-params",
|
|
35209
|
+
`aud=1:repeat-headers=1:keyint=${gopFrames}:min-keyint=${gopFrames}:scenecut=0`,
|
|
35210
|
+
"-preset",
|
|
35211
|
+
encoderPreset,
|
|
35212
|
+
"-tune",
|
|
35213
|
+
"zerolatency",
|
|
35214
|
+
"-crf",
|
|
35215
|
+
String(crf)
|
|
35216
|
+
] : [],
|
|
35217
|
+
...this.options.extraOutputArgs ?? [],
|
|
34970
35218
|
"-f",
|
|
34971
35219
|
"h264",
|
|
34972
35220
|
"pipe:1"
|
|
34973
35221
|
];
|
|
34974
|
-
this.
|
|
34975
|
-
this.
|
|
35222
|
+
const ffmpegBin = this.options.ffmpegPath ?? "ffmpeg";
|
|
35223
|
+
this.logger.log?.(
|
|
35224
|
+
`[CompositeStream] Starting ffmpeg (rtsp inputs): bin=${ffmpegBin} args=${ffmpegArgs.join(" ")}`
|
|
35225
|
+
);
|
|
35226
|
+
this.ffmpegProcess = (0, import_node_child_process7.spawn)(ffmpegBin, ffmpegArgs, {
|
|
34976
35227
|
stdio: ["ignore", "pipe", "pipe"]
|
|
34977
35228
|
});
|
|
34978
35229
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -35039,10 +35290,18 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35039
35290
|
"-i",
|
|
35040
35291
|
"pipe:3"
|
|
35041
35292
|
];
|
|
35293
|
+
const videoEncoder = this.options.videoEncoder ?? "libx264";
|
|
35294
|
+
const isX264 = videoEncoder === "libx264";
|
|
35295
|
+
const encoderPreset = this.options.encoderPreset ?? "ultrafast";
|
|
35296
|
+
const crf = this.options.crf ?? 23;
|
|
35297
|
+
const gopSeconds = this.options.gopSeconds ?? 1;
|
|
35298
|
+
const assumedFps = 30;
|
|
35299
|
+
const gopFrames = Math.max(1, Math.round(gopSeconds * assumedFps));
|
|
35042
35300
|
const ffmpegArgs = [
|
|
35043
35301
|
"-hide_banner",
|
|
35044
35302
|
"-loglevel",
|
|
35045
35303
|
"error",
|
|
35304
|
+
...this.options.extraGlobalArgs ?? [],
|
|
35046
35305
|
"-fflags",
|
|
35047
35306
|
"+genpts",
|
|
35048
35307
|
"-probesize",
|
|
@@ -35061,33 +35320,40 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35061
35320
|
"-map",
|
|
35062
35321
|
"[out]",
|
|
35063
35322
|
"-c:v",
|
|
35064
|
-
|
|
35065
|
-
//
|
|
35066
|
-
//
|
|
35067
|
-
//
|
|
35323
|
+
videoEncoder,
|
|
35324
|
+
// Make the stream easy to join mid-flight: frequent IDRs + in-band
|
|
35325
|
+
// headers + AUD. Without this, a new client may wait many seconds
|
|
35326
|
+
// for the next keyframe.
|
|
35068
35327
|
"-g",
|
|
35069
|
-
|
|
35328
|
+
String(gopFrames),
|
|
35070
35329
|
"-keyint_min",
|
|
35071
|
-
|
|
35330
|
+
String(gopFrames),
|
|
35072
35331
|
"-sc_threshold",
|
|
35073
35332
|
"0",
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35333
|
+
// libx264-specific knobs. We deliberately skip these for HW encoders
|
|
35334
|
+
// — each one has its own option vocabulary (`-q:v`, `-rc`, etc.)
|
|
35335
|
+
// and the user is expected to express them via extraOutputArgs.
|
|
35336
|
+
...isX264 ? [
|
|
35337
|
+
"-x264-params",
|
|
35338
|
+
`aud=1:repeat-headers=1:keyint=${gopFrames}:min-keyint=${gopFrames}:scenecut=0`,
|
|
35339
|
+
"-preset",
|
|
35340
|
+
encoderPreset,
|
|
35341
|
+
"-tune",
|
|
35342
|
+
"zerolatency",
|
|
35343
|
+
"-crf",
|
|
35344
|
+
String(crf)
|
|
35345
|
+
] : [],
|
|
35346
|
+
...this.options.extraOutputArgs ?? [],
|
|
35082
35347
|
"-f",
|
|
35083
35348
|
"h264",
|
|
35084
35349
|
"pipe:1"
|
|
35085
35350
|
// Output (stdout)
|
|
35086
35351
|
];
|
|
35352
|
+
const ffmpegBin = this.options.ffmpegPath ?? "ffmpeg";
|
|
35087
35353
|
this.logger.log?.(
|
|
35088
|
-
`[CompositeStream] Starting ffmpeg:
|
|
35354
|
+
`[CompositeStream] Starting ffmpeg: bin=${ffmpegBin} args=${ffmpegArgs.join(" ")}`
|
|
35089
35355
|
);
|
|
35090
|
-
this.ffmpegProcess = (0, import_node_child_process7.spawn)(
|
|
35356
|
+
this.ffmpegProcess = (0, import_node_child_process7.spawn)(ffmpegBin, ffmpegArgs, {
|
|
35091
35357
|
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
35092
35358
|
});
|
|
35093
35359
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -35795,6 +36061,20 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
35795
36061
|
...forceH264 !== void 0 ? { forceH264 } : defaultForceH264 ? { forceH264: true } : {},
|
|
35796
36062
|
...compositeOptions?.assumeH264Inputs !== void 0 ? { assumeH264Inputs: compositeOptions.assumeH264Inputs } : {},
|
|
35797
36063
|
...compositeOptions?.disableTranscode !== void 0 ? { disableTranscode: compositeOptions.disableTranscode } : {},
|
|
36064
|
+
// Propagate ffmpeg binary path — required when the embedder strips
|
|
36065
|
+
// PATH (Scrypted on Windows, Electron sandboxes, distroless Docker)
|
|
36066
|
+
// and the bundled ffmpeg is at a fixed absolute path only the
|
|
36067
|
+
// embedder knows.
|
|
36068
|
+
...compositeOptions?.ffmpegPath ? { ffmpegPath: compositeOptions.ffmpegPath } : {},
|
|
36069
|
+
// Encoder tuning knobs — see CompositeStreamPipOptions for the
|
|
36070
|
+
// semantic contract on each one. Plumbed verbatim so the
|
|
36071
|
+
// CompositeStream layer can apply defaults.
|
|
36072
|
+
...compositeOptions?.videoEncoder ? { videoEncoder: compositeOptions.videoEncoder } : {},
|
|
36073
|
+
...compositeOptions?.encoderPreset ? { encoderPreset: compositeOptions.encoderPreset } : {},
|
|
36074
|
+
...typeof compositeOptions?.crf === "number" ? { crf: compositeOptions.crf } : {},
|
|
36075
|
+
...typeof compositeOptions?.gopSeconds === "number" ? { gopSeconds: compositeOptions.gopSeconds } : {},
|
|
36076
|
+
...compositeOptions?.extraGlobalArgs ? { extraGlobalArgs: compositeOptions.extraGlobalArgs } : {},
|
|
36077
|
+
...compositeOptions?.extraOutputArgs ? { extraOutputArgs: compositeOptions.extraOutputArgs } : {},
|
|
35798
36078
|
logger
|
|
35799
36079
|
});
|
|
35800
36080
|
isCompositeStream = true;
|
|
@@ -40496,7 +40776,11 @@ async function discoverUidForHost(host, logger) {
|
|
|
40496
40776
|
function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
40497
40777
|
const message = e?.message || e?.toString?.() || "";
|
|
40498
40778
|
if (typeof message !== "string") return false;
|
|
40499
|
-
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("
|
|
40779
|
+
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("EHOSTDOWN") || message.includes("EHOSTUNREACH") || message.includes("ENETUNREACH") || message.includes("ENETDOWN") || message.includes("socket hang up") || message.includes("TCP connection timeout") || // Autodetect's own hard deadline on the TCP login attempt — see
|
|
40780
|
+
// `withTcpDeadline` in `autoDetectDeviceType`. Without this entry the
|
|
40781
|
+
// catch block would rethrow the deadline error instead of awaiting
|
|
40782
|
+
// the speculative UDP race.
|
|
40783
|
+
message.includes("TCP login deadline exceeded") || message.includes("Baichuan socket closed") || message.includes("timeout waiting for nonce") || message.includes("expected encryption info") || message.includes("ECONNRESET") || message.includes("EPIPE");
|
|
40500
40784
|
}
|
|
40501
40785
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
40502
40786
|
if (!host || typeof host !== "string") return false;
|
|
@@ -40644,6 +40928,7 @@ function attachErrorHandler(api, transport, inputs) {
|
|
|
40644
40928
|
}
|
|
40645
40929
|
async function autoDetectDeviceType(inputs) {
|
|
40646
40930
|
const { host, uid, logger } = inputs;
|
|
40931
|
+
const autodetectStartedAt = Date.now();
|
|
40647
40932
|
const mode = inputs.mode ?? "auto";
|
|
40648
40933
|
const maxRetriesRaw = inputs.maxRetries;
|
|
40649
40934
|
const maxRetries = Math.max(
|
|
@@ -40660,9 +40945,31 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40660
40945
|
const sleepMs3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
40661
40946
|
const shouldRetryTcp = (e) => {
|
|
40662
40947
|
const msg = fmtErr(e);
|
|
40663
|
-
if (msg.includes("ECONNREFUSED"))
|
|
40948
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("EHOSTDOWN") || msg.includes("EHOSTUNREACH") || msg.includes("ENETUNREACH") || msg.includes("ENETDOWN")) {
|
|
40949
|
+
return false;
|
|
40950
|
+
}
|
|
40664
40951
|
return isTcpFailureThatShouldFallbackToUdp(e) || msg.includes("timeout waiting for nonce") || msg.includes("expected encryption info") || msg.includes("Baichuan socket closed") || msg.includes("ECONNRESET") || msg.includes("EPIPE");
|
|
40665
40952
|
};
|
|
40953
|
+
const tcpDeadlineMs = typeof inputs.tcpConnectTimeoutMs === "number" && Number.isFinite(inputs.tcpConnectTimeoutMs) && inputs.tcpConnectTimeoutMs > 0 ? inputs.tcpConnectTimeoutMs : 8e3;
|
|
40954
|
+
const withTcpDeadline = async (op) => {
|
|
40955
|
+
let timer;
|
|
40956
|
+
const deadline = new Promise((_, reject) => {
|
|
40957
|
+
timer = setTimeout(
|
|
40958
|
+
() => reject(
|
|
40959
|
+
new Error(
|
|
40960
|
+
`TCP login deadline exceeded (${tcpDeadlineMs}ms) \u2014 host unreachable`
|
|
40961
|
+
)
|
|
40962
|
+
),
|
|
40963
|
+
tcpDeadlineMs
|
|
40964
|
+
);
|
|
40965
|
+
timer.unref?.();
|
|
40966
|
+
});
|
|
40967
|
+
try {
|
|
40968
|
+
return await Promise.race([op, deadline]);
|
|
40969
|
+
} finally {
|
|
40970
|
+
if (timer) clearTimeout(timer);
|
|
40971
|
+
}
|
|
40972
|
+
};
|
|
40666
40973
|
const shouldRetryUdp = (e) => {
|
|
40667
40974
|
const msg = fmtErr(e);
|
|
40668
40975
|
return msg.includes("Not running") || msg.includes("Baichuan UDP stream closed") || msg.includes("Baichuan socket closed") || msg.includes("ETIMEDOUT") || msg.toLowerCase().includes("timeout");
|
|
@@ -40815,6 +41122,127 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40815
41122
|
"Forced UDP autodetect failed for all methods."
|
|
40816
41123
|
);
|
|
40817
41124
|
}
|
|
41125
|
+
const detectOverUdpApi = async (udpApi, udpDiscoveryMethod, resolvedUid) => {
|
|
41126
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
41127
|
+
udpApi.getInfo(),
|
|
41128
|
+
udpApi.getDeviceCapabilities(),
|
|
41129
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
41130
|
+
]);
|
|
41131
|
+
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
41132
|
+
const model = deviceInfo.type?.trim();
|
|
41133
|
+
const normalizedModel = model ? model.trim() : void 0;
|
|
41134
|
+
const isMultifocalByModel = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
41135
|
+
const channelNumValue = typeof channelNum === "string" ? Number.parseInt(channelNum, 10) : channelNum;
|
|
41136
|
+
const hasDualLensChannelCount = (channelNumValue === 2 || channelNumValue === 3) && Number.isFinite(channelNumValue);
|
|
41137
|
+
const isMultifocal = isMultifocalByModel || hasDualLensChannelCount;
|
|
41138
|
+
const hasBattery = capabilities?.capabilities?.hasBattery === true;
|
|
41139
|
+
udpApi.setIdleDisconnect(hasBattery);
|
|
41140
|
+
if (isMultifocal) {
|
|
41141
|
+
const detectionMethod = isMultifocalByModel ? "model match" : "channelNum fallback";
|
|
41142
|
+
logger?.log?.(
|
|
41143
|
+
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum}, hasBattery=${hasBattery}).`
|
|
41144
|
+
);
|
|
41145
|
+
return {
|
|
41146
|
+
type: "multifocal",
|
|
41147
|
+
transport: "udp",
|
|
41148
|
+
uid: resolvedUid,
|
|
41149
|
+
udpDiscoveryMethod,
|
|
41150
|
+
deviceInfo,
|
|
41151
|
+
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41152
|
+
channelNum,
|
|
41153
|
+
hasBattery,
|
|
41154
|
+
api: udpApi
|
|
41155
|
+
};
|
|
41156
|
+
}
|
|
41157
|
+
const deviceType = hasBattery ? "battery-cam" : "udp-camera";
|
|
41158
|
+
logger?.log?.(
|
|
41159
|
+
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected ${deviceType} (hasBattery=${hasBattery}, model=${normalizedModel ?? "unknown"}).`
|
|
41160
|
+
);
|
|
41161
|
+
return {
|
|
41162
|
+
type: deviceType,
|
|
41163
|
+
transport: "udp",
|
|
41164
|
+
uid: resolvedUid,
|
|
41165
|
+
udpDiscoveryMethod,
|
|
41166
|
+
deviceInfo,
|
|
41167
|
+
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41168
|
+
channelNum: 1,
|
|
41169
|
+
hasBattery,
|
|
41170
|
+
api: udpApi
|
|
41171
|
+
};
|
|
41172
|
+
};
|
|
41173
|
+
const udpRaceAbort = new AbortController();
|
|
41174
|
+
const speculativeUdpRace = mode === "auto" ? (async () => {
|
|
41175
|
+
const resolvedUid = await speculativeUidPromise;
|
|
41176
|
+
const viableMethods = selectViableUdpMethods(Boolean(resolvedUid));
|
|
41177
|
+
return await runUdpMethodsParallel(
|
|
41178
|
+
viableMethods,
|
|
41179
|
+
async (m, isInnerAborted) => {
|
|
41180
|
+
const isAborted = () => udpRaceAbort.signal.aborted || isInnerAborted();
|
|
41181
|
+
if (isAborted()) {
|
|
41182
|
+
throw new Error(
|
|
41183
|
+
`UDP(${m}) speculative race aborted before start`
|
|
41184
|
+
);
|
|
41185
|
+
}
|
|
41186
|
+
logger?.log?.(
|
|
41187
|
+
`[AutoDetect] (race) Trying UDP discovery method: ${m}...`
|
|
41188
|
+
);
|
|
41189
|
+
const udpApi = await withRetries(
|
|
41190
|
+
`UDP(${m})`,
|
|
41191
|
+
maxRetries,
|
|
41192
|
+
async (attempt) => {
|
|
41193
|
+
const apiInputs = {
|
|
41194
|
+
...inputs,
|
|
41195
|
+
udpDiscoveryMethod: m
|
|
41196
|
+
};
|
|
41197
|
+
if (resolvedUid) apiInputs.uid = resolvedUid;
|
|
41198
|
+
const api = createBaichuanApi(apiInputs, "udp");
|
|
41199
|
+
try {
|
|
41200
|
+
await api.login();
|
|
41201
|
+
return api;
|
|
41202
|
+
} catch (e) {
|
|
41203
|
+
try {
|
|
41204
|
+
await api.close({
|
|
41205
|
+
reason: `autodetect:udp_failed:${m}:attempt_${attempt}`
|
|
41206
|
+
});
|
|
41207
|
+
} catch {
|
|
41208
|
+
}
|
|
41209
|
+
throw e;
|
|
41210
|
+
}
|
|
41211
|
+
},
|
|
41212
|
+
shouldRetryUdp,
|
|
41213
|
+
isAborted
|
|
41214
|
+
);
|
|
41215
|
+
if (isAborted()) {
|
|
41216
|
+
try {
|
|
41217
|
+
await udpApi.close({
|
|
41218
|
+
reason: "autodetect:udp_aborted_after_tcp_won"
|
|
41219
|
+
});
|
|
41220
|
+
} catch {
|
|
41221
|
+
}
|
|
41222
|
+
throw new Error(
|
|
41223
|
+
`UDP(${m}) speculative race aborted after login`
|
|
41224
|
+
);
|
|
41225
|
+
}
|
|
41226
|
+
return detectOverUdpApi(udpApi, m, resolvedUid ?? "");
|
|
41227
|
+
},
|
|
41228
|
+
"Speculative UDP race failed for all methods."
|
|
41229
|
+
);
|
|
41230
|
+
})() : void 0;
|
|
41231
|
+
speculativeUdpRace?.then(
|
|
41232
|
+
(udpResult) => {
|
|
41233
|
+
if (udpRaceAbort.signal.aborted && udpResult?.api) {
|
|
41234
|
+
udpResult.api.close({ reason: "autodetect:tcp_won_race" }).catch(() => void 0);
|
|
41235
|
+
}
|
|
41236
|
+
},
|
|
41237
|
+
() => void 0
|
|
41238
|
+
);
|
|
41239
|
+
const _tcpWin = (result) => {
|
|
41240
|
+
udpRaceAbort.abort();
|
|
41241
|
+
logger?.log?.(
|
|
41242
|
+
`[AutoDetect] DONE in ${Date.now() - autodetectStartedAt}ms via TCP \u2014 type=${result.type} model=${result.deviceInfo?.type ?? "?"} channels=${result.channelNum}`
|
|
41243
|
+
);
|
|
41244
|
+
return result;
|
|
41245
|
+
};
|
|
40818
41246
|
let tcpApi;
|
|
40819
41247
|
try {
|
|
40820
41248
|
logger?.log?.(`[AutoDetect] Trying TCP connection to ${host}...`);
|
|
@@ -40824,7 +41252,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40824
41252
|
async (attempt) => {
|
|
40825
41253
|
const api2 = createBaichuanApi(inputs, "tcp");
|
|
40826
41254
|
try {
|
|
40827
|
-
await api2.login();
|
|
41255
|
+
await withTcpDeadline(api2.login());
|
|
40828
41256
|
return api2;
|
|
40829
41257
|
} catch (e) {
|
|
40830
41258
|
try {
|
|
@@ -40936,7 +41364,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40936
41364
|
logger?.log?.(
|
|
40937
41365
|
`[AutoDetect] Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum})`
|
|
40938
41366
|
);
|
|
40939
|
-
return {
|
|
41367
|
+
return _tcpWin({
|
|
40940
41368
|
type: "multifocal",
|
|
40941
41369
|
transport: "tcp",
|
|
40942
41370
|
uid: effectiveUid || uid || "",
|
|
@@ -40944,13 +41372,13 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40944
41372
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
40945
41373
|
channelNum: effectiveChannelNum,
|
|
40946
41374
|
api
|
|
40947
|
-
};
|
|
41375
|
+
});
|
|
40948
41376
|
}
|
|
40949
41377
|
if (effectiveChannelNum > 1) {
|
|
40950
41378
|
logger?.log?.(
|
|
40951
41379
|
`[AutoDetect] Detected NVR (${effectiveChannelNum} channels)`
|
|
40952
41380
|
);
|
|
40953
|
-
return {
|
|
41381
|
+
return _tcpWin({
|
|
40954
41382
|
type: "nvr",
|
|
40955
41383
|
transport: "tcp",
|
|
40956
41384
|
uid: effectiveUid || uid || "",
|
|
@@ -40958,10 +41386,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40958
41386
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
40959
41387
|
channelNum: effectiveChannelNum,
|
|
40960
41388
|
api
|
|
40961
|
-
};
|
|
41389
|
+
});
|
|
40962
41390
|
}
|
|
40963
41391
|
logger?.log?.(`[AutoDetect] Detected regular camera (single channel)`);
|
|
40964
|
-
return {
|
|
41392
|
+
return _tcpWin({
|
|
40965
41393
|
type: "camera",
|
|
40966
41394
|
transport: "tcp",
|
|
40967
41395
|
uid: effectiveUid || uid || "",
|
|
@@ -40969,7 +41397,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40969
41397
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
40970
41398
|
channelNum: 1,
|
|
40971
41399
|
api
|
|
40972
|
-
};
|
|
41400
|
+
});
|
|
40973
41401
|
} catch (tcpError) {
|
|
40974
41402
|
if (mode === "tcp") {
|
|
40975
41403
|
throw tcpError;
|
|
@@ -40984,100 +41412,20 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40984
41412
|
throw tcpError;
|
|
40985
41413
|
}
|
|
40986
41414
|
logger?.log?.(`[AutoDetect] TCP failed, trying UDP...`);
|
|
40987
|
-
|
|
40988
|
-
|
|
40989
|
-
|
|
40990
|
-
`[AutoDetect] UID discovery failed; only local-direct can run without a UID. If the camera is sleeping or on a different subnet, supply its UID to enable BCUDP P2P fallback (remote/relay/map) which can wake it via Reolink's servers.`
|
|
40991
|
-
);
|
|
40992
|
-
} else if (effectiveUid === void 0) {
|
|
40993
|
-
logger?.log?.(
|
|
40994
|
-
`[AutoDetect] UID resolved via concurrent broadcast discovery: ${normalizedUid}`
|
|
41415
|
+
if (!speculativeUdpRace) {
|
|
41416
|
+
throw new Error(
|
|
41417
|
+
`AutoDetect internal: speculative UDP race missing in mode=${mode}`
|
|
40995
41418
|
);
|
|
40996
41419
|
}
|
|
40997
41420
|
try {
|
|
40998
|
-
const
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
udpApi.getDeviceCapabilities(),
|
|
41002
|
-
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
41003
|
-
]);
|
|
41004
|
-
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
41005
|
-
const model = deviceInfo.type?.trim();
|
|
41006
|
-
const normalizedModel = model ? model.trim() : void 0;
|
|
41007
|
-
const isMultifocalByModel = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
41008
|
-
const channelNumValue = typeof channelNum === "string" ? Number.parseInt(channelNum, 10) : channelNum;
|
|
41009
|
-
const hasDualLensChannelCount = (channelNumValue === 2 || channelNumValue === 3) && Number.isFinite(channelNumValue);
|
|
41010
|
-
const isMultifocal = isMultifocalByModel || hasDualLensChannelCount;
|
|
41011
|
-
const hasBattery = capabilities?.capabilities?.hasBattery === true;
|
|
41012
|
-
udpApi.setIdleDisconnect(hasBattery);
|
|
41013
|
-
if (isMultifocal) {
|
|
41014
|
-
const detectionMethod = isMultifocalByModel ? "model match" : "channelNum fallback";
|
|
41015
|
-
logger?.log?.(
|
|
41016
|
-
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum}, hasBattery=${hasBattery}).`
|
|
41017
|
-
);
|
|
41018
|
-
return {
|
|
41019
|
-
type: "multifocal",
|
|
41020
|
-
transport: "udp",
|
|
41021
|
-
uid: normalizedUid ?? "",
|
|
41022
|
-
udpDiscoveryMethod,
|
|
41023
|
-
deviceInfo,
|
|
41024
|
-
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41025
|
-
channelNum,
|
|
41026
|
-
hasBattery,
|
|
41027
|
-
api: udpApi
|
|
41028
|
-
};
|
|
41029
|
-
}
|
|
41030
|
-
const deviceType = hasBattery ? "battery-cam" : "udp-camera";
|
|
41031
|
-
logger?.log?.(
|
|
41032
|
-
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected ${deviceType} (hasBattery=${hasBattery}, model=${normalizedModel ?? "unknown"}).`
|
|
41033
|
-
);
|
|
41034
|
-
return {
|
|
41035
|
-
type: deviceType,
|
|
41036
|
-
transport: "udp",
|
|
41037
|
-
uid: normalizedUid ?? "",
|
|
41038
|
-
udpDiscoveryMethod,
|
|
41039
|
-
deviceInfo,
|
|
41040
|
-
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41041
|
-
channelNum: 1,
|
|
41042
|
-
hasBattery,
|
|
41043
|
-
api: udpApi
|
|
41044
|
-
};
|
|
41045
|
-
};
|
|
41046
|
-
const viableMethods = selectViableUdpMethods(Boolean(normalizedUid));
|
|
41047
|
-
return await runUdpMethodsParallel(
|
|
41048
|
-
viableMethods,
|
|
41049
|
-
async (m, isAborted) => {
|
|
41050
|
-
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
41051
|
-
const udpApi = await withRetries(
|
|
41052
|
-
`UDP(${m})`,
|
|
41053
|
-
maxRetries,
|
|
41054
|
-
async (attempt) => {
|
|
41055
|
-
const apiInputs = { ...inputs, udpDiscoveryMethod: m };
|
|
41056
|
-
if (normalizedUid) apiInputs.uid = normalizedUid;
|
|
41057
|
-
const api = createBaichuanApi(apiInputs, "udp");
|
|
41058
|
-
try {
|
|
41059
|
-
await api.login();
|
|
41060
|
-
return api;
|
|
41061
|
-
} catch (e) {
|
|
41062
|
-
try {
|
|
41063
|
-
await api.close({
|
|
41064
|
-
reason: `autodetect:udp_failed:${m}:attempt_${attempt}`
|
|
41065
|
-
});
|
|
41066
|
-
} catch {
|
|
41067
|
-
}
|
|
41068
|
-
throw e;
|
|
41069
|
-
}
|
|
41070
|
-
},
|
|
41071
|
-
shouldRetryUdp,
|
|
41072
|
-
isAborted
|
|
41073
|
-
);
|
|
41074
|
-
return detectOverUdpApi(udpApi, m);
|
|
41075
|
-
},
|
|
41076
|
-
"UDP discovery failed for all methods."
|
|
41421
|
+
const udpResult = await speculativeUdpRace;
|
|
41422
|
+
logger?.log?.(
|
|
41423
|
+
`[AutoDetect] DONE in ${Date.now() - autodetectStartedAt}ms via UDP \u2014 type=${udpResult.type} method=${udpResult.udpDiscoveryMethod ?? "n/a"} model=${udpResult.deviceInfo?.type ?? "?"} channels=${udpResult.channelNum}`
|
|
41077
41424
|
);
|
|
41425
|
+
return udpResult;
|
|
41078
41426
|
} catch (udpError) {
|
|
41079
41427
|
logger?.log?.(
|
|
41080
|
-
`[AutoDetect]
|
|
41428
|
+
`[AutoDetect] FAILED after ${Date.now() - autodetectStartedAt}ms \u2014 neither TCP nor UDP could reach the camera. TCP: ${tcpError?.message ?? tcpError}. UDP: ${udpError?.message ?? udpError}`
|
|
41081
41429
|
);
|
|
41082
41430
|
throw new Error(
|
|
41083
41431
|
`Failed to connect via both TCP and UDP. TCP: ${tcpError?.message || tcpError}, UDP: ${udpError?.message || udpError}`
|
|
@@ -42927,6 +43275,8 @@ function buildInitialStatus(config) {
|
|
|
42927
43275
|
BC_CMD_ID_COVER_STANDALONE_460,
|
|
42928
43276
|
BC_CMD_ID_COVER_STANDALONE_461,
|
|
42929
43277
|
BC_CMD_ID_COVER_STANDALONE_462,
|
|
43278
|
+
BC_CMD_ID_DEVICE_DETECT,
|
|
43279
|
+
BC_CMD_ID_DEVICE_DETECT_CANDIDATES,
|
|
42930
43280
|
BC_CMD_ID_DING_DONG_CTRL,
|
|
42931
43281
|
BC_CMD_ID_DING_DONG_OPT,
|
|
42932
43282
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|