@camstack/addon-pipeline 1.2.229 → 1.2.231

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 (92) hide show
  1. package/dist/stream-broker/index.js +147 -6
  2. package/dist/stream-broker/index.mjs +147 -6
  3. package/package.json +1 -1
  4. package/python/__pycache__/inference_pool.cpython-312.pyc +0 -0
  5. package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
  6. package/python/__pycache__/inference_pool.cpython-314.pyc +0 -0
  7. package/python/__pycache__/test_inference_pool_backpressure.cpython-312-pytest-9.1.1.pyc +0 -0
  8. package/python/__pycache__/test_inference_pool_backpressure.cpython-312.pyc +0 -0
  9. package/python/__pycache__/test_inference_pool_backpressure.cpython-314.pyc +0 -0
  10. package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312-pytest-9.1.1.pyc +0 -0
  11. package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312.pyc +0 -0
  12. package/python/__pycache__/test_inference_pool_coreml_cache.cpython-314.pyc +0 -0
  13. package/python/__pycache__/test_inference_pool_deadline.cpython-314.pyc +0 -0
  14. package/python/__pycache__/test_inference_pool_device_selection.cpython-312-pytest-9.1.1.pyc +0 -0
  15. package/python/__pycache__/test_inference_pool_device_selection.cpython-312.pyc +0 -0
  16. package/python/__pycache__/test_inference_pool_device_selection.cpython-314.pyc +0 -0
  17. package/python/__pycache__/test_inference_pool_layout.cpython-312-pytest-9.1.1.pyc +0 -0
  18. package/python/__pycache__/test_inference_pool_layout.cpython-314.pyc +0 -0
  19. package/python/__pycache__/test_inference_pool_memstats.cpython-312-pytest-9.1.1.pyc +0 -0
  20. package/python/__pycache__/test_inference_pool_memstats.cpython-312.pyc +0 -0
  21. package/python/__pycache__/test_inference_pool_memstats.cpython-314.pyc +0 -0
  22. package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312-pytest-9.1.1.pyc +0 -0
  23. package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312.pyc +0 -0
  24. package/python/__pycache__/test_inference_pool_ov_ppp.cpython-314.pyc +0 -0
  25. package/python/__pycache__/test_inference_pool_ov_requests.cpython-314.pyc +0 -0
  26. package/python/__pycache__/test_inference_pool_preprocess.cpython-312-pytest-9.1.1.pyc +0 -0
  27. package/python/__pycache__/test_inference_pool_preprocess.cpython-314.pyc +0 -0
  28. package/python/__pycache__/test_inference_pool_reconfigure.cpython-314.pyc +0 -0
  29. package/python/__pycache__/test_inference_pool_static_batch.cpython-312-pytest-9.1.1.pyc +0 -0
  30. package/python/__pycache__/test_inference_pool_static_batch.cpython-312.pyc +0 -0
  31. package/python/__pycache__/test_inference_pool_static_batch.cpython-314.pyc +0 -0
  32. package/python/__pycache__/yamnet_audio.cpython-314.pyc +0 -0
  33. package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
  34. package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
  35. package/python/postprocessors/__pycache__/__init__.cpython-314.pyc +0 -0
  36. package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
  37. package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
  38. package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
  39. package/python/postprocessors/__pycache__/arcface.cpython-314.pyc +0 -0
  40. package/python/postprocessors/__pycache__/clip.cpython-312.pyc +0 -0
  41. package/python/postprocessors/__pycache__/clip.cpython-313.pyc +0 -0
  42. package/python/postprocessors/__pycache__/clip.cpython-314.pyc +0 -0
  43. package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
  44. package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
  45. package/python/postprocessors/__pycache__/ctc.cpython-314.pyc +0 -0
  46. package/python/postprocessors/__pycache__/plate_slots.cpython-312.pyc +0 -0
  47. package/python/postprocessors/__pycache__/plate_slots.cpython-314.pyc +0 -0
  48. package/python/postprocessors/__pycache__/rfdetr.cpython-312.pyc +0 -0
  49. package/python/postprocessors/__pycache__/rfdetr.cpython-314.pyc +0 -0
  50. package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
  51. package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
  52. package/python/postprocessors/__pycache__/saliency.cpython-314.pyc +0 -0
  53. package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
  54. package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
  55. package/python/postprocessors/__pycache__/scrfd.cpython-314.pyc +0 -0
  56. package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
  57. package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
  58. package/python/postprocessors/__pycache__/softmax.cpython-314.pyc +0 -0
  59. package/python/postprocessors/__pycache__/ssd.cpython-312.pyc +0 -0
  60. package/python/postprocessors/__pycache__/ssd.cpython-313.pyc +0 -0
  61. package/python/postprocessors/__pycache__/ssd.cpython-314.pyc +0 -0
  62. package/python/postprocessors/__pycache__/test_clip.cpython-312-pytest-9.1.1.pyc +0 -0
  63. package/python/postprocessors/__pycache__/test_clip.cpython-313-pytest-9.1.1.pyc +0 -0
  64. package/python/postprocessors/__pycache__/test_ctc.cpython-312-pytest-9.1.1.pyc +0 -0
  65. package/python/postprocessors/__pycache__/test_ctc.cpython-312.pyc +0 -0
  66. package/python/postprocessors/__pycache__/test_ctc.cpython-313-pytest-9.1.1.pyc +0 -0
  67. package/python/postprocessors/__pycache__/test_ctc.cpython-313.pyc +0 -0
  68. package/python/postprocessors/__pycache__/test_ctc.cpython-314.pyc +0 -0
  69. package/python/postprocessors/__pycache__/test_plate_slots.cpython-312-pytest-9.1.1.pyc +0 -0
  70. package/python/postprocessors/__pycache__/test_rfdetr.cpython-312-pytest-9.1.1.pyc +0 -0
  71. package/python/postprocessors/__pycache__/test_rfdetr.cpython-314.pyc +0 -0
  72. package/python/postprocessors/__pycache__/test_saliency.cpython-312-pytest-9.1.1.pyc +0 -0
  73. package/python/postprocessors/__pycache__/test_saliency.cpython-313-pytest-9.1.1.pyc +0 -0
  74. package/python/postprocessors/__pycache__/test_saliency.cpython-314.pyc +0 -0
  75. package/python/postprocessors/__pycache__/test_softmax.cpython-312-pytest-9.1.1.pyc +0 -0
  76. package/python/postprocessors/__pycache__/test_softmax.cpython-314.pyc +0 -0
  77. package/python/postprocessors/__pycache__/test_yolo_nms.cpython-312-pytest-9.1.1.pyc +0 -0
  78. package/python/postprocessors/__pycache__/test_yolo_nms.cpython-314.pyc +0 -0
  79. package/python/postprocessors/__pycache__/test_yolo_nms.cpython-39.pyc +0 -0
  80. package/python/postprocessors/__pycache__/test_yolonas.cpython-312-pytest-9.1.1.pyc +0 -0
  81. package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
  82. package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
  83. package/python/postprocessors/__pycache__/yamnet.cpython-314.pyc +0 -0
  84. package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
  85. package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
  86. package/python/postprocessors/__pycache__/yolo.cpython-314.pyc +0 -0
  87. package/python/postprocessors/__pycache__/yolo.cpython-39.pyc +0 -0
  88. package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
  89. package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
  90. package/python/postprocessors/__pycache__/yolo_seg.cpython-314.pyc +0 -0
  91. package/python/postprocessors/__pycache__/yolonas.cpython-312.pyc +0 -0
  92. package/python/postprocessors/__pycache__/yolonas.cpython-314.pyc +0 -0
