@camstack/addon-pipeline 0.1.18 → 0.1.19

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 (52) hide show
  1. package/dist/audio-analyzer/index.js +8 -3
  2. package/dist/audio-analyzer/index.js.map +1 -1
  3. package/dist/audio-analyzer/index.mjs +8 -3
  4. package/dist/audio-analyzer/index.mjs.map +1 -1
  5. package/dist/audio-codec-nodeav/index.js +1 -1
  6. package/dist/audio-codec-nodeav/index.mjs +1 -1
  7. package/dist/decoder-nodeav/index.js +1 -1
  8. package/dist/decoder-nodeav/index.mjs +1 -1
  9. package/dist/detection-pipeline/index.js +23 -20
  10. package/dist/detection-pipeline/index.js.map +1 -1
  11. package/dist/detection-pipeline/index.mjs +23 -20
  12. package/dist/detection-pipeline/index.mjs.map +1 -1
  13. package/dist/{index-DLHaHm6u.js → index-BbPPvoCx.js} +414 -45
  14. package/dist/index-BbPPvoCx.js.map +1 -0
  15. package/dist/{index-asZs8U_s.mjs → index-Bmlkm0Fd.mjs} +414 -45
  16. package/dist/index-Bmlkm0Fd.mjs.map +1 -0
  17. package/dist/motion-wasm/index.js +1 -1
  18. package/dist/motion-wasm/index.mjs +1 -1
  19. package/dist/pipeline-runner/index.js +132 -14
  20. package/dist/pipeline-runner/index.js.map +1 -1
  21. package/dist/pipeline-runner/index.mjs +133 -15
  22. package/dist/pipeline-runner/index.mjs.map +1 -1
  23. package/dist/stream-broker/@mf-types.zip +0 -0
  24. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-NjF4kxzW.mjs +19 -0
  25. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BAv_5ISf.mjs +20 -0
  26. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DAssX3h0.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-BsB2G7oY.mjs} +2 -1
  27. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-DFoJJhpt.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-xrRiPUpA.mjs} +1 -1
  28. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-x7XMEeuJ.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-C0E2yCzO.mjs} +1 -1
  29. package/dist/stream-broker/_stub.js +2 -2
  30. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-3TxRVJ5L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CupRlwqG.mjs} +6 -6
  31. package/dist/stream-broker/{client-CZXrddDR.mjs → client-NPZqorv9.mjs} +2 -2
  32. package/dist/stream-broker/{hostInit-De6APW25.mjs → hostInit-Bh4w7o5_.mjs} +12 -12
  33. package/dist/stream-broker/{index-C0BzaWmB.mjs → index-2Qp8vT3w.mjs} +1 -1
  34. package/dist/stream-broker/{index-CZNxa0ad.mjs → index-BBcZvb5t.mjs} +1 -1
  35. package/dist/stream-broker/index-CIJue-4t.mjs +37880 -0
  36. package/dist/stream-broker/{index-BvV3RVTZ.mjs → index-Cc6QBqMk.mjs} +2 -2
  37. package/dist/stream-broker/{index-cYW01SNH.mjs → index-D_1p2K9B.mjs} +1 -1
  38. package/dist/stream-broker/{index-CUXiTSWS.mjs → index-Dy2V7VOm.mjs} +3775 -3279
  39. package/dist/stream-broker/{index-KtR7Pp0O.mjs → index-mX3Kgiv1.mjs} +1 -1
  40. package/dist/stream-broker/index.js +1565 -280
  41. package/dist/stream-broker/index.js.map +1 -1
  42. package/dist/stream-broker/index.mjs +1567 -281
  43. package/dist/stream-broker/index.mjs.map +1 -1
  44. package/dist/stream-broker/{jsx-runtime-B_evVsXl.mjs → jsx-runtime-lb0mH5st.mjs} +1 -1
  45. package/dist/stream-broker/remoteEntry.js +1 -1
  46. package/dist/stream-broker/{schemas-ChN4Ih0h.mjs → schemas-ClCuS4qa.mjs} +151 -141
  47. package/package.json +1 -1
  48. package/dist/index-DLHaHm6u.js.map +0 -1
  49. package/dist/index-asZs8U_s.mjs.map +0 -1
  50. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +0 -19
  51. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-B4l8Nb2y.mjs +0 -20
  52. package/dist/stream-broker/index-Kb4xa8FX.mjs +0 -36403
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
25
- const index = require("../index-DLHaHm6u.js");
25
+ const index = require("../index-BbPPvoCx.js");
26
26
  const fs = require("node:fs");
