@apocaliss92/nodelink-js 0.2.5 → 0.3.4
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/{chunk-EG5IY3CM.js → chunk-YSEFEQYV.js} +412 -19
- package/dist/chunk-YSEFEQYV.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +46 -15
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +480 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -70
- package/dist/index.d.ts +78 -69
- package/dist/index.js +46 -128
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EG5IY3CM.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -7511,7 +7511,11 @@ __export(index_exports, {
|
|
|
7511
7511
|
detectIosClient: () => detectIosClient,
|
|
7512
7512
|
detectVideoCodecFromNal: () => detectVideoCodecFromNal,
|
|
7513
7513
|
discoverReolinkDevices: () => discoverReolinkDevices,
|
|
7514
|
+
discoverViaArpTable: () => discoverViaArpTable,
|
|
7515
|
+
discoverViaDhcpListener: () => discoverViaDhcpListener,
|
|
7514
7516
|
discoverViaHttpScan: () => discoverViaHttpScan,
|
|
7517
|
+
discoverViaOnvif: () => discoverViaOnvif,
|
|
7518
|
+
discoverViaTcpPortScan: () => discoverViaTcpPortScan,
|
|
7515
7519
|
discoverViaUdpBroadcast: () => discoverViaUdpBroadcast,
|
|
7516
7520
|
discoverViaUdpDirect: () => discoverViaUdpDirect,
|
|
7517
7521
|
encodeHeader: () => encodeHeader,
|
|
@@ -13379,14 +13383,16 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13379
13383
|
`;
|
|
13380
13384
|
}
|
|
13381
13385
|
if (body) {
|
|
13382
|
-
|
|
13386
|
+
const bodyBuf = Buffer.from(body, "utf8");
|
|
13387
|
+
response += `Content-Length: ${bodyBuf.length}\r
|
|
13383
13388
|
`;
|
|
13389
|
+
response += "\r\n";
|
|
13390
|
+
socket.write(response);
|
|
13391
|
+
socket.write(bodyBuf);
|
|
13392
|
+
} else {
|
|
13393
|
+
response += "\r\n";
|
|
13394
|
+
socket.write(response);
|
|
13384
13395
|
}
|
|
13385
|
-
response += "\r\n";
|
|
13386
|
-
if (body) {
|
|
13387
|
-
response += body;
|
|
13388
|
-
}
|
|
13389
|
-
socket.write(response);
|
|
13390
13396
|
};
|
|
13391
13397
|
this.rtspDebugLog(`RTSP ${method} ${url}`);
|
|
13392
13398
|
if (this.requireAuth) {
|
|
@@ -13596,10 +13602,25 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13596
13602
|
);
|
|
13597
13603
|
}
|
|
13598
13604
|
}
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13605
|
+
{
|
|
13606
|
+
const baseUrl = `rtsp://${this.listenHost}:${this.listenPort}${this.path}`;
|
|
13607
|
+
const resources = this.clientResources.get(clientId);
|
|
13608
|
+
const rtpInfoParts = [];
|
|
13609
|
+
if (resources?.setupTrack0) {
|
|
13610
|
+
rtpInfoParts.push(`url=${baseUrl}/track0`);
|
|
13611
|
+
}
|
|
13612
|
+
if (resources?.setupTrack1) {
|
|
13613
|
+
rtpInfoParts.push(`url=${baseUrl}/track1`);
|
|
13614
|
+
}
|
|
13615
|
+
const playHeaders = {
|
|
13616
|
+
Session: sessionId,
|
|
13617
|
+
Range: "npt=now-"
|
|
13618
|
+
};
|
|
13619
|
+
if (rtpInfoParts.length > 0) {
|
|
13620
|
+
playHeaders["RTP-Info"] = rtpInfoParts.join(",");
|
|
13621
|
+
}
|
|
13622
|
+
sendResponse(200, "OK", playHeaders);
|
|
13623
|
+
}
|
|
13603
13624
|
} else if (method === "TEARDOWN") {
|
|
13604
13625
|
this.logger.info(
|
|
13605
13626
|
`[rebroadcast] TEARDOWN client=${clientId} session=${sessionId}`
|
|
@@ -13629,6 +13650,8 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
13629
13650
|
sdp += `c=IN IP4 ${this.listenHost}\r
|
|
13630
13651
|
`;
|
|
13631
13652
|
sdp += "t=0 0\r\n";
|
|
13653
|
+
sdp += "a=range:npt=now-\r\n";
|
|
13654
|
+
sdp += "a=control:*\r\n";
|
|
13632
13655
|
sdp += `m=video 0 RTP/AVP ${videoPayloadType}\r
|
|
13633
13656
|
`;
|
|
13634
13657
|
sdp += `a=rtpmap:${videoPayloadType} ${codec}/90000\r
|
|
@@ -14571,7 +14594,14 @@ var BaichuanRtspServer = class _BaichuanRtspServer extends import_node_events4.E
|
|
|
14571
14594
|
this.firstFramePromise = null;
|
|
14572
14595
|
this.firstFrameResolve = null;
|
|
14573
14596
|
this.nativeFanout = null;
|
|
14574
|
-
|
|
14597
|
+
for (const [, resources] of this.clientResources) {
|
|
14598
|
+
const res = resources;
|
|
14599
|
+
res.rtpVideoBaseMicroseconds = void 0;
|
|
14600
|
+
res.rtpVideoBaseTimestamp = void 0;
|
|
14601
|
+
res.rtpVideoLastTimestamp = void 0;
|
|
14602
|
+
res.seenFirstVideoKeyframe = false;
|
|
14603
|
+
res.rtpSentVideoConfig = false;
|
|
14604
|
+
}
|
|
14575
14605
|
if (this.dedicatedSessionRelease) {
|
|
14576
14606
|
const release = this.dedicatedSessionRelease;
|
|
14577
14607
|
this.dedicatedSessionRelease = void 0;
|
|
@@ -22571,13 +22601,13 @@ ${stderr}`)
|
|
|
22571
22601
|
*/
|
|
22572
22602
|
async muxToMp4(params) {
|
|
22573
22603
|
const { spawn: spawn12 } = await import("child_process");
|
|
22574
|
-
const { randomUUID:
|
|
22604
|
+
const { randomUUID: randomUUID3 } = await import("crypto");
|
|
22575
22605
|
const fs6 = await import("fs/promises");
|
|
22576
22606
|
const os2 = await import("os");
|
|
22577
22607
|
const path6 = await import("path");
|
|
22578
22608
|
const ffmpeg = params.ffmpegPath ?? "ffmpeg";
|
|
22579
22609
|
const tmpDir = os2.tmpdir();
|
|
22580
|
-
const id =
|
|
22610
|
+
const id = randomUUID3();
|
|
22581
22611
|
const videoFormat = params.videoCodec === "H265" ? "hevc" : "h264";
|
|
22582
22612
|
const videoPath = path6.join(tmpDir, `reolink-${id}.${videoFormat}`);
|
|
22583
22613
|
const outputPath = path6.join(tmpDir, `reolink-${id}.mp4`);
|
|
@@ -27918,9 +27948,14 @@ function buildHlsRedirectUrl(originalUrl) {
|
|
|
27918
27948
|
}
|
|
27919
27949
|
|
|
27920
27950
|
// src/reolink/discovery.ts
|
|
27951
|
+
var import_node_child_process4 = require("child_process");
|
|
27952
|
+
var import_node_crypto3 = require("crypto");
|
|
27921
27953
|
var import_node_dgram2 = __toESM(require("dgram"), 1);
|
|
27954
|
+
var net3 = __toESM(require("net"), 1);
|
|
27922
27955
|
var import_node_os2 = require("os");
|
|
27956
|
+
var import_node_util = require("util");
|
|
27923
27957
|
init_ReolinkCgiApi();
|
|
27958
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process4.execFile);
|
|
27924
27959
|
async function discoverViaUdpDirect(host, options) {
|
|
27925
27960
|
if (!options.enableUdpDiscovery) return [];
|
|
27926
27961
|
const logger = options.logger;
|
|
@@ -28282,6 +28317,348 @@ async function discoverViaUdpBroadcast(options) {
|
|
|
28282
28317
|
});
|
|
28283
28318
|
});
|
|
28284
28319
|
}
|
|
28320
|
+
var REOLINK_MAC_PREFIXES = [
|
|
28321
|
+
"EC:71:DB",
|
|
28322
|
+
// Most common Reolink OUI
|
|
28323
|
+
"2C:1B:3A",
|
|
28324
|
+
// WiFi cameras (E1 Zoom, etc.)
|
|
28325
|
+
"18:2C:65",
|
|
28326
|
+
// Battery cameras (Video Doorbell, Argus, etc.)
|
|
28327
|
+
"DC:E5:37",
|
|
28328
|
+
// Some newer models
|
|
28329
|
+
"9C:8E:CD",
|
|
28330
|
+
// Some WiFi models
|
|
28331
|
+
"B4:4B:D6",
|
|
28332
|
+
// Some models
|
|
28333
|
+
"E4:3D:1A"
|
|
28334
|
+
// Some models
|
|
28335
|
+
];
|
|
28336
|
+
async function discoverViaArpTable(options) {
|
|
28337
|
+
if (!options.enableArpLookup) return [];
|
|
28338
|
+
const logger = options.logger;
|
|
28339
|
+
logger?.log?.("[Discovery] Starting ARP table lookup for Reolink MAC prefix...");
|
|
28340
|
+
const discovered = [];
|
|
28341
|
+
try {
|
|
28342
|
+
let entries = [];
|
|
28343
|
+
if ((0, import_node_os2.platform)() === "linux") {
|
|
28344
|
+
try {
|
|
28345
|
+
const { readFile } = await import("fs/promises");
|
|
28346
|
+
const content = await readFile("/proc/net/arp", "utf8");
|
|
28347
|
+
for (const line of content.split("\n").slice(1)) {
|
|
28348
|
+
const parts = line.trim().split(/\s+/);
|
|
28349
|
+
if (parts.length >= 4 && parts[0] && parts[3] && parts[3] !== "00:00:00:00:00:00") {
|
|
28350
|
+
entries.push({ ip: parts[0], mac: parts[3].toUpperCase() });
|
|
28351
|
+
}
|
|
28352
|
+
}
|
|
28353
|
+
} catch {
|
|
28354
|
+
const { stdout } = await runArpCommand();
|
|
28355
|
+
entries = parseArpOutput(stdout);
|
|
28356
|
+
}
|
|
28357
|
+
} else {
|
|
28358
|
+
const { stdout } = await runArpCommand();
|
|
28359
|
+
entries = parseArpOutput(stdout);
|
|
28360
|
+
}
|
|
28361
|
+
logger?.log?.(`[Discovery] ARP table has ${entries.length} entries`);
|
|
28362
|
+
for (const { ip, mac } of entries) {
|
|
28363
|
+
const isReolink = REOLINK_MAC_PREFIXES.some(
|
|
28364
|
+
(prefix) => mac.startsWith(prefix)
|
|
28365
|
+
);
|
|
28366
|
+
if (isReolink) {
|
|
28367
|
+
logger?.log?.(`[Discovery] Found Reolink device via ARP: ${ip} (MAC: ${mac})`);
|
|
28368
|
+
discovered.push({
|
|
28369
|
+
host: ip,
|
|
28370
|
+
discoveryMethod: "arp"
|
|
28371
|
+
});
|
|
28372
|
+
}
|
|
28373
|
+
}
|
|
28374
|
+
} catch (err) {
|
|
28375
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28376
|
+
logger?.warn?.(`[Discovery] ARP table lookup failed: ${msg}`);
|
|
28377
|
+
}
|
|
28378
|
+
logger?.log?.(`[Discovery] ARP lookup complete. Found ${discovered.length} device(s).`);
|
|
28379
|
+
return discovered;
|
|
28380
|
+
}
|
|
28381
|
+
async function runArpCommand() {
|
|
28382
|
+
const paths = ["/usr/sbin/arp", "/sbin/arp", "/usr/bin/arp", "arp"];
|
|
28383
|
+
for (const arpPath of paths) {
|
|
28384
|
+
try {
|
|
28385
|
+
return await execFileAsync(arpPath, ["-an"], { timeout: 5e3 });
|
|
28386
|
+
} catch {
|
|
28387
|
+
}
|
|
28388
|
+
}
|
|
28389
|
+
throw new Error("arp command not found");
|
|
28390
|
+
}
|
|
28391
|
+
function parseArpOutput(stdout) {
|
|
28392
|
+
const results = [];
|
|
28393
|
+
for (const line of stdout.split("\n")) {
|
|
28394
|
+
const match = /\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-fA-F:]+)/i.exec(line);
|
|
28395
|
+
if (match && match[1] && match[2] && match[2] !== "(incomplete)") {
|
|
28396
|
+
results.push({ ip: match[1], mac: match[2].toUpperCase() });
|
|
28397
|
+
}
|
|
28398
|
+
}
|
|
28399
|
+
return results;
|
|
28400
|
+
}
|
|
28401
|
+
async function discoverViaDhcpListener(options) {
|
|
28402
|
+
if (!options.enableDhcpListener) return [];
|
|
28403
|
+
const logger = options.logger;
|
|
28404
|
+
const timeoutMs = options.dhcpListenerTimeoutMs ?? 1e4;
|
|
28405
|
+
logger?.log?.(`[Discovery] Starting passive DHCP listener (${timeoutMs}ms)...`);
|
|
28406
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
28407
|
+
return new Promise((resolve) => {
|
|
28408
|
+
let socket;
|
|
28409
|
+
let timeout;
|
|
28410
|
+
try {
|
|
28411
|
+
socket = import_node_dgram2.default.createSocket({ type: "udp4", reuseAddr: true });
|
|
28412
|
+
} catch (err) {
|
|
28413
|
+
logger?.warn?.(`[Discovery] DHCP: failed to create socket: ${err instanceof Error ? err.message : String(err)}`);
|
|
28414
|
+
resolve([]);
|
|
28415
|
+
return;
|
|
28416
|
+
}
|
|
28417
|
+
socket.on("message", (msg) => {
|
|
28418
|
+
try {
|
|
28419
|
+
if (msg.length < 240) return;
|
|
28420
|
+
const op = msg[0];
|
|
28421
|
+
const hlen = msg[2];
|
|
28422
|
+
if (hlen !== 6) return;
|
|
28423
|
+
const mac = [
|
|
28424
|
+
msg[28]?.toString(16).padStart(2, "0"),
|
|
28425
|
+
msg[29]?.toString(16).padStart(2, "0"),
|
|
28426
|
+
msg[30]?.toString(16).padStart(2, "0"),
|
|
28427
|
+
msg[31]?.toString(16).padStart(2, "0"),
|
|
28428
|
+
msg[32]?.toString(16).padStart(2, "0"),
|
|
28429
|
+
msg[33]?.toString(16).padStart(2, "0")
|
|
28430
|
+
].join(":").toUpperCase();
|
|
28431
|
+
const isReolinkMac = REOLINK_MAC_PREFIXES.some((p) => mac.startsWith(p));
|
|
28432
|
+
let hostname = "";
|
|
28433
|
+
let i = 240;
|
|
28434
|
+
while (i < msg.length - 1) {
|
|
28435
|
+
const optType = msg[i];
|
|
28436
|
+
if (optType === 255) break;
|
|
28437
|
+
if (optType === 0) {
|
|
28438
|
+
i++;
|
|
28439
|
+
continue;
|
|
28440
|
+
}
|
|
28441
|
+
const optLen = msg[i + 1] ?? 0;
|
|
28442
|
+
if (optType === 12 && optLen > 0) {
|
|
28443
|
+
hostname = msg.subarray(i + 2, i + 2 + optLen).toString("ascii").toLowerCase();
|
|
28444
|
+
}
|
|
28445
|
+
i += 2 + optLen;
|
|
28446
|
+
}
|
|
28447
|
+
const isReolinkHostname = hostname.startsWith("reolink");
|
|
28448
|
+
if (!isReolinkMac && !isReolinkHostname) return;
|
|
28449
|
+
const yiaddr = `${msg[16]}.${msg[17]}.${msg[18]}.${msg[19]}`;
|
|
28450
|
+
const ciaddr = `${msg[12]}.${msg[13]}.${msg[14]}.${msg[15]}`;
|
|
28451
|
+
const ip = yiaddr !== "0.0.0.0" ? yiaddr : ciaddr;
|
|
28452
|
+
if (ip === "0.0.0.0" || !ip) return;
|
|
28453
|
+
if (!discovered.has(ip)) {
|
|
28454
|
+
logger?.log?.(`[Discovery] DHCP: found Reolink device ${ip} (MAC: ${mac}, hostname: ${hostname || "n/a"}, op: ${op === 1 ? "request" : "reply"})`);
|
|
28455
|
+
const device = {
|
|
28456
|
+
host: ip,
|
|
28457
|
+
discoveryMethod: "dhcp"
|
|
28458
|
+
};
|
|
28459
|
+
if (hostname) device.name = hostname;
|
|
28460
|
+
discovered.set(ip, device);
|
|
28461
|
+
}
|
|
28462
|
+
} catch {
|
|
28463
|
+
}
|
|
28464
|
+
});
|
|
28465
|
+
socket.on("error", (err) => {
|
|
28466
|
+
logger?.warn?.(`[Discovery] DHCP socket error: ${err.message}`);
|
|
28467
|
+
clearTimeout(timeout);
|
|
28468
|
+
socket.close();
|
|
28469
|
+
resolve(Array.from(discovered.values()));
|
|
28470
|
+
});
|
|
28471
|
+
socket.bind(67, "0.0.0.0", () => {
|
|
28472
|
+
logger?.log?.("[Discovery] DHCP listener bound on port 67");
|
|
28473
|
+
timeout = setTimeout(() => {
|
|
28474
|
+
socket.close();
|
|
28475
|
+
logger?.log?.(`[Discovery] DHCP listener complete. Found ${discovered.size} device(s).`);
|
|
28476
|
+
resolve(Array.from(discovered.values()));
|
|
28477
|
+
}, timeoutMs);
|
|
28478
|
+
});
|
|
28479
|
+
});
|
|
28480
|
+
}
|
|
28481
|
+
function probeTcpPort(ip, port, timeoutMs) {
|
|
28482
|
+
return new Promise((resolve) => {
|
|
28483
|
+
const socket = new net3.Socket();
|
|
28484
|
+
let settled = false;
|
|
28485
|
+
const done = (result) => {
|
|
28486
|
+
if (settled) return;
|
|
28487
|
+
settled = true;
|
|
28488
|
+
socket.destroy();
|
|
28489
|
+
resolve(result);
|
|
28490
|
+
};
|
|
28491
|
+
socket.setTimeout(timeoutMs);
|
|
28492
|
+
socket.on("connect", () => done(true));
|
|
28493
|
+
socket.on("timeout", () => done(false));
|
|
28494
|
+
socket.on("error", () => done(false));
|
|
28495
|
+
socket.connect(port, ip);
|
|
28496
|
+
});
|
|
28497
|
+
}
|
|
28498
|
+
async function discoverViaTcpPortScan(options) {
|
|
28499
|
+
if (!options.enableTcpPortScan) return [];
|
|
28500
|
+
const logger = options.logger;
|
|
28501
|
+
const networkCidr = options.networkCidr ?? getLocalNetworks()[0];
|
|
28502
|
+
const timeoutMs = options.tcpProbeTimeoutMs ?? 1500;
|
|
28503
|
+
const maxConcurrent = options.maxConcurrentProbes ?? 80;
|
|
28504
|
+
if (!networkCidr) {
|
|
28505
|
+
logger?.warn?.("[Discovery] No network CIDR available for TCP port scan");
|
|
28506
|
+
return [];
|
|
28507
|
+
}
|
|
28508
|
+
logger?.log?.(`[Discovery] Starting TCP port 9000 scan on network ${networkCidr}...`);
|
|
28509
|
+
const ipRange = parseCidr(networkCidr);
|
|
28510
|
+
if (!ipRange) {
|
|
28511
|
+
logger?.warn?.(`[Discovery] Invalid CIDR: ${networkCidr}`);
|
|
28512
|
+
return [];
|
|
28513
|
+
}
|
|
28514
|
+
const discovered = [];
|
|
28515
|
+
const ipAddresses = [];
|
|
28516
|
+
for (let ipNum = ipRange.start; ipNum <= ipRange.end && ipNum <= ipRange.start + 254; ipNum++) {
|
|
28517
|
+
ipAddresses.push(ipNumberToString(ipNum));
|
|
28518
|
+
}
|
|
28519
|
+
logger?.log?.(`[Discovery] Scanning ${ipAddresses.length} IPs on port 9000...`);
|
|
28520
|
+
for (let i = 0; i < ipAddresses.length; i += maxConcurrent) {
|
|
28521
|
+
const batch = ipAddresses.slice(i, i + maxConcurrent);
|
|
28522
|
+
const batchResults = await Promise.allSettled(
|
|
28523
|
+
batch.map(async (ip) => {
|
|
28524
|
+
const open = await probeTcpPort(ip, 9e3, timeoutMs);
|
|
28525
|
+
if (open) {
|
|
28526
|
+
logger?.log?.(`[Discovery] Found Baichuan device at ${ip}:9000`);
|
|
28527
|
+
return { host: ip, discoveryMethod: "tcp_port_scan" };
|
|
28528
|
+
}
|
|
28529
|
+
return null;
|
|
28530
|
+
})
|
|
28531
|
+
);
|
|
28532
|
+
for (const result of batchResults) {
|
|
28533
|
+
if (result.status === "fulfilled" && result.value) {
|
|
28534
|
+
discovered.push(result.value);
|
|
28535
|
+
}
|
|
28536
|
+
}
|
|
28537
|
+
}
|
|
28538
|
+
logger?.log?.(`[Discovery] TCP port scan complete. Found ${discovered.length} device(s).`);
|
|
28539
|
+
return discovered;
|
|
28540
|
+
}
|
|
28541
|
+
async function discoverViaOnvif(options) {
|
|
28542
|
+
if (!options.enableOnvifDiscovery) return [];
|
|
28543
|
+
const logger = options.logger;
|
|
28544
|
+
const timeoutMs = options.onvifDiscoveryTimeoutMs ?? 5e3;
|
|
28545
|
+
logger?.log?.(`[Discovery] Starting ONVIF WS-Discovery (${timeoutMs}ms)...`);
|
|
28546
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
28547
|
+
const MULTICAST_ADDR = "239.255.255.250";
|
|
28548
|
+
const MULTICAST_PORT = 3702;
|
|
28549
|
+
const messageId = `uuid:${(0, import_node_crypto3.randomUUID)()}`;
|
|
28550
|
+
const probeMessage = [
|
|
28551
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
28552
|
+
'<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"',
|
|
28553
|
+
' xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"',
|
|
28554
|
+
' xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"',
|
|
28555
|
+
' xmlns:dn="http://www.onvif.org/ver10/network/wsdl">',
|
|
28556
|
+
" <s:Header>",
|
|
28557
|
+
` <a:MessageID>${messageId}</a:MessageID>`,
|
|
28558
|
+
" <a:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>",
|
|
28559
|
+
" <a:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>",
|
|
28560
|
+
" </s:Header>",
|
|
28561
|
+
" <s:Body>",
|
|
28562
|
+
" <d:Probe>",
|
|
28563
|
+
" <d:Types>dn:NetworkVideoTransmitter</d:Types>",
|
|
28564
|
+
" </d:Probe>",
|
|
28565
|
+
" </s:Body>",
|
|
28566
|
+
"</s:Envelope>"
|
|
28567
|
+
].join("\n");
|
|
28568
|
+
return new Promise((resolve) => {
|
|
28569
|
+
const socket = import_node_dgram2.default.createSocket({ type: "udp4", reuseAddr: true });
|
|
28570
|
+
let timeout;
|
|
28571
|
+
socket.on("message", (msg, rinfo) => {
|
|
28572
|
+
try {
|
|
28573
|
+
const xml = msg.toString("utf8");
|
|
28574
|
+
const xaddrsMatch = /<[^:]*:?XAddrs>([^<]+)<\/[^:]*:?XAddrs>/i.exec(xml);
|
|
28575
|
+
const scopesMatch = /<[^:]*:?Scopes>([^<]+)<\/[^:]*:?Scopes>/i.exec(xml);
|
|
28576
|
+
let host = rinfo.address;
|
|
28577
|
+
let httpPort;
|
|
28578
|
+
if (xaddrsMatch?.[1]) {
|
|
28579
|
+
const urls = xaddrsMatch[1].trim().split(/\s+/);
|
|
28580
|
+
for (const url of urls) {
|
|
28581
|
+
try {
|
|
28582
|
+
const parsed = new URL(url);
|
|
28583
|
+
if (parsed.hostname) {
|
|
28584
|
+
host = parsed.hostname;
|
|
28585
|
+
const p = Number.parseInt(parsed.port, 10);
|
|
28586
|
+
if (p && p !== 80) httpPort = p;
|
|
28587
|
+
break;
|
|
28588
|
+
}
|
|
28589
|
+
} catch {
|
|
28590
|
+
}
|
|
28591
|
+
}
|
|
28592
|
+
}
|
|
28593
|
+
if (discovered.has(host)) return;
|
|
28594
|
+
let model;
|
|
28595
|
+
let name;
|
|
28596
|
+
let manufacturer;
|
|
28597
|
+
if (scopesMatch?.[1]) {
|
|
28598
|
+
const scopes = scopesMatch[1].trim().split(/\s+/);
|
|
28599
|
+
for (const scope of scopes) {
|
|
28600
|
+
const hwMatch = /\/hardware\/(.+)$/i.exec(scope);
|
|
28601
|
+
if (hwMatch?.[1]) model = decodeURIComponent(hwMatch[1]);
|
|
28602
|
+
const nameMatch = /\/name\/(.+)$/i.exec(scope);
|
|
28603
|
+
if (nameMatch?.[1]) name = decodeURIComponent(nameMatch[1]);
|
|
28604
|
+
const mfgMatch = /\/manufacturer\/(.+)$/i.exec(scope);
|
|
28605
|
+
if (mfgMatch?.[1]) manufacturer = decodeURIComponent(mfgMatch[1]);
|
|
28606
|
+
}
|
|
28607
|
+
}
|
|
28608
|
+
const allText = `${manufacturer ?? ""} ${model ?? ""} ${xaddrsMatch?.[1] ?? ""}`.toLowerCase();
|
|
28609
|
+
const hasReolinkText = allText.includes("reolink");
|
|
28610
|
+
const hasReolinkModel = /^(rlc|rln|rl[ncb]|e1|cw|cx|duo|trackmix|argus|lumus|go|video doorbell|reolink)/i.test(model ?? "");
|
|
28611
|
+
const isReolink = hasReolinkText || hasReolinkModel;
|
|
28612
|
+
if (!isReolink) {
|
|
28613
|
+
logger?.debug?.(`[Discovery] ONVIF: skipping non-Reolink device at ${host} (${model ?? "unknown"}, manufacturer: ${manufacturer ?? "unknown"})`);
|
|
28614
|
+
return;
|
|
28615
|
+
}
|
|
28616
|
+
logger?.log?.(`[Discovery] ONVIF: found Reolink device at ${host}${model ? ` (${model})` : ""}${name ? ` name="${name}"` : ""}`);
|
|
28617
|
+
const device = {
|
|
28618
|
+
host,
|
|
28619
|
+
discoveryMethod: "onvif"
|
|
28620
|
+
};
|
|
28621
|
+
if (model) device.model = model;
|
|
28622
|
+
if (name && name !== "IPC") {
|
|
28623
|
+
device.name = name;
|
|
28624
|
+
} else if (model) {
|
|
28625
|
+
device.name = model;
|
|
28626
|
+
}
|
|
28627
|
+
if (httpPort) device.httpPort = httpPort;
|
|
28628
|
+
discovered.set(host, device);
|
|
28629
|
+
} catch {
|
|
28630
|
+
}
|
|
28631
|
+
});
|
|
28632
|
+
socket.on("error", (err) => {
|
|
28633
|
+
logger?.warn?.(`[Discovery] ONVIF socket error: ${err.message}`);
|
|
28634
|
+
});
|
|
28635
|
+
socket.bind(0, "0.0.0.0", () => {
|
|
28636
|
+
const buf = Buffer.from(probeMessage, "utf8");
|
|
28637
|
+
socket.send(buf, 0, buf.length, MULTICAST_PORT, MULTICAST_ADDR, (err) => {
|
|
28638
|
+
if (err) {
|
|
28639
|
+
logger?.warn?.(`[Discovery] ONVIF: failed to send probe: ${err.message}`);
|
|
28640
|
+
}
|
|
28641
|
+
});
|
|
28642
|
+
setTimeout(() => {
|
|
28643
|
+
try {
|
|
28644
|
+
socket.send(buf, 0, buf.length, MULTICAST_PORT, MULTICAST_ADDR);
|
|
28645
|
+
} catch {
|
|
28646
|
+
}
|
|
28647
|
+
}, 500);
|
|
28648
|
+
timeout = setTimeout(() => {
|
|
28649
|
+
try {
|
|
28650
|
+
socket.close();
|
|
28651
|
+
} catch {
|
|
28652
|
+
}
|
|
28653
|
+
logger?.log?.(`[Discovery] ONVIF WS-Discovery complete. Found ${discovered.size} device(s).`);
|
|
28654
|
+
resolve(Array.from(discovered.values()));
|
|
28655
|
+
}, timeoutMs);
|
|
28656
|
+
});
|
|
28657
|
+
socket.on("close", () => {
|
|
28658
|
+
if (timeout) clearTimeout(timeout);
|
|
28659
|
+
});
|
|
28660
|
+
});
|
|
28661
|
+
}
|
|
28285
28662
|
async function discoverReolinkDevices(options = {}) {
|
|
28286
28663
|
const logger = options.logger;
|
|
28287
28664
|
logger?.log?.("[Discovery] Starting Reolink device discovery...");
|
|
@@ -28304,10 +28681,26 @@ async function discoverReolinkDevices(options = {}) {
|
|
|
28304
28681
|
results.push(seenDevices.get(key));
|
|
28305
28682
|
}
|
|
28306
28683
|
};
|
|
28307
|
-
const [httpDevices, udpDevices] = await Promise.all([
|
|
28684
|
+
const [httpDevices, udpDevices, tcpDevices, arpDevices, dhcpDevices, onvifDevices] = await Promise.all([
|
|
28308
28685
|
discoverViaHttpScan(options),
|
|
28309
|
-
discoverViaUdpBroadcast(options)
|
|
28686
|
+
discoverViaUdpBroadcast(options),
|
|
28687
|
+
discoverViaTcpPortScan(options),
|
|
28688
|
+
discoverViaArpTable(options),
|
|
28689
|
+
discoverViaDhcpListener(options),
|
|
28690
|
+
discoverViaOnvif(options)
|
|
28310
28691
|
]);
|
|
28692
|
+
for (const device of dhcpDevices) {
|
|
28693
|
+
mergeDevice(device);
|
|
28694
|
+
}
|
|
28695
|
+
for (const device of arpDevices) {
|
|
28696
|
+
mergeDevice(device);
|
|
28697
|
+
}
|
|
28698
|
+
for (const device of tcpDevices) {
|
|
28699
|
+
mergeDevice(device);
|
|
28700
|
+
}
|
|
28701
|
+
for (const device of onvifDevices) {
|
|
28702
|
+
mergeDevice(device);
|
|
28703
|
+
}
|
|
28311
28704
|
for (const device of httpDevices) {
|
|
28312
28705
|
mergeDevice(device);
|
|
28313
28706
|
}
|
|
@@ -28325,51 +28718,18 @@ var AutodiscoveryClient = class {
|
|
|
28325
28718
|
scanTimer = null;
|
|
28326
28719
|
isRunning = false;
|
|
28327
28720
|
currentScanPromise = null;
|
|
28328
|
-
/**
|
|
28329
|
-
* Costruttore del client di autodiscovery.
|
|
28330
|
-
*
|
|
28331
|
-
* @param options - Opzioni di configurazione per il discovery
|
|
28332
|
-
*/
|
|
28333
28721
|
constructor(options = {}) {
|
|
28334
28722
|
this.options = {
|
|
28335
|
-
|
|
28336
|
-
|
|
28723
|
+
...options,
|
|
28724
|
+
scanIntervalMs: options.scanIntervalMs ?? 12e4,
|
|
28337
28725
|
autoStart: options.autoStart ?? false
|
|
28338
28726
|
};
|
|
28339
|
-
if (options.networkCidr !== void 0) {
|
|
28340
|
-
this.options.networkCidr = options.networkCidr;
|
|
28341
|
-
}
|
|
28342
|
-
if (options.username !== void 0) {
|
|
28343
|
-
this.options.username = options.username;
|
|
28344
|
-
}
|
|
28345
|
-
if (options.password !== void 0) {
|
|
28346
|
-
this.options.password = options.password;
|
|
28347
|
-
}
|
|
28348
|
-
if (options.httpProbeTimeoutMs !== void 0) {
|
|
28349
|
-
this.options.httpProbeTimeoutMs = options.httpProbeTimeoutMs;
|
|
28350
|
-
}
|
|
28351
|
-
if (options.maxConcurrentProbes !== void 0) {
|
|
28352
|
-
this.options.maxConcurrentProbes = options.maxConcurrentProbes;
|
|
28353
|
-
}
|
|
28354
|
-
if (options.logger !== void 0) {
|
|
28355
|
-
this.options.logger = options.logger;
|
|
28356
|
-
}
|
|
28357
|
-
if (options.httpPorts !== void 0) {
|
|
28358
|
-
this.options.httpPorts = options.httpPorts;
|
|
28359
|
-
}
|
|
28360
|
-
if (options.discoveryMethod !== void 0) {
|
|
28361
|
-
this.options.discoveryMethod = options.discoveryMethod;
|
|
28362
|
-
}
|
|
28363
|
-
if (options.udpBroadcastTimeoutMs !== void 0) {
|
|
28364
|
-
this.options.udpBroadcastTimeoutMs = options.udpBroadcastTimeoutMs;
|
|
28365
|
-
}
|
|
28366
28727
|
if (this.options.autoStart) {
|
|
28367
28728
|
this.start();
|
|
28368
28729
|
}
|
|
28369
28730
|
}
|
|
28370
28731
|
/**
|
|
28371
|
-
*
|
|
28372
|
-
* Se già in esecuzione, non fa nulla.
|
|
28732
|
+
* Start continuous discovery. If already running, does nothing.
|
|
28373
28733
|
*/
|
|
28374
28734
|
start() {
|
|
28375
28735
|
if (this.isRunning) {
|
|
@@ -28384,8 +28744,7 @@ var AutodiscoveryClient = class {
|
|
|
28384
28744
|
this.scheduleNextScan();
|
|
28385
28745
|
}
|
|
28386
28746
|
/**
|
|
28387
|
-
*
|
|
28388
|
-
* Se non è in esecuzione, non fa nulla.
|
|
28747
|
+
* Stop continuous discovery. If not running, does nothing.
|
|
28389
28748
|
*/
|
|
28390
28749
|
stop() {
|
|
28391
28750
|
if (!this.isRunning) {
|
|
@@ -28400,50 +28759,41 @@ var AutodiscoveryClient = class {
|
|
|
28400
28759
|
this.options.logger?.log?.("[Autodiscovery] Discovery stopped");
|
|
28401
28760
|
}
|
|
28402
28761
|
/**
|
|
28403
|
-
*
|
|
28404
|
-
*
|
|
28405
|
-
* @returns Array di dispositivi discoverati, ordinati per host
|
|
28762
|
+
* Returns the current list of discovered devices, sorted by host IP.
|
|
28406
28763
|
*/
|
|
28407
28764
|
getDiscoveredDevices() {
|
|
28408
|
-
return Array.from(this.discoveredDevices.values()).sort(
|
|
28409
|
-
|
|
28410
|
-
|
|
28765
|
+
return Array.from(this.discoveredDevices.values()).sort(
|
|
28766
|
+
(a, b) => a.host.localeCompare(b.host)
|
|
28767
|
+
);
|
|
28411
28768
|
}
|
|
28412
28769
|
/**
|
|
28413
|
-
*
|
|
28414
|
-
*
|
|
28415
|
-
* @returns Numero di dispositivi discoverati
|
|
28770
|
+
* Returns the number of currently discovered devices.
|
|
28416
28771
|
*/
|
|
28417
28772
|
getDeviceCount() {
|
|
28418
28773
|
return this.discoveredDevices.size;
|
|
28419
28774
|
}
|
|
28420
28775
|
/**
|
|
28421
|
-
*
|
|
28422
|
-
*
|
|
28423
|
-
* @returns `true` se il discovery è in esecuzione, `false` altrimenti
|
|
28776
|
+
* Returns whether continuous discovery is currently running.
|
|
28424
28777
|
*/
|
|
28425
28778
|
isActive() {
|
|
28426
28779
|
return this.isRunning;
|
|
28427
28780
|
}
|
|
28428
28781
|
/**
|
|
28429
|
-
*
|
|
28430
|
-
*
|
|
28431
|
-
*
|
|
28432
|
-
* @returns Promise che si risolve quando lo scan è completato
|
|
28782
|
+
* Force an immediate scan (doesn't wait for the scheduled interval).
|
|
28783
|
+
* If a scan is already in progress, waits for it to complete.
|
|
28433
28784
|
*/
|
|
28434
28785
|
async scanNow() {
|
|
28435
28786
|
if (this.currentScanPromise) {
|
|
28436
|
-
this.options.logger?.log?.(
|
|
28787
|
+
this.options.logger?.log?.(
|
|
28788
|
+
"[Autodiscovery] Scan already in progress, waiting for completion..."
|
|
28789
|
+
);
|
|
28437
28790
|
await this.currentScanPromise;
|
|
28438
28791
|
return;
|
|
28439
28792
|
}
|
|
28440
28793
|
await this.performScan();
|
|
28441
28794
|
}
|
|
28442
28795
|
/**
|
|
28443
|
-
*
|
|
28444
|
-
*
|
|
28445
|
-
* @param host - Indirizzo IP del dispositivo da rimuovere
|
|
28446
|
-
* @returns `true` se il dispositivo è stato rimosso, `false` se non era presente
|
|
28796
|
+
* Remove a device from the discovered list.
|
|
28447
28797
|
*/
|
|
28448
28798
|
removeDevice(host) {
|
|
28449
28799
|
const removed = this.discoveredDevices.delete(host);
|
|
@@ -28453,59 +28803,20 @@ var AutodiscoveryClient = class {
|
|
|
28453
28803
|
return removed;
|
|
28454
28804
|
}
|
|
28455
28805
|
/**
|
|
28456
|
-
*
|
|
28806
|
+
* Clear all discovered devices.
|
|
28457
28807
|
*/
|
|
28458
28808
|
clearDevices() {
|
|
28459
28809
|
const count = this.discoveredDevices.size;
|
|
28460
28810
|
this.discoveredDevices.clear();
|
|
28461
|
-
this.options.logger?.log?.(
|
|
28811
|
+
this.options.logger?.log?.(
|
|
28812
|
+
`[Autodiscovery] Removed ${count} device(s) from list`
|
|
28813
|
+
);
|
|
28462
28814
|
}
|
|
28463
|
-
/**
|
|
28464
|
-
* Esegue un singolo scan della rete.
|
|
28465
|
-
*/
|
|
28466
28815
|
async performScan() {
|
|
28467
28816
|
const scanPromise = (async () => {
|
|
28468
28817
|
try {
|
|
28469
28818
|
this.options.logger?.log?.("[Autodiscovery] Starting scan...");
|
|
28470
|
-
const
|
|
28471
|
-
const discoveryOptions = {
|
|
28472
|
-
enableHttpScanning: discoveryMethod === "http" || discoveryMethod === "both",
|
|
28473
|
-
enableUdpDiscovery: discoveryMethod === "udp" || discoveryMethod === "both"
|
|
28474
|
-
};
|
|
28475
|
-
if (this.options.networkCidr !== void 0) {
|
|
28476
|
-
discoveryOptions.networkCidr = this.options.networkCidr;
|
|
28477
|
-
}
|
|
28478
|
-
if (this.options.username !== void 0) {
|
|
28479
|
-
discoveryOptions.username = this.options.username;
|
|
28480
|
-
}
|
|
28481
|
-
if (this.options.password !== void 0) {
|
|
28482
|
-
discoveryOptions.password = this.options.password;
|
|
28483
|
-
}
|
|
28484
|
-
if (this.options.httpProbeTimeoutMs !== void 0) {
|
|
28485
|
-
discoveryOptions.httpProbeTimeoutMs = this.options.httpProbeTimeoutMs;
|
|
28486
|
-
}
|
|
28487
|
-
if (this.options.maxConcurrentProbes !== void 0) {
|
|
28488
|
-
discoveryOptions.maxConcurrentProbes = this.options.maxConcurrentProbes;
|
|
28489
|
-
}
|
|
28490
|
-
if (this.options.logger !== void 0) {
|
|
28491
|
-
discoveryOptions.logger = this.options.logger;
|
|
28492
|
-
}
|
|
28493
|
-
if (this.options.httpPorts !== void 0) {
|
|
28494
|
-
discoveryOptions.httpPorts = this.options.httpPorts;
|
|
28495
|
-
}
|
|
28496
|
-
if (this.options.udpBroadcastTimeoutMs !== void 0) {
|
|
28497
|
-
discoveryOptions.udpBroadcastTimeoutMs = this.options.udpBroadcastTimeoutMs;
|
|
28498
|
-
}
|
|
28499
|
-
let discovered = [];
|
|
28500
|
-
if (discoveryMethod === "http" || discoveryMethod === "both") {
|
|
28501
|
-
const httpDevices = await discoverViaHttpScan(discoveryOptions);
|
|
28502
|
-
discovered.push(...httpDevices);
|
|
28503
|
-
}
|
|
28504
|
-
if (discoveryMethod === "udp" || discoveryMethod === "both") {
|
|
28505
|
-
const udpDevices = await discoverViaUdpBroadcast(discoveryOptions);
|
|
28506
|
-
discovered.push(...udpDevices);
|
|
28507
|
-
}
|
|
28508
|
-
const beforeCount = this.discoveredDevices.size;
|
|
28819
|
+
const discovered = await discoverReolinkDevices(this.options);
|
|
28509
28820
|
const newDevices = [];
|
|
28510
28821
|
const updatedDevices = [];
|
|
28511
28822
|
for (const device of discovered) {
|
|
@@ -28520,38 +28831,35 @@ var AutodiscoveryClient = class {
|
|
|
28520
28831
|
newDevices.push(device);
|
|
28521
28832
|
}
|
|
28522
28833
|
}
|
|
28523
|
-
const afterCount = this.discoveredDevices.size;
|
|
28524
28834
|
this.options.logger?.log?.(
|
|
28525
|
-
`[Autodiscovery] Scan completed: ${newDevices.length} new, ${updatedDevices.length} updated, total: ${
|
|
28835
|
+
`[Autodiscovery] Scan completed: ${newDevices.length} new, ${updatedDevices.length} updated, total: ${this.discoveredDevices.size}`
|
|
28526
28836
|
);
|
|
28527
28837
|
for (const device of newDevices) {
|
|
28528
|
-
const details = [];
|
|
28529
|
-
if (device.model) details.push(`Model: ${device.model}`);
|
|
28530
|
-
if (device.name) details.push(`Name: ${device.name}`);
|
|
28531
|
-
if (device.uid) details.push(`UID: ${device.uid}`);
|
|
28532
|
-
if (device.firmwareVersion) details.push(`Firmware: ${device.firmwareVersion}`);
|
|
28533
|
-
if (device.httpPort) details.push(`HTTP Port: ${device.httpPort}`);
|
|
28534
|
-
if (device.httpsPort) details.push(`HTTPS Port: ${device.httpsPort}`);
|
|
28535
|
-
details.push(`Discovery Method: ${device.discoveryMethod}`);
|
|
28536
|
-
if (device.supportsHttps !== void 0) details.push(`HTTPS: ${device.supportsHttps}`);
|
|
28537
|
-
if (device.httpAccessible !== void 0) details.push(`HTTP Accessible: ${device.httpAccessible}`);
|
|
28538
28838
|
this.options.logger?.log?.(
|
|
28539
|
-
`[Autodiscovery]
|
|
28839
|
+
`[Autodiscovery] NEW DEVICE - ${device.host} | ${device.model ?? "unknown"} | ${device.name ?? ""} | via ${device.discoveryMethod}`
|
|
28540
28840
|
);
|
|
28841
|
+
try {
|
|
28842
|
+
this.options.onDeviceDiscovered?.(device);
|
|
28843
|
+
} catch {
|
|
28844
|
+
}
|
|
28845
|
+
}
|
|
28846
|
+
for (const device of updatedDevices) {
|
|
28847
|
+
try {
|
|
28848
|
+
this.options.onDeviceUpdated?.(device);
|
|
28849
|
+
} catch {
|
|
28850
|
+
}
|
|
28541
28851
|
}
|
|
28542
28852
|
} catch (error) {
|
|
28543
28853
|
const msg = error instanceof Error ? error.message : String(error);
|
|
28544
|
-
this.options.logger?.error?.(
|
|
28854
|
+
this.options.logger?.error?.(
|
|
28855
|
+
`[Autodiscovery] Error during scan: ${msg}`
|
|
28856
|
+
);
|
|
28545
28857
|
}
|
|
28546
28858
|
})();
|
|
28547
28859
|
this.currentScanPromise = scanPromise;
|
|
28548
28860
|
await scanPromise;
|
|
28549
28861
|
this.currentScanPromise = null;
|
|
28550
28862
|
}
|
|
28551
|
-
/**
|
|
28552
|
-
* Unisce le informazioni di un dispositivo esistente con quelle di un nuovo scan.
|
|
28553
|
-
* Restituisce il dispositivo aggiornato se ci sono state modifiche, altrimenti `null`.
|
|
28554
|
-
*/
|
|
28555
28863
|
mergeDeviceInfo(existing, updated) {
|
|
28556
28864
|
let hasChanges = false;
|
|
28557
28865
|
if (!existing.model && updated.model) {
|
|
@@ -28588,13 +28896,8 @@ var AutodiscoveryClient = class {
|
|
|
28588
28896
|
}
|
|
28589
28897
|
return hasChanges ? existing : null;
|
|
28590
28898
|
}
|
|
28591
|
-
/**
|
|
28592
|
-
* Programma il prossimo scan.
|
|
28593
|
-
*/
|
|
28594
28899
|
scheduleNextScan() {
|
|
28595
|
-
if (!this.isRunning)
|
|
28596
|
-
return;
|
|
28597
|
-
}
|
|
28900
|
+
if (!this.isRunning) return;
|
|
28598
28901
|
this.scanTimer = setTimeout(() => {
|
|
28599
28902
|
this.scanTimer = null;
|
|
28600
28903
|
if (this.isRunning) {
|
|
@@ -28633,7 +28936,7 @@ function decideVideoclipTranscodeMode(headers, forceMode) {
|
|
|
28633
28936
|
};
|
|
28634
28937
|
}
|
|
28635
28938
|
const ua = (clientInfo.userAgent ?? "").toLowerCase();
|
|
28636
|
-
const
|
|
28939
|
+
const platform2 = (clientInfo.secChUaPlatform ?? "").toLowerCase().replace(/"/g, "");
|
|
28637
28940
|
const isIos = /iphone|ipad|ipod/.test(ua);
|
|
28638
28941
|
if (isIos) {
|
|
28639
28942
|
return {
|
|
@@ -28650,7 +28953,7 @@ function decideVideoclipTranscodeMode(headers, forceMode) {
|
|
|
28650
28953
|
clientInfo
|
|
28651
28954
|
};
|
|
28652
28955
|
}
|
|
28653
|
-
const isAndroid = ua.includes("android") ||
|
|
28956
|
+
const isAndroid = ua.includes("android") || platform2 === "android";
|
|
28654
28957
|
if (isAndroid) {
|
|
28655
28958
|
return {
|
|
28656
28959
|
mode: "transcode-h264",
|
|
@@ -28659,7 +28962,7 @@ function decideVideoclipTranscodeMode(headers, forceMode) {
|
|
|
28659
28962
|
};
|
|
28660
28963
|
}
|
|
28661
28964
|
const isChromium = ua.includes("chrome") || ua.includes("edg");
|
|
28662
|
-
const isMac = ua.includes("mac os") ||
|
|
28965
|
+
const isMac = ua.includes("mac os") || platform2 === "macos";
|
|
28663
28966
|
if (isChromium && !isMac) {
|
|
28664
28967
|
return {
|
|
28665
28968
|
mode: "transcode-h264",
|
|
@@ -28686,7 +28989,7 @@ init_recordingFileName();
|
|
|
28686
28989
|
|
|
28687
28990
|
// src/reolink/baichuan/endpoints-server.ts
|
|
28688
28991
|
var import_node_http = __toESM(require("http"), 1);
|
|
28689
|
-
var
|
|
28992
|
+
var import_node_child_process5 = require("child_process");
|
|
28690
28993
|
function parseIntParam(v, def) {
|
|
28691
28994
|
if (v == null) return def;
|
|
28692
28995
|
const n = Number.parseInt(v, 10);
|
|
@@ -28925,7 +29228,7 @@ function createBaichuanEndpointsServer(opts) {
|
|
|
28925
29228
|
"Cache-Control": "no-cache",
|
|
28926
29229
|
Connection: "close"
|
|
28927
29230
|
});
|
|
28928
|
-
const ff2 = (0,
|
|
29231
|
+
const ff2 = (0, import_node_child_process5.spawn)("ffmpeg", [
|
|
28929
29232
|
"-hide_banner",
|
|
28930
29233
|
"-loglevel",
|
|
28931
29234
|
"error",
|
|
@@ -28958,7 +29261,7 @@ function createBaichuanEndpointsServer(opts) {
|
|
|
28958
29261
|
);
|
|
28959
29262
|
res.setHeader("Cache-Control", "no-cache");
|
|
28960
29263
|
res.setHeader("Connection", "close");
|
|
28961
|
-
const ff = (0,
|
|
29264
|
+
const ff = (0, import_node_child_process5.spawn)("ffmpeg", [
|
|
28962
29265
|
"-hide_banner",
|
|
28963
29266
|
"-loglevel",
|
|
28964
29267
|
"error",
|
|
@@ -29069,7 +29372,7 @@ init_urls();
|
|
|
29069
29372
|
|
|
29070
29373
|
// src/rtsp/server.ts
|
|
29071
29374
|
var import_node_http2 = __toESM(require("http"), 1);
|
|
29072
|
-
var
|
|
29375
|
+
var import_node_child_process6 = require("child_process");
|
|
29073
29376
|
init_urls();
|
|
29074
29377
|
function createRtspProxyServer(opts) {
|
|
29075
29378
|
return import_node_http2.default.createServer((req, res) => {
|
|
@@ -29110,7 +29413,7 @@ function createRtspProxyServer(opts) {
|
|
|
29110
29413
|
Connection: "close"
|
|
29111
29414
|
});
|
|
29112
29415
|
const rtspTransport = opts.rtspTransport ?? "tcp";
|
|
29113
|
-
const ff = (0,
|
|
29416
|
+
const ff = (0, import_node_child_process6.spawn)("ffmpeg", [
|
|
29114
29417
|
"-hide_banner",
|
|
29115
29418
|
"-loglevel",
|
|
29116
29419
|
"error",
|
|
@@ -29140,7 +29443,7 @@ function createRtspProxyServer(opts) {
|
|
|
29140
29443
|
}
|
|
29141
29444
|
|
|
29142
29445
|
// src/rfc/rfc4571.ts
|
|
29143
|
-
var
|
|
29446
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
29144
29447
|
function buildRfc4571Sdp(video, audio) {
|
|
29145
29448
|
let out = "v=0\r\n";
|
|
29146
29449
|
out += "o=- 0 0 IN IP4 0.0.0.0\r\n";
|
|
@@ -29413,12 +29716,12 @@ function parseAdtsHeader(adtsFrame) {
|
|
|
29413
29716
|
var RtpWriter = class {
|
|
29414
29717
|
constructor(payloadType) {
|
|
29415
29718
|
this.payloadType = payloadType;
|
|
29416
|
-
this.seq =
|
|
29417
|
-
this.timestamp =
|
|
29719
|
+
this.seq = import_node_crypto4.default.randomBytes(2).readUInt16BE(0);
|
|
29720
|
+
this.timestamp = import_node_crypto4.default.randomBytes(4).readUInt32BE(0);
|
|
29418
29721
|
}
|
|
29419
29722
|
seq = 0;
|
|
29420
29723
|
timestamp = 0;
|
|
29421
|
-
ssrc =
|
|
29724
|
+
ssrc = import_node_crypto4.default.randomBytes(4).readUInt32BE(0);
|
|
29422
29725
|
setTimestamp(ts) {
|
|
29423
29726
|
this.timestamp = ts >>> 0;
|
|
29424
29727
|
}
|
|
@@ -29982,8 +30285,8 @@ var import_node_net2 = __toESM(require("net"), 1);
|
|
|
29982
30285
|
init_BaichuanVideoStream();
|
|
29983
30286
|
|
|
29984
30287
|
// src/multifocal/compositeStream.ts
|
|
29985
|
-
var
|
|
29986
|
-
var
|
|
30288
|
+
var import_node_child_process7 = require("child_process");
|
|
30289
|
+
var import_node_crypto5 = require("crypto");
|
|
29987
30290
|
var import_node_events5 = require("events");
|
|
29988
30291
|
function calculateOverlayPosition(position, mainWidth, mainHeight, pipWidth, pipHeight, margin) {
|
|
29989
30292
|
const pipW = Math.floor(pipWidth);
|
|
@@ -30094,7 +30397,7 @@ var CompositeStream = class extends import_node_events5.EventEmitter {
|
|
|
30094
30397
|
if (!buf?.length) return;
|
|
30095
30398
|
const head = buf.subarray(0, Math.min(12, buf.length)).toString("hex");
|
|
30096
30399
|
const slice = buf.subarray(0, Math.min(256, buf.length));
|
|
30097
|
-
const sha1 = (0,
|
|
30400
|
+
const sha1 = (0, import_node_crypto5.createHash)("sha1").update(slice).digest("hex");
|
|
30098
30401
|
return { len: buf.length, headHex: head, sha1_256: sha1 };
|
|
30099
30402
|
}
|
|
30100
30403
|
async primeForFfmpeg(gen, timeoutMs, requireKeyframe) {
|
|
@@ -30323,7 +30626,7 @@ var CompositeStream = class extends import_node_events5.EventEmitter {
|
|
|
30323
30626
|
"pipe:1"
|
|
30324
30627
|
];
|
|
30325
30628
|
this.logger.log?.(`[CompositeStream] Starting ffmpeg (rtsp inputs): ${ffmpegArgs.join(" ")}`);
|
|
30326
|
-
this.ffmpegProcess = (0,
|
|
30629
|
+
this.ffmpegProcess = (0, import_node_child_process7.spawn)("ffmpeg", ffmpegArgs, {
|
|
30327
30630
|
stdio: ["ignore", "pipe", "pipe"]
|
|
30328
30631
|
});
|
|
30329
30632
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -30438,7 +30741,7 @@ var CompositeStream = class extends import_node_events5.EventEmitter {
|
|
|
30438
30741
|
this.logger.log?.(
|
|
30439
30742
|
`[CompositeStream] Starting ffmpeg: ${ffmpegArgs.join(" ")}`
|
|
30440
30743
|
);
|
|
30441
|
-
this.ffmpegProcess = (0,
|
|
30744
|
+
this.ffmpegProcess = (0, import_node_child_process7.spawn)("ffmpeg", ffmpegArgs, {
|
|
30442
30745
|
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
30443
30746
|
});
|
|
30444
30747
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -32167,7 +32470,7 @@ async function createRfc4571TcpServerForReplay(options) {
|
|
|
32167
32470
|
|
|
32168
32471
|
// src/rfc/replay-http-server.ts
|
|
32169
32472
|
var import_node_http3 = __toESM(require("http"), 1);
|
|
32170
|
-
var
|
|
32473
|
+
var import_node_child_process8 = require("child_process");
|
|
32171
32474
|
var import_node_stream2 = require("stream");
|
|
32172
32475
|
async function createReplayHttpServer(options) {
|
|
32173
32476
|
const {
|
|
@@ -32321,7 +32624,7 @@ async function createReplayHttpServer(options) {
|
|
|
32321
32624
|
"pipe:1"
|
|
32322
32625
|
];
|
|
32323
32626
|
log(`spawning ffmpeg: ${ffmpegPath} ${ffmpegArgs.join(" ")}`);
|
|
32324
|
-
ffmpegProcess = (0,
|
|
32627
|
+
ffmpegProcess = (0, import_node_child_process8.spawn)(ffmpegPath, ffmpegArgs, {
|
|
32325
32628
|
stdio: ["pipe", "pipe", "pipe"]
|
|
32326
32629
|
});
|
|
32327
32630
|
ffmpegProcess.stdout?.pipe(outputStream).pipe(res);
|
|
@@ -32423,7 +32726,7 @@ init_BaichuanVideoStream();
|
|
|
32423
32726
|
|
|
32424
32727
|
// src/baichuan/stream/BaichuanHttpStreamServer.ts
|
|
32425
32728
|
var import_node_events6 = require("events");
|
|
32426
|
-
var
|
|
32729
|
+
var import_node_child_process9 = require("child_process");
|
|
32427
32730
|
var http4 = __toESM(require("http"), 1);
|
|
32428
32731
|
var NAL_START_CODE_4B4 = Buffer.from([0, 0, 0, 1]);
|
|
32429
32732
|
var NAL_START_CODE_3B3 = Buffer.from([0, 0, 1]);
|
|
@@ -32533,7 +32836,7 @@ var BaichuanHttpStreamServer = class extends import_node_events6.EventEmitter {
|
|
|
32533
32836
|
this.httpServer.on("error", reject);
|
|
32534
32837
|
});
|
|
32535
32838
|
this.logger.info(`[BaichuanHttpStreamServer] Starting ffmpeg for H.264 -> MPEG-TS conversion...`);
|
|
32536
|
-
const ffmpeg = (0,
|
|
32839
|
+
const ffmpeg = (0, import_node_child_process9.spawn)("ffmpeg", [
|
|
32537
32840
|
"-hide_banner",
|
|
32538
32841
|
// ffmpeg warnings often include non-fatal decode messages (e.g. decode_slice_header),
|
|
32539
32842
|
// which we don't want to treat as application errors.
|
|
@@ -32741,7 +33044,7 @@ var http5 = __toESM(require("http"), 1);
|
|
|
32741
33044
|
|
|
32742
33045
|
// src/baichuan/stream/MjpegTransformer.ts
|
|
32743
33046
|
var import_node_events7 = require("events");
|
|
32744
|
-
var
|
|
33047
|
+
var import_node_child_process10 = require("child_process");
|
|
32745
33048
|
var JPEG_SOI = Buffer.from([255, 216]);
|
|
32746
33049
|
var JPEG_EOI = Buffer.from([255, 217]);
|
|
32747
33050
|
var MjpegTransformer = class extends import_node_events7.EventEmitter {
|
|
@@ -32802,7 +33105,7 @@ var MjpegTransformer = class extends import_node_events7.EventEmitter {
|
|
|
32802
33105
|
"pipe:1"
|
|
32803
33106
|
);
|
|
32804
33107
|
this.log("debug", `Starting FFmpeg with args: ${args.join(" ")}`);
|
|
32805
|
-
this.ffmpeg = (0,
|
|
33108
|
+
this.ffmpeg = (0, import_node_child_process10.spawn)("ffmpeg", args, {
|
|
32806
33109
|
stdio: ["pipe", "pipe", "pipe"]
|
|
32807
33110
|
});
|
|
32808
33111
|
this.ffmpeg.stdout.on("data", (data) => {
|
|
@@ -34168,7 +34471,7 @@ var import_node_fs = __toESM(require("fs"), 1);
|
|
|
34168
34471
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
34169
34472
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
34170
34473
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
34171
|
-
var
|
|
34474
|
+
var import_node_child_process11 = require("child_process");
|
|
34172
34475
|
init_BcMediaAnnexBDecoder();
|
|
34173
34476
|
init_H264Converter();
|
|
34174
34477
|
init_H265Converter();
|
|
@@ -34638,7 +34941,7 @@ var BaichuanHlsServer = class extends import_node_events10.EventEmitter {
|
|
|
34638
34941
|
this.segmentPattern,
|
|
34639
34942
|
this.playlistPath
|
|
34640
34943
|
);
|
|
34641
|
-
const p = (0,
|
|
34944
|
+
const p = (0, import_node_child_process11.spawn)(this.ffmpegPath, args, {
|
|
34642
34945
|
stdio: ["pipe", "ignore", "pipe"]
|
|
34643
34946
|
});
|
|
34644
34947
|
p.on("error", (err) => {
|
|
@@ -34736,8 +35039,8 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
|
34736
35039
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
34737
35040
|
return new Promise((resolve) => {
|
|
34738
35041
|
const { exec } = require("child_process");
|
|
34739
|
-
const
|
|
34740
|
-
const pingCmd =
|
|
35042
|
+
const platform2 = process.platform;
|
|
35043
|
+
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
34741
35044
|
// macOS: -W is in milliseconds (Linux: seconds)
|
|
34742
35045
|
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
34743
35046
|
) : (
|
|
@@ -35245,8 +35548,8 @@ async function autoDetectDeviceType(inputs) {
|
|
|
35245
35548
|
|
|
35246
35549
|
// src/multifocal/compositeRtspServer.ts
|
|
35247
35550
|
var import_node_events11 = require("events");
|
|
35248
|
-
var
|
|
35249
|
-
var
|
|
35551
|
+
var import_node_child_process12 = require("child_process");
|
|
35552
|
+
var net4 = __toESM(require("net"), 1);
|
|
35250
35553
|
var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
35251
35554
|
options;
|
|
35252
35555
|
compositeStream = null;
|
|
@@ -35313,7 +35616,7 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
35313
35616
|
const width = widerStreamInfo?.width ?? 1920;
|
|
35314
35617
|
const height = widerStreamInfo?.height ?? 1080;
|
|
35315
35618
|
const fps = widerStreamInfo?.frameRate ?? 25;
|
|
35316
|
-
this.rtspServer =
|
|
35619
|
+
this.rtspServer = net4.createServer((socket) => {
|
|
35317
35620
|
this.handleRtspConnection(socket);
|
|
35318
35621
|
});
|
|
35319
35622
|
await new Promise((resolve, reject) => {
|
|
@@ -35352,7 +35655,7 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
35352
35655
|
this.logger.log?.(
|
|
35353
35656
|
`[CompositeRtspServer] Starting ffmpeg RTSP server: ${ffmpegArgs.join(" ")}`
|
|
35354
35657
|
);
|
|
35355
|
-
this.ffmpegProcess = (0,
|
|
35658
|
+
this.ffmpegProcess = (0, import_node_child_process12.spawn)("ffmpeg", ffmpegArgs, {
|
|
35356
35659
|
stdio: ["pipe", "pipe", "pipe"]
|
|
35357
35660
|
});
|
|
35358
35661
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -35656,7 +35959,11 @@ var CompositeRtspServer = class extends import_node_events11.EventEmitter {
|
|
|
35656
35959
|
detectIosClient,
|
|
35657
35960
|
detectVideoCodecFromNal,
|
|
35658
35961
|
discoverReolinkDevices,
|
|
35962
|
+
discoverViaArpTable,
|
|
35963
|
+
discoverViaDhcpListener,
|
|
35659
35964
|
discoverViaHttpScan,
|
|
35965
|
+
discoverViaOnvif,
|
|
35966
|
+
discoverViaTcpPortScan,
|
|
35660
35967
|
discoverViaUdpBroadcast,
|
|
35661
35968
|
discoverViaUdpDirect,
|
|
35662
35969
|
encodeHeader,
|