@camstack/addon-pipeline 1.1.32 → 1.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/audio-codec-ffmpeg/index.js +118 -3
- package/dist/audio-codec-ffmpeg/index.mjs +118 -3
- package/dist/decoder-ffmpeg/index.js +75 -10
- package/dist/decoder-ffmpeg/index.mjs +75 -10
- package/dist/decoder-nodeav/index.js +209 -9
- package/dist/decoder-nodeav/index.mjs +209 -9
- package/dist/detection-pipeline/index.js +1 -1
- package/dist/detection-pipeline/index.mjs +1 -1
- package/dist/{dist-Cwc0TUQr.js → dist-BrIE85BG.js} +38 -1
- package/dist/{dist-DjuGmyG9.mjs → dist-CfSORkv2.mjs} +38 -1
- package/dist/{ffmpeg-args-C5GPp8Cw.mjs → ffmpeg-args-CpeRWwL4.mjs} +7 -4
- package/dist/{ffmpeg-args-D6h1edXK.js → ffmpeg-args-Dl9mLcOC.js} +7 -4
- package/dist/{frame-handle-plane-Bkxz-TTD.mjs → frame-handle-plane-C05rq9Zs.mjs} +108 -17
- package/dist/{frame-handle-plane-DQNCTrpC.js → frame-handle-plane-zMju9vmk.js} +108 -17
- package/dist/{frame-ring-sink-sY-8XUsH.mjs → frame-ring-sink-1aLGcNNf.mjs} +81 -31
- package/dist/{frame-ring-sink-DvdhCFr3.js → frame-ring-sink-CvlcAUOu.js} +86 -30
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +86 -16
- package/dist/pipeline-runner/index.mjs +85 -17
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DPFAbcUo.mjs} +3 -3
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtoYGpTp.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BsrrdOBU.mjs} +1 -1
- package/dist/stream-broker/{hostInit-B_b3PIZB.mjs → hostInit-C8slrxw1.mjs} +3 -3
- package/dist/stream-broker/index.js +165 -27
- package/dist/stream-broker/index.mjs +165 -27
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js → MaskShapeCanvas-DI4BY7W2-WUVfFzT-.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DTeuDGCU.js → MotionZonesSettings-NcxxQN8r-CF7FXXLs.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-CirtxO3e.js → PrivacyMaskSettings-APgPLF7p-qQCpVob3.js} +1 -1
- package/embed-dist/assets/{index-CpUy8OIE.js → index-BNhisVUZ.js} +4 -4
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/constants-CAqYVigN.js +0 -6615
- package/dist/constants-oYgW36Ty.mjs +0 -3587
- package/dist/dist-D3Ytt-_Y.js +0 -41111
- package/dist/dist-DluJuxV5.mjs +0 -37976
|
@@ -1,8 +1,8 @@
|
|
|
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-CfSORkv2.mjs";
|
|
3
|
+
import { n as DecoderSessionProxy, t as FrameHandlePlane } from "../frame-handle-plane-C05rq9Zs.mjs";
|
|
4
4
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
5
|
-
import { f as rankDecodeHwAccels, g as logBannerArgs, h as audioEncoderArgs } from "../ffmpeg-args-
|
|
5
|
+
import { f as rankDecodeHwAccels, g as logBannerArgs, h as audioEncoderArgs } from "../ffmpeg-args-CpeRWwL4.mjs";
|
|
6
6
|
import { t as FrameDropper } from "../frame-dropper-AjheBGMG.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import * as crypto$1 from "node:crypto";
|
|
@@ -3945,14 +3945,6 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
3945
3945
|
const frame = mediaFrame.frame;
|
|
3946
3946
|
if (!this.audioSender) continue;
|
|
3947
3947
|
const rtpTs = Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1e6) >>> 0;
|
|
3948
|
-
if (this.debug) this.logger.info("audio frame in (direct-feed)", { meta: {
|
|
3949
|
-
phase: "session",
|
|
3950
|
-
sessionId: this.sessionId,
|
|
3951
|
-
timestampMicros: frame.timestampMicros,
|
|
3952
|
-
dataLen: frame.data.length,
|
|
3953
|
-
computedRtpTs: rtpTs,
|
|
3954
|
-
codec: frame.codec
|
|
3955
|
-
} });
|
|
3956
3948
|
this.recordAudioSourceLatency(frame.timestampMicros);
|
|
3957
3949
|
this.writeAudio(frame.data, rtpTs, frame.codec);
|
|
3958
3950
|
}
|
|
@@ -4072,14 +4064,6 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
4072
4064
|
if (mediaFrame.type === "audio") {
|
|
4073
4065
|
const frame = mediaFrame.frame;
|
|
4074
4066
|
const rtpTs = Math.floor(frame.timestampMicros * AdaptiveSession.PCMU_RTP_CLOCK_HZ / 1e6) >>> 0;
|
|
4075
|
-
if (this.debug) this.logger.info("audio frame in (transcode passthrough)", { meta: {
|
|
4076
|
-
phase: "session",
|
|
4077
|
-
sessionId: this.sessionId,
|
|
4078
|
-
timestampMicros: frame.timestampMicros,
|
|
4079
|
-
dataLen: frame.data.length,
|
|
4080
|
-
computedRtpTs: rtpTs,
|
|
4081
|
-
codec: frame.codec
|
|
4082
|
-
} });
|
|
4083
4067
|
this.recordAudioSourceLatency(frame.timestampMicros);
|
|
4084
4068
|
this.writeAudio(frame.data, rtpTs, frame.codec);
|
|
4085
4069
|
}
|
|
@@ -8784,6 +8768,77 @@ var EncodedRingBuffer = class {
|
|
|
8784
8768
|
}
|
|
8785
8769
|
};
|
|
8786
8770
|
//#endregion
|
|
8771
|
+
//#region src/stream-broker/stream-broker/annexb-param-sets.ts
|
|
8772
|
+
/**
|
|
8773
|
+
* Annex-B parameter-set detection for the decoder-priming path.
|
|
8774
|
+
*
|
|
8775
|
+
* The frame-handle-plane decoder (`ffmpeg -f h264|hevc -i pipe:0`) initialises
|
|
8776
|
+
* only once it sees the codec parameter sets (H.264 SPS+PPS / H.265 SPS+PPS,
|
|
8777
|
+
* plus VPS) in its Annex-B input. Most cameras carry them in-band with each
|
|
8778
|
+
* IDR; some (Reolink high-profile native streams) ship them ONLY in the SDP.
|
|
8779
|
+
* The broker harvests those SDP param sets (`sdpParameterSets`) but must not
|
|
8780
|
+
* blindly re-inject them ahead of every keyframe — that would duplicate the
|
|
8781
|
+
* sets for well-behaved cameras. This scan lets the broker inject only when a
|
|
8782
|
+
* keyframe's own NAL stream is missing them.
|
|
8783
|
+
*
|
|
8784
|
+
* Pure byte scanning: no allocation beyond the walk; safe on any Annex-B blob.
|
|
8785
|
+
*/
|
|
8786
|
+
/** Required parameter-set NAL types per codec (SPS, PPS). */
|
|
8787
|
+
var H264_SPS = 7;
|
|
8788
|
+
var H264_PPS = 8;
|
|
8789
|
+
var H265_SPS = 33;
|
|
8790
|
+
var H265_PPS = 34;
|
|
8791
|
+
function isH265Codec(codec) {
|
|
8792
|
+
const c = codec.toLowerCase();
|
|
8793
|
+
return c.includes("265") || c.includes("hevc");
|
|
8794
|
+
}
|
|
8795
|
+
/**
|
|
8796
|
+
* True when `data` already carries BOTH the SPS and the PPS the decoder needs
|
|
8797
|
+
* to initialise (per `codec`). Walks Annex-B NAL units by their 3- or 4-byte
|
|
8798
|
+
* start codes and reads each NAL header type. Returns as soon as both are seen.
|
|
8799
|
+
*/
|
|
8800
|
+
function annexBHasParameterSets(data, codec) {
|
|
8801
|
+
const h265 = isH265Codec(codec);
|
|
8802
|
+
const spsType = h265 ? H265_SPS : H264_SPS;
|
|
8803
|
+
const ppsType = h265 ? H265_PPS : H264_PPS;
|
|
8804
|
+
let hasSps = false;
|
|
8805
|
+
let hasPps = false;
|
|
8806
|
+
const len = data.length;
|
|
8807
|
+
let i = 0;
|
|
8808
|
+
while (i + 3 < len) {
|
|
8809
|
+
if (data[i] === 0 && data[i + 1] === 0) {
|
|
8810
|
+
let nalStart = -1;
|
|
8811
|
+
if (data[i + 2] === 1) nalStart = i + 3;
|
|
8812
|
+
else if (data[i + 2] === 0 && data[i + 3] === 1) nalStart = i + 4;
|
|
8813
|
+
if (nalStart >= 0 && nalStart < len) {
|
|
8814
|
+
const headerByte = data[nalStart];
|
|
8815
|
+
const nalType = h265 ? headerByte >> 1 & 63 : headerByte & 31;
|
|
8816
|
+
if (nalType === spsType) hasSps = true;
|
|
8817
|
+
else if (nalType === ppsType) hasPps = true;
|
|
8818
|
+
if (hasSps && hasPps) return true;
|
|
8819
|
+
i = nalStart + 1;
|
|
8820
|
+
continue;
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8823
|
+
i += 1;
|
|
8824
|
+
}
|
|
8825
|
+
return hasSps && hasPps;
|
|
8826
|
+
}
|
|
8827
|
+
/**
|
|
8828
|
+
* Concatenate raw parameter-set NAL payloads into a single Annex-B buffer
|
|
8829
|
+
* (each prefixed with a 4-byte start code) — the shape the decoder's stdin
|
|
8830
|
+
* expects. Mirrors the existing SDP-param-set replay for encoded subscribers.
|
|
8831
|
+
*/
|
|
8832
|
+
function concatParameterSetsAnnexB(parameterSets) {
|
|
8833
|
+
const startCode = Buffer.from([
|
|
8834
|
+
0,
|
|
8835
|
+
0,
|
|
8836
|
+
0,
|
|
8837
|
+
1
|
|
8838
|
+
]);
|
|
8839
|
+
return Buffer.concat(parameterSets.flatMap((ps) => [startCode, ps]));
|
|
8840
|
+
}
|
|
8841
|
+
//#endregion
|
|
8787
8842
|
//#region src/stream-broker/stream-broker/g711-mulaw.ts
|
|
8788
8843
|
/**
|
|
8789
8844
|
* G.711 µ-law encoder — converts 16-bit signed PCM samples to µ-law (ITU-T G.711).
|
|
@@ -9004,6 +9059,8 @@ var StreamBroker = class StreamBroker {
|
|
|
9004
9059
|
* falls back to the source passed to `start()`.
|
|
9005
9060
|
*/
|
|
9006
9061
|
sourceProvider = null;
|
|
9062
|
+
/** Resolver for this broker's own RTSP restream URL — see `setRestreamUrlResolver`. */
|
|
9063
|
+
restreamUrlResolver = null;
|
|
9007
9064
|
/**
|
|
9008
9065
|
* Manager-supplied notifier that asks the publishing addon to
|
|
9009
9066
|
* refresh its loopback transport (e.g. re-run
|
|
@@ -9137,6 +9194,17 @@ var StreamBroker = class StreamBroker {
|
|
|
9137
9194
|
pipeServer;
|
|
9138
9195
|
rtspRestreamer;
|
|
9139
9196
|
preBuffer;
|
|
9197
|
+
/**
|
|
9198
|
+
* Keyframe-aligned GOP of the EXACT packets fed to the decoder plane (i.e.
|
|
9199
|
+
* param-set prepended). Replayed to a freshly-armed shm decoder session so it
|
|
9200
|
+
* initialises on a keyframe (SPS/PPS) instead of the next live P-frame — the
|
|
9201
|
+
* decoder cold-start fix for push-mode sessions. Populated for ALL source
|
|
9202
|
+
* types (unlike `preBuffer`, which push-only sources fill for the WebRTC
|
|
9203
|
+
* late-join path): an rfc4571 Reolink source has an EMPTY `preBuffer`, so this
|
|
9204
|
+
* is the only GOP available to seed its decoder. `getPackets()` returns just
|
|
9205
|
+
* the current GOP (from the last keyframe), so a modest retention suffices.
|
|
9206
|
+
*/
|
|
9207
|
+
decoderSeedBuffer;
|
|
9140
9208
|
encodedCallbacks = /* @__PURE__ */ new Map();
|
|
9141
9209
|
/**
|
|
9142
9210
|
* Source-RTP video subscribers — receive raw on-wire RTP bytes
|
|
@@ -9247,6 +9315,13 @@ var StreamBroker = class StreamBroker {
|
|
|
9247
9315
|
static DEFAULT_PRE_BUFFER_SEC = 10;
|
|
9248
9316
|
/** Maximum allowed pre-buffer duration in seconds */
|
|
9249
9317
|
static MAX_PRE_BUFFER_SEC = 30;
|
|
9318
|
+
/**
|
|
9319
|
+
* Retention for the decoder-seed GOP ring. Only ever needs to hold the
|
|
9320
|
+
* current GOP (a fresh keyframe evicts the previous GOP on read), so a few
|
|
9321
|
+
* seconds covers every realistic keyframe interval; cameras with a GOP longer
|
|
9322
|
+
* than this fall back to the plane's live-keyframe bootstrap.
|
|
9323
|
+
*/
|
|
9324
|
+
static DECODER_SEED_BUFFER_SEC = 6;
|
|
9250
9325
|
constructor(deviceId, decoderApi, logger, audioCodecApi, localNodeId) {
|
|
9251
9326
|
this.deviceId = deviceId;
|
|
9252
9327
|
this.decoderApi = decoderApi;
|
|
@@ -9257,6 +9332,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9257
9332
|
this.rtspRestreamer = new RtspRestreamer(deviceId);
|
|
9258
9333
|
if (logger) this.rtspRestreamer.setLogger(logger.child("rtsp"));
|
|
9259
9334
|
this.preBuffer = new EncodedRingBuffer(StreamBroker.DEFAULT_PRE_BUFFER_SEC);
|
|
9335
|
+
this.decoderSeedBuffer = new EncodedRingBuffer(StreamBroker.DECODER_SEED_BUFFER_SEC);
|
|
9260
9336
|
this.audioChunkPlane = new AudioChunkPlane(logger?.child("audio-chunk-plane"));
|
|
9261
9337
|
this.rtspRestreamer.onSessionCountChanged(() => this.checkDemand());
|
|
9262
9338
|
this.pipeServer.onClientCountChanged(() => this.checkDemand());
|
|
@@ -9294,6 +9370,22 @@ var StreamBroker = class StreamBroker {
|
|
|
9294
9370
|
this.restreamers = restreamers;
|
|
9295
9371
|
}
|
|
9296
9372
|
/**
|
|
9373
|
+
* Inject a manager-owned resolver for this broker's OWN RTSP restream URL
|
|
9374
|
+
* (`rtsp://127.0.0.1:<port>/<token>[/muted]`). When the source is an RTP
|
|
9375
|
+
* source ({@link isRtpSource}), the frame-handle decoder decodes via PULL
|
|
9376
|
+
* mode from this restream instead of the broker's hand-depacketized AnnexB
|
|
9377
|
+
* push feed — ffmpeg's own RTSP/RTP demuxer does the FU-A reassembly, which
|
|
9378
|
+
* is robust where the AnnexB depacketizer chokes on heavily-fragmented 4K
|
|
9379
|
+
* NALs (Reolink rfc4571 produced 0 decoded frames on the push path; the same
|
|
9380
|
+
* stream decodes at full fps through the RTSP restream). Mirrors Scrypted's
|
|
9381
|
+
* rebroadcast (prebuffer-mixin) which passes RTP through to the consumer's
|
|
9382
|
+
* demuxer. Returns `null` before a restream token exists → falls back to the
|
|
9383
|
+
* push path.
|
|
9384
|
+
*/
|
|
9385
|
+
setRestreamUrlResolver(resolver) {
|
|
9386
|
+
this.restreamUrlResolver = resolver;
|
|
9387
|
+
}
|
|
9388
|
+
/**
|
|
9297
9389
|
* Inject the resolved `ffmpeg` binary path used by the "ffmpeg parser"
|
|
9298
9390
|
* ingress ({@link startFfmpegSourceReader}). Threaded from the manager's
|
|
9299
9391
|
* system-config (`ffmpeg.binaryPath`). Absent = bare `'ffmpeg'` (PATH lookup).
|
|
@@ -9447,6 +9539,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9447
9539
|
await this.pipeServer.stop();
|
|
9448
9540
|
this.rtspRestreamer.destroy();
|
|
9449
9541
|
this.preBuffer.clear();
|
|
9542
|
+
this.decoderSeedBuffer.clear();
|
|
9450
9543
|
this.pendingParamNals = [];
|
|
9451
9544
|
this.firstKeyframeReceived = false;
|
|
9452
9545
|
this.encodedCallbacks.clear();
|
|
@@ -9543,6 +9636,7 @@ var StreamBroker = class StreamBroker {
|
|
|
9543
9636
|
this.placeholderTimer = void 0;
|
|
9544
9637
|
}
|
|
9545
9638
|
this.preBuffer.clear();
|
|
9639
|
+
this.decoderSeedBuffer.clear();
|
|
9546
9640
|
this.firstKeyframeReceived = false;
|
|
9547
9641
|
this.pendingParamNals = [];
|
|
9548
9642
|
this.rtspRestreamer.resetStreamState();
|
|
@@ -9676,7 +9770,46 @@ var StreamBroker = class StreamBroker {
|
|
|
9676
9770
|
const streamId = `${this.deviceId}/video`;
|
|
9677
9771
|
restreamer.pushPacket(streamId, packet);
|
|
9678
9772
|
}
|
|
9679
|
-
if (packet.type === "video" && this.firstKeyframeReceived)
|
|
9773
|
+
if (packet.type === "video" && this.firstKeyframeReceived) {
|
|
9774
|
+
const decoderPacket = this.decoderPacketWithParamSets(packet);
|
|
9775
|
+
this.decoderSeedBuffer.push(decoderPacket);
|
|
9776
|
+
this.frameHandlePlane?.pushPacket(decoderPacket);
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
/**
|
|
9780
|
+
* Return the packet to feed the decoder plane, prepending the SDP-harvested
|
|
9781
|
+
* SPS/PPS to a keyframe that lacks them in-band.
|
|
9782
|
+
*
|
|
9783
|
+
* The plane seeds a freshly-armed decoder session with the SINGLE keyframe
|
|
9784
|
+
* packet that triggered the arm (`armPendingSessions`), so the parameter sets
|
|
9785
|
+
* MUST travel inside that same packet — a separate param-set packet pushed
|
|
9786
|
+
* just before it reaches only already-live sessions and is lost on the
|
|
9787
|
+
* arming one. Unlike the encoded-subscriber path (`emitSdpParamSetsTo`), the
|
|
9788
|
+
* decoder plane is otherwise never handed `sdpParameterSets`. Cameras that
|
|
9789
|
+
* ship parameter sets ONLY in the SDP (Reolink high-profile native streams)
|
|
9790
|
+
* therefore starve the `ffmpeg -i pipe:0` decoder of SPS/PPS, which loops
|
|
9791
|
+
* `non-existing PPS → no frame` and produces zero decoded frames (verified
|
|
9792
|
+
* live on device 592: WebRTC worked, detection decoded nothing). Only
|
|
9793
|
+
* keyframes actually missing the sets are rewritten — well-behaved cameras
|
|
9794
|
+
* are returned untouched — and the check runs per keyframe so a decoder
|
|
9795
|
+
* restart mid-stream re-primes on the next IDR. The returned object is a
|
|
9796
|
+
* fresh copy; the original `packet` fanned out to the other consumers above
|
|
9797
|
+
* (WebRTC, restreamers, pre-buffer) is never mutated.
|
|
9798
|
+
*/
|
|
9799
|
+
decoderPacketWithParamSets(packet) {
|
|
9800
|
+
if (!packet.keyframe) return packet;
|
|
9801
|
+
const paramSets = this.sdpParameterSets;
|
|
9802
|
+
if (!paramSets || paramSets.length === 0) return packet;
|
|
9803
|
+
if (annexBHasParameterSets(packet.data, packet.codec)) return packet;
|
|
9804
|
+
if (this.streamingDebug) this.logger?.info("Prepending SDP param sets to decoder keyframe (in-band SPS/PPS absent)", { meta: {
|
|
9805
|
+
codec: packet.codec,
|
|
9806
|
+
psCount: paramSets.length,
|
|
9807
|
+
keyframeBytes: packet.data.length
|
|
9808
|
+
} });
|
|
9809
|
+
return {
|
|
9810
|
+
...packet,
|
|
9811
|
+
data: Buffer.concat([concatParameterSetsAnnexB(paramSets), packet.data])
|
|
9812
|
+
};
|
|
9680
9813
|
}
|
|
9681
9814
|
onEncodedData(callback, attribution = UNKNOWN_ATTRIBUTION) {
|
|
9682
9815
|
this.subscriberIdCounter += 1;
|
|
@@ -10017,11 +10150,12 @@ var StreamBroker = class StreamBroker {
|
|
|
10017
10150
|
if (!codec || !this.source) return null;
|
|
10018
10151
|
const slash = this.deviceId.indexOf("/");
|
|
10019
10152
|
const numericDeviceId = slash >= 0 ? Number.parseInt(this.deviceId.slice(0, slash), 10) : Number.parseInt(this.deviceId, 10);
|
|
10153
|
+
const pullModeUrl = this.isRtpSource() ? this.restreamUrlResolver?.() ?? null : null;
|
|
10020
10154
|
return {
|
|
10021
10155
|
codec,
|
|
10022
10156
|
...Number.isFinite(numericDeviceId) ? { numericDeviceId } : {},
|
|
10023
10157
|
tag: `broker:${this.deviceId}`,
|
|
10024
|
-
pullModeUrl
|
|
10158
|
+
pullModeUrl
|
|
10025
10159
|
};
|
|
10026
10160
|
}
|
|
10027
10161
|
/** Lazily construct the frame-handle plane, returning `null` with no decoder. */
|
|
@@ -10035,7 +10169,8 @@ var StreamBroker = class StreamBroker {
|
|
|
10035
10169
|
decoderApi: this.decoderApi,
|
|
10036
10170
|
logger: this.logger?.child("frame-handle-plane"),
|
|
10037
10171
|
resolveStreamInfo: () => this.resolveFrameHandleStreamInfo(),
|
|
10038
|
-
localNodeId: this.localNodeId
|
|
10172
|
+
localNodeId: this.localNodeId,
|
|
10173
|
+
getSeedPackets: () => this.decoderSeedBuffer.getPackets()
|
|
10039
10174
|
});
|
|
10040
10175
|
return this.frameHandlePlane;
|
|
10041
10176
|
}
|
|
@@ -12429,7 +12564,7 @@ var StreamBrokerManager = class {
|
|
|
12429
12564
|
const existing = this.upsertSourceCameraStream(deviceId, input);
|
|
12430
12565
|
const urlRefreshed = existing !== void 0 && input.url !== void 0 && existing.url !== input.url;
|
|
12431
12566
|
const wasAlreadyAssigned = (PUSH_KINDS.has(kind) ? findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId) : null) !== null && existing !== void 0;
|
|
12432
|
-
await this.reconcileAutoAssignment(deviceId, "publish");
|
|
12567
|
+
await this.reconcileAutoAssignment(deviceId, "publish", existing === void 0);
|
|
12433
12568
|
if (wasAlreadyAssigned) {
|
|
12434
12569
|
const finalProfile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId);
|
|
12435
12570
|
if (finalProfile !== null) this.emitCamStreamDemand(deviceId, camStreamId, finalProfile);
|
|
@@ -12477,7 +12612,8 @@ var StreamBrokerManager = class {
|
|
|
12477
12612
|
const urlBefore = /* @__PURE__ */ new Map();
|
|
12478
12613
|
for (const d of sources) urlBefore.set(d.camStreamId, streamMapBefore?.get(d.camStreamId)?.url);
|
|
12479
12614
|
for (const d of sources) this.upsertSourceCameraStream(deviceId, d);
|
|
12480
|
-
|
|
12615
|
+
const anyNewStream = sources.some((d) => streamMapBefore?.get(d.camStreamId) === void 0);
|
|
12616
|
+
await this.reconcileAutoAssignment(deviceId, "publish", anyNewStream);
|
|
12481
12617
|
for (const camStreamId of reEmitDemandFor) {
|
|
12482
12618
|
const profile = findProfileForStream(this.assignments.get(deviceId)?.map ?? {}, camStreamId);
|
|
12483
12619
|
if (profile !== null) this.emitCamStreamDemand(deviceId, camStreamId, profile);
|
|
@@ -13971,14 +14107,15 @@ var StreamBrokerManager = class {
|
|
|
13971
14107
|
* assignment). When the device's assignment is operator-locked
|
|
13972
14108
|
* (`auto === false`) the existing map is re-applied unchanged.
|
|
13973
14109
|
*/
|
|
13974
|
-
async reconcileAutoAssignment(deviceId, reason) {
|
|
14110
|
+
async reconcileAutoAssignment(deviceId, reason, streamAdded = false) {
|
|
13975
14111
|
const current = this.assignments.get(deviceId);
|
|
13976
14112
|
const streams = [...this.cameraStreams.get(deviceId)?.values() ?? []].toSorted((a, b) => a.order - b.order);
|
|
13977
14113
|
const isAuto = current?.auto ?? true;
|
|
13978
14114
|
const newMap = !current || isAuto && this.wouldChangeAutoMap(current.map, streams) ? this.computeInitialAssignment(streams) : current.map;
|
|
13979
14115
|
await this.applyAssignmentUpdate(deviceId, newMap, {
|
|
13980
14116
|
auto: isAuto,
|
|
13981
|
-
reason
|
|
14117
|
+
reason,
|
|
14118
|
+
streamAdded
|
|
13982
14119
|
});
|
|
13983
14120
|
}
|
|
13984
14121
|
wouldChangeAutoMap(current, streams) {
|
|
@@ -14023,7 +14160,7 @@ var StreamBrokerManager = class {
|
|
|
14023
14160
|
if (anyChange || current.auto !== nextAssignment.auto) {
|
|
14024
14161
|
this.persistProfileMap();
|
|
14025
14162
|
this.emitProfileSlotsChanged(deviceId);
|
|
14026
|
-
} else if (opts.reason === "publish") this.emitProfileSlotsChanged(deviceId);
|
|
14163
|
+
} else if (opts.reason === "publish" && opts.streamAdded) this.emitProfileSlotsChanged(deviceId);
|
|
14027
14164
|
this.syncProfileRestreamers(deviceId);
|
|
14028
14165
|
}
|
|
14029
14166
|
countPushConsumers(map, deviceId) {
|
|
@@ -14214,6 +14351,7 @@ var StreamBrokerManager = class {
|
|
|
14214
14351
|
const source = this.buildSourceFromCamStream(cam);
|
|
14215
14352
|
const broker = new StreamBroker(brokerId, this.buildDecoderCapApi(deviceId), brokerLogger, buildAudioCodecCapApi(this.api), this.localNodeId);
|
|
14216
14353
|
broker.setRestreamers(this.restreamers);
|
|
14354
|
+
broker.setRestreamUrlResolver(() => this.rtspProvider.getEntry(brokerId)?.mutedUrl ?? null);
|
|
14217
14355
|
broker.setFfmpegBinaryPath(this.ffmpegConfig.binaryPath);
|
|
14218
14356
|
this.applyPreBufferConfig(broker, deviceId, camStreamId, cam);
|
|
14219
14357
|
if (this.deviceOverrides.get(deviceId)?.streamingDebug) broker.setStreamingDebug(true);
|
|
@@ -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-DPFAbcUo.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-BNhisVUZ.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-BNhisVUZ.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-WUVfFzT-.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-BNhisVUZ.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-WUVfFzT-.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};
|