@@ -670,6 +670,45 @@ function prestreamEligible(input) {
670
670
  };
671
671
  return { eligible: true };
672
672
  }
673
+ //#endregion
674
+ //#region src/stream-broker/stream-broker/prestream-signal-selection.ts
675
+ function nextEnabledSignals(input) {
676
+ const base = input.stored ?? input.declared;
677
+ const set = new Set(base);
678
+ if (input.enabled) set.add(input.code);
679
+ else set.delete(input.code);
680
+ const known = input.declared.filter((c) => set.has(c));
681
+ const extra = [...set].filter((c) => !input.declared.includes(c));
682
+ return [...known, ...extra];
683
+ }
684
+ //#endregion
685
+ //#region src/stream-broker/stream-broker/wake-hold.ts
686
+ /**
687
+ * The HOLD a camera's own signal buys, and what it may not buy.
688
+ *
689
+ * A stream a consumer merely watches is not a reason to wake a battery camera —
690
+ * a Frigate-style puller holds a session open for ever, and treating that as
691
+ * demand keeps the camera awake for ever, which is the whole point of the
692
+ * battery (D173). So a prestream consumer is FED without being INTENT, and the
693
+ * only thing that dials is a signal the camera reported about itself.
694
+ *
695
+ * This module is the timing half of that, and it is pure so the arithmetic can
696
+ * be argued with. Three rules:
697
+ *
698
+ * - **A hold EXTENDS, it never stacks.** Continuous motion keeps a camera up
699
+ * and it sleeps `holdMs` after the LAST signal, not after the sum of them.
700
+ * A counter or a queue would turn a busy afternoon into an hour of wake.
701
+ * - **A run is bounded ABSOLUTELY, from its first signal.** A stuck PIR
702
+ * reporting every second would otherwise extend the hold indefinitely and
703
+ * drain the battery — the one failure this whole design exists to prevent.
704
+ * The same re-arm-under-a-ceiling shape D371 gives `recording-signal`.
705
+ * - **A deadline, not a chain of timers.** The state is one number; the timer
706
+ * only decides when to LOOK at it. A chain would drift and would have to be
707
+ * cancelled correctly on every path.
708
+ */
709
+ /** How long a camera stays awake after the LAST signal. The operator's, per
710
+ * camera; this is the default the broker ships. */
711
+ var DEFAULT_WAKE_HOLD_MS = 15e3;
673
712
  /**
674
713
  * Ceiling on ONE run, measured from its first signal.
675
714
  *
@@ -19307,6 +19346,20 @@ var StreamBrokerManager = class StreamBrokerManager {
19307
19346
  * nothing, and `[]` when it does not declare the cap at all — both mean no
19308
19347
  * signal of that name is acceptable, which is the same refusal.
19309
19348
  */