27
27
  const path = require("node:path");
28
28
  const DEFAULT_CONFIG$1 = {
@@ -1,4 +1,4 @@
1
- import { i as evaluateZoneRules, B as BaseAddon, t as motionDetectionCapability, m as hydrateSchema, D as DeviceType } from "../index-asZs8U_s.mjs";
1
+ import { i as evaluateZoneRules, B as BaseAddon, t as motionDetectionCapability, m as hydrateSchema, D as DeviceType } from "../index-Bmlkm0Fd.mjs";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  const DEFAULT_CONFIG$1 = {
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
25
- const index = require("../index-DLHaHm6u.js");
25
+ const index = require("../index-BbPPvoCx.js");
26
26
  const shmRing = require("@camstack/shm-ring");
27
27
  class FrameQueue {
28
28
  /**
@@ -137,9 +137,9 @@ class PipelineTimingSampler {
137
137
  if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, []);
138
138
  this.detSamples.get(deviceId).push(s);
139
139
  }
140
- addMotionSample(deviceId, ms) {
140
+ addMotionSample(deviceId, ms, frameAge = -1) {
141
141
  if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, []);
142
- this.motSamples.get(deviceId).push(ms);
142
+ this.motSamples.get(deviceId).push({ ms, frameAge });
143
143
  }
144
144
  addAudioSample(deviceId, s) {
145
145
  if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, []);
@@ -164,6 +164,7 @@ class PipelineTimingSampler {
164
164
  if (det.length === 0) continue;
165
165
  const e2e = det.map((s) => s.endToEnd);
166
166
  const inf = det.map((s) => s.inference);
167
+ const frameAge = det.map((s) => s.frameAge).filter((v) => v >= 0);
167
168
  const totalDet = det.reduce((s, d) => s + d.detections, 0);
168
169
  this.log.info(
169
170
  "pipeline stats",
@@ -172,7 +173,20 @@ class PipelineTimingSampler {
172
173
  meta: {
173
174
  frames: det.length,
174
175
  intervalSec: REPORT_INTERVAL_MS / 1e3,
176
+ // enqueue → emit, in ms. Stage breakdown (avg) exposes WHERE the
177
+ // latency sits: queue backlog vs semaphore contention vs inference
178
+ // vs result-emit. inference is usually the floor (model chain).
175
179
  e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },
180
+ // Frame age (capture→inference-pick): if large, the analyzed frame is
181
+ // already stale (decoder/ring behind), which delays the overlay
182
+ // regardless of how fast the result is delivered.
183
+ frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },
184
+ stagesMs: {
185
+ queueWait: avg(det.map((s) => s.queueWait)),
186
+ semaphoreWait: avg(det.map((s) => s.semaphoreWait)),
187
+ inference: avg(inf),
188
+ resultToEmit: avg(det.map((s) => s.resultToEmit))
189
+ },
176
190
  inference: { avg: avg(inf), p95: p95(inf) },
177
191
  detections: totalDet,
178
192
  dropped,
@@ -185,6 +199,8 @@ class PipelineTimingSampler {
185
199
  this.detSamples.clear();
186
200
  for (const [deviceId, mot] of this.motSamples) {
187
201
  if (mot.length === 0) continue;
202
+ const ms = mot.map((s) => s.ms);
203
+ const frameAge = mot.map((s) => s.frameAge).filter((v) => v >= 0);
188
204
  this.log.info(
189
205
  "motion stats",
190
206
  {
@@ -192,10 +208,12 @@ class PipelineTimingSampler {
192
208
  meta: {
193
209
  frames: mot.length,
194
210
  intervalSec: REPORT_INTERVAL_MS / 1e3,
195
- avg: avg(mot),
196
- p95: p95(mot),
197
- max: max(mot)
198
- // motionAddon: rt.motionAddon ?? null,
211
+ avg: avg(ms),
212
+ p95: p95(ms),
213
+ max: max(ms),
214
+ // Frame age at motion analysis (capture→analysis). Large = stale
215
+ // input frame (decoder/ring behind) → motion box lags real movement.
216
+ frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) }
199
217
  }
200
218
  }
201
219
  );
@@ -541,8 +559,7 @@ class PipelineRunner {
541
559
  async processWithSemaphore(deviceId, entry, frameInput, state, streamType) {
542
560
  const pickedAt = Date.now();
543
561
  const { frame, handle } = entry;
544
- const captureTs = frame.timestamp;
545
- const enqueuedAt = frame._enqueuedAt ?? captureTs;
562
+ const enqueuedAt = frame._enqueuedAt ?? pickedAt;
546
563
  const release = await this.semaphore.acquire();
547
564
  const semAcquiredAt = Date.now();
548
565
  try {
@@ -557,13 +574,19 @@ class PipelineRunner {
557
574
  if (result) {
558
575
  await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
559
576
  const emittedAt = Date.now();
577
+ const capturedAt = frame.capturedAt;
560
578
  this.timingSampler.addSample(deviceId, {
561
- captureToEnqueue: enqueuedAt - captureTs,
562
579
  queueWait: pickedAt - enqueuedAt,
563
580
  semaphoreWait: semAcquiredAt - pickedAt,
564
581
  inference: inferenceMs,
565
582
  resultToEmit: emittedAt - inferDoneAt,
566
- endToEnd: emittedAt - captureTs,
583
+ // capturedAt is the shm-ring commit wall-clock; pickedAt − capturedAt
584
+ // is how stale the frame was when inference started. <0/absent → unknown.
585
+ frameAge: typeof capturedAt === "number" && capturedAt > 0 ? pickedAt - capturedAt : -1,
586
+ // Wall-clock pipeline latency: enqueue → result emitted. (Capture →
587
+ // enqueue, i.e. RTSP decode + shm-ring write, is not measured here —
588
+ // the shm frame carries only a PTS, no wall-clock capture stamp.)
589
+ endToEnd: emittedAt - enqueuedAt,
567
590
  detections: result.detections?.length ?? 0
568
591
  });
569
592
  }
@@ -754,6 +777,11 @@ const DEFAULT_CONFIG = {
754
777
  targetLoadPercent: 80,
755
778
  minThrottledFps: 1
756
779
  };
780
+ function shouldStartOnboardAnalyzer(config) {
781
+ if (!config.onboardMotionDrivesAnalyzer) return false;
782
+ if (config.motionSources.includes("analyzer")) return false;
783
+ return true;
784
+ }
757
785
  function toFrameInput(frame) {
758
786
  return {
759
787
  data: frame.data,
@@ -784,6 +812,20 @@ class PipelineRunnerAddon extends index.BaseAddon {
784
812
  * detach.
785
813
  */
786
814
  lastMotionAt = /* @__PURE__ */ new Map();
815
+ /**
816
+ * Dynamic analyzer subscriptions opened on `MotionOnMotionChanged
817
+ * source:'onboard'` when `onboardMotionDrivesAnalyzer === true`. Each
818
+ * entry is the unsubscribe handle returned by `subscribeMotionFrames`.
819
+ * Cleared on teardown timer fire, detach, and shutdown.
820
+ */
821
+ onboardAnalyzerSubs = /* @__PURE__ */ new Map();
822
+ /**
823
+ * Teardown timers that close the dynamic analyzer subscription after
824
+ * `motionCooldownMs` without a new motion event. Re-armed on every
825
+ * `MotionOnMotionChanged source:'onboard'` call so the sub stays open
826
+ * while motion persists.
827
+ */
828
+ onboardAnalyzerTeardownTimers = /* @__PURE__ */ new Map();
787
829
  /**
788
830
  * Snapshot-equality cache for metrics-snapshot defer. The runner
789
831
  * fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;
@@ -848,6 +890,9 @@ class PipelineRunnerAddon extends index.BaseAddon {
848
890
  const attachment = this.attached.get(deviceId);
849
891
  if (!attachment) return;
850
892
  const source = data.source;
893
+ if (source === "onboard") {
894
+ void this.handleOnboardMotionAnalyzer(deviceId, data.detected);
895
+ }
851
896
  if (!attachment.config.motionSources.includes(source)) return;
852
897
  this.runner?.reportMotion(
853
898
  deviceId,
@@ -892,6 +937,9 @@ class PipelineRunnerAddon extends index.BaseAddon {
892
937
  this.unsubMotionEvents = null;
893
938
  }
894
939
  this.lastAnalyzerDetected.clear();
940
+ for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) {
941
+ this.clearOnboardAnalyzer(deviceId);
942
+ }
895
943
  if (this.runner) {
896
944
  this.runner.stop();
897
945
  this.runner = null;
@@ -1326,6 +1374,7 @@ class PipelineRunnerAddon extends index.BaseAddon {
1326
1374
  detachInternal(deviceId) {
1327
1375
  const attachment = this.attached.get(deviceId);
1328
1376
  if (!attachment) return;
1377
+ this.clearOnboardAnalyzer(deviceId);
1329
1378
  attachment.motionUnsubscribe?.();
1330
1379
  attachment.detectionUnsubscribe?.();
1331
1380
  this.attached.delete(deviceId);
@@ -1334,6 +1383,70 @@ class PipelineRunnerAddon extends index.BaseAddon {
1334
1383
  this.runner?.unregisterCamera(deviceId);
1335
1384
  this.ctx?.logger.info("detachCamera", { tags: { deviceId } });
1336
1385
  }
1386
+ /**
1387
+ * Synchronously cancel the teardown timer and call the unsubscribe
1388
+ * handle for the dynamic onboard analyzer, if one is open. Safe to
1389
+ * call when no subscription exists.
1390
+ */
1391
+ clearOnboardAnalyzer(deviceId) {
1392
+ const timer = this.onboardAnalyzerTeardownTimers.get(deviceId);
1393
+ if (timer !== void 0) {
1394
+ clearTimeout(timer);
1395
+ this.onboardAnalyzerTeardownTimers.delete(deviceId);
1396
+ }
1397
+ const unsub = this.onboardAnalyzerSubs.get(deviceId);
1398
+ if (unsub !== void 0) {
1399
+ try {
1400
+ unsub();
1401
+ } catch {
1402
+ }
1403
+ this.onboardAnalyzerSubs.delete(deviceId);
1404
+ }
1405
+ }
1406
+ /**
1407
+ * Dynamic analyzer gate for onboard-motion cameras.
1408
+ *
1409
+ * Called from the `MotionOnMotionChanged` subscriber whenever
1410
+ * `source === 'onboard'`. Opens a `subscribeMotionFrames` subscription
1411
+ * the first time motion is detected (idempotent — a second `detected:true`
1412
+ * while the sub is already open is a no-op). Always re-arms the teardown
1413
+ * timer so the subscription stays open as long as motion events keep
1414
+ * arriving and tears down `motionCooldownMs` after the last event.
1415
+ *
1416
+ * No-op when:
1417
+ * - The camera is not currently attached.
1418
+ * - `shouldStartOnboardAnalyzer(config)` returns false (flag off or
1419
+ * `motionSources` already includes `'analyzer'`).
1420
+ */
1421
+ async handleOnboardMotionAnalyzer(deviceId, detected) {
1422
+ const attachment = this.attached.get(deviceId);
1423
+ if (!attachment) return;
1424
+ const config = attachment.config;
1425
+ if (!shouldStartOnboardAnalyzer(config)) return;
1426
+ const log = this.ctx.logger.withTags({ deviceId });
1427
+ if (detected && !this.onboardAnalyzerSubs.has(deviceId)) {
1428
+ const unsub = await this.subscribeMotionFrames(config);
1429
+ if (unsub) {
1430
+ this.onboardAnalyzerSubs.set(deviceId, unsub);
1431
+ log.debug("onboard-analyzer: opened motion-frame subscription");
1432
+ }
1433
+ }
1434
+ const existing = this.onboardAnalyzerTeardownTimers.get(deviceId);
1435
+ if (existing !== void 0) clearTimeout(existing);
1436
+ const cooldownMs = config.motionCooldownMs;
1437
+ const timer = setTimeout(() => {
1438
+ this.onboardAnalyzerTeardownTimers.delete(deviceId);
1439
+ const unsub = this.onboardAnalyzerSubs.get(deviceId);
1440
+ if (!unsub) return;
1441
+ try {
1442
+ unsub();
1443
+ } catch {
1444
+ }
1445
+ this.onboardAnalyzerSubs.delete(deviceId);
1446
+ log.debug("onboard-analyzer: closed after motion cooldown", { meta: { cooldownMs } });
1447
+ }, cooldownMs);
1448
+ this.onboardAnalyzerTeardownTimers.set(deviceId, timer);
1449
+ }
1337
1450
  async getLocalLoad() {
1338
1451
  const metrics = this.runner?.getMetrics() ?? { avgInferenceTimeMs: 0, queueDepth: 0 };
1339
1452
  const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? [];
@@ -1590,20 +1703,24 @@ class PipelineRunnerAddon extends index.BaseAddon {
1590
1703
  zonesPayload
1591
1704
  ));
1592
1705
  }
1593
- runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart);
1706
+ const capturedAt = frame.capturedAt;
1707
+ const motionFrameAge = typeof capturedAt === "number" && capturedAt > 0 ? motionStart - capturedAt : -1;
1708
+ runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart, motionFrameAge);
1594
1709
  } catch (error) {
1595
1710
  const msg = index.errMsg(error);
1596
1711
  log.error("runMotionAnalysis failed", { meta: { error: msg } });
1597
1712
  }
1598
1713
  }
1599
- emitInferenceResult(deviceId, _frame, result, handle) {
1714
+ emitInferenceResult(deviceId, frame, result, handle) {
1600
1715
  const ctx = this.ctx;
1601
1716
  if (!ctx?.eventBus) return;
1717
+ const capturedAt = frame.capturedAt;
1602
1718
  const payload = {
1603
1719
  deviceId,
1604
1720
  frame: result,
1605
1721
  nodeId: this.nodeId,
1606
- frameHandle: handle
1722
+ frameHandle: handle,
1723
+ ...typeof capturedAt === "number" && capturedAt > 0 ? { capturedAt } : {}
1607
1724
  };
1608
1725
  this.ctx.eventBus.emit(index.createEvent(
1609
1726
  index.EventCategory.PipelineInferenceResult,
@@ -1744,4 +1861,5 @@ exports.PipelineTimingSampler = PipelineTimingSampler;
1744
1861
  exports.Semaphore = Semaphore;
1745
1862
  exports.customActions = pipelineRunnerBenchActions;
1746
1863
  exports.default = PipelineRunnerAddon;
1864
+ exports.shouldStartOnboardAnalyzer = shouldStartOnboardAnalyzer;
1747
1865
  //# sourceMappingURL=index.js.map