@camstack/addon-pipeline 1.2.44 → 1.2.45

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 (36) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +3 -2
  4. package/dist/detection-pipeline/index.mjs +3 -2
  5. package/dist/{dist-DOu93i_g.mjs → dist-BDkEhx4Z.mjs} +692 -123
  6. package/dist/{dist-BuFE5rOK.js → dist-QyZ7QSRP.js} +697 -122
  7. package/dist/{event-loop-stall-monitor-L08yG6bB.js → event-loop-stall-monitor-COEKVBQK.js} +14 -7
  8. package/dist/{event-loop-stall-monitor-CShgQE6l.mjs → event-loop-stall-monitor-IAuNEdmG.mjs} +14 -7
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +3 -3
  12. package/dist/pipeline-runner/index.mjs +3 -3
  13. package/dist/recorder/index.js +1 -1
  14. package/dist/recorder/index.mjs +1 -1
  15. package/dist/session-decode/decode-worker-child.js +1 -1
  16. package/dist/session-decode/decode-worker-child.mjs +1 -1
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DiXoZnou.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C68YfSUH.mjs} +2 -2
  19. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CT2bGWqM.mjs +26 -0
  20. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_hakWAb.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B3GxBnXM.mjs} +1 -1
  21. package/dist/stream-broker/{hostInit-khRXMnjl.mjs → hostInit-BMCrULpc.mjs} +2 -2
  22. package/dist/stream-broker/index.js +735 -424
  23. package/dist/stream-broker/index.mjs +731 -421
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/dist/{worker-protocol-BOXlUhWO.mjs → worker-protocol-DFH_z2-q.mjs} +1 -1
  26. package/dist/{worker-protocol-VURr0nUh.js → worker-protocol-le27Mw_8.js} +1 -1
  27. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DHZuYYjH.js → MaskShapeCanvas-DI4BY7W2-C7bsz4cP.js} +1 -1
  28. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-jFDOzPCD.js → MotionZonesSettings-NcxxQN8r-BmhLinUd.js} +1 -1
  29. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BPBXgIX8.js → PrivacyMaskSettings-APgPLF7p-BJjyGWFy.js} +1 -1
  30. package/embed-dist/assets/{index-4CtQAybX.js → index-a4CMt-Uc.js} +12 -12
  31. package/embed-dist/index.html +1 -1
  32. package/package.json +1 -1
  33. package/python/postprocessors/__init__.py +2 -0
  34. package/python/postprocessors/rfdetr.py +161 -0
  35. package/python/postprocessors/test_rfdetr.py +193 -0
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BRc-m6W-.mjs +0 -26
@@ -6541,7 +6541,20 @@ var BrokerStatsSchema = object({
6541
6541
  sampleRate: number(),
6542
6542
  channels: number(),
6543
6543
  supported: boolean()
6544
- }).nullable().optional()
6544
+ }).nullable().optional(),
6545
+ /**
6546
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
6547
+ * distributing none of the device's audio, on live or recording.
6548
+ *
6549
+ * Present so a silent camera can be told apart from a broken one on the
6550
+ * stream panel itself, without cross-referencing the switch group: a
6551
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
6552
+ * working exactly as asked. `audioMutedDropped` counts the audio units
6553
+ * thrown away since the current dial — it is how you confirm from stats
6554
+ * alone that the mute is on the packet path and not merely persisted.
6555
+ */
6556
+ audioMuted: boolean().optional(),
6557
+ audioMutedDropped: number().optional()
6545
6558
  });
