@camstack/addon-pipeline 1.2.307 → 1.2.309
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/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-DgdiQtQb.js → default-detection-model-BZMS04Wn.js} +1 -1
- package/dist/{default-detection-model-BV9bIuOo.mjs → default-detection-model-ClQ1gcZ9.mjs} +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-DdVMFlJ6.mjs → dist-B3_sbBDM.mjs} +305 -48
- package/dist/{dist-CyJWk-_f.js → dist-BLf0YjAa.js} +310 -47
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-COu_tHy4.mjs → node-DJFKTJ0B.mjs} +1 -53
- package/dist/{node-HsWG5fGp.js → node-Vwf2PuIT.js} +1 -53
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-Bz3oIMzl.mjs → process-memory-BvwauBlX.mjs} +1 -1
- package/dist/{process-memory-TQXG7qrJ.js → process-memory-DGTVVYlX.js} +1 -1
- package/dist/recorder/index.js +147 -22
- package/dist/recorder/index.mjs +147 -22
- package/dist/{segment-demux-js-BrKa7tpr.js → segment-demux-js-BUAAuSVF.js} +22 -4
- package/dist/{segment-demux-js-CEJn4BR0.mjs → segment-demux-js-C0oz7IVm.mjs} +17 -5
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DdYUVQSA.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-7H0Oxf23.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C6EaWavG.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BhO-szf8.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-D--a9rVM.mjs → hostInit-DEY-Dqqo.mjs} +2 -2
- package/dist/stream-broker/index.js +434 -111
- package/dist/stream-broker/index.mjs +434 -111
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DPHv0v3Q.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CdKbuxqG.mjs +0 -26
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-emK7D4bc.js");
|
|
2
|
-
const require_dist = require("./dist-
|
|
2
|
+
const require_dist = require("./dist-BLf0YjAa.js");
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
node_fs = require_chunk.__toESM(node_fs);
|
|
5
5
|
let node_child_process = require("node:child_process");
|
package/dist/recorder/index.js
CHANGED
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
6
|
+
const require_dist = require("../dist-BLf0YjAa.js");
|
|
7
|
+
const require_node = require("../node-Vwf2PuIT.js");
|
|
8
8
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
9
|
const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
|
|
10
10
|
const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
|
|
@@ -3450,7 +3450,20 @@ var StillFrameService = class {
|
|
|
3450
3450
|
return null;
|
|
3451
3451
|
}
|
|
3452
3452
|
try {
|
|
3453
|
-
|
|
3453
|
+
let bin;
|
|
3454
|
+
try {
|
|
3455
|
+
bin = await this.deps.resolveFfmpeg();
|
|
3456
|
+
} catch (err) {
|
|
3457
|
+
this.deps.logger.error("still: refused — no pinned ffmpeg on this node (D540)", {
|
|
3458
|
+
tags: { deviceId },
|
|
3459
|
+
meta: {
|
|
3460
|
+
epochMs,
|
|
3461
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3462
|
+
}
|
|
3463
|
+
});
|
|
3464
|
+
return null;
|
|
3465
|
+
}
|
|
3466
|
+
const jpeg = await this.decodeFrame(bin, abs, source.offsetSec);
|
|
3454
3467
|
if (jpeg === null || jpeg.byteLength === 0) return null;
|
|
3455
3468
|
return {
|
|
3456
3469
|
jpeg,
|
|
@@ -3461,7 +3474,7 @@ var StillFrameService = class {
|
|
|
3461
3474
|
}
|
|
3462
3475
|
}
|
|
3463
3476
|
/** Run ONE ffmpeg input-seek + single-frame decode; JPEG bytes on stdout. */
|
|
3464
|
-
decodeFrame(absPath, offsetSec) {
|
|
3477
|
+
decodeFrame(bin, absPath, offsetSec) {
|
|
3465
3478
|
const args = [
|
|
3466
3479
|
"-hide_banner",
|
|
3467
3480
|
"-loglevel",
|
|
@@ -3477,7 +3490,6 @@ var StillFrameService = class {
|
|
|
3477
3490
|
"pipe:1"
|
|
3478
3491
|
];
|
|
3479
3492
|
const spawn = this.deps.spawn ?? node_child_process.spawn;
|
|
3480
|
-
const bin = this.deps.ffmpegPath ?? "ffmpeg";
|
|
3481
3493
|
return new Promise((resolve) => {
|
|
3482
3494
|
let child;
|
|
3483
3495
|
try {
|
|
@@ -7549,15 +7561,32 @@ var ExportEngine = class {
|
|
|
7549
7561
|
} catch {}
|
|
7550
7562
|
}
|
|
7551
7563
|
}
|
|
7552
|
-
runFfmpeg(id, deviceId, args, progress) {
|
|
7564
|
+
async runFfmpeg(id, deviceId, args, progress) {
|
|
7565
|
+
let ffmpegPath;
|
|
7566
|
+
try {
|
|
7567
|
+
ffmpegPath = await this.deps.resolveFfmpeg();
|
|
7568
|
+
} catch (err) {
|
|
7569
|
+
const reason = `no pinned ffmpeg on this node (D540): ${err instanceof Error ? err.message : String(err)}`;
|
|
7570
|
+
this.deps.logger.error("export render refused — no pinned ffmpeg on this node (D540)", {
|
|
7571
|
+
tags: { deviceId },
|
|
7572
|
+
meta: {
|
|
7573
|
+
exportId: id,
|
|
7574
|
+
error: reason
|
|
7575
|
+
}
|
|
7576
|
+
});
|
|
7577
|
+
return {
|
|
7578
|
+
code: null,
|
|
7579
|
+
stderrTail: [reason]
|
|
7580
|
+
};
|
|
7581
|
+
}
|
|
7553
7582
|
this.deps.logger.debug("export ffmpeg argv", { meta: {
|
|
7554
7583
|
exportId: id,
|
|
7555
7584
|
args
|
|
7556
7585
|
} });
|
|
7557
7586
|
const stallMs = this.deps.stallMs ?? 9e4;
|
|
7558
7587
|
const faststartStallMs = this.deps.faststartStallMs ?? 9e5;
|
|
7559
|
-
return new Promise((resolve) => {
|
|
7560
|
-
const proc = this.deps.spawn(
|
|
7588
|
+
return await new Promise((resolve) => {
|
|
7589
|
+
const proc = this.deps.spawn(ffmpegPath, args);
|
|
7561
7590
|
this.active.set(id, proc);
|
|
7562
7591
|
const stderrTail = [];
|
|
7563
7592
|
let stallTimer = null;
|
|
@@ -9459,6 +9488,17 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
|
|
|
9459
9488
|
const toMs = input.aroundMs + input.postRollSec * 1e3;
|
|
9460
9489
|
if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs <= fromMs) throw new Error(`invalid ${ext} window [${fromMs}, ${toMs}) for device ${input.deviceId} — aroundMs=${input.aroundMs} preRollSec=${input.preRollSec} postRollSec=${input.postRollSec}`);
|
|
9461
9490
|
const renderId = `${ext}-${now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
9491
|
+
let ffmpegPath;
|
|
9492
|
+
try {
|
|
9493
|
+
ffmpegPath = await renderDeps.resolveFfmpeg();
|
|
9494
|
+
} catch (err) {
|
|
9495
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9496
|
+
renderDeps.deps.logger.error(`${ext} render refused — no pinned ffmpeg on this node (D540)`, {
|
|
9497
|
+
tags: { deviceId: input.deviceId },
|
|
9498
|
+
meta: { error: reason }
|
|
9499
|
+
});
|
|
9500
|
+
throw new Error(`${ext} render refused for device ${input.deviceId}: no pinned ffmpeg — ${reason}`, { cause: err });
|
|
9501
|
+
}
|
|
9462
9502
|
const availableEnd = latestFinalizedEnd(renderDeps, input.deviceId);
|
|
9463
9503
|
if (availableEnd === null || availableEnd < toMs) throw new Error(`${FOOTAGE_NOT_READY}: [${fromMs}, ${toMs}) for device ${input.deviceId} — finalized footage ends at ${availableEnd ?? "never"}`);
|
|
9464
9504
|
const window = {
|
|
@@ -9477,7 +9517,7 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
|
|
|
9477
9517
|
if (playlist === null) throw new Error(`no footage covers [${window.fromMs}, ${window.toMs}) for device ${input.deviceId}`);
|
|
9478
9518
|
const outPath = node_path.default.join(node_path.default.dirname(playlist.path), `${renderId}.${ext}`);
|
|
9479
9519
|
try {
|
|
9480
|
-
await runFfmpeg(renderDeps, buildArgs(playlist.path, outPath), ext);
|
|
9520
|
+
await runFfmpeg(renderDeps, ffmpegPath, buildArgs(playlist.path, outPath), ext);
|
|
9481
9521
|
const bytes = await node_fs.promises.readFile(outPath);
|
|
9482
9522
|
if (bytes.byteLength === 0) throw new Error(`${ext} render produced an empty file`);
|
|
9483
9523
|
return {
|
|
@@ -9514,9 +9554,9 @@ function latestFinalizedEnd(renderDeps, deviceId) {
|
|
|
9514
9554
|
* throws ESRCH, which is swallowed, so the cost of being wrong is nothing while
|
|
9515
9555
|
* the cost of NOT killing is a zombie per render.
|
|
9516
9556
|
*/
|
|
9517
|
-
function runFfmpeg(renderDeps, args, ext) {
|
|
9557
|
+
function runFfmpeg(renderDeps, ffmpegPath, args, ext) {
|
|
9518
9558
|
return new Promise((resolve, reject) => {
|
|
9519
|
-
const proc = renderDeps.spawn(
|
|
9559
|
+
const proc = renderDeps.spawn(ffmpegPath, args);
|
|
9520
9560
|
let done = false;
|
|
9521
9561
|
let timer = null;
|
|
9522
9562
|
const settle = (err) => {
|
|
@@ -15658,6 +15698,26 @@ function buildPassthroughArgs(a) {
|
|
|
15658
15698
|
];
|
|
15659
15699
|
}
|
|
15660
15700
|
//#endregion
|
|
15701
|
+
//#region src/recorder/addon/dial-refusal.ts
|
|
15702
|
+
var METHOD_FAILED = /\bmethod ([A-Z_]+) failed: (\d{3})\b/;
|
|
15703
|
+
function isHandshakeMethod(method) {
|
|
15704
|
+
return method === "OPTIONS" || method === "DESCRIBE" || method === "SETUP" || method === "PLAY";
|
|
15705
|
+
}
|
|
15706
|
+
/** The refusal named in this generation's stderr tail, or `null` when there is none. */
|
|
15707
|
+
function readDialRefusal(stderrTail) {
|
|
15708
|
+
for (let i = stderrTail.length - 1; i >= 0; i--) {
|
|
15709
|
+
const match = METHOD_FAILED.exec(stderrTail[i] ?? "");
|
|
15710
|
+
if (match === null) continue;
|
|
15711
|
+
const method = match[1] ?? "";
|
|
15712
|
+
if (!isHandshakeMethod(method)) return null;
|
|
15713
|
+
return {
|
|
15714
|
+
method,
|
|
15715
|
+
status: Number(match[2])
|
|
15716
|
+
};
|
|
15717
|
+
}
|
|
15718
|
+
return null;
|
|
15719
|
+
}
|
|
15720
|
+
//#endregion
|
|
15661
15721
|
//#region src/recorder/addon/segment-writer.ts
|
|
15662
15722
|
/** Max CONSECUTIVE rapid restarts before giving up (reset by a stable run). */
|
|
15663
15723
|
var MAX_RESTARTS = 10;
|
|
@@ -15716,6 +15776,12 @@ var SegmentWriter = class {
|
|
|
15716
15776
|
* half-pruned window.
|
|
15717
15777
|
*/
|
|
15718
15778
|
restartWindow = [];
|
|
15779
|
+
/**
|
|
15780
|
+
* Consecutive generations the source refused at the handshake (D685). Reset
|
|
15781
|
+
* by any generation that was not refused. Only read for the log line — the
|
|
15782
|
+
* rapid breaker is what bounds a source that never comes back.
|
|
15783
|
+
*/
|
|
15784
|
+
refusedInARow = 0;
|
|
15719
15785
|
restartTimer = null;
|
|
15720
15786
|
startedAt = 0;
|
|
15721
15787
|
exitWait = null;
|
|
@@ -15748,7 +15814,7 @@ var SegmentWriter = class {
|
|
|
15748
15814
|
start() {
|
|
15749
15815
|
if (this.stopped || this.proc) return;
|
|
15750
15816
|
const args = buildPassthroughArgs(this.cfg);
|
|
15751
|
-
const proc = this.deps.spawn(this.deps.ffmpegPath
|
|
15817
|
+
const proc = this.deps.spawn(this.deps.ffmpegPath, args);
|
|
15752
15818
|
this.proc = proc;
|
|
15753
15819
|
this.exitWait = new Promise((resolve) => {
|
|
15754
15820
|
this.resolveExit = resolve;
|
|
@@ -15805,10 +15871,13 @@ var SegmentWriter = class {
|
|
|
15805
15871
|
if (this.stopped) return;
|
|
15806
15872
|
const now = Date.now();
|
|
15807
15873
|
const ranMs = now - this.startedAt;
|
|
15808
|
-
|
|
15809
|
-
this.
|
|
15874
|
+
const refusal = termination.reason === "exit" ? readDialRefusal(stderrTail) : null;
|
|
15875
|
+
this.refusedInARow = refusal === null ? 0 : this.refusedInARow + 1;
|
|
15876
|
+
if (ranMs >= STABLE_RUN_MS && refusal === null) this.restarts = 0;
|
|
15877
|
+
const liveWindow = this.restartWindow.filter((at) => at > now - RESTART_WINDOW_MS);
|
|
15878
|
+
this.restartWindow = refusal === null ? [...liveWindow, now] : liveWindow;
|
|
15810
15879
|
const restartsInWindow = this.restartWindow.length;
|
|
15811
|
-
if (termination.reason === "exit") this.deps.logger.warn("SegmentWriter ffmpeg exited", {
|
|
15880
|
+
if (termination.reason === "exit" && refusal === null) this.deps.logger.warn("SegmentWriter ffmpeg exited", {
|
|
15812
15881
|
tags: { deviceId: this.deps.deviceId },
|
|
15813
15882
|
meta: {
|
|
15814
15883
|
outDir: this.cfg.outDir,
|
|
@@ -15880,6 +15949,39 @@ var SegmentWriter = class {
|
|
|
15880
15949
|
}
|
|
15881
15950
|
this.restarts++;
|
|
15882
15951
|
const delayMs = Math.min(RESTART_BASE_MS * 2 ** (this.restarts - 1), RESTART_MAX_MS);
|
|
15952
|
+
if (refusal !== null) this.logDeferredRedial(refusal, delayMs, ranMs, restartsInWindow);
|
|
15953
|
+
else this.logRestarting(termination, delayMs, ranMs, restartsInWindow);
|
|
15954
|
+
this.restartTimer = setTimeout(() => {
|
|
15955
|
+
this.restartTimer = null;
|
|
15956
|
+
this.start();
|
|
15957
|
+
}, delayMs);
|
|
15958
|
+
}
|
|
15959
|
+
/**
|
|
15960
|
+
* A refused dial, named — every one of them, per camera. The FIRST of a run
|
|
15961
|
+
* is a WARN (the source just went away under a live writer); the rest are
|
|
15962
|
+
* the same outage continuing and go out at INFO with the running count.
|
|
15963
|
+
*/
|
|
15964
|
+
logDeferredRedial(refusal, delayMs, ranMs, restartsInWindow) {
|
|
15965
|
+
const payload = {
|
|
15966
|
+
tags: { deviceId: this.deps.deviceId },
|
|
15967
|
+
meta: {
|
|
15968
|
+
outDir: this.cfg.outDir,
|
|
15969
|
+
method: refusal.method,
|
|
15970
|
+
status: refusal.status,
|
|
15971
|
+
refusedInARow: this.refusedInARow,
|
|
15972
|
+
attempt: this.restarts,
|
|
15973
|
+
delayMs,
|
|
15974
|
+
ranMs,
|
|
15975
|
+
restartsInWindow
|
|
15976
|
+
}
|
|
15977
|
+
};
|
|
15978
|
+
if (this.refusedInARow === 1) {
|
|
15979
|
+
this.deps.logger.warn("SegmentWriter dial refused — source not published, re-dialling", payload);
|
|
15980
|
+
return;
|
|
15981
|
+
}
|
|
15982
|
+
this.deps.logger.info("SegmentWriter dial still refused — re-dial deferred", payload);
|
|
15983
|
+
}
|
|
15984
|
+
logRestarting(termination, delayMs, ranMs, restartsInWindow) {
|
|
15883
15985
|
this.deps.logger.info("SegmentWriter restarting", {
|
|
15884
15986
|
tags: { deviceId: this.deps.deviceId },
|
|
15885
15987
|
meta: {
|
|
@@ -15891,10 +15993,6 @@ var SegmentWriter = class {
|
|
|
15891
15993
|
reason: termination.reason
|
|
15892
15994
|
}
|
|
15893
15995
|
});
|
|
15894
|
-
this.restartTimer = setTimeout(() => {
|
|
15895
|
-
this.restartTimer = null;
|
|
15896
|
-
this.start();
|
|
15897
|
-
}, delayMs);
|
|
15898
15996
|
}
|
|
15899
15997
|
stop() {
|
|
15900
15998
|
this.stopAndWait();
|
|
@@ -17495,6 +17593,19 @@ var RecordingController = class {
|
|
|
17495
17593
|
*/
|
|
17496
17594
|
async attachProfile(deviceId, profile, segmentSeconds, audio) {
|
|
17497
17595
|
const attachProfileStartMs = this.now();
|
|
17596
|
+
let ffmpegPath;
|
|
17597
|
+
try {
|
|
17598
|
+
ffmpegPath = await this.deps.resolveFfmpeg();
|
|
17599
|
+
} catch (err) {
|
|
17600
|
+
this.deps.logger.error("recorder: writer refused — no pinned ffmpeg on this node (D540)", {
|
|
17601
|
+
tags: { deviceId },
|
|
17602
|
+
meta: {
|
|
17603
|
+
profile,
|
|
17604
|
+
error: require_dist.errMsg(err)
|
|
17605
|
+
}
|
|
17606
|
+
});
|
|
17607
|
+
throw new Error(`recorder: no pinned ffmpeg for device ${deviceId}/${profile}`);
|
|
17608
|
+
}
|
|
17498
17609
|
const placement = await this.resolvePlacement(deviceId, profile);
|
|
17499
17610
|
const placementDoneMs = this.now();
|
|
17500
17611
|
const outDir = node_path.default.join(placement.root, STAGING_DIR_NAME, String(deviceId), profile);
|
|
@@ -17530,6 +17641,7 @@ var RecordingController = class {
|
|
|
17530
17641
|
consumerTag
|
|
17531
17642
|
}, {
|
|
17532
17643
|
spawn: this.deps.spawn,
|
|
17644
|
+
ffmpegPath,
|
|
17533
17645
|
logger: deviceLog,
|
|
17534
17646
|
deviceId,
|
|
17535
17647
|
claimCost: (pid) => this.deps.costRegistry?.claim({
|
|
@@ -20823,6 +20935,14 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
20823
20935
|
* because those children were this process's children.
|
|
20824
20936
|
*/
|
|
20825
20937
|
costRegistry = new require_node.ChildCostRegistry();
|
|
20938
|
+
/**
|
|
20939
|
+
* This node's PINNED ffmpeg (D540), asked once and shared by every child the
|
|
20940
|
+
* recorder spawns: segment writers, exports, gif/clip renders and stills.
|
|
20941
|
+
* Each of those used to take an optional path that no one passed, and fell
|
|
20942
|
+
* back to a bare `'ffmpeg'` — the HOST's binary. It rejects rather than
|
|
20943
|
+
* naming PATH; each site logs its refusal per camera.
|
|
20944
|
+
*/
|
|
20945
|
+
pinnedFfmpeg = require_dist.memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
|
|
20826
20946
|
/** True when THIS node is the designated recording node (`recordingNodeId`).
|
|
20827
20947
|
* Every other node stays fully inert — registers no providers, serves no
|
|
20828
20948
|
* playback, runs no controller. Resolved in `onInitialize`. */
|
|
@@ -21216,6 +21336,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
21216
21336
|
now: () => Date.now()
|
|
21217
21337
|
});
|
|
21218
21338
|
this.controller = new RecordingController({
|
|
21339
|
+
resolveFfmpeg: this.pinnedFfmpeg,
|
|
21219
21340
|
...this.stagingGuard !== null ? { stagingGuard: this.stagingGuard } : {},
|
|
21220
21341
|
recoverStagingDirectory: (input) => this.recoverReapedDirectory(input),
|
|
21221
21342
|
heapProbe: this.heapProbe,
|
|
@@ -21363,12 +21484,14 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
21363
21484
|
gifRender: (input) => renderGif({
|
|
21364
21485
|
deps: recordingDeps,
|
|
21365
21486
|
exportsDir: this.exportsDirOrThrow(),
|
|
21366
|
-
spawn: node_child_process.spawn
|
|
21487
|
+
spawn: node_child_process.spawn,
|
|
21488
|
+
resolveFfmpeg: this.pinnedFfmpeg
|
|
21367
21489
|
}, input),
|
|
21368
21490
|
clipRender: (input) => renderClip({
|
|
21369
21491
|
deps: recordingDeps,
|
|
21370
21492
|
exportsDir: this.exportsDirOrThrow(),
|
|
21371
|
-
spawn: node_child_process.spawn
|
|
21493
|
+
spawn: node_child_process.spawn,
|
|
21494
|
+
resolveFfmpeg: this.pinnedFfmpeg
|
|
21372
21495
|
}, input),
|
|
21373
21496
|
locations: () => this.resolvedLocations,
|
|
21374
21497
|
refreshLocations: () => this.refreshLocations(),
|
|
@@ -21497,6 +21620,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
21497
21620
|
};
|
|
21498
21621
|
this.exportEngine = new ExportEngine({
|
|
21499
21622
|
spawn: node_child_process.spawn,
|
|
21623
|
+
resolveFfmpeg: this.pinnedFfmpeg,
|
|
21500
21624
|
logger: this.ctx.logger.child("export"),
|
|
21501
21625
|
state: exportsState,
|
|
21502
21626
|
resolvePlaylist: async (rec) => {
|
|
@@ -21649,7 +21773,8 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
21649
21773
|
const stillService = new StillFrameService({
|
|
21650
21774
|
lowSegmentsAt: (deviceId, epochMs) => this.index.segmentsOverlapping(deviceId, "low", epochMs, epochMs + 1),
|
|
21651
21775
|
resolveAbsPath: (row) => this.resolveSegmentAbsPath(row),
|
|
21652
|
-
logger: this.ctx.logger.child("Still")
|
|
21776
|
+
logger: this.ctx.logger.child("Still"),
|
|
21777
|
+
resolveFfmpeg: this.pinnedFfmpeg
|
|
21653
21778
|
});
|
|
21654
21779
|
const served = await this.ctx.dataPlane?.serve({
|
|
21655
21780
|
prefix: STILL_PREFIX,
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-
|
|
1
|
+
import { An as _enum, B as VISIT_MERGE_GAP_MS, Bn as string, Bt as recordingSignalCapability, Ct as mayReadLocation, Dt as modeMayWrite, Et as migrateLegacyDeviceSignalConfig, F as RECORDING_SOURCE_CAMSTACK_ADDON, Hn as EventCategory, Ht as resolveCreateExportSubject, I as RecordingConfigSchema, In as number, J as clipExportFailure, K as batteryCapability, Kt as resolveRecordingProfiles, L as RecordingSignalStatusSchema, Ln as object, Lt as recordingArchiveCapability, Mn as boolean, N as RECORDING_EXPORT_MAX_READ_BYTES, On as selectAssignedProfileSlots, P as RECORDING_SOURCE_CAMSTACK, Qt as stripRetiredBandPreBufferSec, Rn as preprocess, Rt as recordingCapability, S as ExportRecordSchema, Tn as nodePin, Tt as memoizePinnedFfmpeg, Wt as resolveLocationMode, Xt as storageOccupancyCapability, Y as clipExportFailureOf, Yt as storageEvictableCapability, a as BatteryStatusSchema, b as EVENT_PAD_MS, ct as evictionPolicyForMode, dn as errMsg$1, en as videoclipsCapability, et as deriveBatteryPresence, g as DEFAULT_EVENTS_BAND_BUFFER_SEC, jn as array, k as OpsLogEntrySchema, kn as sleep$1, ln as DeviceFeature, lt as evictionPolicyOfLocation, nt as deriveRecordingMode, o as CLIP_BROKER_PACED_RATES, on as evaluateSensorEdge, pn as BaseAddon, pt as gbToBytes, un as DeviceType, ut as exportSubjectOf, vn as customAction, vt as isFirstLevelMacroClass, wt as mayWriteToLocation, xn as hydrateSchema, y as EVENT_OWNER_TYPES, yn as defineCustomActions, yt as loadContributionCapability, z as VIDEOCLIPS_MAX_OFFER_BYTES, zn as record, zt as recordingExportCapability } from "../dist-B3_sbBDM.mjs";
|
|
2
|
+
import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-DJFKTJ0B.mjs";
|
|
3
3
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
4
4
|
import { d as PREBUFFER_MAX_MS, l as withRecordingIntent, r as RECORDING_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
|
|
5
5
|
import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
|
|
@@ -3443,7 +3443,20 @@ var StillFrameService = class {
|
|
|
3443
3443
|
return null;
|
|
3444
3444
|
}
|
|
3445
3445
|
try {
|
|
3446
|
-
|
|
3446
|
+
let bin;
|
|
3447
|
+
try {
|
|
3448
|
+
bin = await this.deps.resolveFfmpeg();
|
|
3449
|
+
} catch (err) {
|
|
3450
|
+
this.deps.logger.error("still: refused — no pinned ffmpeg on this node (D540)", {
|
|
3451
|
+
tags: { deviceId },
|
|
3452
|
+
meta: {
|
|
3453
|
+
epochMs,
|
|
3454
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3455
|
+
}
|
|
3456
|
+
});
|
|
3457
|
+
return null;
|
|
3458
|
+
}
|
|
3459
|
+
const jpeg = await this.decodeFrame(bin, abs, source.offsetSec);
|
|
3447
3460
|
if (jpeg === null || jpeg.byteLength === 0) return null;
|
|
3448
3461
|
return {
|
|
3449
3462
|
jpeg,
|
|
@@ -3454,7 +3467,7 @@ var StillFrameService = class {
|
|
|
3454
3467
|
}
|
|
3455
3468
|
}
|
|
3456
3469
|
/** Run ONE ffmpeg input-seek + single-frame decode; JPEG bytes on stdout. */
|
|
3457
|
-
decodeFrame(absPath, offsetSec) {
|
|
3470
|
+
decodeFrame(bin, absPath, offsetSec) {
|
|
3458
3471
|
const args = [
|
|
3459
3472
|
"-hide_banner",
|
|
3460
3473
|
"-loglevel",
|
|
@@ -3470,7 +3483,6 @@ var StillFrameService = class {
|
|
|
3470
3483
|
"pipe:1"
|
|
3471
3484
|
];
|
|
3472
3485
|
const spawn$1 = this.deps.spawn ?? spawn;
|
|
3473
|
-
const bin = this.deps.ffmpegPath ?? "ffmpeg";
|
|
3474
3486
|
return new Promise((resolve) => {
|
|
3475
3487
|
let child;
|
|
3476
3488
|
try {
|
|
@@ -7541,15 +7553,32 @@ var ExportEngine = class {
|
|
|
7541
7553
|
} catch {}
|
|
7542
7554
|
}
|
|
7543
7555
|
}
|
|
7544
|
-
runFfmpeg(id, deviceId, args, progress) {
|
|
7556
|
+
async runFfmpeg(id, deviceId, args, progress) {
|
|
7557
|
+
let ffmpegPath;
|
|
7558
|
+
try {
|
|
7559
|
+
ffmpegPath = await this.deps.resolveFfmpeg();
|
|
7560
|
+
} catch (err) {
|
|
7561
|
+
const reason = `no pinned ffmpeg on this node (D540): ${err instanceof Error ? err.message : String(err)}`;
|
|
7562
|
+
this.deps.logger.error("export render refused — no pinned ffmpeg on this node (D540)", {
|
|
7563
|
+
tags: { deviceId },
|
|
7564
|
+
meta: {
|
|
7565
|
+
exportId: id,
|
|
7566
|
+
error: reason
|
|
7567
|
+
}
|
|
7568
|
+
});
|
|
7569
|
+
return {
|
|
7570
|
+
code: null,
|
|
7571
|
+
stderrTail: [reason]
|
|
7572
|
+
};
|
|
7573
|
+
}
|
|
7545
7574
|
this.deps.logger.debug("export ffmpeg argv", { meta: {
|
|
7546
7575
|
exportId: id,
|
|
7547
7576
|
args
|
|
7548
7577
|
} });
|
|
7549
7578
|
const stallMs = this.deps.stallMs ?? 9e4;
|
|
7550
7579
|
const faststartStallMs = this.deps.faststartStallMs ?? 9e5;
|
|
7551
|
-
return new Promise((resolve) => {
|
|
7552
|
-
const proc = this.deps.spawn(
|
|
7580
|
+
return await new Promise((resolve) => {
|
|
7581
|
+
const proc = this.deps.spawn(ffmpegPath, args);
|
|
7553
7582
|
this.active.set(id, proc);
|
|
7554
7583
|
const stderrTail = [];
|
|
7555
7584
|
let stallTimer = null;
|
|
@@ -9451,6 +9480,17 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
|
|
|
9451
9480
|
const toMs = input.aroundMs + input.postRollSec * 1e3;
|
|
9452
9481
|
if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs <= fromMs) throw new Error(`invalid ${ext} window [${fromMs}, ${toMs}) for device ${input.deviceId} — aroundMs=${input.aroundMs} preRollSec=${input.preRollSec} postRollSec=${input.postRollSec}`);
|
|
9453
9482
|
const renderId = `${ext}-${now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
9483
|
+
let ffmpegPath;
|
|
9484
|
+
try {
|
|
9485
|
+
ffmpegPath = await renderDeps.resolveFfmpeg();
|
|
9486
|
+
} catch (err) {
|
|
9487
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9488
|
+
renderDeps.deps.logger.error(`${ext} render refused — no pinned ffmpeg on this node (D540)`, {
|
|
9489
|
+
tags: { deviceId: input.deviceId },
|
|
9490
|
+
meta: { error: reason }
|
|
9491
|
+
});
|
|
9492
|
+
throw new Error(`${ext} render refused for device ${input.deviceId}: no pinned ffmpeg — ${reason}`, { cause: err });
|
|
9493
|
+
}
|
|
9454
9494
|
const availableEnd = latestFinalizedEnd(renderDeps, input.deviceId);
|
|
9455
9495
|
if (availableEnd === null || availableEnd < toMs) throw new Error(`${FOOTAGE_NOT_READY}: [${fromMs}, ${toMs}) for device ${input.deviceId} — finalized footage ends at ${availableEnd ?? "never"}`);
|
|
9456
9496
|
const window = {
|
|
@@ -9469,7 +9509,7 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
|
|
|
9469
9509
|
if (playlist === null) throw new Error(`no footage covers [${window.fromMs}, ${window.toMs}) for device ${input.deviceId}`);
|
|
9470
9510
|
const outPath = path.join(path.dirname(playlist.path), `${renderId}.${ext}`);
|
|
9471
9511
|
try {
|
|
9472
|
-
await runFfmpeg(renderDeps, buildArgs(playlist.path, outPath), ext);
|
|
9512
|
+
await runFfmpeg(renderDeps, ffmpegPath, buildArgs(playlist.path, outPath), ext);
|
|
9473
9513
|
const bytes = await promises.readFile(outPath);
|
|
9474
9514
|
if (bytes.byteLength === 0) throw new Error(`${ext} render produced an empty file`);
|
|
9475
9515
|
return {
|
|
@@ -9506,9 +9546,9 @@ function latestFinalizedEnd(renderDeps, deviceId) {
|
|
|
9506
9546
|
* throws ESRCH, which is swallowed, so the cost of being wrong is nothing while
|
|
9507
9547
|
* the cost of NOT killing is a zombie per render.
|
|
9508
9548
|
*/
|
|
9509
|
-
function runFfmpeg(renderDeps, args, ext) {
|
|
9549
|
+
function runFfmpeg(renderDeps, ffmpegPath, args, ext) {
|
|
9510
9550
|
return new Promise((resolve, reject) => {
|
|
9511
|
-
const proc = renderDeps.spawn(
|
|
9551
|
+
const proc = renderDeps.spawn(ffmpegPath, args);
|
|
9512
9552
|
let done = false;
|
|
9513
9553
|
let timer = null;
|
|
9514
9554
|
const settle = (err) => {
|
|
@@ -15650,6 +15690,26 @@ function buildPassthroughArgs(a) {
|
|
|
15650
15690
|
];
|
|
15651
15691
|
}
|
|
15652
15692
|
//#endregion
|
|
15693
|
+
//#region src/recorder/addon/dial-refusal.ts
|
|
15694
|
+
var METHOD_FAILED = /\bmethod ([A-Z_]+) failed: (\d{3})\b/;
|
|
15695
|
+
function isHandshakeMethod(method) {
|
|
15696
|
+
return method === "OPTIONS" || method === "DESCRIBE" || method === "SETUP" || method === "PLAY";
|
|
15697
|
+
}
|
|
15698
|
+
/** The refusal named in this generation's stderr tail, or `null` when there is none. */
|
|
15699
|
+
function readDialRefusal(stderrTail) {
|
|
15700
|
+
for (let i = stderrTail.length - 1; i >= 0; i--) {
|
|
15701
|
+
const match = METHOD_FAILED.exec(stderrTail[i] ?? "");
|
|
15702
|
+
if (match === null) continue;
|
|
15703
|
+
const method = match[1] ?? "";
|
|
15704
|
+
if (!isHandshakeMethod(method)) return null;
|
|
15705
|
+
return {
|
|
15706
|
+
method,
|
|
15707
|
+
status: Number(match[2])
|
|
15708
|
+
};
|
|
15709
|
+
}
|
|
15710
|
+
return null;
|
|
15711
|
+
}
|
|
15712
|
+
//#endregion
|
|
15653
15713
|
//#region src/recorder/addon/segment-writer.ts
|
|
15654
15714
|
/** Max CONSECUTIVE rapid restarts before giving up (reset by a stable run). */
|
|
15655
15715
|
var MAX_RESTARTS = 10;
|
|
@@ -15708,6 +15768,12 @@ var SegmentWriter = class {
|
|
|
15708
15768
|
* half-pruned window.
|
|
15709
15769
|
*/
|
|
15710
15770
|
restartWindow = [];
|
|
15771
|
+
/**
|
|
15772
|
+
* Consecutive generations the source refused at the handshake (D685). Reset
|
|
15773
|
+
* by any generation that was not refused. Only read for the log line — the
|
|
15774
|
+
* rapid breaker is what bounds a source that never comes back.
|
|
15775
|
+
*/
|
|
15776
|
+
refusedInARow = 0;
|
|
15711
15777
|
restartTimer = null;
|
|
15712
15778
|
startedAt = 0;
|
|
15713
15779
|
exitWait = null;
|
|
@@ -15740,7 +15806,7 @@ var SegmentWriter = class {
|
|
|
15740
15806
|
start() {
|
|
15741
15807
|
if (this.stopped || this.proc) return;
|
|
15742
15808
|
const args = buildPassthroughArgs(this.cfg);
|
|
15743
|
-
const proc = this.deps.spawn(this.deps.ffmpegPath
|
|
15809
|
+
const proc = this.deps.spawn(this.deps.ffmpegPath, args);
|
|
15744
15810
|
this.proc = proc;
|
|
15745
15811
|
this.exitWait = new Promise((resolve) => {
|
|
15746
15812
|
this.resolveExit = resolve;
|
|
@@ -15797,10 +15863,13 @@ var SegmentWriter = class {
|
|
|
15797
15863
|
if (this.stopped) return;
|
|
15798
15864
|
const now = Date.now();
|
|
15799
15865
|
const ranMs = now - this.startedAt;
|
|
15800
|
-
|
|
15801
|
-
this.
|
|
15866
|
+
const refusal = termination.reason === "exit" ? readDialRefusal(stderrTail) : null;
|
|
15867
|
+
this.refusedInARow = refusal === null ? 0 : this.refusedInARow + 1;
|
|
15868
|
+
if (ranMs >= STABLE_RUN_MS && refusal === null) this.restarts = 0;
|
|
15869
|
+
const liveWindow = this.restartWindow.filter((at) => at > now - RESTART_WINDOW_MS);
|
|
15870
|
+
this.restartWindow = refusal === null ? [...liveWindow, now] : liveWindow;
|
|
15802
15871
|
const restartsInWindow = this.restartWindow.length;
|
|
15803
|
-
if (termination.reason === "exit") this.deps.logger.warn("SegmentWriter ffmpeg exited", {
|
|
15872
|
+
if (termination.reason === "exit" && refusal === null) this.deps.logger.warn("SegmentWriter ffmpeg exited", {
|
|
15804
15873
|
tags: { deviceId: this.deps.deviceId },
|
|
15805
15874
|
meta: {
|
|
15806
15875
|
outDir: this.cfg.outDir,
|
|
@@ -15872,6 +15941,39 @@ var SegmentWriter = class {
|
|
|
15872
15941
|
}
|
|
15873
15942
|
this.restarts++;
|
|
15874
15943
|
const delayMs = Math.min(RESTART_BASE_MS * 2 ** (this.restarts - 1), RESTART_MAX_MS);
|
|
15944
|
+
if (refusal !== null) this.logDeferredRedial(refusal, delayMs, ranMs, restartsInWindow);
|
|
15945
|
+
else this.logRestarting(termination, delayMs, ranMs, restartsInWindow);
|
|
15946
|
+
this.restartTimer = setTimeout(() => {
|
|
15947
|
+
this.restartTimer = null;
|
|
15948
|
+
this.start();
|
|
15949
|
+
}, delayMs);
|
|
15950
|
+
}
|
|
15951
|
+
/**
|
|
15952
|
+
* A refused dial, named — every one of them, per camera. The FIRST of a run
|
|
15953
|
+
* is a WARN (the source just went away under a live writer); the rest are
|
|
15954
|
+
* the same outage continuing and go out at INFO with the running count.
|
|
15955
|
+
*/
|
|
15956
|
+
logDeferredRedial(refusal, delayMs, ranMs, restartsInWindow) {
|
|
15957
|
+
const payload = {
|
|
15958
|
+
tags: { deviceId: this.deps.deviceId },
|
|
15959
|
+
meta: {
|
|
15960
|
+
outDir: this.cfg.outDir,
|
|
15961
|
+
method: refusal.method,
|
|
15962
|
+
status: refusal.status,
|
|
15963
|
+
refusedInARow: this.refusedInARow,
|
|
15964
|
+
attempt: this.restarts,
|
|
15965
|
+
delayMs,
|
|
15966
|
+
ranMs,
|
|
15967
|
+
restartsInWindow
|
|
15968
|
+
}
|
|
15969
|
+
};
|
|
15970
|
+
if (this.refusedInARow === 1) {
|
|
15971
|
+
this.deps.logger.warn("SegmentWriter dial refused — source not published, re-dialling", payload);
|
|
15972
|
+
return;
|
|
15973
|
+
}
|
|
15974
|
+
this.deps.logger.info("SegmentWriter dial still refused — re-dial deferred", payload);
|
|
15975
|
+
}
|
|
15976
|
+
logRestarting(termination, delayMs, ranMs, restartsInWindow) {
|
|
15875
15977
|
this.deps.logger.info("SegmentWriter restarting", {
|
|
15876
15978
|
tags: { deviceId: this.deps.deviceId },
|
|
15877
15979
|
meta: {
|
|
@@ -15883,10 +15985,6 @@ var SegmentWriter = class {
|
|
|
15883
15985
|
reason: termination.reason
|
|
15884
15986
|
}
|
|
15885
15987
|
});
|
|
15886
|
-
this.restartTimer = setTimeout(() => {
|
|
15887
|
-
this.restartTimer = null;
|
|
15888
|
-
this.start();
|
|
15889
|
-
}, delayMs);
|
|
15890
15988
|
}
|
|
15891
15989
|
stop() {
|
|
15892
15990
|
this.stopAndWait();
|
|
@@ -17487,6 +17585,19 @@ var RecordingController = class {
|
|
|
17487
17585
|
*/
|
|
17488
17586
|
async attachProfile(deviceId, profile, segmentSeconds, audio) {
|
|
17489
17587
|
const attachProfileStartMs = this.now();
|
|
17588
|
+
let ffmpegPath;
|
|
17589
|
+
try {
|
|
17590
|
+
ffmpegPath = await this.deps.resolveFfmpeg();
|
|
17591
|
+
} catch (err) {
|
|
17592
|
+
this.deps.logger.error("recorder: writer refused — no pinned ffmpeg on this node (D540)", {
|
|
17593
|
+
tags: { deviceId },
|
|
17594
|
+
meta: {
|
|
17595
|
+
profile,
|
|
17596
|
+
error: errMsg$1(err)
|
|
17597
|
+
}
|
|
17598
|
+
});
|
|
17599
|
+
throw new Error(`recorder: no pinned ffmpeg for device ${deviceId}/${profile}`);
|
|
17600
|
+
}
|
|
17490
17601
|
const placement = await this.resolvePlacement(deviceId, profile);
|
|
17491
17602
|
const placementDoneMs = this.now();
|
|
17492
17603
|
const outDir = path.join(placement.root, STAGING_DIR_NAME, String(deviceId), profile);
|
|
@@ -17522,6 +17633,7 @@ var RecordingController = class {
|
|
|
17522
17633
|
consumerTag
|
|
17523
17634
|
}, {
|
|
17524
17635
|
spawn: this.deps.spawn,
|
|
17636
|
+
ffmpegPath,
|
|
17525
17637
|
logger: deviceLog,
|
|
17526
17638
|
deviceId,
|
|
17527
17639
|
claimCost: (pid) => this.deps.costRegistry?.claim({
|
|
@@ -20815,6 +20927,14 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
20815
20927
|
* because those children were this process's children.
|
|
20816
20928
|
*/
|
|
20817
20929
|
costRegistry = new ChildCostRegistry();
|
|
20930
|
+
/**
|
|
20931
|
+
* This node's PINNED ffmpeg (D540), asked once and shared by every child the
|
|
20932
|
+
* recorder spawns: segment writers, exports, gif/clip renders and stills.
|
|
20933
|
+
* Each of those used to take an optional path that no one passed, and fell
|
|
20934
|
+
* back to a bare `'ffmpeg'` — the HOST's binary. It rejects rather than
|
|
20935
|
+
* naming PATH; each site logs its refusal per camera.
|
|
20936
|
+
*/
|
|
20937
|
+
pinnedFfmpeg = memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
|
|
20818
20938
|
/** True when THIS node is the designated recording node (`recordingNodeId`).
|
|
20819
20939
|
* Every other node stays fully inert — registers no providers, serves no
|
|
20820
20940
|
* playback, runs no controller. Resolved in `onInitialize`. */
|
|
@@ -21208,6 +21328,7 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
21208
21328
|
now: () => Date.now()
|
|
21209
21329
|
});
|
|
21210
21330
|
this.controller = new RecordingController({
|
|
21331
|
+
resolveFfmpeg: this.pinnedFfmpeg,
|
|
21211
21332
|
...this.stagingGuard !== null ? { stagingGuard: this.stagingGuard } : {},
|
|
21212
21333
|
recoverStagingDirectory: (input) => this.recoverReapedDirectory(input),
|
|
21213
21334
|
heapProbe: this.heapProbe,
|
|
@@ -21355,12 +21476,14 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
21355
21476
|
gifRender: (input) => renderGif({
|
|
21356
21477
|
deps: recordingDeps,
|
|
21357
21478
|
exportsDir: this.exportsDirOrThrow(),
|
|
21358
|
-
spawn
|
|
21479
|
+
spawn,
|
|
21480
|
+
resolveFfmpeg: this.pinnedFfmpeg
|
|
21359
21481
|
}, input),
|
|
21360
21482
|
clipRender: (input) => renderClip({
|
|
21361
21483
|
deps: recordingDeps,
|
|
21362
21484
|
exportsDir: this.exportsDirOrThrow(),
|
|
21363
|
-
spawn
|
|
21485
|
+
spawn,
|
|
21486
|
+
resolveFfmpeg: this.pinnedFfmpeg
|
|
21364
21487
|
}, input),
|
|
21365
21488
|
locations: () => this.resolvedLocations,
|
|
21366
21489
|
refreshLocations: () => this.refreshLocations(),
|
|
@@ -21489,6 +21612,7 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
21489
21612
|
};
|
|
21490
21613
|
this.exportEngine = new ExportEngine({
|
|
21491
21614
|
spawn,
|
|
21615
|
+
resolveFfmpeg: this.pinnedFfmpeg,
|
|
21492
21616
|
logger: this.ctx.logger.child("export"),
|
|
21493
21617
|
state: exportsState,
|
|
21494
21618
|
resolvePlaylist: async (rec) => {
|
|
@@ -21641,7 +21765,8 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
21641
21765
|
const stillService = new StillFrameService({
|
|
21642
21766
|
lowSegmentsAt: (deviceId, epochMs) => this.index.segmentsOverlapping(deviceId, "low", epochMs, epochMs + 1),
|
|
21643
21767
|
resolveAbsPath: (row) => this.resolveSegmentAbsPath(row),
|
|
21644
|
-
logger: this.ctx.logger.child("Still")
|
|
21768
|
+
logger: this.ctx.logger.child("Still"),
|
|
21769
|
+
resolveFfmpeg: this.pinnedFfmpeg
|
|
21645
21770
|
});
|
|
21646
21771
|
const served = await this.ctx.dataPlane?.serve({
|
|
21647
21772
|
prefix: STILL_PREFIX,
|