@camstack/addon-pipeline 1.1.27 → 1.1.29
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 +7 -6
- package/dist/audio-analyzer/index.mjs +7 -6
- package/dist/audio-codec-ffmpeg/index.js +1 -5
- package/dist/audio-codec-ffmpeg/index.mjs +1 -5
- package/dist/decoder-ffmpeg/index.js +138 -356
- package/dist/decoder-ffmpeg/index.mjs +118 -336
- package/dist/decoder-nodeav/index.js +17 -10
- package/dist/decoder-nodeav/index.mjs +17 -10
- package/dist/detection-pipeline/index.js +341 -379
- package/dist/detection-pipeline/index.mjs +341 -379
- package/dist/{dist-DAIlCdAx.js → dist-Cwc0TUQr.js} +378 -76
- package/dist/{dist-CgEP_0OL.mjs → dist-DjuGmyG9.mjs} +379 -65
- package/dist/ffmpeg-args-C5GPp8Cw.mjs +323 -0
- package/dist/ffmpeg-args-D6h1edXK.js +418 -0
- package/dist/frame-dropper-AjheBGMG.mjs +22 -0
- package/dist/frame-dropper-DKLM6pMz.js +27 -0
- package/dist/{frame-handle-plane-Dq20KtKL.mjs → frame-handle-plane-Bkxz-TTD.mjs} +13 -5
- package/dist/{frame-handle-plane-DtTRX_0n.js → frame-handle-plane-DQNCTrpC.js} +13 -5
- package/dist/{frame-ring-sink-BbzrqTWj.js → frame-ring-sink-8LLV-cvH.js} +128 -23
- package/dist/{frame-ring-sink-B_NvPTJZ.mjs → frame-ring-sink-ClEWjiRU.mjs} +117 -24
- package/dist/motion-wasm/index.js +55 -3
- package/dist/motion-wasm/index.mjs +55 -3
- package/dist/node-topology-platform-BkR_k6WT.mjs +15 -0
- package/dist/node-topology-platform-CFZ7F4xW.js +20 -0
- package/dist/pipeline-runner/index.js +50 -16
- package/dist/pipeline-runner/index.mjs +50 -16
- package/dist/recorder/index.js +36 -40
- package/dist/recorder/index.mjs +36 -40
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-5tQlh9h4.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DhY3MZ2C.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-BJK0-svt.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtoYGpTp.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DyLqyJaS.mjs → hostInit-B_b3PIZB.mjs} +3 -3
- package/dist/stream-broker/index.js +198 -115
- package/dist/stream-broker/index.mjs +189 -106
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-BDLNwJ_F.js → MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CoLjNiUN.js → MotionZonesSettings-NcxxQN8r-DTeuDGCU.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-DJE3OU-q.js → PrivacyMaskSettings-APgPLF7p-CirtxO3e.js} +1 -1
- package/embed-dist/assets/index-C5UpuPr8.css +2 -0
- package/embed-dist/assets/{index-C-pL8ETk.js → index-CpUy8OIE.js} +10 -10
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/python/inference_pool.py +93 -102
- package/python/test_inference_pool_device_selection.py +43 -38
- package/dist/frame-dropper-7RTo_YyG.js +0 -68
- package/dist/frame-dropper-CwkBTPGV.mjs +0 -51
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CkOPfV8r.mjs +0 -26
- package/embed-dist/assets/index-DrJ0ee3f.css +0 -2
|
@@ -3,9 +3,10 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-DxM2DSns.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_frame_handle_plane = require("../frame-handle-plane-
|
|
8
|
-
const
|
|
6
|
+
const require_dist = require("../dist-Cwc0TUQr.js");
|
|
7
|
+
const require_frame_handle_plane = require("../frame-handle-plane-DQNCTrpC.js");
|
|
8
|
+
const require_ffmpeg_args = require("../ffmpeg-args-D6h1edXK.js");
|
|
9
|
+
const require_frame_dropper = require("../frame-dropper-DKLM6pMz.js");
|
|
9
10
|
let node_crypto = require("node:crypto");
|
|
10
11
|
node_crypto = require_model_download_service_C_IHWnXx.__toESM(node_crypto);
|
|
11
12
|
let node_fs = require("node:fs");
|
|
@@ -2527,7 +2528,7 @@ function planAudioSilencePadPackets(input) {
|
|
|
2527
2528
|
* audio plane honours the profile literally.
|
|
2528
2529
|
*/
|
|
2529
2530
|
function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl) {
|
|
2530
|
-
const args = [...
|
|
2531
|
+
const args = [...require_ffmpeg_args.logBannerArgs("error")];
|
|
2531
2532
|
if (profile.inputArgs?.length) args.push(...profile.inputArgs);
|
|
2532
2533
|
if (decodeHwAccel && decodeHwAccel !== "none") args.push("-hwaccel", decodeHwAccel);
|
|
2533
2534
|
args.push("-fflags", "+discardcorrupt", "-rtsp_transport", "tcp", "-i", sourceUrl);
|
|
@@ -2549,7 +2550,7 @@ function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl
|
|
|
2549
2550
|
if (profile.audio === "passthrough") args.push("-an");
|
|
2550
2551
|
else {
|
|
2551
2552
|
const a = profile.audio;
|
|
2552
|
-
args.push(...
|
|
2553
|
+
args.push(...require_ffmpeg_args.audioEncoderArgs(a.codec, {
|
|
2553
2554
|
bitrateKbps: a.bitrateKbps,
|
|
2554
2555
|
sampleRateHz: a.sampleRateHz,
|
|
2555
2556
|
channels: a.channels
|
|
@@ -5912,24 +5913,6 @@ function toAudioSidecarCodec(audio) {
|
|
|
5912
5913
|
return audio === "aac" || audio === "opus" || audio === "pcmu" ? audio : null;
|
|
5913
5914
|
}
|
|
5914
5915
|
/**
|
|
5915
|
-
* Pick the decode `-hwaccel` backend, gated by what the configured ffmpeg
|
|
5916
|
-
* binary actually supports (`platformProbe.getHardwareDecodeAccels`).
|
|
5917
|
-
*
|
|
5918
|
-
* `preferred` entries are literal `-hwaccel` names (kernel-resolved), so they
|
|
5919
|
-
* intersect directly with the probed method list. Conservative by design:
|
|
5920
|
-
* - empty `preferred` → `null` (software decode);
|
|
5921
|
-
* - empty `supportedMethods` (probe miss / build reports none) → keep the top
|
|
5922
|
-
* preference and let the egress's per-stream software fallback cover failures;
|
|
5923
|
-
* - otherwise return the first preference the build supports, or `null` when it
|
|
5924
|
-
* supports none of them (positive evidence to skip a doomed hw-decode spawn).
|
|
5925
|
-
*/
|
|
5926
|
-
function pickDecodeHwAccel(preferred, supportedMethods) {
|
|
5927
|
-
if (preferred.length === 0) return null;
|
|
5928
|
-
if (supportedMethods.length === 0) return preferred[0] ?? null;
|
|
5929
|
-
const supported = new Set(supportedMethods.map((m) => m.toLowerCase()));
|
|
5930
|
-
return preferred.find((name) => supported.has(name.toLowerCase())) ?? null;
|
|
5931
|
-
}
|
|
5932
|
-
/**
|
|
5933
5916
|
* Pick the video encoder, honouring the `hwAccel` preference: `'none'`/`'copy'`
|
|
5934
5917
|
* (software-decode settings) force the software ENCODER too; otherwise the
|
|
5935
5918
|
* probe's default (hw when available) is used, falling back to software when no
|
|
@@ -5944,26 +5927,80 @@ function pickVideoEncoder(target, encoders, hwAccel) {
|
|
|
5944
5927
|
if (hwAccel === "none" || hwAccel === "copy" || !encoders) return software;
|
|
5945
5928
|
return target === "H264" ? encoders.defaultH264 : encoders.defaultH265;
|
|
5946
5929
|
}
|
|
5930
|
+
/**
|
|
5931
|
+
* The h264/hevc hardware ENCODER ids for a decode-hwaccel backend name — a
|
|
5932
|
+
* static, deterministic map (the same shape Scrypted uses: platform → encoder,
|
|
5933
|
+
* no probe). Consumed by {@link localHardwareEncoders}. `null` for a backend
|
|
5934
|
+
* with no hardware encoder (software / unknown).
|
|
5935
|
+
*/
|
|
5936
|
+
var ENCODER_IDS_BY_BACKEND = {
|
|
5937
|
+
videotoolbox: {
|
|
5938
|
+
h264: "h264_videotoolbox",
|
|
5939
|
+
h265: "hevc_videotoolbox"
|
|
5940
|
+
},
|
|
5941
|
+
vaapi: {
|
|
5942
|
+
h264: "h264_vaapi",
|
|
5943
|
+
h265: "hevc_vaapi"
|
|
5944
|
+
},
|
|
5945
|
+
cuda: {
|
|
5946
|
+
h264: "h264_nvenc",
|
|
5947
|
+
h265: "hevc_nvenc"
|
|
5948
|
+
},
|
|
5949
|
+
nvdec: {
|
|
5950
|
+
h264: "h264_nvenc",
|
|
5951
|
+
h265: "hevc_nvenc"
|
|
5952
|
+
},
|
|
5953
|
+
qsv: {
|
|
5954
|
+
h264: "h264_qsv",
|
|
5955
|
+
h265: "hevc_qsv"
|
|
5956
|
+
},
|
|
5957
|
+
amf: {
|
|
5958
|
+
h264: "h264_amf",
|
|
5959
|
+
h265: "hevc_amf"
|
|
5960
|
+
}
|
|
5961
|
+
};
|
|
5962
|
+
/**
|
|
5963
|
+
* Build the {@link HardwareEncoders} default pair for THIS node's decode-hwaccel
|
|
5964
|
+
* backend — resolved LOCALLY from `ctx.kernel.hwaccel` — WITHOUT probing
|
|
5965
|
+
* `ffmpeg -encoders` via the `platform-probe` singleton (whose per-node
|
|
5966
|
+
* `nodePin` does not survive a forked agent child's link chain → it answers with
|
|
5967
|
+
* the HUB's encoders). Mirrors how Scrypted derives encoders: a static
|
|
5968
|
+
* platform→encoder map. Returns `null` for software / an unmapped backend, which
|
|
5969
|
+
* makes {@link pickVideoEncoder} fall back to the software encoder. Only
|
|
5970
|
+
* `defaultH264`/`defaultH265` are consumed downstream, so `encoders` is left
|
|
5971
|
+
* empty (the per-encoder capability list was never read here).
|
|
5972
|
+
*/
|
|
5973
|
+
function localHardwareEncoders(backend) {
|
|
5974
|
+
if (backend === null) return null;
|
|
5975
|
+
const ids = ENCODER_IDS_BY_BACKEND[backend.toLowerCase()];
|
|
5976
|
+
if (!ids) return null;
|
|
5977
|
+
return {
|
|
5978
|
+
encoders: [],
|
|
5979
|
+
defaultH264: ids.h264,
|
|
5980
|
+
defaultH265: ids.h265,
|
|
5981
|
+
probedAt: Date.now()
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5947
5984
|
/** Audio encoder args for a transcode target (preset values over the shared low-level builder). */
|
|
5948
5985
|
function pickAudioEncoderArgs(audio) {
|
|
5949
5986
|
switch (audio) {
|
|
5950
|
-
case "aac": return
|
|
5987
|
+
case "aac": return require_ffmpeg_args.audioEncoderArgs("aac", {
|
|
5951
5988
|
bitrateKbps: 128,
|
|
5952
5989
|
sampleRateHz: 48e3,
|
|
5953
5990
|
channels: 2
|
|
5954
5991
|
});
|
|
5955
|
-
case "opus": return
|
|
5992
|
+
case "opus": return require_ffmpeg_args.audioEncoderArgs("opus", {
|
|
5956
5993
|
bitrateKbps: 64,
|
|
5957
5994
|
sampleRateHz: 48e3,
|
|
5958
5995
|
channels: 2
|
|
5959
5996
|
});
|
|
5960
|
-
case "pcmu": return
|
|
5997
|
+
case "pcmu": return require_ffmpeg_args.audioEncoderArgs("pcmu", {
|
|
5961
5998
|
sampleRateHz: 8e3,
|
|
5962
5999
|
channels: 1
|
|
5963
6000
|
});
|
|
5964
|
-
case "copy": return
|
|
6001
|
+
case "copy": return require_ffmpeg_args.audioEncoderArgs("copy");
|
|
5965
6002
|
case "none": return ["-an"];
|
|
5966
|
-
default: return
|
|
6003
|
+
default: return require_ffmpeg_args.audioEncoderArgs("aac", { bitrateKbps: 128 });
|
|
5967
6004
|
}
|
|
5968
6005
|
}
|
|
5969
6006
|
/**
|
|
@@ -6028,7 +6065,7 @@ function buildFfmpegArgs$1(inv) {
|
|
|
6028
6065
|
const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
|
|
6029
6066
|
const audioArgs = inv.sink.kind === "stdout" && (inv.sink.container === "h264" || inv.sink.container === "hevc") ? ["-an"] : pickAudioEncoderArgs(inv.audio);
|
|
6030
6067
|
return [
|
|
6031
|
-
...
|
|
6068
|
+
...require_ffmpeg_args.logBannerArgs("warning"),
|
|
6032
6069
|
...decodeArgs,
|
|
6033
6070
|
"-rtsp_transport",
|
|
6034
6071
|
"tcp",
|
|
@@ -11364,7 +11401,7 @@ var TranscodePipelineManager = class {
|
|
|
11364
11401
|
* async egress start so concurrent acquires share one egress. */
|
|
11365
11402
|
pending = /* @__PURE__ */ new Map();
|
|
11366
11403
|
logger;
|
|
11367
|
-
|
|
11404
|
+
hwaccel;
|
|
11368
11405
|
cameraStreamLookup;
|
|
11369
11406
|
rtspEntryLookup;
|
|
11370
11407
|
resolveProfileSource;
|
|
@@ -11375,7 +11412,7 @@ var TranscodePipelineManager = class {
|
|
|
11375
11412
|
acquireChannel;
|
|
11376
11413
|
constructor(opts) {
|
|
11377
11414
|
this.logger = opts.logger;
|
|
11378
|
-
this.
|
|
11415
|
+
this.hwaccel = opts.hwaccel ?? null;
|
|
11379
11416
|
this.cameraStreamLookup = opts.cameraStreamLookup;
|
|
11380
11417
|
this.rtspEntryLookup = opts.rtspEntryLookup;
|
|
11381
11418
|
this.resolveProfileSource = opts.resolveProfileSource ?? null;
|
|
@@ -11576,13 +11613,21 @@ var TranscodePipelineManager = class {
|
|
|
11576
11613
|
if (!stream) throw new Error(`getStreamWithCodec: profile "${profile}" source "${camStreamId}" not published for device ${deviceId}`);
|
|
11577
11614
|
return stream;
|
|
11578
11615
|
}
|
|
11579
|
-
/**
|
|
11616
|
+
/**
|
|
11617
|
+
* The h264/hevc hardware ENCODER defaults for THIS node — derived locally
|
|
11618
|
+
* from `ctx.kernel.hwaccel` (os.platform + GPU-vendor probe) via a static
|
|
11619
|
+
* backend→encoder map, NOT the `platform-probe` singleton's `ffmpeg -encoders`
|
|
11620
|
+
* probe (whose per-node `nodePin` answers with the HUB's encoders). `null` →
|
|
11621
|
+
* software encode (also the outcome when no local resolver is wired). vaapi is
|
|
11622
|
+
* ranked above qsv (qsv is broken on the Intel stack) so an Intel node
|
|
11623
|
+
* transcodes with `h264_vaapi`.
|
|
11624
|
+
*/
|
|
11580
11625
|
async resolveHardwareEncoders() {
|
|
11581
|
-
if (!this.
|
|
11626
|
+
if (!this.hwaccel) return null;
|
|
11582
11627
|
try {
|
|
11583
|
-
return await this.
|
|
11628
|
+
return localHardwareEncoders(require_ffmpeg_args.rankDecodeHwAccels((await this.hwaccel.resolve(null)).preferred, [])[0] ?? null);
|
|
11584
11629
|
} catch (err) {
|
|
11585
|
-
this.logger.warn("
|
|
11630
|
+
this.logger.warn("local hardware-encoder resolve failed — software encode fallback", { meta: { error: require_dist.errMsg(err) } });
|
|
11586
11631
|
return null;
|
|
11587
11632
|
}
|
|
11588
11633
|
}
|
|
@@ -11610,35 +11655,20 @@ var TranscodePipelineManager = class {
|
|
|
11610
11655
|
if (strategy.kind === "software") return null;
|
|
11611
11656
|
return this.resolveSpecificDecodeHwAccel(strategy.prefer);
|
|
11612
11657
|
}
|
|
11613
|
-
/** Gate a specific backend against the probed support list; null on miss/error. */
|
|
11614
|
-
async resolveSpecificDecodeHwAccel(prefer) {
|
|
11615
|
-
if (!this.api) return null;
|
|
11616
|
-
try {
|
|
11617
|
-
const res = await this.api.platformProbe.resolveHwAccel.query({ prefer });
|
|
11618
|
-
const supported = await this.resolveDecodeAccelMethods();
|
|
11619
|
-
const picked = pickDecodeHwAccel(res.preferred, supported);
|
|
11620
|
-
if (picked === null && res.preferred.length > 0) this.logger.info("decode hw-accel: configured ffmpeg supports none of the preferred backends — software decode", { meta: {
|
|
11621
|
-
preferred: res.preferred,
|
|
11622
|
-
supported
|
|
11623
|
-
} });
|
|
11624
|
-
return picked;
|
|
11625
|
-
} catch (err) {
|
|
11626
|
-
this.logger.warn("platformProbe.resolveHwAccel failed — software decode fallback", { meta: { error: require_dist.errMsg(err) } });
|
|
11627
|
-
return null;
|
|
11628
|
-
}
|
|
11629
|
-
}
|
|
11630
11658
|
/**
|
|
11631
|
-
*
|
|
11632
|
-
*
|
|
11633
|
-
*
|
|
11659
|
+
* Resolve a specific operator-forced decode backend against THIS node's local
|
|
11660
|
+
* hwaccel resolver. `ctx.kernel.hwaccel.resolve(prefer)` echoes the forced
|
|
11661
|
+
* backend (re-ranked so vaapi precedes qsv); the egress's per-stream software
|
|
11662
|
+
* fallback still catches a backend the actual ffmpeg build can't drive. Null
|
|
11663
|
+
* when no local resolver is wired.
|
|
11634
11664
|
*/
|
|
11635
|
-
async
|
|
11636
|
-
if (!this.
|
|
11665
|
+
async resolveSpecificDecodeHwAccel(prefer) {
|
|
11666
|
+
if (!this.hwaccel) return null;
|
|
11637
11667
|
try {
|
|
11638
|
-
return (await this.
|
|
11668
|
+
return require_ffmpeg_args.rankDecodeHwAccels((await this.hwaccel.resolve(prefer)).preferred, [])[0] ?? null;
|
|
11639
11669
|
} catch (err) {
|
|
11640
|
-
this.logger.warn("
|
|
11641
|
-
return
|
|
11670
|
+
this.logger.warn("local decode hw-accel resolve failed — software decode fallback", { meta: { error: require_dist.errMsg(err) } });
|
|
11671
|
+
return null;
|
|
11642
11672
|
}
|
|
11643
11673
|
}
|
|
11644
11674
|
shareEntry(entry) {
|
|
@@ -11956,6 +11986,14 @@ var StreamBrokerManager = class {
|
|
|
11956
11986
|
*/
|
|
11957
11987
|
api = null;
|
|
11958
11988
|
/**
|
|
11989
|
+
* This node's LOCAL hwaccel resolver (`ctx.kernel.hwaccel`), wired by
|
|
11990
|
+
* `setApiAccess`. Drives the transcode manager's decode/encode backend picks
|
|
11991
|
+
* and `getHwAccelOptions` from THIS node's hardware in-process — never the
|
|
11992
|
+
* `platform-probe` singleton (whose per-node `nodePin` answers with the hub's
|
|
11993
|
+
* hardware from a forked agent child).
|
|
11994
|
+
*/
|
|
11995
|
+
hwaccel = null;
|
|
11996
|
+
/**
|
|
11959
11997
|
* This broker node's own Moleculer nodeID (`ctx.kernel.localNodeId`). The
|
|
11960
11998
|
* shm frame plane's decoder sessions MUST be co-located on this node — an
|
|
11961
11999
|
* shm frame ring is node-local, so a decoder placed on any OTHER node can
|
|
@@ -11997,7 +12035,7 @@ var StreamBrokerManager = class {
|
|
|
11997
12035
|
getTranscodeManager() {
|
|
11998
12036
|
if (!this.transcodeManager) this.transcodeManager = new TranscodePipelineManager({
|
|
11999
12037
|
logger: this.logger.child("transcode"),
|
|
12000
|
-
|
|
12038
|
+
hwaccel: this.hwaccel ?? void 0,
|
|
12001
12039
|
cameraStreamLookup: (deviceId) => this.getCameraStreamsForDevice(deviceId),
|
|
12002
12040
|
rtspEntryLookup: (brokerId) => {
|
|
12003
12041
|
const entry = this.rtspProvider.getEntry(brokerId);
|
|
@@ -12203,9 +12241,10 @@ var StreamBrokerManager = class {
|
|
|
12203
12241
|
* resolve the `decoder` capability via tRPC — see
|
|
12204
12242
|
* `buildDecoderCapApi` for the full rationale.
|
|
12205
12243
|
*/
|
|
12206
|
-
setApiAccess(api, localNodeId) {
|
|
12244
|
+
setApiAccess(api, localNodeId, hwaccel) {
|
|
12207
12245
|
this.api = api;
|
|
12208
12246
|
if (localNodeId !== void 0) this.localNodeId = localNodeId;
|
|
12247
|
+
if (hwaccel !== void 0) this.hwaccel = hwaccel;
|
|
12209
12248
|
}
|
|
12210
12249
|
/** Update the system `ffmpeg` config snapshot used by the transcode egress. */
|
|
12211
12250
|
setFfmpegConfig(cfg) {
|
|
@@ -12213,11 +12252,11 @@ var StreamBrokerManager = class {
|
|
|
12213
12252
|
}
|
|
12214
12253
|
/**
|
|
12215
12254
|
* The decode `hwaccel` options surfaceable in a (future) UI: the always-valid
|
|
12216
|
-
* settings (`copy`, `auto`, `none`) plus the concrete backends
|
|
12217
|
-
*
|
|
12218
|
-
*
|
|
12219
|
-
*
|
|
12220
|
-
*
|
|
12255
|
+
* settings (`copy`, `auto`, `none`) plus the concrete hardware backends THIS
|
|
12256
|
+
* node can use, resolved from the LOCAL `ctx.kernel.hwaccel` (os.platform +
|
|
12257
|
+
* GPU-vendor probe), ranked so vaapi precedes qsv. Node-local — never the
|
|
12258
|
+
* `platform-probe` singleton (which answers with the hub's methods from a
|
|
12259
|
+
* forked agent broker). Best-effort: no resolver / error → always-valid only.
|
|
12221
12260
|
*/
|
|
12222
12261
|
async getHwAccelOptions() {
|
|
12223
12262
|
const always = [
|
|
@@ -12225,12 +12264,12 @@ var StreamBrokerManager = class {
|
|
|
12225
12264
|
"auto",
|
|
12226
12265
|
"none"
|
|
12227
12266
|
];
|
|
12228
|
-
if (!this.
|
|
12267
|
+
if (!this.hwaccel) return always;
|
|
12229
12268
|
try {
|
|
12230
|
-
const probed = (await this.
|
|
12269
|
+
const probed = require_ffmpeg_args.rankDecodeHwAccels((await this.hwaccel.resolve(null)).preferred, []).filter((m) => FFMPEG_HW_ACCEL_SETTINGS.includes(m));
|
|
12231
12270
|
return [...always, ...probed];
|
|
12232
12271
|
} catch (err) {
|
|
12233
|
-
this.logger.warn("getHwAccelOptions:
|
|
12272
|
+
this.logger.warn("getHwAccelOptions: local hwaccel resolve failed — static options only", { meta: { error: require_dist.errMsg(err) } });
|
|
12234
12273
|
return always;
|
|
12235
12274
|
}
|
|
12236
12275
|
}
|
|
@@ -17061,8 +17100,24 @@ var WebrtcSessionProvider = class WebrtcSessionProvider {
|
|
|
17061
17100
|
};
|
|
17062
17101
|
//#endregion
|
|
17063
17102
|
//#region src/stream-broker/addon.ts
|
|
17064
|
-
|
|
17065
|
-
|
|
17103
|
+
/**
|
|
17104
|
+
* Hydrate the persisted RTSP restream state (`rtspTokens` / `rtspEnabled`)
|
|
17105
|
+
* through the durable `state()` handles — the sanctioned settings accessor —
|
|
17106
|
+
* instead of a raw `readAddonStore()`. Same store keys, same persisted
|
|
17107
|
+
* shapes; a corrupt blob falls back to empty (durable-state semantics) and
|
|
17108
|
+
* absent keys keep first-boot silence (no loader call, no log).
|
|
17109
|
+
*/
|
|
17110
|
+
async function hydratePersistedRtspState(deps) {
|
|
17111
|
+
const tokens = new Map(Object.entries(await deps.tokensState.get()));
|
|
17112
|
+
if (tokens.size > 0) {
|
|
17113
|
+
deps.loadTokens(tokens);
|
|
17114
|
+
deps.logger.info("Loaded persisted RTSP tokens", { meta: { tokenCount: tokens.size } });
|
|
17115
|
+
}
|
|
17116
|
+
const states = new Map(Object.entries(await deps.enabledState.get()));
|
|
17117
|
+
if (states.size > 0) {
|
|
17118
|
+
deps.loadEnabled(states);
|
|
17119
|
+
deps.logger.info("Loaded persisted RTSP enabled states", { meta: { stateCount: states.size } });
|
|
17120
|
+
}
|
|
17066
17121
|
}
|
|
17067
17122
|
var PROFILE_KEYS = [
|
|
17068
17123
|
"high",
|
|
@@ -17153,39 +17208,54 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17153
17208
|
maxDecodeFps: 5,
|
|
17154
17209
|
initialReconnectDelayMs: 1e3,
|
|
17155
17210
|
maxReconnectDelayMs: 3e4,
|
|
17156
|
-
catalogReconcileIntervalSec: 30
|
|
17211
|
+
catalogReconcileIntervalSec: 30,
|
|
17212
|
+
binaryPath: "",
|
|
17213
|
+
hwaccel: "copy"
|
|
17157
17214
|
});
|
|
17158
17215
|
}
|
|
17159
17216
|
/**
|
|
17160
|
-
*
|
|
17161
|
-
*
|
|
17162
|
-
*
|
|
17163
|
-
*
|
|
17164
|
-
*
|
|
17165
|
-
|
|
17217
|
+
* Resolve the ffmpeg binary for THIS node: a per-node `binaryPath` override
|
|
17218
|
+
* (`binaryPath@<node>`) → else `ensureFfmpeg()` which provisions a guaranteed
|
|
17219
|
+
* portable static binary locally (hub / agent / Mac). Replaces the retired
|
|
17220
|
+
* global `ffmpeg` system section (node-local/empty on agents → a cross-node
|
|
17221
|
+
* footgun). Falls back to PATH `ffmpeg` only if the provision genuinely fails.
|
|
17222
|
+
*/
|
|
17223
|
+
async resolveFfmpegBinaryPath() {
|
|
17224
|
+
const override = this.config.binaryPath;
|
|
17225
|
+
if (typeof override === "string" && override.trim().length > 0) return override;
|
|
17226
|
+
try {
|
|
17227
|
+
return await this.ctx.deps.ensureFfmpeg();
|
|
17228
|
+
} catch (err) {
|
|
17229
|
+
this.ctx.logger.error("stream-broker: ensureFfmpeg() failed to provision ffmpeg — falling back to PATH \"ffmpeg\"", { meta: { error: require_dist.errMsg(err) } });
|
|
17230
|
+
return "ffmpeg";
|
|
17231
|
+
}
|
|
17232
|
+
}
|
|
17233
|
+
/**
|
|
17234
|
+
* Push THIS node's ffmpeg config into the broker — all node-local now, NOT the
|
|
17235
|
+
* retired global `ffmpeg` section. `binaryPath` via {@link resolveFfmpegBinaryPath}
|
|
17236
|
+
* (per-node override → ensureFfmpeg); `hwAccel` = the per-node `hwaccel`
|
|
17237
|
+
* preference (default `'copy'` = software, the safe HEVC-compatible choice; the
|
|
17238
|
+
* concrete backend is resolved node-locally in `resolveTranscodeDecodeHwAccel`
|
|
17239
|
+
* / the transcode manager). `threadCount` is always `0` (auto — ffmpeg uses the
|
|
17240
|
+
* node's own cores; the manual knob was removed).
|
|
17166
17241
|
*/
|
|
17167
17242
|
async loadFfmpegConfig() {
|
|
17168
17243
|
if (!this.brokerManager) return;
|
|
17169
17244
|
try {
|
|
17170
|
-
const
|
|
17171
|
-
const
|
|
17172
|
-
const binaryPath = typeof binaryPathRaw === "string" && binaryPathRaw.trim().length > 0 ? binaryPathRaw : "ffmpeg";
|
|
17173
|
-
const hwAccel = isFfmpegHwAccel(section["hwAccel"]) ? section["hwAccel"] : "copy";
|
|
17174
|
-
const threadCountRaw = section["threadCount"];
|
|
17175
|
-
const threadCount = typeof threadCountRaw === "number" && Number.isFinite(threadCountRaw) && threadCountRaw >= 0 ? Math.floor(threadCountRaw) : 0;
|
|
17245
|
+
const binaryPath = await this.resolveFfmpegBinaryPath();
|
|
17246
|
+
const hwAccel = this.config.hwaccel;
|
|
17176
17247
|
this.brokerManager.setFfmpegConfig({
|
|
17177
17248
|
binaryPath,
|
|
17178
17249
|
hwAccel,
|
|
17179
|
-
threadCount
|
|
17250
|
+
threadCount: 0
|
|
17180
17251
|
});
|
|
17181
17252
|
this.ffmpegBinaryPath = binaryPath;
|
|
17182
17253
|
this.ctx.logger.info("Loaded ffmpeg config", { meta: {
|
|
17183
17254
|
binaryPath,
|
|
17184
|
-
hwAccel
|
|
17185
|
-
threadCount
|
|
17255
|
+
hwAccel
|
|
17186
17256
|
} });
|
|
17187
17257
|
} catch (err) {
|
|
17188
|
-
this.ctx.logger.warn("Failed to
|
|
17258
|
+
this.ctx.logger.warn("Failed to load ffmpeg config — using defaults", { meta: { error: require_dist.errMsg(err) } });
|
|
17189
17259
|
}
|
|
17190
17260
|
}
|
|
17191
17261
|
/**
|
|
@@ -17193,20 +17263,19 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17193
17263
|
* configured `ffmpeg.hwAccel` preference (default `'copy'` = software):
|
|
17194
17264
|
* - `'auto'` → the literal `'auto'` (ffmpeg self-selects + software-fallbacks).
|
|
17195
17265
|
* - `'copy'`/`'none'` → `null` (software decode).
|
|
17196
|
-
* - a specific backend →
|
|
17197
|
-
* (
|
|
17266
|
+
* - a specific backend → resolved from THIS node's LOCAL `ctx.kernel.hwaccel`
|
|
17267
|
+
* (ranked so vaapi precedes qsv); the feed still falls back to software on a
|
|
17198
17268
|
* per-stream miss.
|
|
17199
17269
|
* Mirrors the transcode egress's resolution.
|
|
17200
17270
|
*/
|
|
17201
17271
|
async resolveTranscodeDecodeHwAccel() {
|
|
17202
17272
|
try {
|
|
17203
|
-
const
|
|
17204
|
-
const strategy = classifyDecodeHwAccel(isFfmpegHwAccel(section["hwAccel"]) ? section["hwAccel"] : "copy");
|
|
17273
|
+
const strategy = classifyDecodeHwAccel(this.config.hwaccel);
|
|
17205
17274
|
if (strategy.kind === "auto") return "auto";
|
|
17206
17275
|
if (strategy.kind === "software") return null;
|
|
17207
|
-
const
|
|
17208
|
-
|
|
17209
|
-
return
|
|
17276
|
+
const resolver = this.ctx.kernel.hwaccel;
|
|
17277
|
+
if (!resolver) return null;
|
|
17278
|
+
return require_ffmpeg_args.rankDecodeHwAccels((await resolver.resolve(strategy.prefer)).preferred, [])[0] ?? null;
|
|
17210
17279
|
} catch (err) {
|
|
17211
17280
|
this.ctx.logger.warn("resolveTranscodeDecodeHwAccel failed — software decode", { meta: { error: require_dist.errMsg(err) } });
|
|
17212
17281
|
return null;
|
|
@@ -17218,28 +17287,22 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17218
17287
|
this.brokerManager.setEventBus(this.ctx.eventBus);
|
|
17219
17288
|
const localNode = this.ctx.kernel.localNodeId;
|
|
17220
17289
|
const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
|
|
17221
|
-
this.brokerManager.setApiAccess(this.ctx.api, localNodeId);
|
|
17290
|
+
this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
|
|
17222
17291
|
if (this.capabilities) this.brokerManager.setCapabilitiesAccess(this.capabilities);
|
|
17223
17292
|
await this.loadFfmpegConfig();
|
|
17224
17293
|
const rtspProvider = this.brokerManager.getRtspRestreamProvider();
|
|
17294
|
+
const brokerManager = this.brokerManager;
|
|
17295
|
+
const rtspTokensState = this.state("rtspTokens", RtspTokensSchema, {});
|
|
17296
|
+
const rtspEnabledState = this.state("rtspEnabled", RtspEnabledSchema, {});
|
|
17225
17297
|
try {
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
this.
|
|
17232
|
-
|
|
17233
|
-
}
|
|
17234
|
-
const enabledData = require_dist.asJsonObject(addonStore["rtspEnabled"]);
|
|
17235
|
-
if (enabledData) {
|
|
17236
|
-
const states = /* @__PURE__ */ new Map();
|
|
17237
|
-
for (const [k, v] of Object.entries(enabledData)) states.set(k, Boolean(v));
|
|
17238
|
-
rtspProvider.loadPersistedEnabled(states);
|
|
17239
|
-
this.ctx.logger.info("Loaded persisted RTSP enabled states", { meta: { stateCount: states.size } });
|
|
17240
|
-
}
|
|
17298
|
+
await hydratePersistedRtspState({
|
|
17299
|
+
tokensState: rtspTokensState,
|
|
17300
|
+
enabledState: rtspEnabledState,
|
|
17301
|
+
loadTokens: (tokens) => brokerManager.loadPersistedTokens(tokens),
|
|
17302
|
+
loadEnabled: (states) => rtspProvider.loadPersistedEnabled(states),
|
|
17303
|
+
logger: this.ctx.logger
|
|
17304
|
+
});
|
|
17241
17305
|
} catch {}
|
|
17242
|
-
const rtspTokensState = this.state("rtspTokens", RtspTokensSchema, {});
|
|
17243
17306
|
rtspProvider.setTokenPersister(async (tokens) => {
|
|
17244
17307
|
try {
|
|
17245
17308
|
await rtspTokensState.update((prev) => {
|
|
@@ -17252,7 +17315,6 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17252
17315
|
this.ctx.logger.warn("Failed to persist RTSP tokens", { meta: { error: require_dist.errMsg(err) } });
|
|
17253
17316
|
}
|
|
17254
17317
|
});
|
|
17255
|
-
const rtspEnabledState = this.state("rtspEnabled", RtspEnabledSchema, {});
|
|
17256
17318
|
rtspProvider.setEnabledPersister((states) => {
|
|
17257
17319
|
const obj = {};
|
|
17258
17320
|
for (const [k, v] of states) obj[k] = v;
|
|
@@ -17684,6 +17746,27 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17684
17746
|
step: 1,
|
|
17685
17747
|
default: 5
|
|
17686
17748
|
},
|
|
17749
|
+
{
|
|
17750
|
+
type: "select",
|
|
17751
|
+
key: "hwaccel",
|
|
17752
|
+
label: "Transcode hardware acceleration",
|
|
17753
|
+
description: "Per-node decode/encode hwaccel for the WebRTC/restream transcode path. \"Software (copy)\" is the safe default (decodes every HEVC source); a concrete backend is resolved from this node’s own hardware. Per-node because hardware differs per node.",
|
|
17754
|
+
options: [...FFMPEG_HW_ACCEL_SETTINGS].map((s) => ({
|
|
17755
|
+
value: s,
|
|
17756
|
+
label: hwAccelOptionLabel(s)
|
|
17757
|
+
})),
|
|
17758
|
+
default: "copy",
|
|
17759
|
+
perNode: true
|
|
17760
|
+
},
|
|
17761
|
+
{
|
|
17762
|
+
type: "text",
|
|
17763
|
+
key: "binaryPath",
|
|
17764
|
+
label: "ffmpeg binary path (override)",
|
|
17765
|
+
description: "Optional per-node override for the ffmpeg binary. Leave empty to auto-provision a portable static ffmpeg on this node (recommended).",
|
|
17766
|
+
placeholder: "ffmpeg",
|
|
17767
|
+
default: "",
|
|
17768
|
+
perNode: true
|
|
17769
|
+
},
|
|
17687
17770
|
{
|
|
17688
17771
|
type: "number",
|
|
17689
17772
|
key: "catalogReconcileIntervalSec",
|
|
@@ -17753,7 +17836,7 @@ function codecToInputFormat(codec) {
|
|
|
17753
17836
|
function buildFfmpegArgs(config) {
|
|
17754
17837
|
const inputFormat = codecToInputFormat(config.codec);
|
|
17755
17838
|
const args = [
|
|
17756
|
-
...
|
|
17839
|
+
...require_ffmpeg_args.logBannerArgs("error"),
|
|
17757
17840
|
"-fflags",
|
|
17758
17841
|
"+nobuffer+flush_packets",
|
|
17759
17842
|
"-flags",
|