@camstack/addon-pipeline 1.1.28 → 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 +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/audio-codec-ffmpeg/index.js +1 -5
- package/dist/audio-codec-ffmpeg/index.mjs +1 -5
- package/dist/decoder-ffmpeg/index.js +102 -352
- package/dist/decoder-ffmpeg/index.mjs +82 -332
- package/dist/decoder-nodeav/index.js +6 -4
- package/dist/decoder-nodeav/index.mjs +6 -4
- package/dist/detection-pipeline/index.js +204 -180
- package/dist/detection-pipeline/index.mjs +204 -180
- package/dist/{dist-Biq62zt4.js → dist-Cwc0TUQr.js} +7 -42
- package/dist/{dist-C6_wgXqF.mjs → dist-DjuGmyG9.mjs} +7 -42
- 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-BIoY6nRV.mjs → frame-handle-plane-Bkxz-TTD.mjs} +1 -1
- package/dist/{frame-handle-plane-D6BzyEgy.js → frame-handle-plane-DQNCTrpC.js} +1 -1
- package/dist/{frame-ring-sink-9J0wCdLF.js → frame-ring-sink-8LLV-cvH.js} +1 -1
- package/dist/{frame-ring-sink-Cs9vby6v.mjs → frame-ring-sink-ClEWjiRU.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +2 -2
- package/dist/pipeline-runner/index.mjs +2 -2
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DoReAb4y.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs} +3 -3
- package/dist/stream-broker/{hostInit-C0SuwQhL.mjs → hostInit-B_b3PIZB.mjs} +3 -3
- package/dist/stream-broker/index.js +168 -121
- package/dist/stream-broker/index.mjs +159 -112
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Br2yi8ah.js → MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BSCcPdPf.js → MotionZonesSettings-NcxxQN8r-DTeuDGCU.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-ztxvxSjS.js → PrivacyMaskSettings-APgPLF7p-CirtxO3e.js} +1 -1
- package/embed-dist/assets/{index-CeZV1B-2.js → index-CpUy8OIE.js} +9 -9
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/frame-dropper-7RTo_YyG.js +0 -68
- package/dist/frame-dropper-CwkBTPGV.mjs +0 -51
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as errMsg, G as EventCategory, H as CAM_PROFILE_ORDER, J as makeProfileBrokerId, K as createEvent, L as streamBrokerCapability, O as maskUrlCredentials, U as DeviceFeature, V as BaseAddon, W as DeviceType, Y as makeSourceBrokerId, Z as parseProfileBrokerId, at as literal, ct as record, et as _enum, h as addonWidgetsSourceCapability, j as nodePin, l as EncodeProfileSchema, lt as string, nt as boolean, ot as number, p as RingBuffer, rt as discriminatedUnion, st as object, tt as array, ut as union, y as cameraStreamsCapability, z as webrtcSessionCapability } from "../dist-
|
|
3
|
-
import { n as DecoderSessionProxy, t as FrameHandlePlane } from "../frame-handle-plane-
|
|
2
|
+
import { B as errMsg, G as EventCategory, H as CAM_PROFILE_ORDER, J as makeProfileBrokerId, K as createEvent, L as streamBrokerCapability, O as maskUrlCredentials, U as DeviceFeature, V as BaseAddon, W as DeviceType, Y as makeSourceBrokerId, Z as parseProfileBrokerId, at as literal, ct as record, et as _enum, h as addonWidgetsSourceCapability, j as nodePin, l as EncodeProfileSchema, lt as string, nt as boolean, ot as number, p as RingBuffer, rt as discriminatedUnion, st as object, tt as array, ut as union, y as cameraStreamsCapability, z as webrtcSessionCapability } from "../dist-DjuGmyG9.mjs";
|
|
3
|
+
import { n as DecoderSessionProxy, t as FrameHandlePlane } from "../frame-handle-plane-Bkxz-TTD.mjs";
|
|
4
4
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { f as rankDecodeHwAccels, g as logBannerArgs, h as audioEncoderArgs } from "../ffmpeg-args-C5GPp8Cw.mjs";
|
|
6
|
+
import { t as FrameDropper } from "../frame-dropper-AjheBGMG.mjs";
|
|
6
7
|
import { createRequire } from "node:module";
|
|
7
8
|
import * as crypto$1 from "node:crypto";
|
|
8
9
|
import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -5908,24 +5909,6 @@ function toAudioSidecarCodec(audio) {
|
|
|
5908
5909
|
return audio === "aac" || audio === "opus" || audio === "pcmu" ? audio : null;
|
|
5909
5910
|
}
|
|
5910
5911
|
/**
|
|
5911
|
-
* Pick the decode `-hwaccel` backend, gated by what the configured ffmpeg
|
|
5912
|
-
* binary actually supports (`platformProbe.getHardwareDecodeAccels`).
|
|
5913
|
-
*
|
|
5914
|
-
* `preferred` entries are literal `-hwaccel` names (kernel-resolved), so they
|
|
5915
|
-
* intersect directly with the probed method list. Conservative by design:
|
|
5916
|
-
* - empty `preferred` → `null` (software decode);
|
|
5917
|
-
* - empty `supportedMethods` (probe miss / build reports none) → keep the top
|
|
5918
|
-
* preference and let the egress's per-stream software fallback cover failures;
|
|
5919
|
-
* - otherwise return the first preference the build supports, or `null` when it
|
|
5920
|
-
* supports none of them (positive evidence to skip a doomed hw-decode spawn).
|
|
5921
|
-
*/
|
|
5922
|
-
function pickDecodeHwAccel(preferred, supportedMethods) {
|
|
5923
|
-
if (preferred.length === 0) return null;
|
|
5924
|
-
if (supportedMethods.length === 0) return preferred[0] ?? null;
|
|
5925
|
-
const supported = new Set(supportedMethods.map((m) => m.toLowerCase()));
|
|
5926
|
-
return preferred.find((name) => supported.has(name.toLowerCase())) ?? null;
|
|
5927
|
-
}
|
|
5928
|
-
/**
|
|
5929
5912
|
* Pick the video encoder, honouring the `hwAccel` preference: `'none'`/`'copy'`
|
|
5930
5913
|
* (software-decode settings) force the software ENCODER too; otherwise the
|
|
5931
5914
|
* probe's default (hw when available) is used, falling back to software when no
|
|
@@ -5940,6 +5923,60 @@ function pickVideoEncoder(target, encoders, hwAccel) {
|
|
|
5940
5923
|
if (hwAccel === "none" || hwAccel === "copy" || !encoders) return software;
|
|
5941
5924
|
return target === "H264" ? encoders.defaultH264 : encoders.defaultH265;
|
|
5942
5925
|
}
|
|
5926
|
+
/**
|
|
5927
|
+
* The h264/hevc hardware ENCODER ids for a decode-hwaccel backend name — a
|
|
5928
|
+
* static, deterministic map (the same shape Scrypted uses: platform → encoder,
|
|
5929
|
+
* no probe). Consumed by {@link localHardwareEncoders}. `null` for a backend
|
|
5930
|
+
* with no hardware encoder (software / unknown).
|
|
5931
|
+
*/
|
|
5932
|
+
var ENCODER_IDS_BY_BACKEND = {
|
|
5933
|
+
videotoolbox: {
|
|
5934
|
+
h264: "h264_videotoolbox",
|
|
5935
|
+
h265: "hevc_videotoolbox"
|
|
5936
|
+
},
|
|
5937
|
+
vaapi: {
|
|
5938
|
+
h264: "h264_vaapi",
|
|
5939
|
+
h265: "hevc_vaapi"
|
|
5940
|
+
},
|
|
5941
|
+
cuda: {
|
|
5942
|
+
h264: "h264_nvenc",
|
|
5943
|
+
h265: "hevc_nvenc"
|
|
5944
|
+
},
|
|
5945
|
+
nvdec: {
|
|
5946
|
+
h264: "h264_nvenc",
|
|
5947
|
+
h265: "hevc_nvenc"
|
|
5948
|
+
},
|
|
5949
|
+
qsv: {
|
|
5950
|
+
h264: "h264_qsv",
|
|
5951
|
+
h265: "hevc_qsv"
|
|
5952
|
+
},
|
|
5953
|
+
amf: {
|
|
5954
|
+
h264: "h264_amf",
|
|
5955
|
+
h265: "hevc_amf"
|
|
5956
|
+
}
|
|
5957
|
+
};
|
|
5958
|
+
/**
|
|
5959
|
+
* Build the {@link HardwareEncoders} default pair for THIS node's decode-hwaccel
|
|
5960
|
+
* backend — resolved LOCALLY from `ctx.kernel.hwaccel` — WITHOUT probing
|
|
5961
|
+
* `ffmpeg -encoders` via the `platform-probe` singleton (whose per-node
|
|
5962
|
+
* `nodePin` does not survive a forked agent child's link chain → it answers with
|
|
5963
|
+
* the HUB's encoders). Mirrors how Scrypted derives encoders: a static
|
|
5964
|
+
* platform→encoder map. Returns `null` for software / an unmapped backend, which
|
|
5965
|
+
* makes {@link pickVideoEncoder} fall back to the software encoder. Only
|
|
5966
|
+
* `defaultH264`/`defaultH265` are consumed downstream, so `encoders` is left
|
|
5967
|
+
* empty (the per-encoder capability list was never read here).
|
|
5968
|
+
*/
|
|
5969
|
+
function localHardwareEncoders(backend) {
|
|
5970
|
+
if (backend === null) return null;
|
|
5971
|
+
const ids = ENCODER_IDS_BY_BACKEND[backend.toLowerCase()];
|
|
5972
|
+
if (!ids) return null;
|
|
5973
|
+
return {
|
|
5974
|
+
encoders: [],
|
|
5975
|
+
defaultH264: ids.h264,
|
|
5976
|
+
defaultH265: ids.h265,
|
|
5977
|
+
probedAt: Date.now()
|
|
5978
|
+
};
|
|
5979
|
+
}
|
|
5943
5980
|
/** Audio encoder args for a transcode target (preset values over the shared low-level builder). */
|
|
5944
5981
|
function pickAudioEncoderArgs(audio) {
|
|
5945
5982
|
switch (audio) {
|
|
@@ -11360,8 +11397,7 @@ var TranscodePipelineManager = class {
|
|
|
11360
11397
|
* async egress start so concurrent acquires share one egress. */
|
|
11361
11398
|
pending = /* @__PURE__ */ new Map();
|
|
11362
11399
|
logger;
|
|
11363
|
-
|
|
11364
|
-
localNodeId;
|
|
11400
|
+
hwaccel;
|
|
11365
11401
|
cameraStreamLookup;
|
|
11366
11402
|
rtspEntryLookup;
|
|
11367
11403
|
resolveProfileSource;
|
|
@@ -11372,8 +11408,7 @@ var TranscodePipelineManager = class {
|
|
|
11372
11408
|
acquireChannel;
|
|
11373
11409
|
constructor(opts) {
|
|
11374
11410
|
this.logger = opts.logger;
|
|
11375
|
-
this.
|
|
11376
|
-
this.localNodeId = opts.localNodeId ?? null;
|
|
11411
|
+
this.hwaccel = opts.hwaccel ?? null;
|
|
11377
11412
|
this.cameraStreamLookup = opts.cameraStreamLookup;
|
|
11378
11413
|
this.rtspEntryLookup = opts.rtspEntryLookup;
|
|
11379
11414
|
this.resolveProfileSource = opts.resolveProfileSource ?? null;
|
|
@@ -11575,21 +11610,20 @@ var TranscodePipelineManager = class {
|
|
|
11575
11610
|
return stream;
|
|
11576
11611
|
}
|
|
11577
11612
|
/**
|
|
11578
|
-
*
|
|
11579
|
-
*
|
|
11580
|
-
*
|
|
11581
|
-
*
|
|
11613
|
+
* The h264/hevc hardware ENCODER defaults for THIS node — derived locally
|
|
11614
|
+
* from `ctx.kernel.hwaccel` (os.platform + GPU-vendor probe) via a static
|
|
11615
|
+
* backend→encoder map, NOT the `platform-probe` singleton's `ffmpeg -encoders`
|
|
11616
|
+
* probe (whose per-node `nodePin` answers with the HUB's encoders). `null` →
|
|
11617
|
+
* software encode (also the outcome when no local resolver is wired). vaapi is
|
|
11618
|
+
* ranked above qsv (qsv is broken on the Intel stack) so an Intel node
|
|
11619
|
+
* transcodes with `h264_vaapi`.
|
|
11582
11620
|
*/
|
|
11583
|
-
probePin() {
|
|
11584
|
-
return this.localNodeId ? nodePin(this.localNodeId) : void 0;
|
|
11585
|
-
}
|
|
11586
|
-
/** Query the typed, cached hardware-encoder probe; null on any failure. */
|
|
11587
11621
|
async resolveHardwareEncoders() {
|
|
11588
|
-
if (!this.
|
|
11622
|
+
if (!this.hwaccel) return null;
|
|
11589
11623
|
try {
|
|
11590
|
-
return await this.
|
|
11624
|
+
return localHardwareEncoders(rankDecodeHwAccels((await this.hwaccel.resolve(null)).preferred, [])[0] ?? null);
|
|
11591
11625
|
} catch (err) {
|
|
11592
|
-
this.logger.warn("
|
|
11626
|
+
this.logger.warn("local hardware-encoder resolve failed — software encode fallback", { meta: { error: errMsg(err) } });
|
|
11593
11627
|
return null;
|
|
11594
11628
|
}
|
|
11595
11629
|
}
|
|
@@ -11617,35 +11651,20 @@ var TranscodePipelineManager = class {
|
|
|
11617
11651
|
if (strategy.kind === "software") return null;
|
|
11618
11652
|
return this.resolveSpecificDecodeHwAccel(strategy.prefer);
|
|
11619
11653
|
}
|
|
11620
|
-
/** Gate a specific backend against the probed support list; null on miss/error. */
|
|
11621
|
-
async resolveSpecificDecodeHwAccel(prefer) {
|
|
11622
|
-
if (!this.api) return null;
|
|
11623
|
-
try {
|
|
11624
|
-
const res = await this.api.platformProbe.resolveHwAccel.query({ prefer }, this.probePin());
|
|
11625
|
-
const supported = await this.resolveDecodeAccelMethods();
|
|
11626
|
-
const picked = pickDecodeHwAccel(res.preferred, supported);
|
|
11627
|
-
if (picked === null && res.preferred.length > 0) this.logger.info("decode hw-accel: configured ffmpeg supports none of the preferred backends — software decode", { meta: {
|
|
11628
|
-
preferred: res.preferred,
|
|
11629
|
-
supported
|
|
11630
|
-
} });
|
|
11631
|
-
return picked;
|
|
11632
|
-
} catch (err) {
|
|
11633
|
-
this.logger.warn("platformProbe.resolveHwAccel failed — software decode fallback", { meta: { error: errMsg(err) } });
|
|
11634
|
-
return null;
|
|
11635
|
-
}
|
|
11636
|
-
}
|
|
11637
11654
|
/**
|
|
11638
|
-
*
|
|
11639
|
-
*
|
|
11640
|
-
*
|
|
11655
|
+
* Resolve a specific operator-forced decode backend against THIS node's local
|
|
11656
|
+
* hwaccel resolver. `ctx.kernel.hwaccel.resolve(prefer)` echoes the forced
|
|
11657
|
+
* backend (re-ranked so vaapi precedes qsv); the egress's per-stream software
|
|
11658
|
+
* fallback still catches a backend the actual ffmpeg build can't drive. Null
|
|
11659
|
+
* when no local resolver is wired.
|
|
11641
11660
|
*/
|
|
11642
|
-
async
|
|
11643
|
-
if (!this.
|
|
11661
|
+
async resolveSpecificDecodeHwAccel(prefer) {
|
|
11662
|
+
if (!this.hwaccel) return null;
|
|
11644
11663
|
try {
|
|
11645
|
-
return (await this.
|
|
11664
|
+
return rankDecodeHwAccels((await this.hwaccel.resolve(prefer)).preferred, [])[0] ?? null;
|
|
11646
11665
|
} catch (err) {
|
|
11647
|
-
this.logger.warn("
|
|
11648
|
-
return
|
|
11666
|
+
this.logger.warn("local decode hw-accel resolve failed — software decode fallback", { meta: { error: errMsg(err) } });
|
|
11667
|
+
return null;
|
|
11649
11668
|
}
|
|
11650
11669
|
}
|
|
11651
11670
|
shareEntry(entry) {
|
|
@@ -11963,6 +11982,14 @@ var StreamBrokerManager = class {
|
|
|
11963
11982
|
*/
|
|
11964
11983
|
api = null;
|
|
11965
11984
|
/**
|
|
11985
|
+
* This node's LOCAL hwaccel resolver (`ctx.kernel.hwaccel`), wired by
|
|
11986
|
+
* `setApiAccess`. Drives the transcode manager's decode/encode backend picks
|
|
11987
|
+
* and `getHwAccelOptions` from THIS node's hardware in-process — never the
|
|
11988
|
+
* `platform-probe` singleton (whose per-node `nodePin` answers with the hub's
|
|
11989
|
+
* hardware from a forked agent child).
|
|
11990
|
+
*/
|
|
11991
|
+
hwaccel = null;
|
|
11992
|
+
/**
|
|
11966
11993
|
* This broker node's own Moleculer nodeID (`ctx.kernel.localNodeId`). The
|
|
11967
11994
|
* shm frame plane's decoder sessions MUST be co-located on this node — an
|
|
11968
11995
|
* shm frame ring is node-local, so a decoder placed on any OTHER node can
|
|
@@ -12004,8 +12031,7 @@ var StreamBrokerManager = class {
|
|
|
12004
12031
|
getTranscodeManager() {
|
|
12005
12032
|
if (!this.transcodeManager) this.transcodeManager = new TranscodePipelineManager({
|
|
12006
12033
|
logger: this.logger.child("transcode"),
|
|
12007
|
-
|
|
12008
|
-
localNodeId: this.localNodeId ?? void 0,
|
|
12034
|
+
hwaccel: this.hwaccel ?? void 0,
|
|
12009
12035
|
cameraStreamLookup: (deviceId) => this.getCameraStreamsForDevice(deviceId),
|
|
12010
12036
|
rtspEntryLookup: (brokerId) => {
|
|
12011
12037
|
const entry = this.rtspProvider.getEntry(brokerId);
|
|
@@ -12211,9 +12237,10 @@ var StreamBrokerManager = class {
|
|
|
12211
12237
|
* resolve the `decoder` capability via tRPC — see
|
|
12212
12238
|
* `buildDecoderCapApi` for the full rationale.
|
|
12213
12239
|
*/
|
|
12214
|
-
setApiAccess(api, localNodeId) {
|
|
12240
|
+
setApiAccess(api, localNodeId, hwaccel) {
|
|
12215
12241
|
this.api = api;
|
|
12216
12242
|
if (localNodeId !== void 0) this.localNodeId = localNodeId;
|
|
12243
|
+
if (hwaccel !== void 0) this.hwaccel = hwaccel;
|
|
12217
12244
|
}
|
|
12218
12245
|
/** Update the system `ffmpeg` config snapshot used by the transcode egress. */
|
|
12219
12246
|
setFfmpegConfig(cfg) {
|
|
@@ -12221,11 +12248,11 @@ var StreamBrokerManager = class {
|
|
|
12221
12248
|
}
|
|
12222
12249
|
/**
|
|
12223
12250
|
* The decode `hwaccel` options surfaceable in a (future) UI: the always-valid
|
|
12224
|
-
* settings (`copy`, `auto`, `none`) plus the concrete backends
|
|
12225
|
-
*
|
|
12226
|
-
*
|
|
12227
|
-
*
|
|
12228
|
-
*
|
|
12251
|
+
* settings (`copy`, `auto`, `none`) plus the concrete hardware backends THIS
|
|
12252
|
+
* node can use, resolved from the LOCAL `ctx.kernel.hwaccel` (os.platform +
|
|
12253
|
+
* GPU-vendor probe), ranked so vaapi precedes qsv. Node-local — never the
|
|
12254
|
+
* `platform-probe` singleton (which answers with the hub's methods from a
|
|
12255
|
+
* forked agent broker). Best-effort: no resolver / error → always-valid only.
|
|
12229
12256
|
*/
|
|
12230
12257
|
async getHwAccelOptions() {
|
|
12231
12258
|
const always = [
|
|
@@ -12233,13 +12260,12 @@ var StreamBrokerManager = class {
|
|
|
12233
12260
|
"auto",
|
|
12234
12261
|
"none"
|
|
12235
12262
|
];
|
|
12236
|
-
if (!this.
|
|
12263
|
+
if (!this.hwaccel) return always;
|
|
12237
12264
|
try {
|
|
12238
|
-
const
|
|
12239
|
-
const probed = (await this.api.platformProbe.getHardwareDecodeAccels.query(void 0, pin)).methods.filter((m) => FFMPEG_HW_ACCEL_SETTINGS.includes(m));
|
|
12265
|
+
const probed = rankDecodeHwAccels((await this.hwaccel.resolve(null)).preferred, []).filter((m) => FFMPEG_HW_ACCEL_SETTINGS.includes(m));
|
|
12240
12266
|
return [...always, ...probed];
|
|
12241
12267
|
} catch (err) {
|
|
12242
|
-
this.logger.warn("getHwAccelOptions:
|
|
12268
|
+
this.logger.warn("getHwAccelOptions: local hwaccel resolve failed — static options only", { meta: { error: errMsg(err) } });
|
|
12243
12269
|
return always;
|
|
12244
12270
|
}
|
|
12245
12271
|
}
|
|
@@ -17070,9 +17096,6 @@ var WebrtcSessionProvider = class WebrtcSessionProvider {
|
|
|
17070
17096
|
};
|
|
17071
17097
|
//#endregion
|
|
17072
17098
|
//#region src/stream-broker/addon.ts
|
|
17073
|
-
function isFfmpegHwAccel(v) {
|
|
17074
|
-
return typeof v === "string" && FFMPEG_HW_ACCEL_SETTINGS.includes(v);
|
|
17075
|
-
}
|
|
17076
17099
|
/**
|
|
17077
17100
|
* Hydrate the persisted RTSP restream state (`rtspTokens` / `rtspEnabled`)
|
|
17078
17101
|
* through the durable `state()` handles — the sanctioned settings accessor —
|
|
@@ -17156,15 +17179,6 @@ var StreamBrokerAddon = class extends BaseAddon {
|
|
|
17156
17179
|
/** Handle for the embed SPA data-plane listener (disposed on shutdown). */
|
|
17157
17180
|
embedDataPlane = null;
|
|
17158
17181
|
/**
|
|
17159
|
-
* This broker's bare cluster node id (`ctx.kernel.localNodeId` with any
|
|
17160
|
-
* `node/addon` fork suffix stripped), computed once in onInitialize.
|
|
17161
|
-
* `platform-probe` is a `singleton` cap, so every probe read (transcode
|
|
17162
|
-
* decode hw-accel) MUST be pinned here — an unpinned call from an agent
|
|
17163
|
-
* child resolves to the HUB's probe (the wrong hardware). `undefined` when
|
|
17164
|
-
* the kernel id is unknown → legacy unpinned behaviour.
|
|
17165
|
-
*/
|
|
17166
|
-
localNodeId;
|
|
17167
|
-
/**
|
|
17168
17182
|
* Snapshot-equality cache for the broker-metrics emit. Each
|
|
17169
17183
|
* broker emits a stats snapshot every BROKER_METRICS_SNAPSHOT_-
|
|
17170
17184
|
* INTERVAL_MS; for an idle broker (status=streaming, fps=0,
|
|
@@ -17190,39 +17204,54 @@ var StreamBrokerAddon = class extends BaseAddon {
|
|
|
17190
17204
|
maxDecodeFps: 5,
|
|
17191
17205
|
initialReconnectDelayMs: 1e3,
|
|
17192
17206
|
maxReconnectDelayMs: 3e4,
|
|
17193
|
-
catalogReconcileIntervalSec: 30
|
|
17207
|
+
catalogReconcileIntervalSec: 30,
|
|
17208
|
+
binaryPath: "",
|
|
17209
|
+
hwaccel: "copy"
|
|
17194
17210
|
});
|
|
17195
17211
|
}
|
|
17196
17212
|
/**
|
|
17197
|
-
*
|
|
17198
|
-
*
|
|
17199
|
-
*
|
|
17200
|
-
*
|
|
17201
|
-
*
|
|
17202
|
-
|
|
17213
|
+
* Resolve the ffmpeg binary for THIS node: a per-node `binaryPath` override
|
|
17214
|
+
* (`binaryPath@<node>`) → else `ensureFfmpeg()` which provisions a guaranteed
|
|
17215
|
+
* portable static binary locally (hub / agent / Mac). Replaces the retired
|
|
17216
|
+
* global `ffmpeg` system section (node-local/empty on agents → a cross-node
|
|
17217
|
+
* footgun). Falls back to PATH `ffmpeg` only if the provision genuinely fails.
|
|
17218
|
+
*/
|
|
17219
|
+
async resolveFfmpegBinaryPath() {
|
|
17220
|
+
const override = this.config.binaryPath;
|
|
17221
|
+
if (typeof override === "string" && override.trim().length > 0) return override;
|
|
17222
|
+
try {
|
|
17223
|
+
return await this.ctx.deps.ensureFfmpeg();
|
|
17224
|
+
} catch (err) {
|
|
17225
|
+
this.ctx.logger.error("stream-broker: ensureFfmpeg() failed to provision ffmpeg — falling back to PATH \"ffmpeg\"", { meta: { error: errMsg(err) } });
|
|
17226
|
+
return "ffmpeg";
|
|
17227
|
+
}
|
|
17228
|
+
}
|
|
17229
|
+
/**
|
|
17230
|
+
* Push THIS node's ffmpeg config into the broker — all node-local now, NOT the
|
|
17231
|
+
* retired global `ffmpeg` section. `binaryPath` via {@link resolveFfmpegBinaryPath}
|
|
17232
|
+
* (per-node override → ensureFfmpeg); `hwAccel` = the per-node `hwaccel`
|
|
17233
|
+
* preference (default `'copy'` = software, the safe HEVC-compatible choice; the
|
|
17234
|
+
* concrete backend is resolved node-locally in `resolveTranscodeDecodeHwAccel`
|
|
17235
|
+
* / the transcode manager). `threadCount` is always `0` (auto — ffmpeg uses the
|
|
17236
|
+
* node's own cores; the manual knob was removed).
|
|
17203
17237
|
*/
|
|
17204
17238
|
async loadFfmpegConfig() {
|
|
17205
17239
|
if (!this.brokerManager) return;
|
|
17206
17240
|
try {
|
|
17207
|
-
const
|
|
17208
|
-
const
|
|
17209
|
-
const binaryPath = typeof binaryPathRaw === "string" && binaryPathRaw.trim().length > 0 ? binaryPathRaw : "ffmpeg";
|
|
17210
|
-
const hwAccel = isFfmpegHwAccel(section["hwAccel"]) ? section["hwAccel"] : "copy";
|
|
17211
|
-
const threadCountRaw = section["threadCount"];
|
|
17212
|
-
const threadCount = typeof threadCountRaw === "number" && Number.isFinite(threadCountRaw) && threadCountRaw >= 0 ? Math.floor(threadCountRaw) : 0;
|
|
17241
|
+
const binaryPath = await this.resolveFfmpegBinaryPath();
|
|
17242
|
+
const hwAccel = this.config.hwaccel;
|
|
17213
17243
|
this.brokerManager.setFfmpegConfig({
|
|
17214
17244
|
binaryPath,
|
|
17215
17245
|
hwAccel,
|
|
17216
|
-
threadCount
|
|
17246
|
+
threadCount: 0
|
|
17217
17247
|
});
|
|
17218
17248
|
this.ffmpegBinaryPath = binaryPath;
|
|
17219
17249
|
this.ctx.logger.info("Loaded ffmpeg config", { meta: {
|
|
17220
17250
|
binaryPath,
|
|
17221
|
-
hwAccel
|
|
17222
|
-
threadCount
|
|
17251
|
+
hwAccel
|
|
17223
17252
|
} });
|
|
17224
17253
|
} catch (err) {
|
|
17225
|
-
this.ctx.logger.warn("Failed to
|
|
17254
|
+
this.ctx.logger.warn("Failed to load ffmpeg config — using defaults", { meta: { error: errMsg(err) } });
|
|
17226
17255
|
}
|
|
17227
17256
|
}
|
|
17228
17257
|
/**
|
|
@@ -17230,21 +17259,19 @@ var StreamBrokerAddon = class extends BaseAddon {
|
|
|
17230
17259
|
* configured `ffmpeg.hwAccel` preference (default `'copy'` = software):
|
|
17231
17260
|
* - `'auto'` → the literal `'auto'` (ffmpeg self-selects + software-fallbacks).
|
|
17232
17261
|
* - `'copy'`/`'none'` → `null` (software decode).
|
|
17233
|
-
* - a specific backend →
|
|
17234
|
-
* (
|
|
17262
|
+
* - a specific backend → resolved from THIS node's LOCAL `ctx.kernel.hwaccel`
|
|
17263
|
+
* (ranked so vaapi precedes qsv); the feed still falls back to software on a
|
|
17235
17264
|
* per-stream miss.
|
|
17236
17265
|
* Mirrors the transcode egress's resolution.
|
|
17237
17266
|
*/
|
|
17238
17267
|
async resolveTranscodeDecodeHwAccel() {
|
|
17239
17268
|
try {
|
|
17240
|
-
const
|
|
17241
|
-
const strategy = classifyDecodeHwAccel(isFfmpegHwAccel(section["hwAccel"]) ? section["hwAccel"] : "copy");
|
|
17269
|
+
const strategy = classifyDecodeHwAccel(this.config.hwaccel);
|
|
17242
17270
|
if (strategy.kind === "auto") return "auto";
|
|
17243
17271
|
if (strategy.kind === "software") return null;
|
|
17244
|
-
const
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
return pickDecodeHwAccel(res.preferred, supported.methods);
|
|
17272
|
+
const resolver = this.ctx.kernel.hwaccel;
|
|
17273
|
+
if (!resolver) return null;
|
|
17274
|
+
return rankDecodeHwAccels((await resolver.resolve(strategy.prefer)).preferred, [])[0] ?? null;
|
|
17248
17275
|
} catch (err) {
|
|
17249
17276
|
this.ctx.logger.warn("resolveTranscodeDecodeHwAccel failed — software decode", { meta: { error: errMsg(err) } });
|
|
17250
17277
|
return null;
|
|
@@ -17256,8 +17283,7 @@ var StreamBrokerAddon = class extends BaseAddon {
|
|
|
17256
17283
|
this.brokerManager.setEventBus(this.ctx.eventBus);
|
|
17257
17284
|
const localNode = this.ctx.kernel.localNodeId;
|
|
17258
17285
|
const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
|
|
17259
|
-
this.localNodeId
|
|
17260
|
-
this.brokerManager.setApiAccess(this.ctx.api, localNodeId);
|
|
17286
|
+
this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
|
|
17261
17287
|
if (this.capabilities) this.brokerManager.setCapabilitiesAccess(this.capabilities);
|
|
17262
17288
|
await this.loadFfmpegConfig();
|
|
17263
17289
|
const rtspProvider = this.brokerManager.getRtspRestreamProvider();
|
|
@@ -17716,6 +17742,27 @@ var StreamBrokerAddon = class extends BaseAddon {
|
|
|
17716
17742
|
step: 1,
|
|
17717
17743
|
default: 5
|
|
17718
17744
|
},
|
|
17745
|
+
{
|
|
17746
|
+
type: "select",
|
|
17747
|
+
key: "hwaccel",
|
|
17748
|
+
label: "Transcode hardware acceleration",
|
|
17749
|
+
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.",
|
|
17750
|
+
options: [...FFMPEG_HW_ACCEL_SETTINGS].map((s) => ({
|
|
17751
|
+
value: s,
|
|
17752
|
+
label: hwAccelOptionLabel(s)
|
|
17753
|
+
})),
|
|
17754
|
+
default: "copy",
|
|
17755
|
+
perNode: true
|
|
17756
|
+
},
|
|
17757
|
+
{
|
|
17758
|
+
type: "text",
|
|
17759
|
+
key: "binaryPath",
|
|
17760
|
+
label: "ffmpeg binary path (override)",
|
|
17761
|
+
description: "Optional per-node override for the ffmpeg binary. Leave empty to auto-provision a portable static ffmpeg on this node (recommended).",
|
|
17762
|
+
placeholder: "ffmpeg",
|
|
17763
|
+
default: "",
|
|
17764
|
+
perNode: true
|
|
17765
|
+
},
|
|
17719
17766
|
{
|
|
17720
17767
|
type: "number",
|
|
17721
17768
|
key: "catalogReconcileIntervalSec",
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-
|
|
1
|
+
import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-CpUy8OIE.js";var o=n(t()),s=n(e(),1),c=Math.PI/180;function l(){return typeof window<`u`&&({}.toString.call(window)===`[object Window]`||{}.toString.call(window)===`[object global]`)}var u=typeof global<`u`?global:typeof window<`u`?window:typeof WorkerGlobalScope<`u`?self:{},d={_global:u,version:`10.3.0`,isBrowser:l(),isUnminified:/param/.test(function(e){}.toString()),dblClickWindow:400,getAngle(e){return d.angleDeg?e*c:e},enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_renderBackend:`web`,legacyTextRendering:!1,pixelRatio:typeof window<`u`&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging(){return d.DD.isDragging},isTransforming(){return d.Transformer?.isTransforming()??!1},isDragReady(){return!!d.DD.node},releaseCanvasOnDestroy:!0,document:u.document,_injectGlobal(e){u.Konva!==void 0&&console.error(`Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.`),u.Konva=e}},f=e=>{d[e.prototype.getClassName()]=e};d._injectGlobal(d);var p=`Konva.js unsupported environment.
|
|
2
2
|
|
|
3
3
|
Looks like you are trying to use Konva.js in Node.js environment. because "document" object is undefined.
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-CpUy8OIE.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js";var d=i(r(),1),f=i(n(),1),p=o(`grid-2x2`,[[`path`,{d:`M12 3v18`,key:`108xh3`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}]]),m=110,h=`motion-zones`,g=0,_=[1,2,3],v=1,y=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,b=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function x(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function S(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function C(e,t){return Math.ceil(e/t)}function w(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function T(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+w(n,t,i)]=!0)}return o}function E(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+w(n,t,i)]===!0}return o}function D({deviceId:n}){let r=t(l().trpcClient,n),[i,o]=(0,d.useState)(null),[w,D]=(0,d.useState)(!1),[O,k]=(0,d.useState)(null),[A,j]=(0,d.useState)(null),[M,N]=(0,d.useState)(v),[P,F]=(0,d.useState)(!1),[I,L]=(0,d.useState)(!1),R=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(!r)return;let e=!1;return R.current=!1,o(null),D(!1),k(null),j(null),N(v),L(!1),(async()=>{try{let t=await r.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(o(t),R.current)return;let n=await r.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);R.current=!0;let i=n.regions.find(e=>e.shape.kind===`grid`),a=x(i?i.shape.cells:[],t.grid.width,t.grid.height);j(a),k(T(a,t.grid.width,t.grid.height,v))}catch(t){if(e)return;c(t)?D(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[r]);let z=i?i.grid.width:0,B=i?i.grid.height:0,V=C(z,M),H=C(B,M),U=(0,d.useMemo)(()=>O&&i?E(O,z,B,M):null,[O,i,z,B,M]),W=(0,d.useMemo)(()=>U!==null&&A!==null&&!S(U,A),[U,A]),G=(0,d.useMemo)(()=>U?U.reduce((e,t)=>t?e+1:e,0):0,[U]),K=z*B,q=V*H,J=(0,d.useCallback)(e=>{q>0&&k(Array.from({length:q},()=>e))},[q]),Y=(0,d.useCallback)(()=>{k(e=>e&&e.map(e=>!e))},[]),X=(0,d.useCallback)(()=>{A&&i&&k(T(A,z,B,M))},[A,i,z,B,M]),Z=(0,d.useCallback)(e=>{e===M||!i||k(t=>{if(!t)return N(e),t;let n=T(E(t,z,B,M),z,B,e);return N(e),n})},[M,i,z,B]),Q=(0,d.useMemo)(()=>i&&O?[{id:g,shape:{kind:`grid`,gridWidth:V,gridHeight:H,cells:[...O]}}]:[],[i,O,V,H]),$=(0,d.useCallback)((e,t)=>{t.kind===`grid`&&k(t.cells)},[]),ee=(0,d.useCallback)(async()=>{if(!(!r||!O||!i)){F(!0);try{let e=E(O,i.grid.width,i.grid.height,M),t={kind:`grid`,gridWidth:i.grid.width,gridHeight:i.grid.height,cells:e};await r.motionZones?.setZone({patch:{regions:[{id:g,enabled:!0,shape:t}]}});let n=await r.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=x(e?e.shape.cells:[],i.grid.width,i.grid.height);j(t),k(T(t,i.grid.width,i.grid.height,M))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{F(!1)}}},[r,O,i,M]);a((0,d.useMemo)(()=>I&&!w&&i&&O?{id:h,order:m,node:(0,f.jsx)(u,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:V,height:H},selectedId:g,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[I,w,i,O,Q,$,V,H]));let te=!w&&i!==null&&O!==null;return r?(0,f.jsx)(e,{title:`Motion Zones`,icon:(0,f.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,f.jsx)(`div`,{className:`flex flex-col gap-3`,children:w?(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`This camera doesn't expose an on-board motion zones grid.`}):te?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`p`,{className:`${s} leading-relaxed`,children:[`Toggle `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Edit grid`}),` to paint the region directly on the live frame, then `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push the mask to the camera. Pick a bigger`,` `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Cell size`}),` for quicker, broad-stroke painting — it's resampled to the camera's native `,i.grid.width,`×`,i.grid.height,` grid on save (×1 is the finest).`]}),(0,f.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>L(e=>!e),disabled:P,"aria-pressed":I,className:I?b:y,children:I?`Done editing`:`Edit grid`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!0),disabled:P,className:y,children:`All on`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!1),disabled:P,className:y,children:`All off`}),(0,f.jsx)(`button`,{type:`button`,onClick:Y,disabled:P,className:y,children:`Invert`}),(0,f.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":`Cell size`,children:[(0,f.jsx)(`span`,{className:`${s} mr-0.5`,children:`Cell size`}),_.map(e=>(0,f.jsxs)(`button`,{type:`button`,onClick:()=>Z(e),disabled:P,"aria-pressed":M===e,title:e===1?`Camera grid ${z}×${B} (finest)`:`${C(z,e)}×${C(B,e)} painting grid · cells ×${e} bigger`,className:M===e?b:y,children:[`×`,e]},e))]}),(0,f.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[G,` / `,K,` cells · `,i.grid.width,`×`,i.grid.height,M===1?``:` · paint ${V}×${H}`]}),(0,f.jsx)(`span`,{className:`flex-1`}),(0,f.jsx)(`button`,{type:`button`,onClick:X,disabled:P||!W,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:`Revert`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void ee(),disabled:P||!W,className:b,children:P?`Saving…`:`Save`})]})]}):(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`Loading the camera's grid…`})})}):null}export{D as MotionZonesSettings};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-CpUy8OIE.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js";var f=i(r(),1),p=i(n(),1),m=c(`hexagon`,[[`path`,{d:`M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z`,key:`yt0hxn`}]]),h=120,g=`privacy-mask`,_=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,v=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function y(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function b(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function x(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(JSON.stringify(e[n])!==JSON.stringify(t[n]))return!1;return!0}function S({deviceId:n}){let r=t(ee().trpcClient,n),[i,c]=(0,f.useState)(null),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(!1),[M,N]=(0,f.useState)(null),[P,F]=(0,f.useState)(null),I=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!r)return;let e=!1;return I.current=!1,c(null),C(!1),T(null),D(null),j(!1),N(null),F(null),(async()=>{try{let t=await r.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(c(t),I.current)return;let n=await r.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);I.current=!0;let i={enabled:n.enabled,regions:n.regions};D(i),T(i)}catch(t){if(e)return;u(t)?C(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[r]);let L=(0,f.useMemo)(()=>w!==null&&E!==null&&(w.enabled!==E.enabled||!x(w.regions,E.regions)),[w,E]),R=w?w.regions.length:0,z=i?i.maxRegions:0,B=(0,f.useRef)(0);(0,f.useEffect)(()=>{B.current=z},[z]);let V=z>0&&R>=z,H=(0,f.useCallback)(()=>{T(e=>e&&{...e,enabled:!e.enabled})},[]),U=(0,f.useCallback)(e=>{N(typeof e==`number`?e:null)},[]),W=(0,f.useMemo)(()=>w?w.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:`Zone ${String(e.id)}`})):[],[w]),G=(0,f.useCallback)((e,t)=>{let n=y(t);n&&T(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),K=(0,f.useCallback)(e=>{let t=y(e);t&&(F(null),T(e=>{if(!e)return e;let n=B.current;if(n>0&&e.regions.length>=n)return e;let r=b(e.regions),i={id:r,enabled:!0,shape:t};return N(r),{...e,regions:[...e.regions,i]}}))},[]),q=(0,f.useCallback)(e=>{j(!0),N(null),F(e)},[]),J=(0,f.useCallback)(e=>{j(!0),F(null),N(e)},[]),Y=(0,f.useCallback)(e=>{N(t=>t===e?null:t),T(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),X=(0,f.useCallback)(()=>{F(null),N(null),E&&T({enabled:E.enabled,regions:E.regions})},[E]),Z=(0,f.useCallback)(async()=>{if(!(!r||!w)){k(!0);try{await r.privacyMask?.setMask({patch:{enabled:w.enabled,regions:[...w.regions]}});let e=await r.privacyMask?.getStatus({});if(e){let t={enabled:e.enabled,regions:e.regions};D(t),T(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{k(!1)}}},[r,w]),ne=(0,f.useCallback)(()=>{j(e=>(e&&(F(null),N(null)),!e))},[]),Q=i?.supportedShapes??[];o((0,f.useMemo)(()=>A&&!S&&i&&w?{id:g,order:h,node:(0,p.jsx)(te,{transparent:!0,items:W,supportedShapes:Q,polygonVertices:i.polygonVertices,selectedId:M,onSelect:U,onShapeChange:G,onDrawComplete:K,drawingKind:P})}:null,[A,S,i,w,W,Q,M,U,G,K,P]));let re=i?.supportedShapes.includes(`rect`)??!1,ie=i?.supportedShapes.includes(`polygon`)??!1,$=i!==null&&(i.maxRegions<=0||i.supportedShapes.length===0),ae=!S&&!$&&i!==null&&w!==null;return r?(0,p.jsx)(e,{title:`Privacy Mask`,icon:(0,p.jsx)(d,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,p.jsx)(`div`,{className:`flex flex-col gap-3`,children:S||$?(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`This camera doesn't support an on-board privacy mask.`}):ae?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`p`,{className:`${l} leading-relaxed`,children:[`Toggle `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Edit mask`}),` to draw blanked-out zones on the live frame, then `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push them to the camera. Drag a rectangle to move, its corner to resize; drag polygon vertices, click an edge midpoint to add one, or right-click a vertex to remove it.`]}),(0,p.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,p.jsx)(`button`,{type:`button`,onClick:ne,disabled:O,"aria-pressed":A,className:A?v:_,children:A?`Done editing`:`Edit mask`}),(0,p.jsx)(`button`,{type:`button`,onClick:H,disabled:O,"aria-pressed":w.enabled,className:w.enabled?v:_,children:w.enabled?`Mask on`:`Mask off`}),re&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`rect`),disabled:O||V,"aria-pressed":P===`rect`,className:P===`rect`?v:_,title:V?`Maximum zones reached`:`Add a rectangle zone`,children:`+ Rect`}),ie&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`polygon`),disabled:O||V,"aria-pressed":P===`polygon`,className:P===`polygon`?v:_,title:V?`Maximum zones reached`:`Add a polygon zone`,children:`+ Polygon`}),(0,p.jsxs)(`span`,{className:`${l} ml-1 tabular-nums`,children:[R,` / `,z,` zones`]}),(0,p.jsx)(`span`,{className:`flex-1`}),(0,p.jsx)(`button`,{type:`button`,onClick:X,disabled:O||!L,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:`Revert`}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>void Z(),disabled:O||!L,className:v,children:O?`Saving…`:`Save`})]}),R>0?(0,p.jsx)(`div`,{className:`flex flex-col gap-1`,children:w.regions.map(e=>{let t=M===e.id,n=e.shape.kind===`polygon`?m:s;return(0,p.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md border px-2 py-1 transition-colors ${t?`border-primary/50 bg-primary/10`:`border-border bg-surface`}`,children:[(0,p.jsxs)(`button`,{type:`button`,onClick:()=>J(e.id),disabled:O,className:`flex flex-1 items-center gap-2 text-left text-[11px] font-medium text-foreground-subtle hover:text-foreground disabled:opacity-40 transition-colors`,children:[(0,p.jsx)(n,{className:`h-3.5 w-3.5 shrink-0`}),(0,p.jsxs)(`span`,{children:[`Zone `,e.id]}),(0,p.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>Y(e.id),disabled:O,"aria-label":`Delete zone ${String(e.id)}`,title:`Delete zone`,className:`inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-surface text-foreground-subtle hover:border-red-400/40 hover:bg-red-500/10 hover:text-red-400 disabled:opacity-40 transition-colors`,children:(0,p.jsx)(a,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`Loading the camera's privacy mask…`})})}):null}export{S as PrivacyMaskSettings};
|