6546
6559
  /**
6547
6560
  * Exporter-facing "profile restream" entry. Returned by
@@ -7120,116 +7133,7 @@ function sleep(ms) {
7120
7133
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
7121
7134
  }
7122
7135
  //#endregion
7123
- //#region ../types/dist/canonical-hash-7nfBbEqR.mjs
7124
- /**
7125
- * Deterministic SHA-256 hash of an arbitrary serialisable value. The
7126
- * canonical form sorts object keys alphabetically at every depth so two
7127
- * structurally-equal inputs with different key insertion orders produce
7128
- * the same hash. Returns a 64-char lowercase hex digest.
7129
- *
7130
- * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /
7131
- * accessory-rebuild work when the upstream shape is byte-identical to
7132
- * the last applied state — preventing user-visible "re-discovery"
7133
- * notifications on every addon-runner respawn. Each respawn re-fires
7134
- * `DeviceBindingsChanged` for every cap registration, which without
7135
- * this guard would propagate redundant pushes.
7136
- *
7137
- * Note: this is a SYMPTOMATIC fix layered on top of the binding-change
7138
- * subscription. The proper fix is a single "device ready" lifecycle
7139
- * barrier so exports react only when the full cap set has landed —
7140
- * tracked separately for post-HA-integration work.
7141
- */
7142
- function canonicalHash(value) {
7143
- const canonical = JSON.stringify(value, replaceWithSortedKeys);
7144
- return (0, node_crypto.createHash)("sha256").update(canonical ?? "").digest("hex");
7145
- }
7146
- function replaceWithSortedKeys(_key, value) {
7147
- if (value && typeof value === "object" && !Array.isArray(value)) {
7148
- const obj = value;
7149
- const out = {};
7150
- for (const k of Object.keys(obj).toSorted()) out[k] = obj[k];
7151
- return out;
7152
- }
7153
- return value;
7154
- }
7155
- //#endregion
7156
- //#region ../types/dist/err-msg-IQTHeDzc.mjs
7157
- /**
7158
- import { errMsg } from '@camstack/types'
7159
- * Extract a human-readable message from an unknown error value.
7160
- * Replaces the ubiquitous `errMsg(err)` pattern.
7161
- */
7162
- function errMsg(err) {
7163
- if (err instanceof Error) return err.message;
7164
- if (typeof err === "string") return err;
7165
- return String(err);
7166
- }
7167
- var EncodeProfileSchema = object({
7168
- video: object({
7169
- codec: _enum([
7170
- "h264",
7171
- "h265",
7172
- "copy"
7173
- ]),
7174
- profile: _enum([
7175
- "baseline",
7176
- "main",
7177
- "high"
7178
- ]).optional(),
7179
- /**
7180
- * `-level`, e.g. `'3.1'`. A consumer that ADVERTISES a level in its SDP
7181
- * (`profile-level-id=42e01f` is Baseline 3.1) must constrain the encoder to
7182
- * it, or it ships a stream that does not match its own advertisement — the
7183
- * defect class that kept HomeKit black for a year and that Alexa carried
7184
- * silently. Optional because a browser negotiates the level itself.
7185
- */
7186
- level: string().optional(),
7187
- width: number().int().positive().optional(),
7188
- height: number().int().positive().optional(),
7189
- fps: number().positive().optional(),
7190
- bitrateKbps: number().int().positive().optional(),
7191
- gopFrames: number().int().positive().optional(),
7192
- bf: number().int().min(0).optional(),
7193
- preset: _enum([
7194
- "ultrafast",
7195
- "superfast",
7196
- "veryfast",
7197
- "faster",
7198
- "fast",
7199
- "medium"
7200
- ]).optional(),
7201
- tune: _enum([
7202
- "zerolatency",
7203
- "film",
7204
- "animation"
7205
- ]).optional()
7206
- }),
7207
- audio: union([literal("passthrough"), object({
7208
- codec: _enum([
7209
- "opus",
7210
- "aac",
7211
- "pcmu",
7212
- "pcma",
7213
- "copy"
7214
- ]),
7215
- bitrateKbps: number().int().positive().optional(),
7216
- sampleRateHz: number().int().positive().optional(),
7217
- channels: union([literal(1), literal(2)]).optional()
7218
- })]),
7219
- /**
7220
- * ffmpeg input-side args, inserted between the fixed global flags
7221
- * (`-hide_banner -loglevel error`) and `-i pipe:0`. Free-text array
7222
- * — the widget surfaces a textarea + suggestion chips for the most-
7223
- * used demuxer/format options.
7224
- */
7225
- inputArgs: array(string()).optional(),
7226
- /**
7227
- * ffmpeg output-side args, inserted between the encode block and
7228
- * the final `-f <muxer> pipe:1`. Use for muxer options, bitstream
7229
- * filters, codec-specific overrides. Free-text array.
7230
- */
7231
- outputArgs: array(string()).optional()
7232
- });
7136
+ //#region ../types/dist/fmp4-box-splitter-B53u9-Nu.mjs
7233
7137
  var AUDIO_ENCODER_BY_CODEC = {
7234
7138
  opus: "libopus",
7235
7139
  aac: "aac",
@@ -7410,13 +7314,42 @@ function buildStdoutOrRtspSinkArgs(sink) {
7410
7314
  "pipe:1"
7411
7315
  ];
7412
7316
  }
7317
+ /**
7318
+ * How far BELOW the negotiated fragment length `-min_frag_duration` is set.
7319
+ *
7320
+ * `-min_frag_duration` refuses to cut before that much media has accumulated,
7321
+ * and then waits for the next key frame. Set to exactly `fragmentMs`, the
7322
+ * commonest camera configuration in existence — a key-frame grid EQUAL to the
7323
+ * requested fragment length — lands the deadline on the same instant as the key
7324
+ * frame, loses the race, and skips to the following one: **every fragment comes
7325
+ * out at twice the requested length.**
7326
+ *
7327
+ * Measured on the live fleet 2026-08-07, camera 615, `-c:v copy` (D84):
7328
+ *
7329
+ * | slot | GOP | `-min_frag_duration` | median gap |
7330
+ * | --- | --- | --- | --- |
7331
+ * | 1280×720 | 40 f @ 10 fps = 4.0 s | 4000 ms | **7944 ms** |
7332
+ * | 1280×720 | 40 f @ 10 fps = 4.0 s | 3600 ms | 3973 ms |
7333
+ * | 3840×2160 | 100 f @ 25 fps = 4.0 s | 4000 ms | 8042 ms |
7334
+ * | 3840×2160 | 100 f @ 25 fps = 4.0 s | 3600 ms | 3998 ms |
7335
+ *
7336
+ * A doubled fragment is not a cosmetic overshoot: HKSV requires every fragment
7337
+ * to be no longer than the length the controller SELECTED, so the shipped-but-
7338
+ * inert phase-1 sink would have violated the contract on its first real clip.
7339
+ *
7340
+ * 10 % is chosen against the two failures either side of it. Too small and
7341
+ * ordinary jitter (measured spread 3953-4096 ms) re-loses the race; too large
7342
+ * and a source with a key frame slightly EARLY than the grid gets cut there,
7343
+ * yielding a short fragment for no reason.
7344
+ */
7345
+ var FMP4_MIN_FRAG_MARGIN = .9;
7413
7346
  /** `-movflags … -min_frag_duration <us> -f mp4 pipe:1`. */
