@camstack/addon-pipeline 0.1.17 → 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-p-6GfKOg.js → index-BbPPvoCx.js} +469 -57
  14. package/dist/index-BbPPvoCx.js.map +1 -0
  15. package/dist/{index-CVzLrojg.mjs → index-Bmlkm0Fd.mjs} +469 -57
  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-Sx8tgpFZ.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-D0jPgChu.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-BCEx31Mh.mjs → index-Dy2V7VOm.mjs} +3808 -3277
  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-CVzLrojg.mjs.map +0 -1
  49. package/dist/index-p-6GfKOg.js.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
@@ -1,4 +1,4 @@
1
- import { e as errMsg, o as object, s as string, _ as _enum, l as lazy, a as array, b as boolean, n as number, d as defineCustomActions, c as customAction, B as BaseAddon, E as EventCategory, p as pipelineRunnerCapability, f as createEvent } from "../index-CVzLrojg.mjs";
1
+ import { e as errMsg, o as object, s as string, _ as _enum, l as lazy, a as array, b as boolean, n as number, d as defineCustomActions, c as customAction, B as BaseAddon, E as EventCategory, p as pipelineRunnerCapability, f as createEvent } from "../index-Bmlkm0Fd.mjs";
2
2
  import { FrameRingReaderCache } from "@camstack/shm-ring";
