@camstack/addon-pipeline 1.2.45 → 1.2.46
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/detection-pipeline/index.js +43 -13
- package/dist/detection-pipeline/index.mjs +43 -13
- package/dist/{dist-QyZ7QSRP.js → dist-CPfFUIl9.js} +379 -3
- package/dist/{dist-BDkEhx4Z.mjs → dist-CUj9mdhh.mjs} +379 -3
- package/dist/{event-loop-stall-monitor-COEKVBQK.js → event-loop-stall-monitor-BGbgcrEN.js} +7 -1
- package/dist/{event-loop-stall-monitor-IAuNEdmG.mjs → event-loop-stall-monitor-md__iwWh.mjs} +7 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +15 -6
- package/dist/pipeline-runner/index.mjs +15 -6
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +1 -1
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C68YfSUH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-aUWVCncO.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DN2e2GJL.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-B3GxBnXM.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-2D_L2y3g.mjs} +1 -1
- package/dist/stream-broker/{hostInit-BMCrULpc.mjs → hostInit-CRwWRwJH.mjs} +2 -2
- package/dist/stream-broker/index.js +262 -8
- package/dist/stream-broker/index.mjs +262 -8
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DFH_z2-q.mjs → worker-protocol-CD0M9YD7.mjs} +1 -1
- package/dist/{worker-protocol-le27Mw_8.js → worker-protocol-hdTR-F5q.js} +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C7bsz4cP.js → MaskShapeCanvas-DI4BY7W2-C8eGXtf-.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BmhLinUd.js → MotionZonesSettings-NcxxQN8r-vvEcAgxS.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BJjyGWFy.js → PrivacyMaskSettings-APgPLF7p-BLhx7VI-.js} +1 -1
- package/embed-dist/assets/{index-a4CMt-Uc.js → index-DvOgpqcE.js} +12 -12
- package/embed-dist/assets/index-ICnBEqOy.css +2 -0
- package/embed-dist/index.html +2 -2
- 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-CT2bGWqM.mjs +0 -26
- package/embed-dist/assets/index-DC63og2c.css +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { $ as errMsg, At as EventCategory, Dt as record, E as createHwAccelCache, Et as object, N as egressTranscodeSharingKey, Ot as string, P as egressTransportFromRequest, Q as webrtcSessionCapability, S as addonWidgetsSourceCapability, St as discriminatedUnion, T as cameraStreamsCapability, Tt as number, X as streamBrokerCapability, _ as RATE_CONTROL_RELAXED, at as BaseAddon, b as RingBuffer, bt as array, ct as DeviceType, d as EncodeProfileSchema, dt as isEvent, et as AUDIO_PRESETS, ft as makeProfileBrokerId, gt as parseProfileBrokerId, it as isSoftwareDecode, kt as union, lt as createEvent, mt as nodePin, nt as buildFfmpegArgs$1, ot as CAM_PROFILE_ORDER, pt as makeSourceBrokerId, q as resolveEgressDecodeHwAccel, rt as invocationFromEncodeProfile, st as DeviceFeature, tt as Fmp4BoxSplitter, v as RATE_CONTROL_TIGHT, wt as literal, xt as boolean, yt as _enum, z as maskUrlCredentials } from "../dist-
|
|
2
|
+
import { $ as errMsg, At as EventCategory, Dt as record, E as createHwAccelCache, Et as object, N as egressTranscodeSharingKey, Ot as string, P as egressTransportFromRequest, Q as webrtcSessionCapability, S as addonWidgetsSourceCapability, St as discriminatedUnion, T as cameraStreamsCapability, Tt as number, X as streamBrokerCapability, _ as RATE_CONTROL_RELAXED, at as BaseAddon, b as RingBuffer, bt as array, ct as DeviceType, d as EncodeProfileSchema, dt as isEvent, et as AUDIO_PRESETS, ft as makeProfileBrokerId, gt as parseProfileBrokerId, it as isSoftwareDecode, kt as union, lt as createEvent, mt as nodePin, nt as buildFfmpegArgs$1, ot as CAM_PROFILE_ORDER, pt as makeSourceBrokerId, q as resolveEgressDecodeHwAccel, rt as invocationFromEncodeProfile, st as DeviceFeature, tt as Fmp4BoxSplitter, v as RATE_CONTROL_TIGHT, wt as literal, xt as boolean, yt as _enum, z as maskUrlCredentials } from "../dist-CUj9mdhh.mjs";
|
|
3
3
|
import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
|
|
4
4
|
import { n as createFileDataPlaneHandler } from "../model-download-service-D8B-4ktF-BB7oZL3y.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -3006,6 +3006,147 @@ function buildAudioCodecCapApi(api) {
|
|
|
3006
3006
|
};
|
|
3007
3007
|
}
|
|
3008
3008
|
//#endregion
|
|
3009
|
+
//#region src/stream-broker/stream-broker/dead-decode-backends.ts
|
|
3010
|
+
/**
|
|
3011
|
+
* "This decode backend cannot create a device in THIS process" — remembered
|
|
3012
|
+
* across children, not per child.
|
|
3013
|
+
*
|
|
3014
|
+
* ## The thing this stops
|
|
3015
|
+
*
|
|
3016
|
+
* `-hwaccel vaapi` works for the decoder addon on this hub (ten concurrent 4K
|
|
3017
|
+
* decodes) and returns ENOMEM inside the stream-broker runner: `code=244`,
|
|
3018
|
+
* "Device creation failed: -12 … Cannot allocate memory". Every egress child
|
|
3019
|
+
* therefore spends ~3 s dying before its own software retry rescues it.
|
|
3020
|
+
*
|
|
3021
|
+
* That was already reported — `EgressTranscodeManager.onDecodeHwAccelFailed`
|
|
3022
|
+
* poisoned the broker's 60 s `egressHwAccelCache`. The memo is the wrong shape
|
|
3023
|
+
* for the verdict: it exists to avoid a per-session cross-process
|
|
3024
|
+
* `decoder.getInfo`, so it expires on a **read** cadence, and one minute later
|
|
3025
|
+
* the decoder answers `vaapi` again and the next cold child re-pays the death.
|
|
3026
|
+
* Measured on camera 615 on 2026-08-07: the hardware failure recurs at 22:17:07,
|
|
3027
|
+
* 22:18:42, 22:28:48, 22:30:52, 22:32:39, 22:34:07, 22:52:07 — once per memo
|
|
3028
|
+
* window, forever, on a hub where it has never once succeeded.
|
|
3029
|
+
*
|
|
3030
|
+
* ## Why this is a quarantine and not a tombstone
|
|
3031
|
+
*
|
|
3032
|
+
* A permanent verdict is wrong for the failure that is actually observed:
|
|
3033
|
+
* ENOMEM on device creation is a RESOURCE condition, and a resource condition
|
|
3034
|
+
* can clear (the operator gives the container more of the device, another
|
|
3035
|
+
* process releases it). A process that decided "never again" at 03:00 would
|
|
3036
|
+
* still be decoding in software at noon with a working accelerator, and nothing
|
|
3037
|
+
* would ever say so.
|
|
3038
|
+
*
|
|
3039
|
+
* So the verdict LASTS and escalates: each consecutive failure of the same
|
|
3040
|
+
* backend widens its quarantine — 30 min, 2 h, then 6 h — so a hub where the
|
|
3041
|
+
* backend is genuinely dead pays one probe per six hours instead of one per
|
|
3042
|
+
* minute, while a hub where it recovers still finds out.
|
|
3043
|
+
*
|
|
3044
|
+
* The FIRST rung is deliberately not a minute. One report is already a strong
|
|
3045
|
+
* signal: the manager only reports a backend after its child produced ZERO
|
|
3046
|
+
* packets *and* the software retry proved the same argv otherwise works, which
|
|
3047
|
+
* is a statement about the backend and not about the source. And the cost of
|
|
3048
|
+
* being wrong is bounded and already being paid — the egress encodes with
|
|
3049
|
+
* `libx264` either way, so an over-long quarantine costs a software DECODE on a
|
|
3050
|
+
* path that is decoding in software today, 100 % of the time.
|
|
3051
|
+
*
|
|
3052
|
+
* The invalidations are motivated, and there are exactly three:
|
|
3053
|
+
*
|
|
3054
|
+
* 1. **The window elapses.** The next acquire probes hardware once. If it
|
|
3055
|
+
* works, {@link DeadDecodeBackends.markAlive} clears the record and the
|
|
3056
|
+
* escalation starts from zero again.
|
|
3057
|
+
* 2. **The backend changes.** The record is keyed by backend name, so an
|
|
3058
|
+
* operator switching the decoder from vaapi to qsv is obeyed immediately —
|
|
3059
|
+
* qsv has no record.
|
|
3060
|
+
* 3. **The runner respawns.** This is instance state on the broker manager,
|
|
3061
|
+
* never a module global, for the same reason `HwAccelCache` is: a module
|
|
3062
|
+
* global outlives an addon respawn, and an addon respawn is exactly the
|
|
3063
|
+
* event after which a stale verdict must not survive.
|
|
3064
|
+
*
|
|
3065
|
+
* ## Why it lives here and not in `@camstack/types`
|
|
3066
|
+
*
|
|
3067
|
+
* `addon-export-hap` has the identical gap (its `failedBackend` memo forgets on
|
|
3068
|
+
* the same 60 s cadence) and this is deliberately NOT shared with it yet: a
|
|
3069
|
+
* framework package is only live once a `@camstack/server` containing it is
|
|
3070
|
+
* installed, whereas this ships with `camstack deploy packages/addon-pipeline`.
|
|
3071
|
+
* The cross-PROCESS gap D67 names — addons may not import each other and no cap
|
|
3072
|
+
* carries "this backend is dead on this node right now" — is unchanged either
|
|
3073
|
+
* way; what is closed here is the broker's own repetition of the cost.
|
|
3074
|
+
*/
|
|
3075
|
+
/**
|
|
3076
|
+
* How long a backend stays quarantined after its 1st, 2nd, 3rd+ consecutive
|
|
3077
|
+
* failure. Escalating rather than fixed, and it starts at HALF AN HOUR: a
|
|
3078
|
+
* client watchdog cycles in seconds, so any first rung measured in minutes is
|
|
3079
|
+
* re-paid by the very churn this exists to stop. The last rung makes a
|
|
3080
|
+
* permanently-broken backend cost ~nothing while still being re-probed.
|
|
3081
|
+
*/
|
|
3082
|
+
var DEAD_BACKEND_QUARANTINE_MS = [
|
|
3083
|
+
18e5,
|
|
3084
|
+
72e5,
|
|
3085
|
+
216e5
|
|
3086
|
+
];
|
|
3087
|
+
/**
|
|
3088
|
+
* The decoder's answer, overruled while the backend it names is quarantined.
|
|
3089
|
+
*
|
|
3090
|
+
* Composed rather than folded into `resolveEgressDecodeHwAccel`: that function
|
|
3091
|
+
* answers "what SHOULD decode this", which is a per-node property of the
|
|
3092
|
+
* hardware; this answers "what has been observed to work in THIS process",
|
|
3093
|
+
* which is a property of the runner. Keeping them apart is what stops a
|
|
3094
|
+
* runner-local ENOMEM from being mistaken for a node that has no accelerator —
|
|
3095
|
+
* the first is a flow bug to fix, the second a capability limit to accept.
|
|
3096
|
+
*/
|
|
3097
|
+
async function resolveWithQuarantine(input) {
|
|
3098
|
+
const backend = await input.resolve();
|
|
3099
|
+
if (backend === null || !input.dead.isQuarantined(backend)) return {
|
|
3100
|
+
backend,
|
|
3101
|
+
veto: null
|
|
3102
|
+
};
|
|
3103
|
+
return {
|
|
3104
|
+
backend: null,
|
|
3105
|
+
veto: {
|
|
3106
|
+
backend,
|
|
3107
|
+
failures: input.dead.failuresOf(backend),
|
|
3108
|
+
remainingMs: input.dead.remainingMs(backend)
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
function createDeadDecodeBackends(options = {}) {
|
|
3113
|
+
const now = options.now ?? (() => Date.now());
|
|
3114
|
+
const records = /* @__PURE__ */ new Map();
|
|
3115
|
+
const quarantineFor = (failures) => {
|
|
3116
|
+
return DEAD_BACKEND_QUARANTINE_MS[Math.min(failures, DEAD_BACKEND_QUARANTINE_MS.length) - 1] ?? DEAD_BACKEND_QUARANTINE_MS[0] ?? 0;
|
|
3117
|
+
};
|
|
3118
|
+
return {
|
|
3119
|
+
markDead(backend) {
|
|
3120
|
+
const failures = (records.get(backend)?.failures ?? 0) + 1;
|
|
3121
|
+
const quarantineMs = quarantineFor(failures);
|
|
3122
|
+
records.set(backend, {
|
|
3123
|
+
failures,
|
|
3124
|
+
until: now() + quarantineMs
|
|
3125
|
+
});
|
|
3126
|
+
return {
|
|
3127
|
+
backend,
|
|
3128
|
+
failures,
|
|
3129
|
+
quarantineMs
|
|
3130
|
+
};
|
|
3131
|
+
},
|
|
3132
|
+
markAlive(backend) {
|
|
3133
|
+
records.delete(backend);
|
|
3134
|
+
},
|
|
3135
|
+
isQuarantined(backend) {
|
|
3136
|
+
const record = records.get(backend);
|
|
3137
|
+
return record !== void 0 && now() < record.until;
|
|
3138
|
+
},
|
|
3139
|
+
remainingMs(backend) {
|
|
3140
|
+
const record = records.get(backend);
|
|
3141
|
+
if (record === void 0) return 0;
|
|
3142
|
+
return Math.max(0, record.until - now());
|
|
3143
|
+
},
|
|
3144
|
+
failuresOf(backend) {
|
|
3145
|
+
return records.get(backend)?.failures ?? 0;
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
//#endregion
|
|
3009
3150
|
//#region src/stream-broker/stream-broker/ffmpeg-invocation.ts
|
|
3010
3151
|
/** The full set of hwaccel settings (for validation + option surfaces). */
|
|
3011
3152
|
var FFMPEG_HW_ACCEL_SETTINGS = [
|
|
@@ -4354,6 +4495,7 @@ var EgressTranscodeManager = class {
|
|
|
4354
4495
|
}
|
|
4355
4496
|
const actualHwAccel = child.decodeHwAccel();
|
|
4356
4497
|
if (decodeHwAccel !== null && !isSoftwareDecode(decodeHwAccel) && actualHwAccel === null) this.opts.onDecodeHwAccelFailed?.(decodeHwAccel);
|
|
4498
|
+
else if (actualHwAccel !== null && !isSoftwareDecode(actualHwAccel)) this.opts.onDecodeHwAccelSucceeded?.(actualHwAccel);
|
|
4357
4499
|
const entry = {
|
|
4358
4500
|
key,
|
|
4359
4501
|
deviceId,
|
|
@@ -13310,6 +13452,15 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
13310
13452
|
*/
|
|
13311
13453
|
egressHwAccelCache = createHwAccelCache({ ttlMs: 6e4 });
|
|
13312
13454
|
/**
|
|
13455
|
+
* Backends that could not create a device in THIS process. Separate from the
|
|
13456
|
+
* memo above on purpose: the memo answers "what should decode this" on a read
|
|
13457
|
+
* cadence and expires in a minute, so folding the failure into it meant the
|
|
13458
|
+
* next cold child one minute later re-paid the same ~3 s death — measured
|
|
13459
|
+
* seven times on 615 between 22:17 and 22:52 on 2026-08-07. See
|
|
13460
|
+
* `dead-decode-backends.ts`.
|
|
13461
|
+
*/
|
|
13462
|
+
deadDecodeBackends = createDeadDecodeBackends();
|
|
13463
|
+
/**
|
|
13313
13464
|
* Snapshot of the system `ffmpeg` config section (binaryPath / hwAccel /
|
|
13314
13465
|
* threadCount). Refreshed by the host addon via `setFfmpegConfig`; the
|
|
13315
13466
|
* transcode manager reads it live per spawn. Defaults are safe (PATH ffmpeg,
|
|
@@ -13509,8 +13660,17 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
13509
13660
|
};
|
|
13510
13661
|
},
|
|
13511
13662
|
onDecodeHwAccelFailed: (backend) => {
|
|
13512
|
-
this.
|
|
13513
|
-
this.logger.warn("egress transcode: decode backend FAILED in this process — software until
|
|
13663
|
+
const verdict = this.deadDecodeBackends.markDead(backend);
|
|
13664
|
+
this.logger.warn("egress transcode: decode backend FAILED in this process — QUARANTINED, software until it is re-probed", { meta: {
|
|
13665
|
+
backend,
|
|
13666
|
+
failures: verdict.failures,
|
|
13667
|
+
quarantineMs: verdict.quarantineMs
|
|
13668
|
+
} });
|
|
13669
|
+
},
|
|
13670
|
+
onDecodeHwAccelSucceeded: (backend) => {
|
|
13671
|
+
if (this.deadDecodeBackends.failuresOf(backend) === 0) return;
|
|
13672
|
+
this.deadDecodeBackends.markAlive(backend);
|
|
13673
|
+
this.logger.info("egress transcode: decode backend RECOVERED in this process — the quarantine is lifted", { meta: { backend } });
|
|
13514
13674
|
}
|
|
13515
13675
|
});
|
|
13516
13676
|
}
|
|
@@ -13533,6 +13693,22 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
13533
13693
|
* hub).
|
|
13534
13694
|
*/
|
|
13535
13695
|
async resolveEgressHwAccel(deviceId, override) {
|
|
13696
|
+
const resolved = await resolveWithQuarantine({
|
|
13697
|
+
resolve: () => this.readEgressHwAccel(deviceId, override),
|
|
13698
|
+
dead: this.deadDecodeBackends
|
|
13699
|
+
});
|
|
13700
|
+
if (resolved.veto !== null) this.logger.warn("egress transcode: decoding in SOFTWARE — the backend is QUARANTINED after failing in this process", {
|
|
13701
|
+
tags: { deviceId },
|
|
13702
|
+
meta: {
|
|
13703
|
+
backend: resolved.veto.backend,
|
|
13704
|
+
failures: resolved.veto.failures,
|
|
13705
|
+
remainingMs: resolved.veto.remainingMs
|
|
13706
|
+
}
|
|
13707
|
+
});
|
|
13708
|
+
return resolved.backend;
|
|
13709
|
+
}
|
|
13710
|
+
/** The decoder addon's answer for this node — before the process quarantine. */
|
|
13711
|
+
async readEgressHwAccel(deviceId, override) {
|
|
13536
13712
|
const decoderApi = this.buildDecoderCapApi(deviceId);
|
|
13537
13713
|
return resolveEgressDecodeHwAccel({
|
|
13538
13714
|
readDecoderBackend: async () => {
|
|
@@ -18241,6 +18417,17 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
18241
18417
|
acquireTranscodeEgress;
|
|
18242
18418
|
/** The acquired egress, while this session holds a reference to it. */
|
|
18243
18419
|
transcodeEgressLeg = null;
|
|
18420
|
+
/**
|
|
18421
|
+
* An acquire started at the ANSWER and not yet consumed by the feed.
|
|
18422
|
+
*
|
|
18423
|
+
* Read-and-null-out is the whole ownership protocol: whichever of
|
|
18424
|
+
* `startTranscodeFeed` and `releaseTranscodeEgress` reads a non-null value
|
|
18425
|
+
* owns the release, and since both read it synchronously exactly one can.
|
|
18426
|
+
* Without that, a session closing while its prewarm is in flight either
|
|
18427
|
+
* double-releases (the manager logs it as a consumer bookkeeping bug) or
|
|
18428
|
+
* leaks a child the primitive has no TTL to reclaim.
|
|
18429
|
+
*/
|
|
18430
|
+
transcodeEgressPending = null;
|
|
18244
18431
|
/** Unsubscribe from the acquired egress's video plane. */
|
|
18245
18432
|
detachTranscodeEgress = null;
|
|
18246
18433
|
/** Codec actually negotiated with the browser after SDP answer. */
|
|
@@ -18880,6 +19067,7 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
18880
19067
|
negotiated: this.negotiatedCodec,
|
|
18881
19068
|
needsTranscode: this.needsTranscode
|
|
18882
19069
|
} });
|
|
19070
|
+
this.prewarmTranscodeEgress();
|
|
18883
19071
|
if (this.debug) this.logger.info("Codec negotiation detail", { meta: {
|
|
18884
19072
|
phase: "session",
|
|
18885
19073
|
sessionId: this.sessionId,
|
|
@@ -19078,6 +19266,7 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19078
19266
|
setRemoteMs,
|
|
19079
19267
|
createAnswerMs
|
|
19080
19268
|
} });
|
|
19269
|
+
this.prewarmTranscodeEgress();
|
|
19081
19270
|
if (this.debug) this.logger.info("Codec negotiation detail (client-offer)", { meta: {
|
|
19082
19271
|
phase: "session",
|
|
19083
19272
|
sessionId: this.sessionId,
|
|
@@ -19798,6 +19987,51 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19798
19987
|
* unchanged — the request asks for `audio: 'passthrough'` precisely so the
|
|
19799
19988
|
* child does not encode a plane this session never reads.
|
|
19800
19989
|
*/
|
|
19990
|
+
/**
|
|
19991
|
+
* Start the egress acquire NOW, in parallel with ICE and DTLS.
|
|
19992
|
+
*
|
|
19993
|
+
* `needsTranscode` is final the moment the answer's codec is known — the
|
|
19994
|
+
* "Codec negotiated" line already prints it — but the acquire used to happen
|
|
19995
|
+
* inside `startFeedingFrames`, which runs only after the DTLS wait. On camera
|
|
19996
|
+
* 615 that put the leg ~2 s after session creation and the first packet ~5 s
|
|
19997
|
+
* after that, against a browser watchdog that re-offers every ~5 s: the viewer
|
|
19998
|
+
* was always gone before the media existed, and the next session paid for a
|
|
19999
|
+
* cold child again. Three minutes of that on 2026-08-07 22:17–22:20.
|
|
20000
|
+
*
|
|
20001
|
+
* Only the ffmpeg moves. The ATTACH stays behind DTLS, because `sendRtp`
|
|
20002
|
+
* silently drops before the transport is connected.
|
|
20003
|
+
*
|
|
20004
|
+
* Idempotent and safe to call from any negotiation path; a session that needs
|
|
20005
|
+
* no transcode does nothing. Public for the same reason
|
|
20006
|
+
* {@link armSourceSilenceWatchdog} is — the acquire/release contract is
|
|
20007
|
+
* testable without standing up werift.
|
|
20008
|
+
*/
|
|
20009
|
+
prewarmTranscodeEgress() {
|
|
20010
|
+
if (this.closed) return;
|
|
20011
|
+
if (!this.needsTranscode) return;
|
|
20012
|
+
if (this.transcodeEgressPending !== null || this.transcodeEgressLeg !== null) return;
|
|
20013
|
+
const acquire = this.acquireTranscodeEgress;
|
|
20014
|
+
if (acquire === null) return;
|
|
20015
|
+
this.logger.info("Transcode leg: prewarming the egress while ICE/DTLS negotiate", { meta: {
|
|
20016
|
+
phase: "session",
|
|
20017
|
+
sessionId: this.sessionId,
|
|
20018
|
+
deviceId: this.deviceId,
|
|
20019
|
+
sourceCodec: this.sourceCodec
|
|
20020
|
+
} });
|
|
20021
|
+
this.transcodeEgressPending = acquire({
|
|
20022
|
+
profile: this.transcodeProfile,
|
|
20023
|
+
sessionId: this.sessionId
|
|
20024
|
+
});
|
|
20025
|
+
}
|
|
20026
|
+
/**
|
|
20027
|
+
* Take ownership of the in-flight prewarm, if there is one. Nulls the field
|
|
20028
|
+
* synchronously so the release path can never also claim it — see the field.
|
|
20029
|
+
*/
|
|
20030
|
+
claimPrewarmedEgress() {
|
|
20031
|
+
const pending = this.transcodeEgressPending;
|
|
20032
|
+
this.transcodeEgressPending = null;
|
|
20033
|
+
return pending;
|
|
20034
|
+
}
|
|
19801
20035
|
startTranscodeFeed(signal) {
|
|
19802
20036
|
const acquire = this.acquireTranscodeEgress;
|
|
19803
20037
|
if (acquire === null) {
|
|
@@ -19809,20 +20043,32 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19809
20043
|
if (!this.closed) this.close();
|
|
19810
20044
|
return;
|
|
19811
20045
|
}
|
|
20046
|
+
const prewarmed = this.claimPrewarmedEgress();
|
|
19812
20047
|
this.logger.info(`Starting ${this.sourceCodec}→H.264 Baseline transcode leg`, { meta: {
|
|
19813
20048
|
phase: "session",
|
|
19814
20049
|
sessionId: this.sessionId,
|
|
19815
20050
|
deviceId: this.deviceId,
|
|
19816
20051
|
sourceCodec: this.sourceCodec,
|
|
19817
|
-
mode: "egress-primitive"
|
|
20052
|
+
mode: "egress-primitive",
|
|
20053
|
+
prewarmed: prewarmed !== null
|
|
19818
20054
|
} });
|
|
20055
|
+
const acquireNow = () => acquire({
|
|
20056
|
+
profile: this.transcodeProfile,
|
|
20057
|
+
sessionId: this.sessionId
|
|
20058
|
+
});
|
|
19819
20059
|
(async () => {
|
|
19820
20060
|
let leg;
|
|
19821
20061
|
try {
|
|
19822
|
-
leg = await
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
20062
|
+
leg = await (prewarmed === null ? acquireNow() : prewarmed.catch((err) => {
|
|
20063
|
+
if (signal.aborted || this.closed) throw err;
|
|
20064
|
+
this.logger.warn("Transcode leg: the prewarmed acquire failed — retrying now that the feed has started", { meta: {
|
|
20065
|
+
phase: "session",
|
|
20066
|
+
sessionId: this.sessionId,
|
|
20067
|
+
deviceId: this.deviceId,
|
|
20068
|
+
error: errMsg(err)
|
|
20069
|
+
} });
|
|
20070
|
+
return acquireNow();
|
|
20071
|
+
}));
|
|
19826
20072
|
} catch (err) {
|
|
19827
20073
|
this.logger.error("Transcode feed: acquiring the egress transcode FAILED", { meta: {
|
|
19828
20074
|
phase: "session",
|
|
@@ -19890,6 +20136,14 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
19890
20136
|
try {
|
|
19891
20137
|
detach?.();
|
|
19892
20138
|
} catch {}
|
|
20139
|
+
const orphan = this.claimPrewarmedEgress();
|
|
20140
|
+
if (orphan !== null) orphan.then((prewarmedLeg) => prewarmedLeg.release()).catch((err) => {
|
|
20141
|
+
this.logger.debug("Transcode leg: the unclaimed prewarm ended without a handle", { meta: {
|
|
20142
|
+
phase: "session",
|
|
20143
|
+
sessionId: this.sessionId,
|
|
20144
|
+
error: errMsg(err)
|
|
20145
|
+
} });
|
|
20146
|
+
});
|
|
19893
20147
|
const leg = this.transcodeEgressLeg;
|
|
19894
20148
|
this.transcodeEgressLeg = null;
|
|
19895
20149
|
if (!leg) return;
|
|
@@ -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-aUWVCncO.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as DEFAULT_NATIVE_LEASE_SETTINGS, h as NativeLeaseSettingsSchema, m as NativeLeaseAdmissionSchema } from "./dist-
|
|
1
|
+
import { c as DEFAULT_NATIVE_LEASE_SETTINGS, h as NativeLeaseSettingsSchema, m as NativeLeaseAdmissionSchema } from "./dist-CUj9mdhh.mjs";
|
|
2
2
|
//#region src/session-decode/native-lease-config.ts
|
|
3
3
|
/**
|
|
4
4
|
* Resolution of the native-frame lease knobs for one decode worker.
|
|
@@ -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-DvOgpqcE.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-DvOgpqcE.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-C8eGXtf-.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-DvOgpqcE.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-C8eGXtf-.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};
|