@camstack/addon-pipeline 1.2.51 → 1.2.53
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/{addon-utils-DveoBR6G.js → addon-utils-CQs-AjTJ.js} +177 -177
- package/dist/{addon-utils-QuxtOdK1.mjs → addon-utils-CZ2xo67g.mjs} +177 -177
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +3 -3
- package/dist/detection-pipeline/index.mjs +3 -3
- package/dist/{dist-Dkvk5ykl.mjs → dist-B55xTTsS.mjs} +16337 -15811
- package/dist/{dist-BsBEe_rX.js → dist-D8TX_P_A.js} +16337 -15811
- package/dist/{event-loop-stall-monitor-DqJXi0_B.js → event-loop-stall-monitor-Bs6d-u_F.js} +1 -1
- package/dist/{event-loop-stall-monitor-BvtjRcal.mjs → event-loop-stall-monitor-Cq_KOlfb.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +160 -40
- package/dist/pipeline-runner/index.mjs +160 -40
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +2 -2
- package/dist/session-decode/decode-worker-child.js +6 -2
- package/dist/session-decode/decode-worker-child.mjs +6 -2
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CUZ0nS_r.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C-9q6y1s.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CAmiVOsA.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CaGsMvou.mjs +26 -0
- package/dist/stream-broker/{hostInit-B8O23a6l.mjs → hostInit-DiXYwnXH.mjs} +3 -3
- package/dist/stream-broker/index.js +337 -312
- package/dist/stream-broker/index.mjs +337 -312
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-B0c3i2km.mjs → worker-protocol-Bol291_n.mjs} +1 -1
- package/dist/{worker-protocol-CWhkLkHx.js → worker-protocol-C2uctCrd.js} +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-kAtChdz2.js → MaskShapeCanvas-DI4BY7W2-CXHeTkIT.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DEwkvjzt.js → MotionZonesSettings-NcxxQN8r-De5VKf09.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Brv9VL0h.js → PrivacyMaskSettings-APgPLF7p-Z7dsjOG5.js} +1 -1
- package/embed-dist/assets/{index-Dv7fSuDA.js → index-NJB31mpg.js} +12 -12
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Mv3UeZUE.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Czrsozsg.mjs +0 -26
package/dist/{event-loop-stall-monitor-BvtjRcal.mjs → event-loop-stall-monitor-Cq_KOlfb.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-
|
|
1
|
+
import { R as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-B55xTTsS.mjs";
|
|
2
2
|
import { PerformanceObserver } from "node:perf_hooks";
|
|
3
3
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
4
4
|
var HF_REPO = "camstack/camstack-models";
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-D8TX_P_A.js");
|
|
6
6
|
const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as evaluateZoneRules, V as motionDetectionCapability, dt as hydrateSchema, lt as DeviceType, ot as BaseAddon } from "../dist-
|
|
1
|
+
import { L as evaluateZoneRules, V as motionDetectionCapability, dt as hydrateSchema, lt as DeviceType, ot as BaseAddon } from "../dist-B55xTTsS.mjs";
|
|
2
2
|
import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
8
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
6
|
+
const require_dist = require("../dist-D8TX_P_A.js");
|
|
7
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-Bs6d-u_F.js");
|
|
8
|
+
const require_worker_protocol = require("../worker-protocol-C2uctCrd.js");
|
|
9
9
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
10
10
|
const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
|
|
11
11
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
@@ -543,11 +543,35 @@ function cropRgb(frame, bbox, maxWidth) {
|
|
|
543
543
|
/** How long to wait after `kill()` before escalating to `kill('SIGKILL')`. */
|
|
544
544
|
var SIGKILL_FALLBACK_MS = 1e3;
|
|
545
545
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
546
|
+
* How often the cold-start wait REPORTS that it is still waiting. Task 0
|
|
547
|
+
* live-measured cold-start (restream dial + hwaccel context + keyframe wait) at
|
|
548
|
+
* 5-8s, so this must tolerate a slow-but-healthy start.
|
|
549
|
+
*
|
|
550
|
+
* It is no longer the point at which the session is abandoned — see
|
|
551
|
+
* {@link DEFAULT_FIRST_FRAME_MAX_WAIT_MS}.
|
|
549
552
|
*/
|
|
550
553
|
var DEFAULT_FIRST_FRAME_TIMEOUT_MS = 2e4;
|
|
554
|
+
/**
|
|
555
|
+
* Hard ceiling on the TOTAL wait for the FIRST frame of a dial.
|
|
556
|
+
*
|
|
557
|
+
* The first decodable frame is not a fixed cost: it is one IDR interval plus
|
|
558
|
+
* every GOP the decoder has to discard. Measured on device 618 (2560x1440
|
|
559
|
+
* h264, 8.0 s IDR interval, `Invalid NAL unit` / `corrupt decoded frame` in its
|
|
560
|
+
* bitstream), dial → first decoded frame over the broker's own muted restream
|
|
561
|
+
* was 1.8 / 6.5 / 7.0 / 7.2 / 9.2 / 10.0 / 10.8 / 12.9 / 14.9 / 20.0 / 30.3 /
|
|
562
|
+
* >40 s across 12 trials — against a healthy 587's 2.0 s ± 0.02.
|
|
563
|
+
*
|
|
564
|
+
* Abandoning at 20 s was therefore not a timeout, it was a coin flip, and the
|
|
565
|
+
* recovery made it worse: ending the session RE-DIALS, which throws the
|
|
566
|
+
* accumulated wait away and re-pays the RTSP setup + probe from zero, and two
|
|
567
|
+
* ends inside 60 s DETACH the camera. That loop cost 618 25.9% of its assigned
|
|
568
|
+
* decode airtime in one day (325 of 1257 ten-second windows delivered nothing).
|
|
569
|
+
*
|
|
570
|
+
* The wait is bounded instead of deadlined: the worker keeps the outstanding
|
|
571
|
+
* `pull` latched, so re-arming costs nothing but the wait, and the ceiling is
|
|
572
|
+
* what stops a genuinely dead input from holding a spawn permit forever.
|
|
573
|
+
*/
|
|
574
|
+
var DEFAULT_FIRST_FRAME_MAX_WAIT_MS = 45e3;
|
|
551
575
|
/** Default timeout for every `pull` round-trip AFTER the first frame arrives. */
|
|
552
576
|
var DEFAULT_FRAME_TIMEOUT_MS = 1e4;
|
|
553
577
|
/** Narrow an 'exit' payload to {@link WorkerExitInfo} (fakes may emit undefined). */
|
|
@@ -567,8 +591,10 @@ function toError(err) {
|
|
|
567
591
|
var SessionDecodeSession = class {
|
|
568
592
|
child;
|
|
569
593
|
logger;
|
|
594
|
+
deviceId;
|
|
570
595
|
firstFrameTimeoutMs;
|
|
571
596
|
frameTimeoutMs;
|
|
597
|
+
firstFrameMaxWaitMs;
|
|
572
598
|
pendingPull = null;
|
|
573
599
|
pendingToBuffers = /* @__PURE__ */ new Map();
|
|
574
600
|
/** Outstanding `nativeCrop` requests, keyed by the monotonic `requestId`. */
|
|
@@ -580,11 +606,15 @@ var SessionDecodeSession = class {
|
|
|
580
606
|
sigkillTimer = null;
|
|
581
607
|
pullTimeoutTimer = null;
|
|
582
608
|
firstFrameReceived = false;
|
|
583
|
-
|
|
609
|
+
/** `Date.now()` of the FIRST `pull` — the origin of the cold-start ceiling. */
|
|
610
|
+
firstPullAt = 0;
|
|
611
|
+
constructor(child, logger, deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs) {
|
|
584
612
|
this.child = child;
|
|
585
613
|
this.logger = logger;
|
|
614
|
+
this.deviceId = deviceId;
|
|
586
615
|
this.firstFrameTimeoutMs = firstFrameTimeoutMs;
|
|
587
616
|
this.frameTimeoutMs = frameTimeoutMs;
|
|
617
|
+
this.firstFrameMaxWaitMs = firstFrameMaxWaitMs;
|
|
588
618
|
this.child.on("message", (m) => this.handleMessage(m));
|
|
589
619
|
this.child.on("exit", (m) => this.handleExit(toWorkerExitInfo(m)));
|
|
590
620
|
}
|
|
@@ -604,6 +634,7 @@ var SessionDecodeSession = class {
|
|
|
604
634
|
*/
|
|
605
635
|
pullNext() {
|
|
606
636
|
if (this.exited) return Promise.resolve({ kind: "ended" });
|
|
637
|
+
if (this.firstPullAt === 0) this.firstPullAt = Date.now();
|
|
607
638
|
return new Promise((resolve, reject) => {
|
|
608
639
|
this.pendingPull = {
|
|
609
640
|
resolve,
|
|
@@ -785,25 +816,63 @@ var SessionDecodeSession = class {
|
|
|
785
816
|
}
|
|
786
817
|
/**
|
|
787
818
|
* Fires when a `pull` round-trip outlives its liveness timeout — the child
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
819
|
+
* has not produced a frame and has not exited.
|
|
820
|
+
*
|
|
821
|
+
* BEFORE the first frame this is not yet a failure. The worker latches the
|
|
822
|
+
* outstanding `pull` (`decode-worker-child.handlePull` sets `pendingPull` and
|
|
823
|
+
* delivers on the next decoded frame), so re-arming the timer costs nothing
|
|
824
|
+
* but the wait and the session keeps the dial, the RTSP setup and the probe
|
|
825
|
+
* it has already paid for. Only the {@link firstFrameMaxWaitMs} ceiling ends
|
|
826
|
+
* it — see that constant for the measurement that set it.
|
|
827
|
+
*
|
|
828
|
+
* AFTER the first frame the deadline is unchanged: a steady stream that stops
|
|
829
|
+
* is a hung decode, and ending settles the outstanding pull as `ended` (not a
|
|
830
|
+
* throw) so the consumer's async generator resumes cleanly, runs its
|
|
831
|
+
* `finally`, and tears the child down via {@link teardown}.
|
|
832
|
+
*
|
|
833
|
+
* Every line here carries `tags: { deviceId }` and a `reason`, because
|
|
834
|
+
* "detaching" a camera is the loudest dropped work in this path and an
|
|
835
|
+
* operator has to be able to answer "why did 618 stop" from the logs alone.
|
|
793
836
|
*/
|
|
794
837
|
handlePullTimeout(timeoutMs) {
|
|
795
838
|
this.pullTimeoutTimer = null;
|
|
796
839
|
const waiter = this.pendingPull;
|
|
797
840
|
if (!waiter) return;
|
|
841
|
+
const waitedMs = this.firstPullAt === 0 ? timeoutMs : Date.now() - this.firstPullAt;
|
|
842
|
+
if (!this.firstFrameReceived && waitedMs < this.firstFrameMaxWaitMs) {
|
|
843
|
+
this.logger.warn("session-decode-coordinator: still waiting for its first decodable frame — holding the dial", {
|
|
844
|
+
tags: { deviceId: this.deviceId },
|
|
845
|
+
meta: {
|
|
846
|
+
waitedMs,
|
|
847
|
+
reportEveryMs: timeoutMs,
|
|
848
|
+
maxWaitMs: this.firstFrameMaxWaitMs,
|
|
849
|
+
hint: "long IDR interval or a discarded GOP looks exactly like this"
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
this.armPullTimeout();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
798
855
|
this.pendingPull = null;
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
856
|
+
const reason = this.firstFrameReceived ? "stalled-after-first-frame" : "no-first-frame";
|
|
857
|
+
this.logger.warn(this.firstFrameReceived ? "session-decode-coordinator: gave up waiting for the next frame — ending session" : "session-decode-coordinator: gave up waiting for the first decodable frame — ending session", {
|
|
858
|
+
tags: { deviceId: this.deviceId },
|
|
859
|
+
meta: {
|
|
860
|
+
reason,
|
|
861
|
+
waitedMs,
|
|
862
|
+
timeoutMs,
|
|
863
|
+
...this.firstFrameReceived ? {} : { maxWaitMs: this.firstFrameMaxWaitMs }
|
|
864
|
+
}
|
|
865
|
+
});
|
|
803
866
|
waiter.resolve({ kind: "ended" });
|
|
804
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Arm the next round-trip deadline. Before the first frame the timer is
|
|
870
|
+
* clamped to what is LEFT of the ceiling, so the give-up lands exactly on
|
|
871
|
+
* {@link firstFrameMaxWaitMs} instead of on the next report boundary past it
|
|
872
|
+
* — otherwise a 45 s ceiling reported every 20 s would really give up at 60 s.
|
|
873
|
+
*/
|
|
805
874
|
armPullTimeout() {
|
|
806
|
-
const timeoutMs = this.firstFrameReceived ? this.frameTimeoutMs : this.firstFrameTimeoutMs;
|
|
875
|
+
const timeoutMs = this.firstFrameReceived ? this.frameTimeoutMs : Math.max(1, Math.min(this.firstFrameTimeoutMs, this.firstFrameMaxWaitMs - (this.firstPullAt === 0 ? 0 : Date.now() - this.firstPullAt)));
|
|
807
876
|
this.pullTimeoutTimer = setTimeout(() => this.handlePullTimeout(timeoutMs), timeoutMs);
|
|
808
877
|
this.pullTimeoutTimer.unref?.();
|
|
809
878
|
}
|
|
@@ -925,8 +994,9 @@ function buildFrameImage(session, reply) {
|
|
|
925
994
|
function createSessionDecodeCoordinator(deps) {
|
|
926
995
|
const firstFrameTimeoutMs = deps.firstFrameTimeoutMs ?? DEFAULT_FIRST_FRAME_TIMEOUT_MS;
|
|
927
996
|
const frameTimeoutMs = deps.frameTimeoutMs ?? DEFAULT_FRAME_TIMEOUT_MS;
|
|
997
|
+
const firstFrameMaxWaitMs = Math.max(deps.firstFrameMaxWaitMs ?? DEFAULT_FIRST_FRAME_MAX_WAIT_MS, firstFrameTimeoutMs);
|
|
928
998
|
return { async *generateVideoFrames(source, opts) {
|
|
929
|
-
const session = new SessionDecodeSession(deps.fork(source), deps.logger, firstFrameTimeoutMs, frameTimeoutMs);
|
|
999
|
+
const session = new SessionDecodeSession(deps.fork(source), deps.logger, source.deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs);
|
|
930
1000
|
session.start(source, opts);
|
|
931
1001
|
try {
|
|
932
1002
|
while (true) {
|
|
@@ -1036,6 +1106,7 @@ function startSessionDecodePump(deps) {
|
|
|
1036
1106
|
break;
|
|
1037
1107
|
}
|
|
1038
1108
|
try {
|
|
1109
|
+
if (deps.shouldMaterializeFrame && !deps.shouldMaterializeFrame()) continue;
|
|
1039
1110
|
const decoded = await toDecodedFrame(frame, format);
|
|
1040
1111
|
const image = frame.image;
|
|
1041
1112
|
const cropFn = image.nativeCrop;
|
|
@@ -3067,15 +3138,8 @@ var PipelineRunner = class {
|
|
|
3067
3138
|
* (the FrameQueue implementation is latest-only and ignores maxSize
|
|
3068
3139
|
* anyway — see `frame-queue.ts` — so the field is effectively a
|
|
3069
3140
|
* metadata hint for observability).
|
|
3070
|
-
* - `targetLoadPercent` / `minThrottledFps
|
|
3071
|
-
*
|
|
3072
|
-
* governor is built once, at construction, from the config snapshot
|
|
3073
|
-
* passed to the constructor. A hot patch to either knob changes what
|
|
3074
|
-
* `getLimits()` reports but does not change live admission pacing until
|
|
3075
|
-
* the runner is reconstructed. (Rebuilding the governor live is a
|
|
3076
|
-
* follow-up, not in scope here — `maxConcurrentInferences` has the same
|
|
3077
|
-
* gap: the semaphore resizes live but the governor's budget calc keeps
|
|
3078
|
-
* the construction-time value.)
|
|
3141
|
+
* - `targetLoadPercent` / `minThrottledFps` and inference concurrency:
|
|
3142
|
+
* rebuild the live governor and immediately recompute admission intervals.
|
|
3079
3143
|
*
|
|
3080
3144
|
* Only keys present in the patch are overwritten; unspecified keys
|
|
3081
3145
|
* retain their current value. Any illegal combination (e.g.
|
|
@@ -3091,6 +3155,12 @@ var PipelineRunner = class {
|
|
|
3091
3155
|
};
|
|
3092
3156
|
if (next.maxConcurrentInferences !== this.config.maxConcurrentInferences) this.semaphore.resize(next.maxConcurrentInferences);
|
|
3093
3157
|
this.config = next;
|
|
3158
|
+
this.governor = new FrameGovernor({
|
|
3159
|
+
targetLoadPercent: next.targetLoadPercent,
|
|
3160
|
+
minThrottledFps: next.minThrottledFps,
|
|
3161
|
+
maxConcurrentInferences: next.maxConcurrentInferences
|
|
3162
|
+
});
|
|
3163
|
+
this.recomputeGovernor();
|
|
3094
3164
|
}
|
|
3095
3165
|
/** Read the current tuning fields for diagnostics / tests. */
|
|
3096
3166
|
getLimits() {
|
|
@@ -3184,17 +3254,36 @@ var PipelineRunner = class {
|
|
|
3184
3254
|
* saving; do not turn it back into `void` for tidiness.
|
|
3185
3255
|
*/
|
|
3186
3256
|
enqueueDetectionFrame(deviceId, frame, handle) {
|
|
3257
|
+
if (!this.reserveDetectionFrame(deviceId)) return false;
|
|
3258
|
+
return this.enqueueReservedDetectionFrame(deviceId, frame, handle);
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Reserve adaptive admission before a session-decode frame is materialized.
|
|
3262
|
+
* This is intentionally pixel-free: the pump can skip `toBuffer()` entirely
|
|
3263
|
+
* when the frame is outside the governor interval, avoiding RGB conversion,
|
|
3264
|
+
* IPC and retention work. Raw arrivals are counted here, before the gate, so
|
|
3265
|
+
* recomputation never feeds the throttled output rate back in as source FPS.
|
|
3266
|
+
*/
|
|
3267
|
+
reserveDetectionFrame(deviceId, now = Date.now()) {
|
|
3187
3268
|
const state = this.cameras.get(deviceId);
|
|
3188
3269
|
if (!state) return false;
|
|
3189
3270
|
this.arrivalCounts.set(deviceId, (this.arrivalCounts.get(deviceId) ?? 0) + 1);
|
|
3190
3271
|
if (state.phase !== "active") return false;
|
|
3191
3272
|
const interval = this.governor.minIntervalMs(deviceId);
|
|
3192
3273
|
if (interval > 0) {
|
|
3193
|
-
|
|
3194
|
-
const now = Date.now();
|
|
3195
|
-
if (now - last < interval) return false;
|
|
3274
|
+
if (now - (this.lastAdmittedAt.get(deviceId) ?? 0) < interval) return false;
|
|
3196
3275
|
this.lastAdmittedAt.set(deviceId, now);
|
|
3197
3276
|
}
|
|
3277
|
+
return true;
|
|
3278
|
+
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Queue a frame whose admission was already reserved synchronously through
|
|
3281
|
+
* {@link reserveDetectionFrame}. Kept separate so the ordinary public path
|
|
3282
|
+
* cannot accidentally count one source frame twice.
|
|
3283
|
+
*/
|
|
3284
|
+
enqueueReservedDetectionFrame(deviceId, frame, handle) {
|
|
3285
|
+
const state = this.cameras.get(deviceId);
|
|
3286
|
+
if (!state || state.phase !== "active") return false;
|
|
3198
3287
|
frame._enqueuedAt = Date.now();
|
|
3199
3288
|
state.detectionQueue.enqueue({
|
|
3200
3289
|
frame,
|
|
@@ -5580,21 +5669,44 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5580
5669
|
* no second dial, no second decode, no GPU). Mirrors the shm shared-motion
|
|
5581
5670
|
* seam (`subscribeMotionFrames` `shared` branch).
|
|
5582
5671
|
*/
|
|
5583
|
-
enqueueSharedDetectionFrame(deviceId, frame, handle) {
|
|
5672
|
+
enqueueSharedDetectionFrame(deviceId, frame, handle, reservation) {
|
|
5584
5673
|
const runner = this.runner;
|
|
5585
5674
|
if (!runner) return false;
|
|
5586
|
-
const admitted = runner.enqueueDetectionFrame(deviceId, frame, handle);
|
|
5587
|
-
|
|
5588
|
-
if (
|
|
5589
|
-
const
|
|
5590
|
-
if (
|
|
5591
|
-
|
|
5592
|
-
|
|
5675
|
+
const admitted = reservation ? reservation.detection && runner.enqueueReservedDetectionFrame(deviceId, frame, handle) : runner.enqueueDetectionFrame(deviceId, frame, handle);
|
|
5676
|
+
if (reservation?.motion) runner.enqueueMotionFrame(deviceId, rgbFrameToGrayFrame(frame), void 0);
|
|
5677
|
+
else if (!reservation) {
|
|
5678
|
+
const sink = this.sharedMotionSinks.get(deviceId);
|
|
5679
|
+
if (sink) {
|
|
5680
|
+
const now = this.sharedSinkNow();
|
|
5681
|
+
if (now - sink.lastMotionEnqueueTs >= sink.minIntervalMs) {
|
|
5682
|
+
sink.lastMotionEnqueueTs = now;
|
|
5683
|
+
runner.enqueueMotionFrame(deviceId, rgbFrameToGrayFrame(frame), void 0);
|
|
5684
|
+
}
|
|
5593
5685
|
}
|
|
5594
5686
|
}
|
|
5595
5687
|
return admitted;
|
|
5596
5688
|
}
|
|
5597
5689
|
/**
|
|
5690
|
+
* Reserve all consumers of one lazy RGB frame before materializing pixels.
|
|
5691
|
+
* Detection follows the adaptive governor; shared motion keeps its independent
|
|
5692
|
+
* cadence so throttling inference cannot starve the analyzer.
|
|
5693
|
+
*/
|
|
5694
|
+
reserveSharedDetectionFrame(deviceId) {
|
|
5695
|
+
const detection = this.runner?.reserveDetectionFrame(deviceId) ?? false;
|
|
5696
|
+
const sink = this.sharedMotionSinks.get(deviceId);
|
|
5697
|
+
if (!sink) return {
|
|
5698
|
+
detection,
|
|
5699
|
+
motion: false
|
|
5700
|
+
};
|
|
5701
|
+
const now = this.sharedSinkNow();
|
|
5702
|
+
const motion = now - sink.lastMotionEnqueueTs >= sink.minIntervalMs;
|
|
5703
|
+
if (motion) sink.lastMotionEnqueueTs = now;
|
|
5704
|
+
return {
|
|
5705
|
+
detection,
|
|
5706
|
+
motion
|
|
5707
|
+
};
|
|
5708
|
+
}
|
|
5709
|
+
/**
|
|
5598
5710
|
* Epic C P1 detection path for a flagged camera: decode `rgb` in a
|
|
5599
5711
|
* per-session forked worker and feed the runner's detection queue INLINE (no
|
|
5600
5712
|
* `FrameHandle`), exactly like the shared shm path. Teardown stops the pump
|
|
@@ -5647,6 +5759,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5647
5759
|
streamId: config.detectionStreamId,
|
|
5648
5760
|
codec: source.codec
|
|
5649
5761
|
} });
|
|
5762
|
+
let reservation = null;
|
|
5650
5763
|
return startSessionDecodePump({
|
|
5651
5764
|
fork: this.forkDecodeWorkerWithLease,
|
|
5652
5765
|
logger: log,
|
|
@@ -5655,12 +5768,19 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5655
5768
|
fps: config.detectionFps,
|
|
5656
5769
|
format: "rgb"
|
|
5657
5770
|
},
|
|
5771
|
+
shouldMaterializeFrame: () => {
|
|
5772
|
+
reservation = this.reserveSharedDetectionFrame(config.deviceId);
|
|
5773
|
+
return reservation.detection || reservation.motion;
|
|
5774
|
+
},
|
|
5658
5775
|
acquireSpawnSlot: this.buildDecodeSpawnAcquire(log, "detect"),
|
|
5659
5776
|
onEnded: () => this.handleDetectionDecodeEnded(config.deviceId),
|
|
5660
5777
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
5661
|
-
const
|
|
5778
|
+
const current = reservation;
|
|
5779
|
+
reservation = null;
|
|
5780
|
+
if (!current) return;
|
|
5781
|
+
const handle = current.detection ? retainedFrames?.write(config.deviceId, frame) ?? void 0 : void 0;
|
|
5662
5782
|
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
5663
|
-
if (this.enqueueSharedDetectionFrame(config.deviceId, frame, handle)) nativeCrop?.markInferred();
|
|
5783
|
+
if (this.enqueueSharedDetectionFrame(config.deviceId, frame, handle, current)) nativeCrop?.markInferred();
|
|
5664
5784
|
}
|
|
5665
5785
|
});
|
|
5666
5786
|
}
|