3
3
  class FrameQueue {
4
4
  /**
@@ -113,9 +113,9 @@ class PipelineTimingSampler {
113
113
  if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, []);
114
114
  this.detSamples.get(deviceId).push(s);
115
115
  }
116
- addMotionSample(deviceId, ms) {
116
+ addMotionSample(deviceId, ms, frameAge = -1) {
117
117
  if (!this.motSamples.has(deviceId)) this.motSamples.set(deviceId, []);
118
- this.motSamples.get(deviceId).push(ms);
118
+ this.motSamples.get(deviceId).push({ ms, frameAge });
119
119
  }
120
120
  addAudioSample(deviceId, s) {
121
121
  if (!this.audioSamples.has(deviceId)) this.audioSamples.set(deviceId, []);
@@ -140,6 +140,7 @@ class PipelineTimingSampler {
140
140
  if (det.length === 0) continue;
141
141
  const e2e = det.map((s) => s.endToEnd);
142
142
  const inf = det.map((s) => s.inference);
143
+ const frameAge = det.map((s) => s.frameAge).filter((v) => v >= 0);
143
144
  const totalDet = det.reduce((s, d) => s + d.detections, 0);
144
145
  this.log.info(
145
146
  "pipeline stats",
@@ -148,7 +149,20 @@ class PipelineTimingSampler {
148
149
  meta: {
149
150
  frames: det.length,
150
151
  intervalSec: REPORT_INTERVAL_MS / 1e3,
152
+ // enqueue → emit, in ms. Stage breakdown (avg) exposes WHERE the
153
+ // latency sits: queue backlog vs semaphore contention vs inference
154
+ // vs result-emit. inference is usually the floor (model chain).
151
155
  e2e: { avg: avg(e2e), p95: p95(e2e), max: max(e2e) },
156
+ // Frame age (capture→inference-pick): if large, the analyzed frame is
157
+ // already stale (decoder/ring behind), which delays the overlay
158
+ // regardless of how fast the result is delivered.
159
+ frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) },
160
+ stagesMs: {
161
+ queueWait: avg(det.map((s) => s.queueWait)),
162
+ semaphoreWait: avg(det.map((s) => s.semaphoreWait)),
163
+ inference: avg(inf),
164
+ resultToEmit: avg(det.map((s) => s.resultToEmit))
165
+ },
152
166
  inference: { avg: avg(inf), p95: p95(inf) },
153
167
  detections: totalDet,
154
168
  dropped,
@@ -161,6 +175,8 @@ class PipelineTimingSampler {
161
175
  this.detSamples.clear();
162
176
  for (const [deviceId, mot] of this.motSamples) {
163
177
  if (mot.length === 0) continue;
178
+ const ms = mot.map((s) => s.ms);
179
+ const frameAge = mot.map((s) => s.frameAge).filter((v) => v >= 0);
164
180
  this.log.info(
165
181
  "motion stats",
166
182
  {
@@ -168,10 +184,12 @@ class PipelineTimingSampler {
168
184
  meta: {
169
185
  frames: mot.length,
170
186
  intervalSec: REPORT_INTERVAL_MS / 1e3,
171
- avg: avg(mot),
172
- p95: p95(mot),
173
- max: max(mot)
174
- // motionAddon: rt.motionAddon ?? null,
187
+ avg: avg(ms),
188
+ p95: p95(ms),
189
+ max: max(ms),
190
+ // Frame age at motion analysis (capture→analysis). Large = stale
191
+ // input frame (decoder/ring behind) → motion box lags real movement.
192
+ frameAge: { avg: avg(frameAge), p95: p95(frameAge), max: max(frameAge) }
175
193
  }
176
194
  }
177
195
  );
@@ -517,8 +535,7 @@ class PipelineRunner {
517
535
  async processWithSemaphore(deviceId, entry, frameInput, state, streamType) {
518
536
  const pickedAt = Date.now();
519
537
  const { frame, handle } = entry;
520
- const captureTs = frame.timestamp;
521
- const enqueuedAt = frame._enqueuedAt ?? captureTs;
538
+ const enqueuedAt = frame._enqueuedAt ?? pickedAt;
522
539
  const release = await this.semaphore.acquire();
523
540
  const semAcquiredAt = Date.now();
524
541
  try {
@@ -533,13 +550,19 @@ class PipelineRunner {
533
550
  if (result) {
534
551
  await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
535
552
  const emittedAt = Date.now();
553
+ const capturedAt = frame.capturedAt;
536
554
  this.timingSampler.addSample(deviceId, {
537
- captureToEnqueue: enqueuedAt - captureTs,
538
555
  queueWait: pickedAt - enqueuedAt,
539
556
  semaphoreWait: semAcquiredAt - pickedAt,
540
557
  inference: inferenceMs,
541
558
  resultToEmit: emittedAt - inferDoneAt,
542
- endToEnd: emittedAt - captureTs,
559
+ // capturedAt is the shm-ring commit wall-clock; pickedAt − capturedAt
560
+ // is how stale the frame was when inference started. <0/absent → unknown.
561
+ frameAge: typeof capturedAt === "number" && capturedAt > 0 ? pickedAt - capturedAt : -1,
562
+ // Wall-clock pipeline latency: enqueue → result emitted. (Capture →
563
+ // enqueue, i.e. RTSP decode + shm-ring write, is not measured here —
564
+ // the shm frame carries only a PTS, no wall-clock capture stamp.)
565
+ endToEnd: emittedAt - enqueuedAt,
543
566
  detections: result.detections?.length ?? 0
544
567
  });
545
568
  }
@@ -730,6 +753,11 @@ const DEFAULT_CONFIG = {
730
753
  targetLoadPercent: 80,
731
754
  minThrottledFps: 1
732
755
  };
756
+ function shouldStartOnboardAnalyzer(config) {
757
+ if (!config.onboardMotionDrivesAnalyzer) return false;
758
+ if (config.motionSources.includes("analyzer")) return false;
759
+ return true;
760
+ }
733
761
  function toFrameInput(frame) {
734
762
  return {
735
763
  data: frame.data,
@@ -760,6 +788,20 @@ class PipelineRunnerAddon extends BaseAddon {
760
788
  * detach.
761
789
  */
762
790
  lastMotionAt = /* @__PURE__ */ new Map();
791
+ /**
792
+ * Dynamic analyzer subscriptions opened on `MotionOnMotionChanged
793
+ * source:'onboard'` when `onboardMotionDrivesAnalyzer === true`. Each
794
+ * entry is the unsubscribe handle returned by `subscribeMotionFrames`.
795
+ * Cleared on teardown timer fire, detach, and shutdown.
796
+ */
797
+ onboardAnalyzerSubs = /* @__PURE__ */ new Map();
798
+ /**
799
+ * Teardown timers that close the dynamic analyzer subscription after
800
+ * `motionCooldownMs` without a new motion event. Re-armed on every
801
+ * `MotionOnMotionChanged source:'onboard'` call so the sub stays open
802
+ * while motion persists.
803
+ */
804
+ onboardAnalyzerTeardownTimers = /* @__PURE__ */ new Map();
763
805
  /**
764
806
  * Snapshot-equality cache for metrics-snapshot defer. The runner
765
807
  * fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;
@@ -824,6 +866,9 @@ class PipelineRunnerAddon extends BaseAddon {
824
866
  const attachment = this.attached.get(deviceId);
825
867
  if (!attachment) return;
826
868
  const source = data.source;
869
+ if (source === "onboard") {
870
+ void this.handleOnboardMotionAnalyzer(deviceId, data.detected);
871
+ }
827
872
  if (!attachment.config.motionSources.includes(source)) return;
828
873
  this.runner?.reportMotion(
829
874
  deviceId,
@@ -868,6 +913,9 @@ class PipelineRunnerAddon extends BaseAddon {
868
913
  this.unsubMotionEvents = null;
869
914
  }
870
915
  this.lastAnalyzerDetected.clear();
916
+ for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) {
917
+ this.clearOnboardAnalyzer(deviceId);
918
+ }
871
919
  if (this.runner) {
872
920
  this.runner.stop();
873
921
  this.runner = null;
@@ -1302,6 +1350,7 @@ class PipelineRunnerAddon extends BaseAddon {
1302
1350
  detachInternal(deviceId) {
1303
1351
  const attachment = this.attached.get(deviceId);
1304
1352
  if (!attachment) return;
1353
+ this.clearOnboardAnalyzer(deviceId);
1305
1354
  attachment.motionUnsubscribe?.();
1306
1355
  attachment.detectionUnsubscribe?.();
1307
1356
  this.attached.delete(deviceId);
@@ -1310,6 +1359,70 @@ class PipelineRunnerAddon extends BaseAddon {
1310
1359
  this.runner?.unregisterCamera(deviceId);
1311
1360
  this.ctx?.logger.info("detachCamera", { tags: { deviceId } });
1312
1361
  }
1362
+ /**
1363
+ * Synchronously cancel the teardown timer and call the unsubscribe
1364
+ * handle for the dynamic onboard analyzer, if one is open. Safe to
1365
+ * call when no subscription exists.
1366
+ */
1367
+ clearOnboardAnalyzer(deviceId) {
1368
+ const timer = this.onboardAnalyzerTeardownTimers.get(deviceId);
1369
+ if (timer !== void 0) {
1370
+ clearTimeout(timer);
1371
+ this.onboardAnalyzerTeardownTimers.delete(deviceId);
1372
+ }
1373
+ const unsub = this.onboardAnalyzerSubs.get(deviceId);
1374
+ if (unsub !== void 0) {
1375
+ try {
1376
+ unsub();
1377
+ } catch {
1378
+ }
1379
+ this.onboardAnalyzerSubs.delete(deviceId);
1380
+ }
1381
+ }
1382
+ /**
1383
+ * Dynamic analyzer gate for onboard-motion cameras.
1384
+ *
1385
+ * Called from the `MotionOnMotionChanged` subscriber whenever
1386
+ * `source === 'onboard'`. Opens a `subscribeMotionFrames` subscription
1387
+ * the first time motion is detected (idempotent — a second `detected:true`
1388
+ * while the sub is already open is a no-op). Always re-arms the teardown
1389
+ * timer so the subscription stays open as long as motion events keep
1390
+ * arriving and tears down `motionCooldownMs` after the last event.
1391
+ *
1392
+ * No-op when:
1393
+ * - The camera is not currently attached.
1394
+ * - `shouldStartOnboardAnalyzer(config)` returns false (flag off or
1395
+ * `motionSources` already includes `'analyzer'`).
1396
+ */
1397
+ async handleOnboardMotionAnalyzer(deviceId, detected) {
1398
+ const attachment = this.attached.get(deviceId);
1399
+ if (!attachment) return;
1400
+ const config = attachment.config;
1401
+ if (!shouldStartOnboardAnalyzer(config)) return;
1402
+ const log = this.ctx.logger.withTags({ deviceId });
1403
+ if (detected && !this.onboardAnalyzerSubs.has(deviceId)) {
1404
+ const unsub = await this.subscribeMotionFrames(config);
1405
+ if (unsub) {
1406
+ this.onboardAnalyzerSubs.set(deviceId, unsub);
1407
+ log.debug("onboard-analyzer: opened motion-frame subscription");
1408
+ }
1409
+ }
1410
+ const existing = this.onboardAnalyzerTeardownTimers.get(deviceId);
1411
+ if (existing !== void 0) clearTimeout(existing);
1412
+ const cooldownMs = config.motionCooldownMs;
1413
+ const timer = setTimeout(() => {
1414
+ this.onboardAnalyzerTeardownTimers.delete(deviceId);
1415
+ const unsub = this.onboardAnalyzerSubs.get(deviceId);
1416
+ if (!unsub) return;
1417
+ try {
1418
+ unsub();
1419
+ } catch {
1420
+ }
1421
+ this.onboardAnalyzerSubs.delete(deviceId);
1422
+ log.debug("onboard-analyzer: closed after motion cooldown", { meta: { cooldownMs } });
1423
+ }, cooldownMs);
1424
+ this.onboardAnalyzerTeardownTimers.set(deviceId, timer);
1425
+ }
1313
1426
  async getLocalLoad() {
1314
1427
  const metrics = this.runner?.getMetrics() ?? { avgInferenceTimeMs: 0, queueDepth: 0 };
1315
1428
  const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? [];
@@ -1566,20 +1679,24 @@ class PipelineRunnerAddon extends BaseAddon {
1566
1679
  zonesPayload
1567
1680
  ));
1568
1681
  }