7414
7347
  function buildFmp4SinkArgs(sink) {
7415
7348
  return [
7416
7349
  "-movflags",
7417
7350
  FMP4_MOVFLAGS,
7418
7351
  "-min_frag_duration",
7419
- String(Math.max(0, Math.round(sink.fragmentMs * 1e3))),
7352
+ String(Math.max(0, Math.round(sink.fragmentMs * FMP4_MIN_FRAG_MARGIN * 1e3))),
7420
7353
  "-f",
7421
7354
  "mp4",
7422
7355
  "pipe:1"
@@ -7606,6 +7539,287 @@ function invocationFromEncodeProfile(input) {
7606
7539
  };
7607
7540
  }
7608
7541
  /**
7542
+ * Deterministic SHA-256 hash of an arbitrary serialisable value. The
7543
+ * canonical form sorts object keys alphabetically at every depth so two
7544
+ * structurally-equal inputs with different key insertion orders produce
7545
+ * the same hash. Returns a 64-char lowercase hex digest.
7546
+ *
7547
+ * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /
7548
+ * accessory-rebuild work when the upstream shape is byte-identical to
7549
+ * the last applied state — preventing user-visible "re-discovery"
7550
+ * notifications on every addon-runner respawn. Each respawn re-fires
7551
+ * `DeviceBindingsChanged` for every cap registration, which without
7552
+ * this guard would propagate redundant pushes.
7553
+ *
7554
+ * Note: this is a SYMPTOMATIC fix layered on top of the binding-change
7555
+ * subscription. The proper fix is a single "device ready" lifecycle
7556
+ * barrier so exports react only when the full cap set has landed —
7557
+ * tracked separately for post-HA-integration work.
7558
+ */
7559
+ function canonicalHash(value) {
7560
+ const canonical = JSON.stringify(value, replaceWithSortedKeys);
7561
+ return (0, node_crypto.createHash)("sha256").update(canonical ?? "").digest("hex");
7562
+ }
7563
+ function replaceWithSortedKeys(_key, value) {
7564
+ if (value && typeof value === "object" && !Array.isArray(value)) {
7565
+ const obj = value;
7566
+ const out = {};
7567
+ for (const k of Object.keys(obj).toSorted()) out[k] = obj[k];
7568
+ return out;
7569
+ }
7570
+ return value;
7571
+ }
7572
+ var DEFAULT_MAX_UNIT_BYTES = 16 * 1024 * 1024;
7573
+ /** Header size for a normal box, and for one carrying a 64-bit `largesize`. */
7574
+ var BOX_HEADER_BYTES = 8;
7575
+ var LARGE_BOX_HEADER_BYTES = 16;
7576
+ var Fmp4BoxSplitter = class {
7577
+ maxUnitBytes;
7578
+ /** Bytes of the CURRENT unit plus any partial box after it. */
7579
+ buffer = new Uint8Array(0);
7580
+ /** Where the current unit starts inside {@link buffer}. */
7581
+ unitStart = 0;
7582
+ /** Where the box scanner has reached inside {@link buffer}. */
7583
+ cursor = 0;
7584
+ state = "init";
7585
+ nextSequence = 0;
7586
+ faultReason = null;
7587
+ interstitial = /* @__PURE__ */ new Set();
7588
+ constructor(options = {}) {
7589
+ this.maxUnitBytes = options.maxUnitBytes ?? DEFAULT_MAX_UNIT_BYTES;
7590
+ }
7591
+ /**
7592
+ * Non-null once the stream cannot be split. The splitter emits nothing
7593
+ * further, so a caller polls this to kill the child rather than watching a
7594
+ * silent stall — a fragmenter that quietly stops producing looks exactly like
7595
+ * a camera with no motion.
7596
+ */
7597
+ get fault() {
7598
+ return this.faultReason;
7599
+ }
7600
+ /** Bytes currently held. The memory bound, observable rather than asserted. */
7601
+ get pendingBytes() {
7602
+ return this.buffer.length - this.unitStart;
7603
+ }
7604
+ /**
7605
+ * Top-level box types seen BETWEEN fragments and discarded — `mfra`, `free`,
7606
+ * a stray `sidx`. Reported rather than dropped in silence: they are legal and
7607
+ * useless to a fragment consumer, but a type nobody expected showing up here
7608
+ * is the first symptom of a muxer that is not writing what we think it is.
7609
+ */
7610
+ get discardedInterstitialTypes() {
7611
+ return [...this.interstitial];
7612
+ }
7613
+ /**
7614
+ * Feed bytes; get back whatever units completed. Returns `[]` once faulted.
7615
+ */
7616
+ push(chunk) {
7617
+ if (this.faultReason !== null || chunk.length === 0) return [];
7618
+ this.append(chunk);
7619
+ if (this.pendingBytes > this.maxUnitBytes) return this.fail(`a single fMP4 unit exceeded ${this.maxUnitBytes} bytes — this stream is not fragmented`);
7620
+ return this.drainBoxes();
7621
+ }
7622
+ append(chunk) {
7623
+ if (this.buffer.length === 0) {
7624
+ this.buffer = chunk.slice();
7625
+ return;
7626
+ }
7627
+ const next = new Uint8Array(this.buffer.length + chunk.length);
7628
+ next.set(this.buffer, 0);
7629
+ next.set(chunk, this.buffer.length);
7630
+ this.buffer = next;
7631
+ }
7632
+ /** Consume every COMPLETE top-level box now in the buffer. */
7633
+ drainBoxes() {
7634
+ const units = [];
7635
+ for (;;) {
7636
+ const header = this.readHeader();
7637
+ if (this.faultReason !== null) return units;
7638
+ if (header === null) break;
7639
+ if (this.cursor + header.totalBytes > this.buffer.length) break;
7640
+ const boxStart = this.cursor;
7641
+ const boxEnd = boxStart + header.totalBytes;
7642
+ this.cursor = boxEnd;
7643
+ const unit = this.consumeBox(header.type, boxStart, boxEnd);
7644
+ if (this.faultReason !== null) return units;
7645
+ if (unit !== null) units.push(unit);
7646
+ }
7647
+ this.compact();
7648
+ return units;
7649
+ }
7650
+ /**
7651
+ * Apply one box to the state machine. Returns a unit when this box CLOSED
7652
+ * one, `null` otherwise.
7653
+ */
7654
+ consumeBox(type, boxStart, boxEnd) {
7655
+ if (this.state === "init") {
7656
+ if (type !== "moof") return null;
7657
+ if (boxStart === this.unitStart) {
7658
+ this.fail("a moof arrived before any initialisation box — there is no ftyp/moov to send");
7659
+ return null;
7660
+ }
7661
+ const init = this.emit("init", this.unitStart, boxStart);
7662
+ this.unitStart = boxStart;
7663
+ this.state = "fragment";
7664
+ return init;
7665
+ }
7666
+ if (this.state === "idle") {
7667
+ if (type !== "moof") {
7668
+ this.interstitial.add(type);
7669
+ this.unitStart = boxEnd;
7670
+ return null;
7671
+ }
7672
+ this.unitStart = boxStart;
7673
+ this.state = "fragment";
7674
+ return null;
7675
+ }
7676
+ if (type !== "mdat") return null;
7677
+ const fragment = this.emit("fragment", this.unitStart, boxEnd);
7678
+ this.unitStart = boxEnd;
7679
+ this.state = "idle";
7680
+ return fragment;
7681
+ }
7682
+ /**
7683
+ * Parse the header at {@link cursor}, or `null` when too few bytes have
7684
+ * arrived to know. Faults on a size the splitter cannot honour.
7685
+ */
7686
+ readHeader() {
7687
+ const available = this.buffer.length - this.cursor;
7688
+ if (available < BOX_HEADER_BYTES) return null;
7689
+ const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
7690
+ const size = view.getUint32(this.cursor);
7691
+ const type = String.fromCharCode(this.buffer[this.cursor + 4] ?? 0, this.buffer[this.cursor + 5] ?? 0, this.buffer[this.cursor + 6] ?? 0, this.buffer[this.cursor + 7] ?? 0);
7692
+ if (size === 0) {
7693
+ this.fail(`box "${type}" declares size 0 (to EOF) — an unbounded box cannot be fragmented`);
7694
+ return null;
7695
+ }
7696
+ if (size === 1) {
7697
+ if (available < LARGE_BOX_HEADER_BYTES) return null;
7698
+ const large = view.getBigUint64(this.cursor + BOX_HEADER_BYTES);
7699
+ if (large > BigInt(this.maxUnitBytes)) {
7700
+ this.fail(`box "${type}" declares ${large} bytes, over the ${this.maxUnitBytes} byte bound`);
7701
+ return null;
7702
+ }
7703
+ return {
7704
+ type,
7705
+ totalBytes: Number(large)
7706
+ };
7707
+ }
7708
+ if (size < BOX_HEADER_BYTES) {
7709
+ this.fail(`box "${type}" declares an impossible size of ${size} bytes`);
7710
+ return null;
7711
+ }
7712
+ return {
7713
+ type,
7714
+ totalBytes: size
7715
+ };
7716
+ }
7717
+ emit(kind, start, end) {
7718
+ const sequence = this.nextSequence;
7719
+ this.nextSequence += 1;
7720
+ return {
7721
+ kind,
7722
+ data: this.buffer.slice(start, end),
7723
+ sequence
7724
+ };
7725
+ }
7726
+ /**
7727
+ * Drop everything already emitted or discarded. Without this the buffer is
7728
+ * the whole stream and the process dies in hours, not minutes.
7729
+ */
7730
+ compact() {
7731
+ if (this.unitStart === 0) return;
7732
+ this.buffer = this.buffer.slice(this.unitStart);
7733
+ this.cursor -= this.unitStart;
7734
+ this.unitStart = 0;
7735
+ }
7736
+ fail(reason) {
7737
+ this.faultReason = reason;
7738
+ this.buffer = new Uint8Array(0);
7739
+ this.unitStart = 0;
7740
+ this.cursor = 0;
7741
+ return [];
7742
+ }
7743
+ };
7744
+ //#endregion
7745
+ //#region ../types/dist/err-msg-IQTHeDzc.mjs
7746
+ /**
7747
+ import { errMsg } from '@camstack/types'
7748
+ * Extract a human-readable message from an unknown error value.
7749
+ * Replaces the ubiquitous `errMsg(err)` pattern.
7750
+ */
7751
+ function errMsg(err) {
7752
+ if (err instanceof Error) return err.message;
7753
+ if (typeof err === "string") return err;
7754
+ return String(err);
7755
+ }
7756
+ var EncodeProfileSchema = object({
7757
+ video: object({
7758
+ codec: _enum([
7759
+ "h264",
7760
+ "h265",
7761
+ "copy"
7762
+ ]),
7763
+ profile: _enum([
7764
+ "baseline",
7765
+ "main",
7766
+ "high"
7767
+ ]).optional(),
7768
+ /**
7769
+ * `-level`, e.g. `'3.1'`. A consumer that ADVERTISES a level in its SDP
7770
+ * (`profile-level-id=42e01f` is Baseline 3.1) must constrain the encoder to
7771
+ * it, or it ships a stream that does not match its own advertisement — the
7772
+ * defect class that kept HomeKit black for a year and that Alexa carried
7773
+ * silently. Optional because a browser negotiates the level itself.
7774
+ */
7775
+ level: string().optional(),
7776
+ width: number().int().positive().optional(),
7777
+ height: number().int().positive().optional(),
7778
+ fps: number().positive().optional(),
7779
+ bitrateKbps: number().int().positive().optional(),
7780
+ gopFrames: number().int().positive().optional(),
7781
+ bf: number().int().min(0).optional(),
7782
+ preset: _enum([
7783
+ "ultrafast",
7784
+ "superfast",
7785
+ "veryfast",
7786
+ "faster",
7787
+ "fast",
7788
+ "medium"
7789
+ ]).optional(),
7790
+ tune: _enum([
7791
+ "zerolatency",
7792
+ "film",
7793
+ "animation"
7794
+ ]).optional()
7795
+ }),
7796
+ audio: union([literal("passthrough"), object({
7797
+ codec: _enum([
7798
+ "opus",
7799
+ "aac",
7800
+ "pcmu",
7801
+ "pcma",
7802
+ "copy"
7803
+ ]),
7804
+ bitrateKbps: number().int().positive().optional(),
7805
+ sampleRateHz: number().int().positive().optional(),
7806
+ channels: union([literal(1), literal(2)]).optional()
7807
+ })]),
7808
+ /**
7809
+ * ffmpeg input-side args, inserted between the fixed global flags
7810
+ * (`-hide_banner -loglevel error`) and `-i pipe:0`. Free-text array
7811
+ * — the widget surfaces a textarea + suggestion chips for the most-
7812
+ * used demuxer/format options.
7813
+ */
7814
+ inputArgs: array(string()).optional(),
7815
+ /**
7816
+ * ffmpeg output-side args, inserted between the encode block and
7817
+ * the final `-f <muxer> pipe:1`. Use for muxer options, bitstream
7818
+ * filters, codec-specific overrides. Free-text array.
7819
+ */
7820
+ outputArgs: array(string()).optional()
7821
+ });
7822
+ /**
7609
7823
  * The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
7610
7824
  * Baseline because it is the one profile every consumer in this repo decodes
7611
7825
  * (Echo, iOS, an old browser); 3.1 because that is what the SDPs advertise.
@@ -7850,6 +8064,19 @@ object({
7850
8064
  * | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
7851
8065
  * | `privacy-mask` | `privacyMask.setMask({ enabled })` → the CAMERA | the camera blanks the masked regions itself; every stream and recording carries the black boxes |
7852
8066
  * | `device-audio` | `privacyMask.setAudioEnabled` → the CAMERA | the camera stops encoding an audio track at all; every consumer sees silent video |
8067
+ * | `broker-audio` | `streamBroker.setDeviceAudioMute` → `DeviceOverride.audioMuted` | `StreamBroker.setAudioMuted` drops the audio plane at the source: no `type:'audio'` packet leaves `fanOutEncoded`, no RTP reaches the restreamer, and the restreamer serves the video-only SDP |
8068
+ *
8069
+ * ## `device-audio` and `broker-audio` are two functions, not two knobs
8070
+ *
8071
+ * They look adjacent and they are not the same control ([D83](../../../../docs/decisions/adr-0083.md)):
8072
+ * `device-audio` writes the CAMERA, so it is hardware privacy — the microphone
8073
+ * genuinely stops, it survives CamStack entirely, and it costs a multi-second
8074
+ * encoder restart on every flip. `broker-audio` writes THIS server, so it is
8075
+ * instant, vendor-independent and reversible without touching the camera, and
8076
+ * a camera that ignores or lacks the ISAPI/Reolink control is still silenced.
8077
+ * D62 forbids a second switch that *disagrees* with the first; these two
8078
+ * cannot disagree, because neither reads the other's store — the camera holds
8079
+ * one, the broker holds the other, and each reports its own fact.
7853
8080
  *
7854
8081
  * ## The two switches whose authority is not on this server
7855
8082
  *
@@ -7911,6 +8138,7 @@ var CameraSwitchIdSchema = _enum([
7911
8138
  "object-detection",
7912
8139
  "privacy-mask",
7913
8140
  "device-audio",
8141
+ "broker-audio",
7914
8142
  "audio-analysis",
7915
8143
  "recording",
7916
8144
  "notifications"
@@ -7936,7 +8164,8 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7936
8164
  object({
7937
8165
  kind: literal("camera-mask"),
7938
8166
  capName: string()
7939
- })
8167
+ }),
8168
+ object({ kind: literal("broker-audio-mute") })
7940
8169
  ]);
7941
8170
  /**
7942
8171
  * Why a switch is not offered for this camera. Rendered instead of the
@@ -10405,7 +10634,44 @@ var streamBrokerCapability = {
10405
10634
  kind: "mutation",
10406
10635
  auth: "admin"
10407
10636
  }),
10408
- isRtspEnabled: method(object({ brokerId: string() }), boolean())
10637
+ isRtspEnabled: method(object({ brokerId: string() }), boolean()),
10638
+ /**
10639
+ * ── Per-device audio-plane policy (D83) ───────────────────────────
10640
+ *
10641
+ * The BROKER-side mute: while `muted`, this node distributes none of
10642
+ * the device's audio, on any plane it serves — live (WebRTC / encoded
10643
+ * subscribers) AND recording (the RTSP restreamer the recorder pulls,
10644
+ * which additionally serves the video-only SDP so the recorder's ffmpeg
10645
+ * never declares an audio stream it will not receive).
10646
+ *
10647
+ * Keyed by `deviceId`, not `brokerId`: every one of a camera's streams
10648
+ * carries the same microphone, and a per-stream answer would let main
10649
+ * and sub disagree about whether the camera is silent.
10650
+ *
10651
+ * The state lives in the broker's existing `DeviceOverride` blob — no
10652
+ * new store — and the mute is applied to a fresh broker at creation, so
10653
+ * a restart, a re-dial or a catalog republish never un-mutes a camera.
10654
+ */
10655
+ getDeviceAudioMute: method(object({ deviceId: number().int() }), object({
10656
+ muted: boolean(),
10657
+ /**
10658
+ * How many live non-derived brokers currently hold the mute. Purely
10659
+ * diagnostic: `muted` is the policy and is authoritative on its own
10660
+ * (it applies to brokers that do not exist yet), while this says
10661
+ * whether anything is presently being silenced.
10662
+ */
10663
+ appliedBrokers: number().int().nonnegative()
10664
+ })),
10665
+ setDeviceAudioMute: method(object({
10666
+ deviceId: number().int(),
10667
+ muted: boolean()
10668
+ }), object({
10669
+ muted: boolean(),
10670
+ appliedBrokers: number().int().nonnegative()
10671
+ }), {
10672
+ kind: "mutation",
10673
+ auth: "admin"
10674
+ })
10409
10675
  },
