@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.
Files changed (41) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/audio-codec-ffmpeg/index.js +118 -3
  4. package/dist/audio-codec-ffmpeg/index.mjs +118 -3
  5. package/dist/decoder-ffmpeg/index.js +75 -10
  6. package/dist/decoder-ffmpeg/index.mjs +75 -10
  7. package/dist/decoder-nodeav/index.js +209 -9
  8. package/dist/decoder-nodeav/index.mjs +209 -9
  9. package/dist/detection-pipeline/index.js +1 -1
  10. package/dist/detection-pipeline/index.mjs +1 -1
  11. package/dist/{dist-Cwc0TUQr.js → dist-BrIE85BG.js} +38 -1
  12. package/dist/{dist-DjuGmyG9.mjs → dist-CfSORkv2.mjs} +38 -1
  13. package/dist/{ffmpeg-args-C5GPp8Cw.mjs → ffmpeg-args-CpeRWwL4.mjs} +7 -4
  14. package/dist/{ffmpeg-args-D6h1edXK.js → ffmpeg-args-Dl9mLcOC.js} +7 -4
  15. package/dist/{frame-handle-plane-Bkxz-TTD.mjs → frame-handle-plane-C05rq9Zs.mjs} +108 -17
  16. package/dist/{frame-handle-plane-DQNCTrpC.js → frame-handle-plane-zMju9vmk.js} +108 -17
  17. package/dist/{frame-ring-sink-sY-8XUsH.mjs → frame-ring-sink-1aLGcNNf.mjs} +81 -31
  18. package/dist/{frame-ring-sink-DvdhCFr3.js → frame-ring-sink-CvlcAUOu.js} +86 -30
  19. package/dist/motion-wasm/index.js +1 -1
  20. package/dist/motion-wasm/index.mjs +1 -1
  21. package/dist/pipeline-runner/index.js +86 -16
  22. package/dist/pipeline-runner/index.mjs +85 -17
  23. package/dist/recorder/index.js +1 -1
  24. package/dist/recorder/index.mjs +1 -1
  25. package/dist/stream-broker/_stub.js +1 -1
  26. 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
  27. 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
  28. package/dist/stream-broker/{hostInit-B_b3PIZB.mjs → hostInit-C8slrxw1.mjs} +3 -3
  29. package/dist/stream-broker/index.js +165 -27
  30. package/dist/stream-broker/index.mjs +165 -27
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js → MaskShapeCanvas-DI4BY7W2-WUVfFzT-.js} +1 -1
  33. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DTeuDGCU.js → MotionZonesSettings-NcxxQN8r-CF7FXXLs.js} +1 -1
  34. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-CirtxO3e.js → PrivacyMaskSettings-APgPLF7p-qQCpVob3.js} +1 -1
  35. package/embed-dist/assets/{index-CpUy8OIE.js → index-BNhisVUZ.js} +4 -4
  36. package/embed-dist/index.html +1 -1
  37. package/package.json +1 -1
  38. package/dist/constants-CAqYVigN.js +0 -6615
  39. package/dist/constants-oYgW36Ty.mjs +0 -3587
  40. package/dist/dist-D3Ytt-_Y.js +0 -41111
  41. 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-DjuGmyG9.mjs";
3
- import { n as DecoderSessionProxy, t as FrameHandlePlane } from "../frame-handle-plane-Bkxz-TTD.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-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-C5GPp8Cw.mjs";
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) this.frameHandlePlane?.pushPacket(packet);
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: null
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
- await this.reconcileAutoAssignment(deviceId, "publish");
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-D567z31g.mjs")).catch((e) => {
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-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.
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-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
+ 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-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};
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};