1569
- runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart);
1682
+ const capturedAt = frame.capturedAt;
1683
+ const motionFrameAge = typeof capturedAt === "number" && capturedAt > 0 ? motionStart - capturedAt : -1;
1684
+ runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart, motionFrameAge);
1570
1685
  } catch (error) {
1571
1686
  const msg = errMsg(error);
1572
1687
  log.error("runMotionAnalysis failed", { meta: { error: msg } });
1573
1688
  }
1574
1689
  }
1575
- emitInferenceResult(deviceId, _frame, result, handle) {
1690
+ emitInferenceResult(deviceId, frame, result, handle) {
1576
1691
  const ctx = this.ctx;
1577
1692
  if (!ctx?.eventBus) return;
1693
+ const capturedAt = frame.capturedAt;
1578
1694
  const payload = {
1579
1695
  deviceId,
1580
1696
  frame: result,
1581
1697
  nodeId: this.nodeId,
1582
- frameHandle: handle
1698
+ frameHandle: handle,
1699
+ ...typeof capturedAt === "number" && capturedAt > 0 ? { capturedAt } : {}
1583
1700
  };
1584
1701
  this.ctx.eventBus.emit(createEvent(
1585
1702
  EventCategory.PipelineInferenceResult,
@@ -1720,6 +1837,7 @@ export {
1720
1837
  PipelineTimingSampler,
1721
1838
  Semaphore,
1722
1839
  pipelineRunnerBenchActions as customActions,
1723
- PipelineRunnerAddon as default
1840
+ PipelineRunnerAddon as default,
1841
+ shouldStartOnboardAnalyzer
1724
1842
  };
1725
1843
  //# sourceMappingURL=index.mjs.map