10410
10676
  events: {
10411
10677
  onCamStreamDemand: event(object({
@@ -16141,6 +16407,30 @@ var TrackSourceSchema = _enum([
16141
16407
  "audio"
16142
16408
  ]);
16143
16409
  /**
16410
+ * Where a track sits in the RETRAIN lifecycle (D81).
16411
+ *
16412
+ * - `none` — never marked, or un-marked. Evictable.
16413
+ * - `staging` — the operator wants this track as training material and has not
16414
+ * finished with it. **This is the only state retention holds**: the track and
16415
+ * everything it owns (object events, crops, keyframes, CLIP vector) survive
16416
+ * the device's age window.
16417
+ * - `trained` — the retrain page has taken what it needed. The frames it chose
16418
+ * were COPIED into the retrain dataset at selection time, so the dataset no
16419
+ * longer depends on the track's media and the track becomes EVICTABLE again.
16420
+ * Terminal for the plain `markForTrain` toggle: returning it to `staging` is
16421
+ * a deliberate action of the retrain page, not a side effect of a checkbox.
16422
+ *
16423
+ * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
16424
+ * the store's filter language has only positive equality and `whereIn` — no
16425
+ * negation, no IS NULL — so a NULL would be unselectable by ANY predicate and
16426
+ * would make the entire pre-column history immortal in one deploy.
16427
+ */
16428
+ var RetrainStatusSchema = _enum([
16429
+ "none",
16430
+ "staging",
16431
+ "trained"
16432
+ ]);
16433
+ /**
16144
16434
  * Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
16145
16435
  * by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
16146
16436
  * so the two surfaces cannot drift.
@@ -16150,18 +16440,31 @@ var TrackSourceSchema = _enum([
16150
16440
  * columns existed read as absent, and a consumer that needs a boolean should say
16151
16441
  * `flag === true`, not `flag !== false`.
16152
16442
  *
16153
- * What the flags DO is deliberately UNDEFINED at the time of writing: they are
16154
- * operator curation, and the behaviour they drive will be specified separately.
16155
- * In particular a `markForTrain` track is NOT pinned against retention — see
16156
- * `docs/decisions/adr-0059.md` for why that is a store-level change, not a flag.
16443
+ * `markForTrain` is the WIRE FACE of {@link RetrainStatusSchema}, not a column:
16444
+ * it is exactly `retrainStatus === 'staging'`, in both directions. Writing
16445
+ * `true` moves `none → staging`, writing `false` moves `staging none`, and a
16446
+ * `trained` track reports `false` while refusing both writes. The boolean is
16447
+ * kept because three surfaces drive a toggle off it; anything that needs to tell
16448
+ * "never marked" from "already trained" must read `retrainStatus`.
16449
+ *
16450
+ * `debug` does NOT pin; it is attention, not durability.
16157
16451
  */
16158
16452
  var TrackFlagFields = {
16159
- /** Operator marked this track as training material. */
16453
+ /** Operator marked this track as training material — i.e. `retrainStatus` is
16454
+ * `'staging'`. */
16160
16455
  markForTrain: boolean().optional(),
16161
16456
  /** Operator marked this track for diagnostic attention. */
16162
16457
  debug: boolean().optional()
16163
16458
  };
16164
16459
  /**
16460
+ * The lifecycle field itself, on the READ surfaces only (`Track`, `KeyEvent`).
16461
+ * Deliberately NOT part of {@link TrackFlagFields}: that group also builds the
16462
+ * write patch, and the status is not something the toggle sets — it is what the
16463
+ * toggle's boolean is derived from. Absent on an in-RAM track never touched;
16464
+ * always present on a persisted row (the column default materialises `'none'`).
16465
+ */
16466
+ var TrackRetrainFields = { retrainStatus: RetrainStatusSchema.optional() };
16467
+ /**
16165
16468
  * The write half: a PARTIAL patch. An omitted key is left untouched, so setting
16166
16469
  * one flag can never clear the other — the toggles are independent and are
16167
16470
  * driven from three surfaces that do not know about each other.
@@ -16175,7 +16478,32 @@ var TrackFlagsPatchSchema = object(TrackFlagFields);
16175
16478
  var TrackFlagsSchema = object({
16176
16479
  trackId: string(),
16177
16480
  markForTrain: boolean(),
16178
- debug: boolean()
16481
+ debug: boolean(),
16482
+ /** The lifecycle state the boolean was derived from. Required here (unlike on
16483
+ * a track row) because this shape is only ever produced by the write body,
16484
+ * which always knows it — and a surface that has just written needs to render
16485
+ * `trained` without a re-fetch. */
16486
+ retrainStatus: RetrainStatusSchema
16487
+ });
16488
+ /** Per-camera slice of a training-export estimate. */
16489
+ var TrainingExportDeviceTotalsSchema = object({
16490
+ deviceId: number(),
16491
+ tracks: number().int(),
16492
+ files: number().int(),
16493
+ bytes: number().int()
16494
+ });
16495
+ /**
16496
+ * What a training export WOULD contain. Computed from media index rows only —
16497
+ * no blob is read to produce this.
16498
+ */
16499
+ var TrainingExportSummarySchema = object({
16500
+ generatedAt: number(),
16501
+ trackCount: number().int(),
16502
+ fileCount: number().int(),
16503
+ byteCount: number().int(),
16504
+ /** More marked tracks exist than a single pass carries. */
16505
+ truncated: boolean(),
16506
+ devices: array(TrainingExportDeviceTotalsSchema).readonly()
16179
16507
  });
16180
16508
  var TrackSchema = object({
16181
16509
  trackId: string(),
@@ -16220,7 +16548,8 @@ var TrackSchema = object({
16220
16548
  * Populated from the persisted envelope columns on historical reads;
16221
16549
  * absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
16222
16550
  envelope: TrackEnvelopeSchema.optional(),
16223
- ...TrackFlagFields
16551
+ ...TrackFlagFields,
16552
+ ...TrackRetrainFields
16224
16553
  });
16225
16554
  var BaseEventFields = {
16226
16555
  id: string(),
@@ -16434,7 +16763,8 @@ var KeyEventSchema = object({
16434
16763
  bestEventId: string(),
16435
16764
  /** Track lifetime in ms (lastSeen - firstSeen). */
16436
16765
  windowMs: number().optional(),
16437
- ...TrackFlagFields
16766
+ ...TrackFlagFields,
16767
+ ...TrackRetrainFields
16438
16768
  });
16439
16769
  object({
16440
16770
  trackId: string(),
@@ -16695,6 +17025,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16695
17025
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16696
17026
  kind: "query",
16697
17027
  auth: "admin"
17028
+ }), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
17029
+ kind: "query",
17030
+ auth: "admin"
17031
+ }), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
17032
+ kind: "query",
17033
+ auth: "admin"
16698
17034
  }), method(object({
16699
17035
  eventId: string(),
16700
17036
  kind: MediaFileKindEnum.optional()
@@ -22744,6 +23080,173 @@ DeviceType.Camera, method(object({
22744
23080
  status: OsdStatusSchema
22745
23081
  });
22746
23082
  /**
23083
+ * `osd-manager` — the ORCHESTRATOR over the device-scope `osd` cap.
23084
+ *
23085
+ * The `osd` cap is the firmware contract: it probes a camera's overlay
23086
+ * SLOTS and writes literal text into one. It has no idea WHERE that text
23087
+ * comes from, and it must not — a driver that grew a "show the temperature
23088
+ * here" feature would grow it once per vendor.
23089
+ *
23090
+ * This cap owns the other half: a per-(camera, slot) BINDING that says
23091
+ * which value feeds the slot, how it is formatted, and under which
23092
+ * conditions it is shown at all. One addon renders every binding on every
23093
+ * camera, so a new source costs zero driver code.
23094
+ *
23095
+ * Three deliberate choices, each with a rejected alternative:
23096
+ *
23097
+ * 1. A source is `(capName, valuePath)` over the kernel's device
23098
+ * runtime-state mirror — NOT a closed enum of source kinds. Every
23099
+ * cap-keyed slice a device publishes is bindable the day the cap
23100
+ * ships. The rejected alternative (one enum member per source, with
23101
+ * a resolver branch each) is what makes "add the humidity too" a
23102
+ * code change.
23103
+ * 2. The display gate reuses `NcConditionsSchema` verbatim — the
23104
+ * notification centre's condition vocabulary — rather than a parallel
23105
+ * model. An operator who has learned one condition editor has learned
23106
+ * both.
23107
+ * 3. Because the renderer's facts are device STATE and not a detection
23108
+ * record, only a SUBSET of that vocabulary can be answered here.
23109
+ * `setSlotBinding` REJECTS the rest at write time (see
23110
+ * `getConditionSupport`). It does not accept-then-fail-closed: a
23111
+ * condition that can never be true renders a permanently blank
23112
+ * overlay, and a blank overlay looks exactly like a broken camera.
23113
+ */
23114
+ /** Where a slot's value comes from. */
23115
+ var OsdSourceSchema = discriminatedUnion("kind", [
23116
+ object({
23117
+ kind: literal("static"),
23118
+ text: string().max(64)
23119
+ }),
23120
+ object({
23121
+ kind: literal("clock"),
23122
+ /** Token pattern: `YYYY MM DD HH mm ss`. Everything else is literal. */
23123
+ pattern: string().min(1).max(32).default("HH:mm"),
23124
+ /** IANA zone. Omitted = the server's zone. */
23125
+ timezone: string().min(1).max(64).optional()
23126
+ }),
23127
+ object({
23128
+ kind: literal("device-state"),
23129
+ deviceId: number().int().optional(),
23130
+ capName: string().min(1).max(64),
23131
+ /** Dot path inside the slice, e.g. `detected`, `value`, `mode`. */
23132
+ valuePath: string().min(1).max(64)
23133
+ })
23134
+ ]);
23135
+ var OsdSlotBindingSchema = object({
23136
+ /** Off = the manager stops driving this slot. It does NOT clear it. */
23137
+ enabled: boolean().default(true),
23138
+ source: OsdSourceSchema,
23139
+ /** `${value}` and `${unit}` are substituted; every occurrence. */
23140
+ template: string().max(96).default("${value}"),
23141
+ /** Truncate with an ellipsis past this length. Absent = no limit. */
23142
+ maxCharacters: number().int().min(4).max(64).optional(),
23143
+ /**
23144
+ * Decimal places for a numeric value. `0` yields an integer — the
23145
+ * documented workaround for firmwares that reject `.` in overlay text.
23146
+ */
23147
+ maxDecimals: number().int().min(0).max(4).default(1),
23148
+ /** Appended via `${unit}`. The state mirror does not carry units. */
23149
+ unitLabel: string().max(8).optional(),
23150
+ /** Raw value → display text, e.g. `{"true":"MOTION","false":""}`. */
23151
+ valueMap: record(string(), string()).optional(),
23152
+ /** Time windows in which the slot is shown. Absent = always. */
23153
+ schedule: NcScheduleSchema.optional(),
23154
+ /**
23155
+ * Display gate, in the notification centre's condition vocabulary.
23156
+ * Only the keys reported by `getConditionSupport` are accepted.
23157
+ */
23158
+ conditions: NcConditionsSchema.optional(),
23159
+ /** Rendered when the gate is closed or the value unreadable. Empty = hide. */
23160
+ fallbackText: string().max(64).default("")
23161
+ });
23162
+ /** One camera slot, as the operator sees it: firmware truth + our binding. */
23163
+ var OsdSlotViewSchema = object({
23164
+ slotId: string(),
23165
+ kind: OsdOverlayKindEnum,
23166
+ /** Firmware refuses text edits (a timestamp, the channel name). */
23167
+ readOnly: boolean(),
23168
+ cameraEnabled: boolean(),
23169
+ cameraText: string().optional(),
23170
+ binding: OsdSlotBindingSchema.nullable()
23171
+ });
23172
+ /**
23173
+ * What happened to one slot on one render pass. `unchanged` exists so the
23174
+ * operator can tell "we are driving this and the value is steady" from
23175
+ * "we never got there" — and so the loop can prove it is not rewriting
23176
+ * identical text to the camera every tick.
23177
+ */
23178
+ var OsdRenderOutcomeEnum = _enum([
23179
+ "written",
23180
+ "unchanged",
23181
+ "gated",
23182
+ "unreadable",
23183
+ "disabled",
23184
+ "unbound",
23185
+ "failed"
23186
+ ]);
23187
+ var OsdRenderResultSchema = object({
23188
+ slotId: string(),
23189
+ outcome: OsdRenderOutcomeEnum,
23190
+ /** The text the slot should carry. Empty = the slot is switched off. */
23191
+ text: string(),
23192
+ /** Why, whenever the outcome is not a plain write. Never silent. */
23193
+ reason: string().optional()
23194
+ });
23195
+ var OsdSourceValueTypeEnum = _enum([
23196
+ "number",
23197
+ "boolean",
23198
+ "string",
23199
+ "enum"
23200
+ ]);
23201
+ /**
23202
+ * One bindable value, derived from a cap's `runtimeState` schema — never
23203
+ * hand-listed. The editor renders from this, so a cap that ships a new
23204
+ * state field becomes bindable with no UI change.
23205
+ */
23206
+ var OsdSourceOptionSchema = object({
23207
+ deviceId: number().int(),
23208
+ deviceName: string(),
23209
+ capName: string(),
23210
+ valuePath: string(),
23211
+ label: string(),
23212
+ valueType: OsdSourceValueTypeEnum,
23213
+ /** Present for `enum`; the editor offers these as `valueMap` keys. */
23214
+ enumValues: array(string()).readonly().optional()
23215
+ });
23216
+ method(object({ deviceId: number().int() }), object({
23217
+ supported: boolean(),
23218
+ slots: array(OsdSlotViewSchema)
23219
+ }), { auth: "admin" }), method(object({ deviceId: number().int() }), object({ sources: array(OsdSourceOptionSchema) }), { auth: "admin" }), method(object({}), object({
23220
+ supported: array(string()),
23221
+ catalog: array(NcConditionDescriptorSchema)
23222
+ }), { auth: "admin" }), method(object({
23223
+ deviceId: number().int(),
23224
+ slotId: string().min(1),
23225
+ binding: OsdSlotBindingSchema
23226
+ }), object({
23227
+ slot: OsdSlotViewSchema,
23228
+ render: OsdRenderResultSchema
23229
+ }), {
23230
+ kind: "mutation",
23231
+ auth: "admin"
23232
+ }), method(object({
23233
+ deviceId: number().int(),
23234
+ slotId: string().min(1)
23235
+ }), object({ success: literal(true) }), {
23236
+ kind: "mutation",
23237
+ auth: "admin"
23238
+ }), method(object({
23239
+ deviceId: number().int(),
23240
+ slotId: string().min(1),
23241
+ binding: OsdSlotBindingSchema.optional()
23242
+ }), OsdRenderResultSchema, {
23243
+ kind: "mutation",
23244
+ auth: "admin"
23245
+ }), method(object({ deviceId: number().int() }), object({ results: array(OsdRenderResultSchema) }), {
23246
+ kind: "mutation",
23247
+ auth: "admin"
23248
+ });
23249
+ /**
22747
23250
  * Feeder connectivity / power status — mirrors the HA petkit device-status
22748
23251
  * enum: `normal` (online, mains), `offline` (not reaching PetKit cloud),
22749
23252
  * `on_batteries` (running on battery backup). `null` until first reported.
@@ -27800,6 +28303,48 @@ Object.freeze({
27800
28303
  addonId: null,
27801
28304
  access: "create"
27802
28305
  },
28306
+ "osdManager.clearSlotBinding": {
28307
+ capName: "osd-manager",
28308
+ capScope: "system",
28309
+ addonId: null,
28310
+ access: "delete"
28311
+ },
28312
+ "osdManager.getConditionSupport": {
28313
+ capName: "osd-manager",
28314
+ capScope: "system",
28315
+ addonId: null,
28316
+ access: "view"
28317
+ },
28318
+ "osdManager.getDeviceOsd": {
28319
+ capName: "osd-manager",
28320
+ capScope: "system",
28321
+ addonId: null,
28322
+ access: "view"
28323
+ },
28324
+ "osdManager.getSourceCatalog": {
28325
+ capName: "osd-manager",
28326
+ capScope: "system",
28327
+ addonId: null,
28328
+ access: "view"
28329
+ },
28330
+ "osdManager.previewSlot": {
28331
+ capName: "osd-manager",
28332
+ capScope: "system",
28333
+ addonId: null,
28334
+ access: "create"
28335
+ },
28336
+ "osdManager.renderDevice": {
28337
+ capName: "osd-manager",
28338
+ capScope: "system",
28339
+ addonId: null,
28340
+ access: "create"
28341
+ },
28342
+ "osdManager.setSlotBinding": {
28343
+ capName: "osd-manager",
28344
+ capScope: "system",
28345
+ addonId: null,
28346
+ access: "create"
28347
+ },
27803
28348
  "petFeeder.callPet": {
27804
28349
  capName: "pet-feeder",
27805
28350
  capScope: "device",
@@ -27962,6 +28507,18 @@ Object.freeze({
27962
28507
  addonId: null,
27963
28508
  access: "view"
27964
28509
  },
28510
+ "pipelineAnalytics.getTrainingExportSummary": {
28511
+ capName: "pipeline-analytics",
28512
+ capScope: "device",
28513
+ addonId: null,
28514
+ access: "view"
28515
+ },
28516
+ "pipelineAnalytics.getTrainingExportUrl": {
28517
+ capName: "pipeline-analytics",
28518
+ capScope: "device",
28519
+ addonId: null,
28520
+ access: "view"
28521
+ },
27965
28522
  "pipelineAnalytics.listEventKinds": {
27966
28523
  capName: "pipeline-analytics",
27967
28524
  capScope: "device",
@@ -29438,6 +29995,12 @@ Object.freeze({
29438
29995
  addonId: null,
29439
29996
  access: "view"
29440
29997
  },
29998
+ "streamBroker.getDeviceAudioMute": {
29999
+ capName: "stream-broker",
30000
+ capScope: "system",
30001
+ addonId: null,
30002
+ access: "view"
30003
+ },
29441
30004
  "streamBroker.getPreBufferInfo": {
29442
30005
  capName: "stream-broker",
29443
30006
  capScope: "system",
@@ -29558,6 +30121,12 @@ Object.freeze({
29558
30121
  addonId: null,
29559
30122
  access: "create"
29560
30123
  },
30124
+ "streamBroker.setDeviceAudioMute": {
30125
+ capName: "stream-broker",
30126
+ capScope: "system",
30127
+ addonId: null,
30128
+ access: "create"
30129
+ },
29561
30130
  "streamBroker.setPreBufferDuration": {
29562
30131
  capName: "stream-broker",
29563
30132
  capScope: "system",
@@ -31124,6 +31693,12 @@ Object.defineProperty(exports, "ExportRecordSchema", {
31124
31693
  return ExportRecordSchema;
31125
31694
  }
31126
31695
  });
31696
+ Object.defineProperty(exports, "Fmp4BoxSplitter", {
31697
+ enumerable: true,
31698
+ get: function() {
31699
+ return Fmp4BoxSplitter;
31700
+ }
31701
+ });
31127
31702
  Object.defineProperty(exports, "HF_BASE_URL", {
31128
31703
  enumerable: true,
31129
31704
  get: function() {