@camstack/addon-pipeline 0.1.18 → 0.1.20
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.
- package/dist/audio-analyzer/index.js +12 -4
- package/dist/audio-analyzer/index.js.map +1 -1
- package/dist/audio-analyzer/index.mjs +12 -4
- package/dist/audio-analyzer/index.mjs.map +1 -1
- package/dist/audio-codec-nodeav/index.js +1 -1
- package/dist/audio-codec-nodeav/index.mjs +1 -1
- package/dist/decoder-nodeav/index.js +2 -2
- package/dist/decoder-nodeav/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +47 -44
- package/dist/detection-pipeline/index.js.map +1 -1
- package/dist/detection-pipeline/index.mjs +47 -44
- package/dist/detection-pipeline/index.mjs.map +1 -1
- package/dist/{index-asZs8U_s.mjs → index-5aYef068.mjs} +4020 -820
- package/dist/index-5aYef068.mjs.map +1 -0
- package/dist/{index-DLHaHm6u.js → index-B36NMAdu.js} +3996 -796
- package/dist/index-B36NMAdu.js.map +1 -0
- package/dist/{index-D_cl0Qqb.js → index-CMcx_k6Y.js} +48 -48
- package/dist/{index-D_cl0Qqb.js.map → index-CMcx_k6Y.js.map} +1 -1
- package/dist/{index-UbcdLS7a.mjs → index-CYb7cFrv.mjs} +46 -46
- package/dist/{index-UbcdLS7a.mjs.map → index-CYb7cFrv.mjs.map} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +205 -90
- package/dist/pipeline-runner/index.js.map +1 -1
- package/dist/pipeline-runner/index.mjs +206 -91
- package/dist/pipeline-runner/index.mjs.map +1 -1
- package/dist/recorder/index.js +2209 -0
- package/dist/recorder/index.js.map +1 -0
- package/dist/recorder/index.mjs +2209 -0
- package/dist/recorder/index.mjs.map +1 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/FfmpegParamsField.d.ts +41 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/GeometryBuilder.d.ts +54 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/format-ua.d.ts +13 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/index.d.ts +2 -0
- package/dist/stream-broker/@mf-types.zip +0 -0
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs} +1 -1
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-DJ3UNg7O.mjs +30 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-CYXy_bhS.mjs +21 -0
- 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-CaDEYBIU.mjs} +9 -7
- 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-D6EROtlA.mjs} +1 -1
- package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-gBEZsQrp.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-x6pP3Ghk.mjs} +2 -2
- 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-CcnN6sbA.mjs} +1 -1
- package/dist/stream-broker/_stub.js +963 -333
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-3TxRVJ5L.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CL9DR49k.mjs} +6 -6
- package/dist/stream-broker/{client-CZXrddDR.mjs → client-BvTmMOQu.mjs} +2 -2
- package/dist/stream-broker/{hostInit-De6APW25.mjs → hostInit-ChmiMPS0.mjs} +12 -12
- package/dist/stream-broker/{index-cYW01SNH.mjs → index-BxsFuFmE.mjs} +24 -24
- package/dist/stream-broker/{index-KtR7Pp0O.mjs → index-C-248uOU.mjs} +2 -2
- package/dist/stream-broker/{index-C0BzaWmB.mjs → index-C05B6jqp.mjs} +1 -1
- package/dist/stream-broker/index-DOJoSShD.mjs +67784 -0
- package/dist/stream-broker/index-DtOI1aTU.mjs +18504 -0
- package/dist/stream-broker/{index-BvV3RVTZ.mjs → index-oMq6ilgR.mjs} +254 -268
- package/dist/stream-broker/{index-CZNxa0ad.mjs → index-vIWZQBIL.mjs} +1 -1
- package/dist/stream-broker/index.js +4666 -756
- package/dist/stream-broker/index.js.map +1 -1
- package/dist/stream-broker/index.mjs +4668 -756
- package/dist/stream-broker/index.mjs.map +1 -1
- package/dist/stream-broker/{jsx-runtime-B_evVsXl.mjs → jsx-runtime-BRT_HL0A.mjs} +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/stream-broker/{schemas-ChN4Ih0h.mjs → schemas-B7L0qZtq.mjs} +530 -515
- package/package.json +51 -3
- package/dist/index-DLHaHm6u.js.map +0 -1
- package/dist/index-asZs8U_s.mjs.map +0 -1
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-d8PmLbO2.mjs +0 -19
- 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
- package/dist/stream-broker/index-CUXiTSWS.mjs +0 -13883
- 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-
|
|
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, m as makeSourceBrokerId, f as createEvent } from "../index-5aYef068.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(
|
|
172
|
-
p95: p95(
|
|
173
|
-
max: max(
|
|
174
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -583,36 +606,87 @@ class PipelineRunner {
|
|
|
583
606
|
const PULL_MAX_COUNT = 4;
|
|
584
607
|
const MIN_POLL_INTERVAL_MS = 20;
|
|
585
608
|
const FALLBACK_POLL_INTERVAL_MS = 200;
|
|
609
|
+
const INITIAL_SUBSCRIBE_RETRY_BACKOFF_MS = 250;
|
|
610
|
+
const MAX_SUBSCRIBE_RETRY_BACKOFF_MS = 5e3;
|
|
586
611
|
async function startFrameHandlePoller(options) {
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
612
|
+
const lifecycle = {
|
|
613
|
+
stopped: false,
|
|
614
|
+
retryTimer: void 0,
|
|
615
|
+
activeTeardown: null
|
|
616
|
+
};
|
|
617
|
+
const teardown = () => {
|
|
618
|
+
if (lifecycle.stopped) return;
|
|
619
|
+
lifecycle.stopped = true;
|
|
620
|
+
if (lifecycle.retryTimer) {
|
|
621
|
+
clearTimeout(lifecycle.retryTimer);
|
|
622
|
+
lifecycle.retryTimer = void 0;
|
|
623
|
+
}
|
|
624
|
+
lifecycle.activeTeardown?.();
|
|
625
|
+
};
|
|
626
|
+
void subscribeWithRetry(options, lifecycle);
|
|
627
|
+
return teardown;
|
|
628
|
+
}
|
|
629
|
+
async function subscribeWithRetry(options, lifecycle) {
|
|
630
|
+
const { api, brokerId, format, maxFps, tag, logger } = options;
|
|
631
|
+
let backoffMs = INITIAL_SUBSCRIBE_RETRY_BACKOFF_MS;
|
|
632
|
+
let attempt = 0;
|
|
633
|
+
while (!lifecycle.stopped) {
|
|
634
|
+
attempt += 1;
|
|
635
|
+
try {
|
|
636
|
+
const result = await api.streamBroker.subscribeFrames.mutate({
|
|
637
|
+
brokerId,
|
|
638
|
+
format,
|
|
639
|
+
maxFps,
|
|
640
|
+
tag
|
|
641
|
+
});
|
|
642
|
+
if (lifecycle.stopped) {
|
|
643
|
+
await api.streamBroker.unsubscribeFrames.mutate({ subscriptionId: result.subscriptionId }).catch((err) => {
|
|
644
|
+
logger.warn("frame-handle poller: late unsubscribe failed", {
|
|
645
|
+
meta: { brokerId, subscriptionId: result.subscriptionId, error: errMsg(err) }
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
lifecycle.activeTeardown = startPolling(options, result.subscriptionId, result.maxFps, lifecycle);
|
|
651
|
+
return;
|
|
652
|
+
} catch (err) {
|
|
653
|
+
if (lifecycle.stopped) return;
|
|
654
|
+
if (attempt === 1) {
|
|
655
|
+
logger.warn("frame-handle poller: subscribeFrames failed, retrying", {
|
|
656
|
+
meta: { brokerId, format, tag, error: errMsg(err), nextRetryInMs: backoffMs }
|
|
657
|
+
});
|
|
658
|
+
} else {
|
|
659
|
+
logger.debug("frame-handle poller: subscribeFrames still failing", {
|
|
660
|
+
meta: { brokerId, format, tag, attempt, error: errMsg(err), nextRetryInMs: backoffMs }
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
await sleep(backoffMs, lifecycle);
|
|
664
|
+
backoffMs = Math.min(MAX_SUBSCRIBE_RETRY_BACKOFF_MS, backoffMs * 2);
|
|
665
|
+
}
|
|
601
666
|
}
|
|
602
|
-
|
|
667
|
+
}
|
|
668
|
+
function sleep(ms, lifecycle) {
|
|
669
|
+
return new Promise((resolve) => {
|
|
670
|
+
lifecycle.retryTimer = setTimeout(() => {
|
|
671
|
+
lifecycle.retryTimer = void 0;
|
|
672
|
+
resolve();
|
|
673
|
+
}, ms);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
function startPolling(options, subscriptionId, resolvedMaxFps, lifecycle) {
|
|
677
|
+
const { api, brokerId, onFrame, logger } = options;
|
|
603
678
|
const readers = new FrameRingReaderCache(logger);
|
|
604
|
-
const pollIntervalMs =
|
|
605
|
-
let stopped = false;
|
|
679
|
+
const pollIntervalMs = resolvedMaxFps > 0 ? Math.max(MIN_POLL_INTERVAL_MS, Math.round(1e3 / resolvedMaxFps)) : FALLBACK_POLL_INTERVAL_MS;
|
|
606
680
|
let timer;
|
|
607
681
|
const tick = async () => {
|
|
608
|
-
if (stopped) return;
|
|
682
|
+
if (lifecycle.stopped) return;
|
|
609
683
|
try {
|
|
610
684
|
const handles = await api.streamBroker.pullFrameHandles.query({
|
|
611
685
|
subscriptionId,
|
|
612
686
|
maxCount: PULL_MAX_COUNT
|
|
613
687
|
});
|
|
614
688
|
for (const handle of handles) {
|
|
615
|
-
if (stopped) break;
|
|
689
|
+
if (lifecycle.stopped) break;
|
|
616
690
|
const frame = readers.read(handle);
|
|
617
691
|
if (frame) onFrame(frame, handle);
|
|
618
692
|
}
|
|
@@ -621,14 +695,12 @@ async function startFrameHandlePoller(options) {
|
|
|
621
695
|
meta: { brokerId, subscriptionId, error: errMsg(err) }
|
|
622
696
|
});
|
|
623
697
|
}
|
|
624
|
-
if (!stopped) {
|
|
698
|
+
if (!lifecycle.stopped) {
|
|
625
699
|
timer = setTimeout(() => void tick(), pollIntervalMs);
|
|
626
700
|
}
|
|
627
701
|
};
|
|
628
702
|
void tick();
|
|
629
703
|
return () => {
|
|
630
|
-
if (stopped) return;
|
|
631
|
-
stopped = true;
|
|
632
704
|
if (timer) {
|
|
633
705
|
clearTimeout(timer);
|
|
634
706
|
timer = void 0;
|
|
@@ -730,6 +802,11 @@ const DEFAULT_CONFIG = {
|
|
|
730
802
|
targetLoadPercent: 80,
|
|
731
803
|
minThrottledFps: 1
|
|
732
804
|
};
|
|
805
|
+
function shouldStartOnboardAnalyzer(config) {
|
|
806
|
+
if (!config.onboardMotionDrivesAnalyzer) return false;
|
|
807
|
+
if (config.motionSources.includes("analyzer")) return false;
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
733
810
|
function toFrameInput(frame) {
|
|
734
811
|
return {
|
|
735
812
|
data: frame.data,
|
|
@@ -739,14 +816,12 @@ function toFrameInput(frame) {
|
|
|
739
816
|
timestamp: frame.timestamp
|
|
740
817
|
};
|
|
741
818
|
}
|
|
742
|
-
const STEP_LOG_INTERVAL_MS = 3e4;
|
|
743
819
|
const METRICS_SNAPSHOT_INTERVAL_MS = 1e3;
|
|
744
820
|
const METRICS_HEARTBEAT_MS = 3e4;
|
|
745
821
|
class PipelineRunnerAddon extends BaseAddon {
|
|
746
822
|
runner = null;
|
|
747
823
|
attached = /* @__PURE__ */ new Map();
|
|
748
824
|
nodeId = "unknown";
|
|
749
|
-
stepLogTimer = null;
|
|
750
825
|
metricsSnapshotTimer = null;
|
|
751
826
|
unsubMotionEvents = null;
|
|
752
827
|
/** Last analyzer-detected state per device — gates the
|
|
@@ -760,6 +835,20 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
760
835
|
* detach.
|
|
761
836
|
*/
|
|
762
837
|
lastMotionAt = /* @__PURE__ */ new Map();
|
|
838
|
+
/**
|
|
839
|
+
* Dynamic analyzer subscriptions opened on `MotionOnMotionChanged
|
|
840
|
+
* source:'onboard'` when `onboardMotionDrivesAnalyzer === true`. Each
|
|
841
|
+
* entry is the unsubscribe handle returned by `subscribeMotionFrames`.
|
|
842
|
+
* Cleared on teardown timer fire, detach, and shutdown.
|
|
843
|
+
*/
|
|
844
|
+
onboardAnalyzerSubs = /* @__PURE__ */ new Map();
|
|
845
|
+
/**
|
|
846
|
+
* Teardown timers that close the dynamic analyzer subscription after
|
|
847
|
+
* `motionCooldownMs` without a new motion event. Re-armed on every
|
|
848
|
+
* `MotionOnMotionChanged source:'onboard'` call so the sub stays open
|
|
849
|
+
* while motion persists.
|
|
850
|
+
*/
|
|
851
|
+
onboardAnalyzerTeardownTimers = /* @__PURE__ */ new Map();
|
|
763
852
|
/**
|
|
764
853
|
* Snapshot-equality cache for metrics-snapshot defer. The runner
|
|
765
854
|
* fires per-camera metrics every `METRICS_SNAPSHOT_INTERVAL_MS`;
|
|
@@ -824,6 +913,9 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
824
913
|
const attachment = this.attached.get(deviceId);
|
|
825
914
|
if (!attachment) return;
|
|
826
915
|
const source = data.source;
|
|
916
|
+
if (source === "onboard") {
|
|
917
|
+
void this.handleOnboardMotionAnalyzer(deviceId, data.detected);
|
|
918
|
+
}
|
|
827
919
|
if (!attachment.config.motionSources.includes(source)) return;
|
|
828
920
|
this.runner?.reportMotion(
|
|
829
921
|
deviceId,
|
|
@@ -834,7 +926,6 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
834
926
|
}
|
|
835
927
|
);
|
|
836
928
|
}
|
|
837
|
-
this.stepLogTimer = setInterval(() => this.logAttachedSteps(), STEP_LOG_INTERVAL_MS);
|
|
838
929
|
this.metricsSnapshotTimer = setInterval(
|
|
839
930
|
() => this.emitMetricsSnapshot(),
|
|
840
931
|
METRICS_SNAPSHOT_INTERVAL_MS
|
|
@@ -854,10 +945,6 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
854
945
|
clearInterval(this.metricsSnapshotTimer);
|
|
855
946
|
this.metricsSnapshotTimer = null;
|
|
856
947
|
}
|
|
857
|
-
if (this.stepLogTimer) {
|
|
858
|
-
clearInterval(this.stepLogTimer);
|
|
859
|
-
this.stepLogTimer = null;
|
|
860
|
-
}
|
|
861
948
|
if (this.benchFrameSweeper) {
|
|
862
949
|
clearInterval(this.benchFrameSweeper);
|
|
863
950
|
this.benchFrameSweeper = null;
|
|
@@ -868,6 +955,9 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
868
955
|
this.unsubMotionEvents = null;
|
|
869
956
|
}
|
|
870
957
|
this.lastAnalyzerDetected.clear();
|
|
958
|
+
for (const deviceId of [...this.onboardAnalyzerTeardownTimers.keys(), ...this.onboardAnalyzerSubs.keys()]) {
|
|
959
|
+
this.clearOnboardAnalyzer(deviceId);
|
|
960
|
+
}
|
|
871
961
|
if (this.runner) {
|
|
872
962
|
this.runner.stop();
|
|
873
963
|
this.runner = null;
|
|
@@ -1254,54 +1344,10 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
1254
1344
|
this.runner?.reportMotion(input.deviceId, input.detected, input.source, input.regions);
|
|
1255
1345
|
return { success: true };
|
|
1256
1346
|
}
|
|
1257
|
-
/**
|
|
1258
|
-
* Periodic per-camera step roster dump. Once every
|
|
1259
|
-
* STEP_LOG_INTERVAL_MS (30s) emits one log line per attached camera
|
|
1260
|
-
* with the configured detection step tree + audio classifier branch
|
|
1261
|
-
* so an operator looking at the agent log can quickly see what each
|
|
1262
|
-
* camera is currently running without crossing tRPC. Skips when no
|
|
1263
|
-
* cameras are attached so quiet dev runs stay silent.
|
|
1264
|
-
*/
|
|
1265
|
-
logAttachedSteps() {
|
|
1266
|
-
if (this.attached.size === 0) return;
|
|
1267
|
-
for (const [deviceId, attachment] of this.attached) {
|
|
1268
|
-
const cfg = attachment.config;
|
|
1269
|
-
const detectionSteps = cfg.steps && cfg.steps.length > 0 ? this.flattenSteps(cfg.steps).filter((s) => s.enabled) : [];
|
|
1270
|
-
const detectionLabel = detectionSteps.length > 0 ? detectionSteps.map((s) => `${s.addonId}/${s.modelId}`).join(" → ") : "<none>";
|
|
1271
|
-
const audioLabel = cfg.audio && cfg.audio.enabled ? `${cfg.audio.engine.runtime}/${cfg.audio.engine.backend}/${cfg.audio.modelId}` : "<off>";
|
|
1272
|
-
const engineLabel = cfg.engine ? `${cfg.engine.runtime}/${cfg.engine.backend}${cfg.engine.device ? `/${cfg.engine.device}` : ""}` : "<unset>";
|
|
1273
|
-
this.ctx.logger.info("Camera pipeline roster", {
|
|
1274
|
-
tags: { deviceId },
|
|
1275
|
-
meta: {
|
|
1276
|
-
phase: "roster",
|
|
1277
|
-
intervalSec: STEP_LOG_INTERVAL_MS / 1e3,
|
|
1278
|
-
pipelineEnabled: cfg.pipelineEnabled,
|
|
1279
|
-
motionSources: cfg.motionSources,
|
|
1280
|
-
motionFps: cfg.motionFps,
|
|
1281
|
-
detectionFps: cfg.detectionFps,
|
|
1282
|
-
engine: engineLabel,
|
|
1283
|
-
videoSteps: detectionLabel,
|
|
1284
|
-
videoStepCount: detectionSteps.length,
|
|
1285
|
-
audio: audioLabel
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
/** Recursively flatten the step tree → ordered list of every node. */
|
|
1291
|
-
flattenSteps(steps) {
|
|
1292
|
-
const out = [];
|
|
1293
|
-
const walk = (s) => {
|
|
1294
|
-
out.push(s);
|
|
1295
|
-
if (s.children) {
|
|
1296
|
-
for (const c of s.children) walk(c);
|
|
1297
|
-
}
|
|
1298
|
-
};
|
|
1299
|
-
for (const s of steps) walk(s);
|
|
1300
|
-
return out;
|
|
1301
|
-
}
|
|
1302
1347
|
detachInternal(deviceId) {
|
|
1303
1348
|
const attachment = this.attached.get(deviceId);
|
|
1304
1349
|
if (!attachment) return;
|
|
1350
|
+
this.clearOnboardAnalyzer(deviceId);
|
|
1305
1351
|
attachment.motionUnsubscribe?.();
|
|
1306
1352
|
attachment.detectionUnsubscribe?.();
|
|
1307
1353
|
this.attached.delete(deviceId);
|
|
@@ -1310,6 +1356,70 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
1310
1356
|
this.runner?.unregisterCamera(deviceId);
|
|
1311
1357
|
this.ctx?.logger.info("detachCamera", { tags: { deviceId } });
|
|
1312
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Synchronously cancel the teardown timer and call the unsubscribe
|
|
1361
|
+
* handle for the dynamic onboard analyzer, if one is open. Safe to
|
|
1362
|
+
* call when no subscription exists.
|
|
1363
|
+
*/
|
|
1364
|
+
clearOnboardAnalyzer(deviceId) {
|
|
1365
|
+
const timer = this.onboardAnalyzerTeardownTimers.get(deviceId);
|
|
1366
|
+
if (timer !== void 0) {
|
|
1367
|
+
clearTimeout(timer);
|
|
1368
|
+
this.onboardAnalyzerTeardownTimers.delete(deviceId);
|
|
1369
|
+
}
|
|
1370
|
+
const unsub = this.onboardAnalyzerSubs.get(deviceId);
|
|
1371
|
+
if (unsub !== void 0) {
|
|
1372
|
+
try {
|
|
1373
|
+
unsub();
|
|
1374
|
+
} catch {
|
|
1375
|
+
}
|
|
1376
|
+
this.onboardAnalyzerSubs.delete(deviceId);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Dynamic analyzer gate for onboard-motion cameras.
|
|
1381
|
+
*
|
|
1382
|
+
* Called from the `MotionOnMotionChanged` subscriber whenever
|
|
1383
|
+
* `source === 'onboard'`. Opens a `subscribeMotionFrames` subscription
|
|
1384
|
+
* the first time motion is detected (idempotent — a second `detected:true`
|
|
1385
|
+
* while the sub is already open is a no-op). Always re-arms the teardown
|
|
1386
|
+
* timer so the subscription stays open as long as motion events keep
|
|
1387
|
+
* arriving and tears down `motionCooldownMs` after the last event.
|
|
1388
|
+
*
|
|
1389
|
+
* No-op when:
|
|
1390
|
+
* - The camera is not currently attached.
|
|
1391
|
+
* - `shouldStartOnboardAnalyzer(config)` returns false (flag off or
|
|
1392
|
+
* `motionSources` already includes `'analyzer'`).
|
|
1393
|
+
*/
|
|
1394
|
+
async handleOnboardMotionAnalyzer(deviceId, detected) {
|
|
1395
|
+
const attachment = this.attached.get(deviceId);
|
|
1396
|
+
if (!attachment) return;
|
|
1397
|
+
const config = attachment.config;
|
|
1398
|
+
if (!shouldStartOnboardAnalyzer(config)) return;
|
|
1399
|
+
const log = this.ctx.logger.withTags({ deviceId });
|
|
1400
|
+
if (detected && !this.onboardAnalyzerSubs.has(deviceId)) {
|
|
1401
|
+
const unsub = await this.subscribeMotionFrames(config);
|
|
1402
|
+
if (unsub) {
|
|
1403
|
+
this.onboardAnalyzerSubs.set(deviceId, unsub);
|
|
1404
|
+
log.debug("onboard-analyzer: opened motion-frame subscription");
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
const existing = this.onboardAnalyzerTeardownTimers.get(deviceId);
|
|
1408
|
+
if (existing !== void 0) clearTimeout(existing);
|
|
1409
|
+
const cooldownMs = config.motionCooldownMs;
|
|
1410
|
+
const timer = setTimeout(() => {
|
|
1411
|
+
this.onboardAnalyzerTeardownTimers.delete(deviceId);
|
|
1412
|
+
const unsub = this.onboardAnalyzerSubs.get(deviceId);
|
|
1413
|
+
if (!unsub) return;
|
|
1414
|
+
try {
|
|
1415
|
+
unsub();
|
|
1416
|
+
} catch {
|
|
1417
|
+
}
|
|
1418
|
+
this.onboardAnalyzerSubs.delete(deviceId);
|
|
1419
|
+
log.debug("onboard-analyzer: closed after motion cooldown", { meta: { cooldownMs } });
|
|
1420
|
+
}, cooldownMs);
|
|
1421
|
+
this.onboardAnalyzerTeardownTimers.set(deviceId, timer);
|
|
1422
|
+
}
|
|
1313
1423
|
async getLocalLoad() {
|
|
1314
1424
|
const metrics = this.runner?.getMetrics() ?? { avgInferenceTimeMs: 0, queueDepth: 0 };
|
|
1315
1425
|
const allCameraMetrics = this.runner?.getAllCameraMetrics() ?? [];
|
|
@@ -1358,7 +1468,7 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
1358
1468
|
}
|
|
1359
1469
|
return startFrameHandlePoller({
|
|
1360
1470
|
api,
|
|
1361
|
-
brokerId:
|
|
1471
|
+
brokerId: makeSourceBrokerId(config.deviceId, config.motionStreamId),
|
|
1362
1472
|
format: "gray",
|
|
1363
1473
|
maxFps: config.motionFps,
|
|
1364
1474
|
tag: "motion",
|
|
@@ -1444,7 +1554,7 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
1444
1554
|
}
|
|
1445
1555
|
return startFrameHandlePoller({
|
|
1446
1556
|
api,
|
|
1447
|
-
brokerId:
|
|
1557
|
+
brokerId: makeSourceBrokerId(config.deviceId, config.detectionStreamId),
|
|
1448
1558
|
format: "rgb",
|
|
1449
1559
|
maxFps: config.detectionFps,
|
|
1450
1560
|
tag: "detection",
|
|
@@ -1566,20 +1676,24 @@ class PipelineRunnerAddon extends BaseAddon {
|
|
|
1566
1676
|
zonesPayload
|
|
1567
1677
|
));
|
|
1568
1678
|
}
|
|
1569
|
-
|
|
1679
|
+
const capturedAt = frame.capturedAt;
|
|
1680
|
+
const motionFrameAge = typeof capturedAt === "number" && capturedAt > 0 ? motionStart - capturedAt : -1;
|
|
1681
|
+
runner.timingSampler.addMotionSample(deviceId, Date.now() - motionStart, motionFrameAge);
|
|
1570
1682
|
} catch (error) {
|
|
1571
1683
|
const msg = errMsg(error);
|
|
1572
1684
|
log.error("runMotionAnalysis failed", { meta: { error: msg } });
|
|
1573
1685
|
}
|
|
1574
1686
|
}
|
|
1575
|
-
emitInferenceResult(deviceId,
|
|
1687
|
+
emitInferenceResult(deviceId, frame, result, handle) {
|
|
1576
1688
|
const ctx = this.ctx;
|
|
1577
1689
|
if (!ctx?.eventBus) return;
|
|
1690
|
+
const capturedAt = frame.capturedAt;
|
|
1578
1691
|
const payload = {
|
|
1579
1692
|
deviceId,
|
|
1580
1693
|
frame: result,
|
|
1581
1694
|
nodeId: this.nodeId,
|
|
1582
|
-
frameHandle: handle
|
|
1695
|
+
frameHandle: handle,
|
|
1696
|
+
...typeof capturedAt === "number" && capturedAt > 0 ? { capturedAt } : {}
|
|
1583
1697
|
};
|
|
1584
1698
|
this.ctx.eventBus.emit(createEvent(
|
|
1585
1699
|
EventCategory.PipelineInferenceResult,
|
|
@@ -1720,6 +1834,7 @@ export {
|
|
|
1720
1834
|
PipelineTimingSampler,
|
|
1721
1835
|
Semaphore,
|
|
1722
1836
|
pipelineRunnerBenchActions as customActions,
|
|
1723
|
-
PipelineRunnerAddon as default
|
|
1837
|
+
PipelineRunnerAddon as default,
|
|
1838
|
+
shouldStartOnboardAnalyzer
|
|
1724
1839
|
};
|
|
1725
1840
|
//# sourceMappingURL=index.mjs.map
|