@apocaliss92/nodelink-js 0.5.2 → 0.6.1
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-7HSTETZR.js → chunk-EAHRVNEX.js} +61 -11
- package/dist/chunk-EAHRVNEX.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +60 -10
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +132 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +73 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7HSTETZR.js.map +0 -1
package/dist/cli/rtsp-server.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -9047,12 +9047,28 @@ async function getServerBinding(uid, options = {}) {
|
|
|
9047
9047
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
9048
9048
|
const logger = options.logger;
|
|
9049
9049
|
if (typeof fetchImpl !== "function") {
|
|
9050
|
-
logger?.
|
|
9050
|
+
logger?.log?.(
|
|
9051
9051
|
`[server-binding] global fetch unavailable; skipping cloud lookup`
|
|
9052
9052
|
);
|
|
9053
9053
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9054
9054
|
return void 0;
|
|
9055
9055
|
}
|
|
9056
|
+
try {
|
|
9057
|
+
const apiHostname = new URL(baseUrl).hostname;
|
|
9058
|
+
const dns2 = await import("dns/promises");
|
|
9059
|
+
const answers = await dns2.lookup(apiHostname, { family: 4, all: true });
|
|
9060
|
+
const sinkholed = answers.find(
|
|
9061
|
+
(a) => a.address?.startsWith("127.") || a.address === "0.0.0.0" || a.address?.startsWith("10.") || a.address?.startsWith("192.168.") || /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(a.address ?? "")
|
|
9062
|
+
);
|
|
9063
|
+
if (sinkholed) {
|
|
9064
|
+
logger?.log?.(
|
|
9065
|
+
`[server-binding] ${uid}: DNS for ${apiHostname} resolves to ${sinkholed.address} (sinkhole / /etc/hosts override). Cloud directory unreachable \u2014 falling back to the 22-hostname P2P sweep. Whitelist ${apiHostname} to enable.`
|
|
9066
|
+
);
|
|
9067
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9068
|
+
return void 0;
|
|
9069
|
+
}
|
|
9070
|
+
} catch {
|
|
9071
|
+
}
|
|
9056
9072
|
const url = `${baseUrl}/devices/${encodeURIComponent(uid)}/server-binding?language=${encodeURIComponent(language)}`;
|
|
9057
9073
|
const controller = new AbortController();
|
|
9058
9074
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -9063,8 +9079,8 @@ async function getServerBinding(uid, options = {}) {
|
|
|
9063
9079
|
headers: { Accept: "application/json" }
|
|
9064
9080
|
});
|
|
9065
9081
|
if (!res.ok) {
|
|
9066
|
-
logger?.
|
|
9067
|
-
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText}`
|
|
9082
|
+
logger?.log?.(
|
|
9083
|
+
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText} from ${url}`
|
|
9068
9084
|
);
|
|
9069
9085
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9070
9086
|
return void 0;
|
|
@@ -9072,8 +9088,15 @@ async function getServerBinding(uid, options = {}) {
|
|
|
9072
9088
|
const json = await res.json();
|
|
9073
9089
|
const parsed = parseServerBindingResponse(json);
|
|
9074
9090
|
if (!parsed) {
|
|
9075
|
-
logger?.
|
|
9076
|
-
`[server-binding] ${uid}: response shape did not match expectations`
|
|
9091
|
+
logger?.log?.(
|
|
9092
|
+
`[server-binding] ${uid}: response shape did not match expectations (Reolink schema change?)`
|
|
9093
|
+
);
|
|
9094
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9095
|
+
return void 0;
|
|
9096
|
+
}
|
|
9097
|
+
if (parsed.availableZones.length === 0) {
|
|
9098
|
+
logger?.log?.(
|
|
9099
|
+
`[server-binding] ${uid}: cloud returned 0 zones \u2014 UID not registered with Reolink cloud (or wrong region)`
|
|
9077
9100
|
);
|
|
9078
9101
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9079
9102
|
return void 0;
|
|
@@ -9092,9 +9115,23 @@ async function getServerBinding(uid, options = {}) {
|
|
|
9092
9115
|
);
|
|
9093
9116
|
return parsed;
|
|
9094
9117
|
} catch (e) {
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
)
|
|
9118
|
+
const msg = e?.message ?? String(e);
|
|
9119
|
+
const errName = e?.name;
|
|
9120
|
+
if (errName === "AbortError" || msg.includes("aborted")) {
|
|
9121
|
+
logger?.log?.(
|
|
9122
|
+
`[server-binding] ${uid}: timed out after ${timeoutMs}ms (cloud unreachable)`
|
|
9123
|
+
);
|
|
9124
|
+
} else if (msg.includes("ENOTFOUND") || msg.includes("EAI_AGAIN")) {
|
|
9125
|
+
logger?.log?.(
|
|
9126
|
+
`[server-binding] ${uid}: DNS failed (${msg}) \u2014 apis.reolink.com may be blocked at resolver`
|
|
9127
|
+
);
|
|
9128
|
+
} else if (msg.includes("ECONNREFUSED") || msg.includes("EHOSTUNREACH") || msg.includes("ENETUNREACH")) {
|
|
9129
|
+
logger?.log?.(
|
|
9130
|
+
`[server-binding] ${uid}: network unreachable (${msg}) \u2014 cloud port blocked`
|
|
9131
|
+
);
|
|
9132
|
+
} else {
|
|
9133
|
+
logger?.log?.(`[server-binding] ${uid}: fetch failed \u2014 ${msg}`);
|
|
9134
|
+
}
|
|
9098
9135
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9099
9136
|
return void 0;
|
|
9100
9137
|
} finally {
|
|
@@ -9530,12 +9567,19 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9530
9567
|
const tid = (Math.floor(Math.random() * 2147483647) | 0) >>> 0;
|
|
9531
9568
|
const xml = buildC2mQ({ uid });
|
|
9532
9569
|
const pkt = encodeDiscoveryPacket(tid, xml);
|
|
9570
|
+
const counters = { sentBytes: 0, rxBytes: 0 };
|
|
9533
9571
|
return await new Promise((resolve, reject) => {
|
|
9534
9572
|
const deadline = setTimeout(() => {
|
|
9535
9573
|
cleanup();
|
|
9536
|
-
|
|
9574
|
+
const err = new Error(
|
|
9575
|
+
`P2P UID lookup timeout (${dest.host}:${dest.port}) \u2014 sent=${counters.sentBytes}B rx=${counters.rxBytes}B`
|
|
9576
|
+
);
|
|
9577
|
+
err.sentBytes = counters.sentBytes;
|
|
9578
|
+
err.rxBytes = counters.rxBytes;
|
|
9579
|
+
reject(err);
|
|
9537
9580
|
}, timeoutMs);
|
|
9538
9581
|
const onMsg = (msg) => {
|
|
9582
|
+
counters.rxBytes += msg.length;
|
|
9539
9583
|
try {
|
|
9540
9584
|
const p = decodeBcUdpPacket(msg);
|
|
9541
9585
|
if (p.kind !== "discovery") return;
|
|
@@ -9543,13 +9587,19 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9543
9587
|
const qr = parseM2cQr(p.xml);
|
|
9544
9588
|
if (!qr?.reg || !qr?.relay) return;
|
|
9545
9589
|
cleanup();
|
|
9546
|
-
resolve({
|
|
9590
|
+
resolve({
|
|
9591
|
+
reg: qr.reg,
|
|
9592
|
+
relay: qr.relay,
|
|
9593
|
+
sentBytes: counters.sentBytes,
|
|
9594
|
+
rxBytes: counters.rxBytes
|
|
9595
|
+
});
|
|
9547
9596
|
} catch {
|
|
9548
9597
|
}
|
|
9549
9598
|
};
|
|
9550
9599
|
const send = () => {
|
|
9551
9600
|
try {
|
|
9552
9601
|
sock.send(pkt, dest.port, dest.host);
|
|
9602
|
+
counters.sentBytes += pkt.length;
|
|
9553
9603
|
} catch {
|
|
9554
9604
|
}
|
|
9555
9605
|
};
|
|
@@ -35165,10 +35215,18 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35165
35215
|
}
|
|
35166
35216
|
}
|
|
35167
35217
|
async startFfmpegCompositionFromRtspUrls(mainWidth, mainHeight, pipWidth, pipHeight, position, widerRtspUrl, teleRtspUrl, rtspTransport) {
|
|
35218
|
+
const videoEncoder = this.options.videoEncoder ?? "libx264";
|
|
35219
|
+
const isX264 = videoEncoder === "libx264";
|
|
35220
|
+
const encoderPreset = this.options.encoderPreset ?? "ultrafast";
|
|
35221
|
+
const crf = this.options.crf ?? 23;
|
|
35222
|
+
const gopSeconds = this.options.gopSeconds ?? 1;
|
|
35223
|
+
const assumedFps = 30;
|
|
35224
|
+
const gopFrames = Math.max(1, Math.round(gopSeconds * assumedFps));
|
|
35168
35225
|
const ffmpegArgs = [
|
|
35169
35226
|
"-hide_banner",
|
|
35170
35227
|
"-loglevel",
|
|
35171
35228
|
"error",
|
|
35229
|
+
...this.options.extraGlobalArgs ?? [],
|
|
35172
35230
|
"-fflags",
|
|
35173
35231
|
"+genpts",
|
|
35174
35232
|
// Input 0: wider
|
|
@@ -35189,27 +35247,33 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35189
35247
|
// Output: always H.264 Annex-B
|
|
35190
35248
|
"-an",
|
|
35191
35249
|
"-c:v",
|
|
35192
|
-
|
|
35250
|
+
videoEncoder,
|
|
35193
35251
|
"-g",
|
|
35194
|
-
|
|
35252
|
+
String(gopFrames),
|
|
35195
35253
|
"-keyint_min",
|
|
35196
|
-
|
|
35254
|
+
String(gopFrames),
|
|
35197
35255
|
"-sc_threshold",
|
|
35198
35256
|
"0",
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35257
|
+
...isX264 ? [
|
|
35258
|
+
"-x264-params",
|
|
35259
|
+
`aud=1:repeat-headers=1:keyint=${gopFrames}:min-keyint=${gopFrames}:scenecut=0`,
|
|
35260
|
+
"-preset",
|
|
35261
|
+
encoderPreset,
|
|
35262
|
+
"-tune",
|
|
35263
|
+
"zerolatency",
|
|
35264
|
+
"-crf",
|
|
35265
|
+
String(crf)
|
|
35266
|
+
] : [],
|
|
35267
|
+
...this.options.extraOutputArgs ?? [],
|
|
35207
35268
|
"-f",
|
|
35208
35269
|
"h264",
|
|
35209
35270
|
"pipe:1"
|
|
35210
35271
|
];
|
|
35211
|
-
this.
|
|
35212
|
-
this.
|
|
35272
|
+
const ffmpegBin = this.options.ffmpegPath ?? "ffmpeg";
|
|
35273
|
+
this.logger.log?.(
|
|
35274
|
+
`[CompositeStream] Starting ffmpeg (rtsp inputs): bin=${ffmpegBin} args=${ffmpegArgs.join(" ")}`
|
|
35275
|
+
);
|
|
35276
|
+
this.ffmpegProcess = (0, import_node_child_process7.spawn)(ffmpegBin, ffmpegArgs, {
|
|
35213
35277
|
stdio: ["ignore", "pipe", "pipe"]
|
|
35214
35278
|
});
|
|
35215
35279
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -35276,10 +35340,18 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35276
35340
|
"-i",
|
|
35277
35341
|
"pipe:3"
|
|
35278
35342
|
];
|
|
35343
|
+
const videoEncoder = this.options.videoEncoder ?? "libx264";
|
|
35344
|
+
const isX264 = videoEncoder === "libx264";
|
|
35345
|
+
const encoderPreset = this.options.encoderPreset ?? "ultrafast";
|
|
35346
|
+
const crf = this.options.crf ?? 23;
|
|
35347
|
+
const gopSeconds = this.options.gopSeconds ?? 1;
|
|
35348
|
+
const assumedFps = 30;
|
|
35349
|
+
const gopFrames = Math.max(1, Math.round(gopSeconds * assumedFps));
|
|
35279
35350
|
const ffmpegArgs = [
|
|
35280
35351
|
"-hide_banner",
|
|
35281
35352
|
"-loglevel",
|
|
35282
35353
|
"error",
|
|
35354
|
+
...this.options.extraGlobalArgs ?? [],
|
|
35283
35355
|
"-fflags",
|
|
35284
35356
|
"+genpts",
|
|
35285
35357
|
"-probesize",
|
|
@@ -35298,33 +35370,40 @@ var CompositeStream = class extends import_node_events6.EventEmitter {
|
|
|
35298
35370
|
"-map",
|
|
35299
35371
|
"[out]",
|
|
35300
35372
|
"-c:v",
|
|
35301
|
-
|
|
35302
|
-
//
|
|
35303
|
-
//
|
|
35304
|
-
//
|
|
35373
|
+
videoEncoder,
|
|
35374
|
+
// Make the stream easy to join mid-flight: frequent IDRs + in-band
|
|
35375
|
+
// headers + AUD. Without this, a new client may wait many seconds
|
|
35376
|
+
// for the next keyframe.
|
|
35305
35377
|
"-g",
|
|
35306
|
-
|
|
35378
|
+
String(gopFrames),
|
|
35307
35379
|
"-keyint_min",
|
|
35308
|
-
|
|
35380
|
+
String(gopFrames),
|
|
35309
35381
|
"-sc_threshold",
|
|
35310
35382
|
"0",
|
|
35311
|
-
|
|
35312
|
-
|
|
35313
|
-
|
|
35314
|
-
|
|
35315
|
-
|
|
35316
|
-
|
|
35317
|
-
|
|
35318
|
-
|
|
35383
|
+
// libx264-specific knobs. We deliberately skip these for HW encoders
|
|
35384
|
+
// — each one has its own option vocabulary (`-q:v`, `-rc`, etc.)
|
|
35385
|
+
// and the user is expected to express them via extraOutputArgs.
|
|
35386
|
+
...isX264 ? [
|
|
35387
|
+
"-x264-params",
|
|
35388
|
+
`aud=1:repeat-headers=1:keyint=${gopFrames}:min-keyint=${gopFrames}:scenecut=0`,
|
|
35389
|
+
"-preset",
|
|
35390
|
+
encoderPreset,
|
|
35391
|
+
"-tune",
|
|
35392
|
+
"zerolatency",
|
|
35393
|
+
"-crf",
|
|
35394
|
+
String(crf)
|
|
35395
|
+
] : [],
|
|
35396
|
+
...this.options.extraOutputArgs ?? [],
|
|
35319
35397
|
"-f",
|
|
35320
35398
|
"h264",
|
|
35321
35399
|
"pipe:1"
|
|
35322
35400
|
// Output (stdout)
|
|
35323
35401
|
];
|
|
35402
|
+
const ffmpegBin = this.options.ffmpegPath ?? "ffmpeg";
|
|
35324
35403
|
this.logger.log?.(
|
|
35325
|
-
`[CompositeStream] Starting ffmpeg:
|
|
35404
|
+
`[CompositeStream] Starting ffmpeg: bin=${ffmpegBin} args=${ffmpegArgs.join(" ")}`
|
|
35326
35405
|
);
|
|
35327
|
-
this.ffmpegProcess = (0, import_node_child_process7.spawn)(
|
|
35406
|
+
this.ffmpegProcess = (0, import_node_child_process7.spawn)(ffmpegBin, ffmpegArgs, {
|
|
35328
35407
|
stdio: ["pipe", "pipe", "pipe", "pipe"]
|
|
35329
35408
|
});
|
|
35330
35409
|
this.ffmpegProcess.on("error", (error) => {
|
|
@@ -36032,6 +36111,20 @@ async function createRfc4571TcpServerInternal(options) {
|
|
|
36032
36111
|
...forceH264 !== void 0 ? { forceH264 } : defaultForceH264 ? { forceH264: true } : {},
|
|
36033
36112
|
...compositeOptions?.assumeH264Inputs !== void 0 ? { assumeH264Inputs: compositeOptions.assumeH264Inputs } : {},
|
|
36034
36113
|
...compositeOptions?.disableTranscode !== void 0 ? { disableTranscode: compositeOptions.disableTranscode } : {},
|
|
36114
|
+
// Propagate ffmpeg binary path — required when the embedder strips
|
|
36115
|
+
// PATH (Scrypted on Windows, Electron sandboxes, distroless Docker)
|
|
36116
|
+
// and the bundled ffmpeg is at a fixed absolute path only the
|
|
36117
|
+
// embedder knows.
|
|
36118
|
+
...compositeOptions?.ffmpegPath ? { ffmpegPath: compositeOptions.ffmpegPath } : {},
|
|
36119
|
+
// Encoder tuning knobs — see CompositeStreamPipOptions for the
|
|
36120
|
+
// semantic contract on each one. Plumbed verbatim so the
|
|
36121
|
+
// CompositeStream layer can apply defaults.
|
|
36122
|
+
...compositeOptions?.videoEncoder ? { videoEncoder: compositeOptions.videoEncoder } : {},
|
|
36123
|
+
...compositeOptions?.encoderPreset ? { encoderPreset: compositeOptions.encoderPreset } : {},
|
|
36124
|
+
...typeof compositeOptions?.crf === "number" ? { crf: compositeOptions.crf } : {},
|
|
36125
|
+
...typeof compositeOptions?.gopSeconds === "number" ? { gopSeconds: compositeOptions.gopSeconds } : {},
|
|
36126
|
+
...compositeOptions?.extraGlobalArgs ? { extraGlobalArgs: compositeOptions.extraGlobalArgs } : {},
|
|
36127
|
+
...compositeOptions?.extraOutputArgs ? { extraOutputArgs: compositeOptions.extraOutputArgs } : {},
|
|
36035
36128
|
logger
|
|
36036
36129
|
});
|
|
36037
36130
|
isCompositeStream = true;
|