19349
+ /**
19350
+ * The signals a camera declares, with their labels — what the form renders.
19351
+ * `[]` on any failure, which the caller reads as "not eligible": a form built
19352
+ * from a list we could not read would offer wakes nobody confirmed.
19353
+ */
19354
+ async declaredSignalList(deviceId) {
19355
+ const read = this.deviceSignalDetailReader;
19356
+ if (read === null) return [];
19357
+ try {
19358
+ return await read(deviceId);
19359
+ } catch {
19360
+ return [];
19361
+ }
19362
+ }
19310
19363
  async declaredSignalCodes(deviceId) {
19311
19364
  const read = this.deviceSignalReader;
19312
19365
  if (read === null) return [];
@@ -19324,6 +19377,12 @@ var StreamBrokerManager = class StreamBrokerManager {
19324
19377
  setDeviceSignalReader(read) {
19325
19378
  this.deviceSignalReader = read;
19326
19379
  }
19380
+ /** The same read, with labels — the form needs them, the wake decision does
19381
+ * not. Injected by the host addon for the same reason. */
19382
+ deviceSignalDetailReader = null;
19383
+ setDeviceSignalDetailReader(read) {
19384
+ this.deviceSignalDetailReader = read;
19385
+ }
19327
19386
  /**
19328
19387
  * Whether this device runs on a battery.
19329
19388
  *
@@ -19445,6 +19504,12 @@ var StreamBrokerManager = class StreamBrokerManager {
19445
19504
  value: setting,
19446
19505
  label: hwAccelOptionLabel(setting)
19447
19506
  }));
19507
+ const declaredSignals = await this.declaredSignalList(deviceId);
19508
+ const prestreamGate = prestreamEligible({
19509
+ batteryOperated: await this.isBatteryOperated(deviceId),
19510
+ declaresSignals: declaredSignals.length > 0
19511
+ });
19512
+ const prestreamOffered = prestreamGate.eligible;
19448
19513
  const streamTabs = streams.filter((s) => !s.camStreamId.startsWith("derived:")).map((stream) => {
19449
19514
  const camStreamId = stream.camStreamId;
19450
19515
  const brokerId = brokerIdFor(deviceId, camStreamId);
@@ -19544,6 +19609,20 @@ var StreamBrokerManager = class StreamBrokerManager {
19544
19609
  tooltip: "Copy muted URL to clipboard"
19545
19610
  }]
19546
19611
  },
19612
+ ...prestreamOffered ? [{
19613
+ type: "text",
19614
+ key: `rtspPrestreamUrl:${camStreamId}`,
19615
+ label: "RTSP URL (prestream)",
19616
+ description: "Video-only. Shows this camera’s last picture while it sleeps and cuts to live video when one of its wake signals fires — without a consumer on this URL ever keeping the camera awake.",
19617
+ readonlyField: true,
19618
+ span: 2,
19619
+ value: rtspEntry?.url ? `${rtspEntry.url}/prestream` : "inactive",
19620
+ actions: [{
19621
+ action: "copy-value",
19622
+ icon: "copy",
19623
+ tooltip: "Copy prestream URL to clipboard"
19624
+ }]
19625
+ }] : [],
19547
19626
  {
19548
19627
  type: "boolean",
19549
19628
  style: "switch",
@@ -19711,7 +19790,7 @@ var StreamBrokerManager = class StreamBrokerManager {
19711
19790
  }
19712
19791
  ]
19713
19792
  }));
19714
- return { sections: [{
19793
+ const streamBrokerSection = {
19715
19794
  id: "stream-broker-streams",
19716
19795
  title: "",
19717
19796
  tab: "stream-broker",
@@ -19728,7 +19807,37 @@ var StreamBrokerManager = class StreamBrokerManager {
19728
19807
  ...derivedTabs
19729
19808
  ]
19730
19809
  }]
19731
- }, {
19810
+ };
19811
+ const prestreamCfg = override?.prestream;
19812
+ const prestreamSections = prestreamGate.eligible ? [{
19813
+ id: "stream-broker-prestream",
19814
+ title: "Wake signals",
19815
+ tab: "stream-broker",
19816
+ location: "top-tab",
19817
+ order: 50,
19818
+ description: "A consumer can dial this camera’s prestream URL (the profile URL with /prestream) and be shown the last picture while the camera sleeps. It is fed without keeping the camera awake: only the signals below wake it, and it goes back to sleep once they stop.",
19819
+ fields: [...declaredSignals.map((signal) => ({
19820
+ type: "boolean",
19821
+ style: "switch",
19822
+ key: `prestreamSignal:${signal.code}`,
19823
+ label: signal.label,
19824
+ description: signal.recommended ? "Recommended by this camera’s driver." : "Offered by this camera’s driver.",
19825
+ default: signal.recommended,
19826
+ value: prestreamCfg?.enabledSignals === void 0 ? true : prestreamCfg.enabledSignals.includes(signal.code)
19827
+ })), {
19828
+ type: "number",
19829
+ key: "prestreamHoldMs",
19830
+ label: "Stay awake after the last signal",
19831
+ description: "How long the camera keeps streaming once its signals stop. Extended by each new signal, never stacked — continuous motion keeps it up and it sleeps this long after the last one. One run is capped at five minutes whatever this says, and a camera whose sensor never stops is left alone for ten minutes after that.",
19832
+ min: 1e3,
19833
+ max: 3e5,
19834
+ step: 1e3,
19835
+ unit: "ms",
19836
+ default: DEFAULT_WAKE_HOLD_MS,
19837
+ value: prestreamCfg?.holdMs ?? 15e3
19838
+ }]
19839
+ }] : [];
19840
+ const diagnosticsSection = {
19732
19841
  id: "stream-broker-diagnostics",
19733
19842
  title: "Diagnostics",
19734
19843
  tab: "stream-broker",
@@ -19751,7 +19860,12 @@ var StreamBrokerManager = class StreamBrokerManager {
19751
19860
  default: false,
19752
19861
  value: override?.webrtcDebug === true
19753
19862
  }]
19754
- }] };
19863
+ };
19864
+ return { sections: [
19865
+ streamBrokerSection,
19866
+ ...prestreamSections,
19867
+ diagnosticsSection
19868
+ ] };
19755
19869
  }
19756
19870
  async getDeviceLiveContribution(_input) {
19757
19871
  return null;
@@ -19863,6 +19977,31 @@ var StreamBrokerManager = class StreamBrokerManager {
19863
19977
  nextOverride.sourceParserArgs = spa;
19864
19978
  continue;
19865
19979
  }
19980
+ if (fieldKey.startsWith("prestreamSignal:")) {
19981
+ overrideDirty = true;
19982
+ const next = nextEnabledSignals({
19983
+ declared: await this.declaredSignalCodes(deviceId),
19984
+ stored: nextOverride.prestream?.enabledSignals,
19985
+ code: fieldKey.slice(16),
19986
+ enabled: value === null || value === void 0 ? true : Boolean(value)
19987
+ });
19988
+ nextOverride.prestream = {
19989
+ ...nextOverride.prestream,
19990
+ enabledSignals: [...next]
19991
+ };
19992
+ continue;
19993
+ }
19994
+ if (fieldKey === "prestreamHoldMs") {
19995
+ overrideDirty = true;
19996
+ const raw = typeof value === "number" ? value : Number(value);
19997
+ if (!Number.isFinite(raw)) continue;
19998
+ const clamped = Math.min(3e5, Math.max(1e3, Math.round(raw)));
19999
+ nextOverride.prestream = {
20000
+ ...nextOverride.prestream,
20001
+ holdMs: clamped
20002
+ };
20003
+ continue;
20004
+ }
19866
20005
  if (fieldKey === "streamingDebug") {
19867
20006
  overrideDirty = true;
19868
20007
  const next = value === null || value === void 0 ? void 0 : Boolean(value);
@@ -31266,9 +31405,11 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
31266
31405
  const localNode = this.ctx.kernel.localNodeId;
31267
31406
  const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
31268
31407
  this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
31269
- this.brokerManager.setDeviceSignalReader(async (deviceId) => {
31270
- return (await (await this.ctx.fetchDevice(deviceId)).streamSignals?.listSignals({}) ?? []).map((s) => s.code);
31271
- });
31408
+ const readSignals = async (deviceId) => {
31409
+ return await (await this.ctx.fetchDevice(deviceId)).streamSignals?.listSignals({}) ?? [];
31410
+ };
31411
+ this.brokerManager.setDeviceSignalReader(async (deviceId) => (await readSignals(deviceId)).map((s) => s.code));
31412
+ this.brokerManager.setDeviceSignalDetailReader(readSignals);
31272
31413
  await this.loadFfmpegConfig();
31273
31414
  await this.declareRowCollections();
31274
31415
  await this.purgeFlattenedSettingsKeys();
@@ -666,6 +666,45 @@ function prestreamEligible(input) {
666
666
  };
667
667
  return { eligible: true };
668
668
  }
669
+ //#endregion
670
+ //#region src/stream-broker/stream-broker/prestream-signal-selection.ts
671
+ function nextEnabledSignals(input) {
672
+ const base = input.stored ?? input.declared;
673
+ const set = new Set(base);
674
+ if (input.enabled) set.add(input.code);
675
+ else set.delete(input.code);
676
+ const known = input.declared.filter((c) => set.has(c));
677
+ const extra = [...set].filter((c) => !input.declared.includes(c));
678
+ return [...known, ...extra];
679
+ }
680
+ //#endregion
681
+ //#region src/stream-broker/stream-broker/wake-hold.ts
682
+ /**
683
+ * The HOLD a camera's own signal buys, and what it may not buy.
684
+ *
685
+ * A stream a consumer merely watches is not a reason to wake a battery camera —
686
+ * a Frigate-style puller holds a session open for ever, and treating that as
687
+ * demand keeps the camera awake for ever, which is the whole point of the
688
+ * battery (D173). So a prestream consumer is FED without being INTENT, and the
689
+ * only thing that dials is a signal the camera reported about itself.
690
+ *
691
+ * This module is the timing half of that, and it is pure so the arithmetic can
692
+ * be argued with. Three rules:
693
+ *
694
+ * - **A hold EXTENDS, it never stacks.** Continuous motion keeps a camera up
695
+ * and it sleeps `holdMs` after the LAST signal, not after the sum of them.
696
+ * A counter or a queue would turn a busy afternoon into an hour of wake.
697
+ * - **A run is bounded ABSOLUTELY, from its first signal.** A stuck PIR
698
+ * reporting every second would otherwise extend the hold indefinitely and
699
+ * drain the battery — the one failure this whole design exists to prevent.
700
+ * The same re-arm-under-a-ceiling shape D371 gives `recording-signal`.
701
+ * - **A deadline, not a chain of timers.** The state is one number; the timer
702
+ * only decides when to LOOK at it. A chain would drift and would have to be
703
+ * cancelled correctly on every path.
704
+ */
705
+ /** How long a camera stays awake after the LAST signal. The operator's, per
706
+ * camera; this is the default the broker ships. */
707
+ var DEFAULT_WAKE_HOLD_MS = 15e3;
669
708
  /**
670
709
  * Ceiling on ONE run, measured from its first signal.
671
710
  *
@@ -19303,6 +19342,20 @@ var StreamBrokerManager = class StreamBrokerManager {
19303
19342
  * nothing, and `[]` when it does not declare the cap at all — both mean no
19304
19343
  * signal of that name is acceptable, which is the same refusal.
19305
19344
  */
19345
+ /**
19346
+ * The signals a camera declares, with their labels — what the form renders.
19347
+ * `[]` on any failure, which the caller reads as "not eligible": a form built
19348
+ * from a list we could not read would offer wakes nobody confirmed.
19349
+ */
19350
+ async declaredSignalList(deviceId) {
19351
+ const read = this.deviceSignalDetailReader;
19352
+ if (read === null) return [];
19353
+ try {
19354
+ return await read(deviceId);
19355
+ } catch {
19356
+ return [];
19357
+ }
19358
+ }
19306
19359
  async declaredSignalCodes(deviceId) {
19307
19360
  const read = this.deviceSignalReader;
19308
19361
  if (read === null) return [];
@@ -19320,6 +19373,12 @@ var StreamBrokerManager = class StreamBrokerManager {
19320
19373
  setDeviceSignalReader(read) {
19321
19374
  this.deviceSignalReader = read;
19322
19375
  }
19376
+ /** The same read, with labels — the form needs them, the wake decision does
19377
+ * not. Injected by the host addon for the same reason. */
19378
+ deviceSignalDetailReader = null;
19379
+ setDeviceSignalDetailReader(read) {
19380
+ this.deviceSignalDetailReader = read;
19381
+ }
19323
19382
  /**
19324
19383
  * Whether this device runs on a battery.
19325
19384
  *
@@ -19441,6 +19500,12 @@ var StreamBrokerManager = class StreamBrokerManager {
19441
19500
  value: setting,
19442
19501
  label: hwAccelOptionLabel(setting)
19443
19502
  }));
19503
+ const declaredSignals = await this.declaredSignalList(deviceId);
19504
+ const prestreamGate = prestreamEligible({
19505
+ batteryOperated: await this.isBatteryOperated(deviceId),
19506
+ declaresSignals: declaredSignals.length > 0
19507
+ });
19508
+ const prestreamOffered = prestreamGate.eligible;
19444
19509
  const streamTabs = streams.filter((s) => !s.camStreamId.startsWith("derived:")).map((stream) => {
19445
19510
  const camStreamId = stream.camStreamId;
19446
19511
  const brokerId = brokerIdFor(deviceId, camStreamId);
@@ -19540,6 +19605,20 @@ var StreamBrokerManager = class StreamBrokerManager {
19540
19605
  tooltip: "Copy muted URL to clipboard"
19541
19606
  }]
19542
19607
  },
19608
+ ...prestreamOffered ? [{
19609
+ type: "text",
19610
+ key: `rtspPrestreamUrl:${camStreamId}`,
19611
+ label: "RTSP URL (prestream)",
19612
+ description: "Video-only. Shows this camera’s last picture while it sleeps and cuts to live video when one of its wake signals fires — without a consumer on this URL ever keeping the camera awake.",
19613
+ readonlyField: true,
19614
+ span: 2,
19615
+ value: rtspEntry?.url ? `${rtspEntry.url}/prestream` : "inactive",
19616
+ actions: [{
19617
+ action: "copy-value",
19618
+ icon: "copy",
19619
+ tooltip: "Copy prestream URL to clipboard"
19620
+ }]
19621
+ }] : [],
19543
19622
  {
19544
19623
  type: "boolean",
19545
19624
  style: "switch",
@@ -19707,7 +19786,7 @@ var StreamBrokerManager = class StreamBrokerManager {
19707
19786
  }
19708
19787
  ]
19709
19788
  }));
19710
- return { sections: [{
19789
+ const streamBrokerSection = {
19711
19790
  id: "stream-broker-streams",
19712
19791
  title: "",
19713
19792
  tab: "stream-broker",
@@ -19724,7 +19803,37 @@ var StreamBrokerManager = class StreamBrokerManager {
19724
19803
  ...derivedTabs
19725
19804
  ]
19726
19805
  }]
19727
- }, {
19806
+ };
19807
+ const prestreamCfg = override?.prestream;
19808
+ const prestreamSections = prestreamGate.eligible ? [{
19809
+ id: "stream-broker-prestream",
19810
+ title: "Wake signals",
19811
+ tab: "stream-broker",
19812
+ location: "top-tab",
19813
+ order: 50,
19814
+ description: "A consumer can dial this camera’s prestream URL (the profile URL with /prestream) and be shown the last picture while the camera sleeps. It is fed without keeping the camera awake: only the signals below wake it, and it goes back to sleep once they stop.",
19815
+ fields: [...declaredSignals.map((signal) => ({
19816
+ type: "boolean",
19817
+ style: "switch",
19818
+ key: `prestreamSignal:${signal.code}`,
19819
+ label: signal.label,
19820
+ description: signal.recommended ? "Recommended by this camera’s driver." : "Offered by this camera’s driver.",
19821
+ default: signal.recommended,
19822
+ value: prestreamCfg?.enabledSignals === void 0 ? true : prestreamCfg.enabledSignals.includes(signal.code)
19823
+ })), {
19824
+ type: "number",
19825
+ key: "prestreamHoldMs",
19826
+ label: "Stay awake after the last signal",
19827
+ description: "How long the camera keeps streaming once its signals stop. Extended by each new signal, never stacked — continuous motion keeps it up and it sleeps this long after the last one. One run is capped at five minutes whatever this says, and a camera whose sensor never stops is left alone for ten minutes after that.",
19828
+ min: 1e3,
19829
+ max: 3e5,
19830
+ step: 1e3,
19831
+ unit: "ms",
19832
+ default: DEFAULT_WAKE_HOLD_MS,
19833
+ value: prestreamCfg?.holdMs ?? 15e3
19834
+ }]
19835
+ }] : [];
19836
+ const diagnosticsSection = {
19728
19837
  id: "stream-broker-diagnostics",
19729
19838
  title: "Diagnostics",
19730
19839
  tab: "stream-broker",
@@ -19747,7 +19856,12 @@ var StreamBrokerManager = class StreamBrokerManager {
19747
19856
  default: false,
19748
19857
  value: override?.webrtcDebug === true
19749
19858
  }]
19750
- }] };
19859
+ };
19860
+ return { sections: [
19861
+ streamBrokerSection,
19862
+ ...prestreamSections,
19863
+ diagnosticsSection
19864
+ ] };
19751
19865
  }
19752
19866
  async getDeviceLiveContribution(_input) {
19753
19867
  return null;
@@ -19859,6 +19973,31 @@ var StreamBrokerManager = class StreamBrokerManager {
19859
19973
  nextOverride.sourceParserArgs = spa;
19860
19974
  continue;
19861
19975
  }
19976
+ if (fieldKey.startsWith("prestreamSignal:")) {
19977
+ overrideDirty = true;
19978
+ const next = nextEnabledSignals({
19979
+ declared: await this.declaredSignalCodes(deviceId),
19980
+ stored: nextOverride.prestream?.enabledSignals,
19981
+ code: fieldKey.slice(16),
19982
+ enabled: value === null || value === void 0 ? true : Boolean(value)
19983
+ });
19984
+ nextOverride.prestream = {
19985
+ ...nextOverride.prestream,
19986
+ enabledSignals: [...next]
19987
+ };
19988
+ continue;
19989
+ }
19990
+ if (fieldKey === "prestreamHoldMs") {
19991
+ overrideDirty = true;
19992
+ const raw = typeof value === "number" ? value : Number(value);
19993
+ if (!Number.isFinite(raw)) continue;
19994
+ const clamped = Math.min(3e5, Math.max(1e3, Math.round(raw)));
19995
+ nextOverride.prestream = {
19996
+ ...nextOverride.prestream,
19997
+ holdMs: clamped
19998
+ };
19999
+ continue;
20000
+ }
19862
20001
  if (fieldKey === "streamingDebug") {
19863
20002
  overrideDirty = true;
19864
20003
  const next = value === null || value === void 0 ? void 0 : Boolean(value);
@@ -31262,9 +31401,11 @@ var StreamBrokerAddon = class extends BaseAddon {
31262
31401
  const localNode = this.ctx.kernel.localNodeId;
31263
31402
  const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
31264
31403
  this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
31265
- this.brokerManager.setDeviceSignalReader(async (deviceId) => {
31266
- return (await (await this.ctx.fetchDevice(deviceId)).streamSignals?.listSignals({}) ?? []).map((s) => s.code);
31267
- });
31404
+ const readSignals = async (deviceId) => {
31405
+ return await (await this.ctx.fetchDevice(deviceId)).streamSignals?.listSignals({}) ?? [];
31406
+ };
31407
+ this.brokerManager.setDeviceSignalReader(async (deviceId) => (await readSignals(deviceId)).map((s) => s.code));
31408
+ this.brokerManager.setDeviceSignalDetailReader(readSignals);
31268
31409
  await this.loadFfmpegConfig();
31269
31410
  await this.declareRowCollections();
31270
31411
  await this.purgeFlattenedSettingsKeys();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.229",
3
+ "version": "1.2.231",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",