@camstack/addon-pipeline 1.1.27 → 1.1.28
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 +7 -6
- package/dist/audio-analyzer/index.mjs +7 -6
- package/dist/audio-codec-ffmpeg/index.js +1 -1
- package/dist/audio-codec-ffmpeg/index.mjs +1 -1
- package/dist/decoder-ffmpeg/index.js +43 -11
- package/dist/decoder-ffmpeg/index.mjs +43 -11
- package/dist/decoder-nodeav/index.js +13 -8
- package/dist/decoder-nodeav/index.mjs +13 -8
- package/dist/detection-pipeline/index.js +157 -219
- package/dist/detection-pipeline/index.mjs +157 -219
- package/dist/{dist-DAIlCdAx.js → dist-Biq62zt4.js} +372 -35
- package/dist/{dist-CgEP_0OL.mjs → dist-C6_wgXqF.mjs} +373 -24
- package/dist/{frame-handle-plane-Dq20KtKL.mjs → frame-handle-plane-BIoY6nRV.mjs} +13 -5
- package/dist/{frame-handle-plane-DtTRX_0n.js → frame-handle-plane-D6BzyEgy.js} +13 -5
- package/dist/{frame-ring-sink-BbzrqTWj.js → frame-ring-sink-9J0wCdLF.js} +128 -23
- package/dist/{frame-ring-sink-B_NvPTJZ.mjs → frame-ring-sink-Cs9vby6v.mjs} +117 -24
- package/dist/motion-wasm/index.js +55 -3
- package/dist/motion-wasm/index.mjs +55 -3
- package/dist/node-topology-platform-BkR_k6WT.mjs +15 -0
- package/dist/node-topology-platform-CFZ7F4xW.js +20 -0
- package/dist/pipeline-runner/index.js +50 -16
- package/dist/pipeline-runner/index.mjs +50 -16
- package/dist/recorder/index.js +36 -40
- package/dist/recorder/index.mjs +36 -40
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-5tQlh9h4.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DoReAb4y.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DhY3MZ2C.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-BJK0-svt.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtoYGpTp.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DyLqyJaS.mjs → hostInit-C0SuwQhL.mjs} +3 -3
- package/dist/stream-broker/index.js +61 -25
- package/dist/stream-broker/index.mjs +61 -25
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-BDLNwJ_F.js → MaskShapeCanvas-DI4BY7W2-Br2yi8ah.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CoLjNiUN.js → MotionZonesSettings-NcxxQN8r-BSCcPdPf.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-DJE3OU-q.js → PrivacyMaskSettings-APgPLF7p-ztxvxSjS.js} +1 -1
- package/embed-dist/assets/index-C5UpuPr8.css +2 -0
- package/embed-dist/assets/{index-C-pL8ETk.js → index-CeZV1B-2.js} +10 -10
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/python/inference_pool.py +93 -102
- package/python/test_inference_pool_device_selection.py +43 -38
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CkOPfV8r.mjs +0 -26
- package/embed-dist/assets/index-DrJ0ee3f.css +0 -2
|
@@ -1,30 +1,40 @@
|
|
|
1
|
+
const require_dist = require("./dist-Biq62zt4.js");
|
|
1
2
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
2
|
-
//#region src/shared/decoder-backend.ts
|
|
3
|
-
/**
|
|
4
|
-
var DECODER_BACKEND_SECTION = "decoder";
|
|
5
|
-
/** Cluster-wide default when nothing is configured — the subprocess decoder. */
|
|
3
|
+
//#region src/shared/decoder-backend-keys.ts
|
|
4
|
+
/** Built-in default when a node has no selection — the subprocess decoder. */
|
|
6
5
|
var DEFAULT_DECODER_BACKEND = "ffmpeg";
|
|
7
6
|
/** Narrow an unknown settings value to a {@link DecoderBackend}, else `null`. */
|
|
8
7
|
function parseDecoderBackend(value) {
|
|
9
8
|
return value === "ffmpeg" || value === "nodeav" ? value : null;
|
|
10
9
|
}
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
12
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the decoder selection is
|
|
13
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
14
|
+
*/
|
|
15
|
+
function normalizeDecoderNodeId(rawNodeId) {
|
|
16
|
+
const raw = rawNodeId ?? "hub";
|
|
17
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/shared/decoder-backend.ts
|
|
21
|
+
/** The addon whose global settings OWN the per-node `backend` field. */
|
|
22
|
+
var DECODER_OWNER_ADDON_ID = "decoder-ffmpeg";
|
|
23
|
+
function isHydratedField(entry) {
|
|
24
|
+
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
14
25
|
}
|
|
15
26
|
/**
|
|
16
|
-
* Pure selection from an already-read
|
|
17
|
-
*
|
|
18
|
-
*
|
|
27
|
+
* Pure selection from an already-read hydrated settings payload: extract the
|
|
28
|
+
* owner's `backend` field value (which the owner projected per-node from its
|
|
29
|
+
* scoped store key) and narrow it. A missing/invalid field or a null payload
|
|
30
|
+
* resolves to {@link DEFAULT_DECODER_BACKEND} — never a bare store key.
|
|
19
31
|
*/
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
function pickDecoderBackendFromSettings(view) {
|
|
33
|
+
if (view === null) return DEFAULT_DECODER_BACKEND;
|
|
34
|
+
for (const section of view.sections) for (const entry of section.fields) {
|
|
35
|
+
if (!isHydratedField(entry) || entry.key !== "backend") continue;
|
|
36
|
+
return parseDecoderBackend(entry.value) ?? "ffmpeg";
|
|
25
37
|
}
|
|
26
|
-
const clusterWide = parseDecoderBackend(section["backend"]);
|
|
27
|
-
if (clusterWide !== null) return clusterWide;
|
|
28
38
|
return DEFAULT_DECODER_BACKEND;
|
|
29
39
|
}
|
|
30
40
|
/**
|
|
@@ -32,14 +42,28 @@ function pickDecoderBackend(section, nodeId) {
|
|
|
32
42
|
* addons at the start of `onInitialize`; the addon whose backend is NOT
|
|
33
43
|
* selected registers nothing.
|
|
34
44
|
*
|
|
35
|
-
* Fails safe: no
|
|
36
|
-
* {@link DEFAULT_DECODER_BACKEND} — the default
|
|
37
|
-
* and the non-default one stands down (never both).
|
|
45
|
+
* Fails safe: no api surface, a read error, or a null payload (owner not
|
|
46
|
+
* answering yet) resolves to {@link DEFAULT_DECODER_BACKEND} — the default
|
|
47
|
+
* backend still registers, and the non-default one stands down (never both).
|
|
38
48
|
*/
|
|
39
|
-
async function resolveDecoderBackend(
|
|
40
|
-
if (!
|
|
49
|
+
async function resolveDecoderBackend(api, nodeId, logger) {
|
|
50
|
+
if (!api) {
|
|
51
|
+
logger.warn("decoder-backend: no api surface — using default backend", { meta: { default: DEFAULT_DECODER_BACKEND } });
|
|
52
|
+
return DEFAULT_DECODER_BACKEND;
|
|
53
|
+
}
|
|
41
54
|
try {
|
|
42
|
-
|
|
55
|
+
const view = await api.addonSettings.getGlobalSettings.query({
|
|
56
|
+
addonId: DECODER_OWNER_ADDON_ID,
|
|
57
|
+
nodeId: normalizeDecoderNodeId(nodeId)
|
|
58
|
+
});
|
|
59
|
+
if (view === null) {
|
|
60
|
+
logger.warn("decoder-backend: owner settings unavailable — using default backend", { meta: {
|
|
61
|
+
default: DEFAULT_DECODER_BACKEND,
|
|
62
|
+
owner: DECODER_OWNER_ADDON_ID
|
|
63
|
+
} });
|
|
64
|
+
return DEFAULT_DECODER_BACKEND;
|
|
65
|
+
}
|
|
66
|
+
return pickDecoderBackendFromSettings(view);
|
|
43
67
|
} catch (err) {
|
|
44
68
|
logger.warn("decoder-backend: settings read failed — using default backend", { meta: {
|
|
45
69
|
default: DEFAULT_DECODER_BACKEND,
|
|
@@ -49,6 +73,75 @@ async function resolveDecoderBackend(settings, nodeId, logger) {
|
|
|
49
73
|
}
|
|
50
74
|
}
|
|
51
75
|
//#endregion
|
|
76
|
+
//#region src/shared/notifying-ring-buffer.ts
|
|
77
|
+
/**
|
|
78
|
+
* A {@link RingBuffer} that can notify a single blocked consumer the moment an
|
|
79
|
+
* item commits — the server side of the `pullFrames`/`pullHandles` long-poll.
|
|
80
|
+
*
|
|
81
|
+
* The decoder's `onFrame`/`onFrameHandle` callback drives `push()`; a consumer
|
|
82
|
+
* that finds the ring momentarily empty calls {@link waitForItem} and is woken
|
|
83
|
+
* on the very next `push()` (a real commit signal) rather than spinning at
|
|
84
|
+
* ~1kHz on `setTimeout(…,1)`. If nothing commits it resolves after `waitMs`,
|
|
85
|
+
* and {@link cancel} (session teardown) resolves any in-flight wait promptly so
|
|
86
|
+
* a blocked long-poll never waits out `waitMs` on destroy.
|
|
87
|
+
*
|
|
88
|
+
* Semantics are otherwise identical to `RingBuffer`: fixed capacity,
|
|
89
|
+
* latest-wins overwrite when full, FIFO `drain`.
|
|
90
|
+
*/
|
|
91
|
+
var NotifyingRingBuffer = class {
|
|
92
|
+
ring;
|
|
93
|
+
waiters = /* @__PURE__ */ new Set();
|
|
94
|
+
constructor(capacity) {
|
|
95
|
+
this.ring = new require_dist.RingBuffer(capacity);
|
|
96
|
+
}
|
|
97
|
+
get size() {
|
|
98
|
+
return this.ring.size;
|
|
99
|
+
}
|
|
100
|
+
push(item) {
|
|
101
|
+
this.ring.push(item);
|
|
102
|
+
this.wakeAll();
|
|
103
|
+
}
|
|
104
|
+
drain(maxCount) {
|
|
105
|
+
return this.ring.drain(maxCount);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve as soon as an item commits to the ring, or after `waitMs`,
|
|
109
|
+
* whichever comes first. Returns immediately when items are already present
|
|
110
|
+
* or `waitMs <= 0` (legacy immediate-return). {@link cancel} also resolves an
|
|
111
|
+
* in-flight wait.
|
|
112
|
+
*/
|
|
113
|
+
async waitForItem(waitMs) {
|
|
114
|
+
if (this.ring.size > 0 || waitMs <= 0) return;
|
|
115
|
+
await new Promise((resolve) => {
|
|
116
|
+
let settled = false;
|
|
117
|
+
const finish = () => {
|
|
118
|
+
if (settled) return;
|
|
119
|
+
settled = true;
|
|
120
|
+
this.waiters.delete(wake);
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
const wake = () => finish();
|
|
125
|
+
const timer = setTimeout(finish, waitMs);
|
|
126
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
127
|
+
this.waiters.add(wake);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Cancel every in-flight {@link waitForItem} — called on session teardown so
|
|
132
|
+
* a blocked long-poll returns promptly instead of waiting out `waitMs`.
|
|
133
|
+
*/
|
|
134
|
+
cancel() {
|
|
135
|
+
this.wakeAll();
|
|
136
|
+
}
|
|
137
|
+
wakeAll() {
|
|
138
|
+
if (this.waiters.size === 0) return;
|
|
139
|
+
const pending = [...this.waiters];
|
|
140
|
+
this.waiters.clear();
|
|
141
|
+
for (const wake of pending) wake();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
52
145
|
//#region src/decoder-ffmpeg/frame-ring-sink.ts
|
|
53
146
|
/**
|
|
54
147
|
* `DecoderFrameRingSink` — the decoder's shared-memory write side (Phase 5 / D9).
|
|
@@ -313,12 +406,24 @@ var DecoderFrameRingSink = class {
|
|
|
313
406
|
}
|
|
314
407
|
};
|
|
315
408
|
//#endregion
|
|
409
|
+
Object.defineProperty(exports, "DEFAULT_DECODER_BACKEND", {
|
|
410
|
+
enumerable: true,
|
|
411
|
+
get: function() {
|
|
412
|
+
return DEFAULT_DECODER_BACKEND;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
316
415
|
Object.defineProperty(exports, "DecoderFrameRingSink", {
|
|
317
416
|
enumerable: true,
|
|
318
417
|
get: function() {
|
|
319
418
|
return DecoderFrameRingSink;
|
|
320
419
|
}
|
|
321
420
|
});
|
|
421
|
+
Object.defineProperty(exports, "NotifyingRingBuffer", {
|
|
422
|
+
enumerable: true,
|
|
423
|
+
get: function() {
|
|
424
|
+
return NotifyingRingBuffer;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
322
427
|
Object.defineProperty(exports, "RING_BUDGET_MB", {
|
|
323
428
|
enumerable: true,
|
|
324
429
|
get: function() {
|
|
@@ -1,30 +1,40 @@
|
|
|
1
|
+
import { p as RingBuffer } from "./dist-C6_wgXqF.mjs";
|
|
1
2
|
import { FrameRingWriter, MIN_RING_SLOTS, computeSegmentSize, computeSlotByteLength, createSegment, deriveSlotCount } from "@camstack/shm-ring";
|
|
2
|
-
//#region src/shared/decoder-backend.ts
|
|
3
|
-
/**
|
|
4
|
-
var DECODER_BACKEND_SECTION = "decoder";
|
|
5
|
-
/** Cluster-wide default when nothing is configured — the subprocess decoder. */
|
|
3
|
+
//#region src/shared/decoder-backend-keys.ts
|
|
4
|
+
/** Built-in default when a node has no selection — the subprocess decoder. */
|
|
6
5
|
var DEFAULT_DECODER_BACKEND = "ffmpeg";
|
|
7
6
|
/** Narrow an unknown settings value to a {@link DecoderBackend}, else `null`. */
|
|
8
7
|
function parseDecoderBackend(value) {
|
|
9
8
|
return value === "ffmpeg" || value === "nodeav" ? value : null;
|
|
10
9
|
}
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
12
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the decoder selection is
|
|
13
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
14
|
+
*/
|
|
15
|
+
function normalizeDecoderNodeId(rawNodeId) {
|
|
16
|
+
const raw = rawNodeId ?? "hub";
|
|
17
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/shared/decoder-backend.ts
|
|
21
|
+
/** The addon whose global settings OWN the per-node `backend` field. */
|
|
22
|
+
var DECODER_OWNER_ADDON_ID = "decoder-ffmpeg";
|
|
23
|
+
function isHydratedField(entry) {
|
|
24
|
+
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
14
25
|
}
|
|
15
26
|
/**
|
|
16
|
-
* Pure selection from an already-read
|
|
17
|
-
*
|
|
18
|
-
*
|
|
27
|
+
* Pure selection from an already-read hydrated settings payload: extract the
|
|
28
|
+
* owner's `backend` field value (which the owner projected per-node from its
|
|
29
|
+
* scoped store key) and narrow it. A missing/invalid field or a null payload
|
|
30
|
+
* resolves to {@link DEFAULT_DECODER_BACKEND} — never a bare store key.
|
|
19
31
|
*/
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
function pickDecoderBackendFromSettings(view) {
|
|
33
|
+
if (view === null) return DEFAULT_DECODER_BACKEND;
|
|
34
|
+
for (const section of view.sections) for (const entry of section.fields) {
|
|
35
|
+
if (!isHydratedField(entry) || entry.key !== "backend") continue;
|
|
36
|
+
return parseDecoderBackend(entry.value) ?? "ffmpeg";
|
|
25
37
|
}
|
|
26
|
-
const clusterWide = parseDecoderBackend(section["backend"]);
|
|
27
|
-
if (clusterWide !== null) return clusterWide;
|
|
28
38
|
return DEFAULT_DECODER_BACKEND;
|
|
29
39
|
}
|
|
30
40
|
/**
|
|
@@ -32,14 +42,28 @@ function pickDecoderBackend(section, nodeId) {
|
|
|
32
42
|
* addons at the start of `onInitialize`; the addon whose backend is NOT
|
|
33
43
|
* selected registers nothing.
|
|
34
44
|
*
|
|
35
|
-
* Fails safe: no
|
|
36
|
-
* {@link DEFAULT_DECODER_BACKEND} — the default
|
|
37
|
-
* and the non-default one stands down (never both).
|
|
45
|
+
* Fails safe: no api surface, a read error, or a null payload (owner not
|
|
46
|
+
* answering yet) resolves to {@link DEFAULT_DECODER_BACKEND} — the default
|
|
47
|
+
* backend still registers, and the non-default one stands down (never both).
|
|
38
48
|
*/
|
|
39
|
-
async function resolveDecoderBackend(
|
|
40
|
-
if (!
|
|
49
|
+
async function resolveDecoderBackend(api, nodeId, logger) {
|
|
50
|
+
if (!api) {
|
|
51
|
+
logger.warn("decoder-backend: no api surface — using default backend", { meta: { default: DEFAULT_DECODER_BACKEND } });
|
|
52
|
+
return DEFAULT_DECODER_BACKEND;
|
|
53
|
+
}
|
|
41
54
|
try {
|
|
42
|
-
|
|
55
|
+
const view = await api.addonSettings.getGlobalSettings.query({
|
|
56
|
+
addonId: DECODER_OWNER_ADDON_ID,
|
|
57
|
+
nodeId: normalizeDecoderNodeId(nodeId)
|
|
58
|
+
});
|
|
59
|
+
if (view === null) {
|
|
60
|
+
logger.warn("decoder-backend: owner settings unavailable — using default backend", { meta: {
|
|
61
|
+
default: DEFAULT_DECODER_BACKEND,
|
|
62
|
+
owner: DECODER_OWNER_ADDON_ID
|
|
63
|
+
} });
|
|
64
|
+
return DEFAULT_DECODER_BACKEND;
|
|
65
|
+
}
|
|
66
|
+
return pickDecoderBackendFromSettings(view);
|
|
43
67
|
} catch (err) {
|
|
44
68
|
logger.warn("decoder-backend: settings read failed — using default backend", { meta: {
|
|
45
69
|
default: DEFAULT_DECODER_BACKEND,
|
|
@@ -49,6 +73,75 @@ async function resolveDecoderBackend(settings, nodeId, logger) {
|
|
|
49
73
|
}
|
|
50
74
|
}
|
|
51
75
|
//#endregion
|
|
76
|
+
//#region src/shared/notifying-ring-buffer.ts
|
|
77
|
+
/**
|
|
78
|
+
* A {@link RingBuffer} that can notify a single blocked consumer the moment an
|
|
79
|
+
* item commits — the server side of the `pullFrames`/`pullHandles` long-poll.
|
|
80
|
+
*
|
|
81
|
+
* The decoder's `onFrame`/`onFrameHandle` callback drives `push()`; a consumer
|
|
82
|
+
* that finds the ring momentarily empty calls {@link waitForItem} and is woken
|
|
83
|
+
* on the very next `push()` (a real commit signal) rather than spinning at
|
|
84
|
+
* ~1kHz on `setTimeout(…,1)`. If nothing commits it resolves after `waitMs`,
|
|
85
|
+
* and {@link cancel} (session teardown) resolves any in-flight wait promptly so
|
|
86
|
+
* a blocked long-poll never waits out `waitMs` on destroy.
|
|
87
|
+
*
|
|
88
|
+
* Semantics are otherwise identical to `RingBuffer`: fixed capacity,
|
|
89
|
+
* latest-wins overwrite when full, FIFO `drain`.
|
|
90
|
+
*/
|
|
91
|
+
var NotifyingRingBuffer = class {
|
|
92
|
+
ring;
|
|
93
|
+
waiters = /* @__PURE__ */ new Set();
|
|
94
|
+
constructor(capacity) {
|
|
95
|
+
this.ring = new RingBuffer(capacity);
|
|
96
|
+
}
|
|
97
|
+
get size() {
|
|
98
|
+
return this.ring.size;
|
|
99
|
+
}
|
|
100
|
+
push(item) {
|
|
101
|
+
this.ring.push(item);
|
|
102
|
+
this.wakeAll();
|
|
103
|
+
}
|
|
104
|
+
drain(maxCount) {
|
|
105
|
+
return this.ring.drain(maxCount);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve as soon as an item commits to the ring, or after `waitMs`,
|
|
109
|
+
* whichever comes first. Returns immediately when items are already present
|
|
110
|
+
* or `waitMs <= 0` (legacy immediate-return). {@link cancel} also resolves an
|
|
111
|
+
* in-flight wait.
|
|
112
|
+
*/
|
|
113
|
+
async waitForItem(waitMs) {
|
|
114
|
+
if (this.ring.size > 0 || waitMs <= 0) return;
|
|
115
|
+
await new Promise((resolve) => {
|
|
116
|
+
let settled = false;
|
|
117
|
+
const finish = () => {
|
|
118
|
+
if (settled) return;
|
|
119
|
+
settled = true;
|
|
120
|
+
this.waiters.delete(wake);
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
const wake = () => finish();
|
|
125
|
+
const timer = setTimeout(finish, waitMs);
|
|
126
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
127
|
+
this.waiters.add(wake);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Cancel every in-flight {@link waitForItem} — called on session teardown so
|
|
132
|
+
* a blocked long-poll returns promptly instead of waiting out `waitMs`.
|
|
133
|
+
*/
|
|
134
|
+
cancel() {
|
|
135
|
+
this.wakeAll();
|
|
136
|
+
}
|
|
137
|
+
wakeAll() {
|
|
138
|
+
if (this.waiters.size === 0) return;
|
|
139
|
+
const pending = [...this.waiters];
|
|
140
|
+
this.waiters.clear();
|
|
141
|
+
for (const wake of pending) wake();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
52
145
|
//#region src/decoder-ffmpeg/frame-ring-sink.ts
|
|
53
146
|
/**
|
|
54
147
|
* `DecoderFrameRingSink` — the decoder's shared-memory write side (Phase 5 / D9).
|
|
@@ -313,4 +406,4 @@ var DecoderFrameRingSink = class {
|
|
|
313
406
|
}
|
|
314
407
|
};
|
|
315
408
|
//#endregion
|
|
316
|
-
export { resolveDecoderBackend as i, RING_BUDGET_MB as n, makeSegmentName as r, DecoderFrameRingSink as t };
|
|
409
|
+
export { resolveDecoderBackend as a, NotifyingRingBuffer as i, RING_BUDGET_MB as n, DEFAULT_DECODER_BACKEND as o, makeSegmentName as r, DecoderFrameRingSink as t };
|
|
@@ -2,7 +2,8 @@ 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-Biq62zt4.js");
|
|
6
|
+
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
6
7
|
let node_fs = require("node:fs");
|
|
7
8
|
let node_path = require("node:path");
|
|
8
9
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -135,6 +136,13 @@ var DEFAULT_CONFIG = {
|
|
|
135
136
|
};
|
|
136
137
|
var MotionWasmAddon = class MotionWasmAddon extends require_dist.BaseAddon {
|
|
137
138
|
detector = null;
|
|
139
|
+
/**
|
|
140
|
+
* CB5 shm passthrough — process-local, long-lived reader cache for
|
|
141
|
+
* `analyze({ frameHandle })`. Opens each named shm segment once and reuses
|
|
142
|
+
* the mapping; lazily created on first LOCAL handle, unmapped in
|
|
143
|
+
* {@link onShutdown}. Never opened/closed per-call.
|
|
144
|
+
*/
|
|
145
|
+
frameHandleReaders = null;
|
|
138
146
|
cameras = /* @__PURE__ */ new Map();
|
|
139
147
|
deviceConfigCache = /* @__PURE__ */ new Map();
|
|
140
148
|
static DEVICE_CONFIG_TTL_MS = 6e4;
|
|
@@ -187,9 +195,51 @@ var MotionWasmAddon = class MotionWasmAddon extends require_dist.BaseAddon {
|
|
|
187
195
|
* motion-detection cap: analyze a frame, return regions + stats.
|
|
188
196
|
* Per-device state keyed by the numeric deviceId (stringified internally
|
|
189
197
|
* so the pipeline-step sentinel shares the same Map).
|
|
198
|
+
*
|
|
199
|
+
* CB5: pixels arrive either inline (`frame`) or as a zero-pixel shm
|
|
200
|
+
* `FrameHandle`. A LOCAL handle is read back zero-copy here; a foreign /
|
|
201
|
+
* recycled / unmappable handle yields a null read and an empty result (the
|
|
202
|
+
* runner ships `frame` as the fallback, queue-depth gated).
|
|
190
203
|
*/
|
|
191
|
-
async analyze({ deviceId, frame }) {
|
|
192
|
-
|
|
204
|
+
async analyze({ deviceId, frame, frameHandle }) {
|
|
205
|
+
const resolved = frame ?? (frameHandle ? this.resolveFrameHandle(frameHandle) : null);
|
|
206
|
+
if (!resolved) return this.emptyMotionResult(frameHandle);
|
|
207
|
+
return this.analyzeInternal(String(deviceId), resolved);
|
|
208
|
+
}
|
|
209
|
+
/** This node's cluster id (suffix-stripped), for local-vs-foreign gating. */
|
|
210
|
+
ownNodeId() {
|
|
211
|
+
const raw = this.ctx.kernel?.localNodeId ?? "hub";
|
|
212
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* CB5 — resolve a shm `FrameHandle` to gray pixels zero-copy. Returns null
|
|
216
|
+
* for a FOREIGN handle (segment on another node — the runner only sends
|
|
217
|
+
* local handles), a recycled ring slot, or an unmappable segment.
|
|
218
|
+
*/
|
|
219
|
+
resolveFrameHandle(handle) {
|
|
220
|
+
if (handle.nodeId !== this.ownNodeId()) return null;
|
|
221
|
+
if (!this.frameHandleReaders) this.frameHandleReaders = new _camstack_shm_ring.FrameRingReaderCache(this.ctx.logger);
|
|
222
|
+
const decoded = this.frameHandleReaders.read(handle);
|
|
223
|
+
if (!decoded) return null;
|
|
224
|
+
return {
|
|
225
|
+
data: decoded.data,
|
|
226
|
+
width: decoded.width,
|
|
227
|
+
height: decoded.height,
|
|
228
|
+
format: decoded.format,
|
|
229
|
+
timestamp: decoded.timestamp
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** Zeroed motion result for a missed `frameHandle` read (dropped frame). */
|
|
233
|
+
emptyMotionResult(handle) {
|
|
234
|
+
return {
|
|
235
|
+
detected: false,
|
|
236
|
+
regionCount: 0,
|
|
237
|
+
regions: [],
|
|
238
|
+
rawRegions: [],
|
|
239
|
+
frameWidth: handle?.width ?? 0,
|
|
240
|
+
frameHeight: handle?.height ?? 0,
|
|
241
|
+
analysisMs: 0
|
|
242
|
+
};
|
|
193
243
|
}
|
|
194
244
|
async analyzeInternal(cameraId, frame) {
|
|
195
245
|
if (!this.detector) return {
|
|
@@ -351,6 +401,8 @@ var MotionWasmAddon = class MotionWasmAddon extends require_dist.BaseAddon {
|
|
|
351
401
|
this.deviceConfigCache.clear();
|
|
352
402
|
for (const id of this.proxies.keys()) this.releaseProxy(id);
|
|
353
403
|
this.detector = null;
|
|
404
|
+
this.frameHandleReaders?.close();
|
|
405
|
+
this.frameHandleReaders = null;
|
|
354
406
|
}
|
|
355
407
|
deviceSettingsSchema() {
|
|
356
408
|
return this.schema({ sections: [{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { A as motionDetectionCapability, T as evaluateZoneRules, V as BaseAddon, W as DeviceType,
|
|
1
|
+
import { A as motionDetectionCapability, T as evaluateZoneRules, V as BaseAddon, W as DeviceType, q as hydrateSchema } from "../dist-C6_wgXqF.mjs";
|
|
2
|
+
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
2
3
|
import { readFileSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -131,6 +132,13 @@ var DEFAULT_CONFIG = {
|
|
|
131
132
|
};
|
|
132
133
|
var MotionWasmAddon = class MotionWasmAddon extends BaseAddon {
|
|
133
134
|
detector = null;
|
|
135
|
+
/**
|
|
136
|
+
* CB5 shm passthrough — process-local, long-lived reader cache for
|
|
137
|
+
* `analyze({ frameHandle })`. Opens each named shm segment once and reuses
|
|
138
|
+
* the mapping; lazily created on first LOCAL handle, unmapped in
|
|
139
|
+
* {@link onShutdown}. Never opened/closed per-call.
|
|
140
|
+
*/
|
|
141
|
+
frameHandleReaders = null;
|
|
134
142
|
cameras = /* @__PURE__ */ new Map();
|
|
135
143
|
deviceConfigCache = /* @__PURE__ */ new Map();
|
|
136
144
|
static DEVICE_CONFIG_TTL_MS = 6e4;
|
|
@@ -183,9 +191,51 @@ var MotionWasmAddon = class MotionWasmAddon extends BaseAddon {
|
|
|
183
191
|
* motion-detection cap: analyze a frame, return regions + stats.
|
|
184
192
|
* Per-device state keyed by the numeric deviceId (stringified internally
|
|
185
193
|
* so the pipeline-step sentinel shares the same Map).
|
|
194
|
+
*
|
|
195
|
+
* CB5: pixels arrive either inline (`frame`) or as a zero-pixel shm
|
|
196
|
+
* `FrameHandle`. A LOCAL handle is read back zero-copy here; a foreign /
|
|
197
|
+
* recycled / unmappable handle yields a null read and an empty result (the
|
|
198
|
+
* runner ships `frame` as the fallback, queue-depth gated).
|
|
186
199
|
*/
|
|
187
|
-
async analyze({ deviceId, frame }) {
|
|
188
|
-
|
|
200
|
+
async analyze({ deviceId, frame, frameHandle }) {
|
|
201
|
+
const resolved = frame ?? (frameHandle ? this.resolveFrameHandle(frameHandle) : null);
|
|
202
|
+
if (!resolved) return this.emptyMotionResult(frameHandle);
|
|
203
|
+
return this.analyzeInternal(String(deviceId), resolved);
|
|
204
|
+
}
|
|
205
|
+
/** This node's cluster id (suffix-stripped), for local-vs-foreign gating. */
|
|
206
|
+
ownNodeId() {
|
|
207
|
+
const raw = this.ctx.kernel?.localNodeId ?? "hub";
|
|
208
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* CB5 — resolve a shm `FrameHandle` to gray pixels zero-copy. Returns null
|
|
212
|
+
* for a FOREIGN handle (segment on another node — the runner only sends
|
|
213
|
+
* local handles), a recycled ring slot, or an unmappable segment.
|
|
214
|
+
*/
|
|
215
|
+
resolveFrameHandle(handle) {
|
|
216
|
+
if (handle.nodeId !== this.ownNodeId()) return null;
|
|
217
|
+
if (!this.frameHandleReaders) this.frameHandleReaders = new FrameRingReaderCache(this.ctx.logger);
|
|
218
|
+
const decoded = this.frameHandleReaders.read(handle);
|
|
219
|
+
if (!decoded) return null;
|
|
220
|
+
return {
|
|
221
|
+
data: decoded.data,
|
|
222
|
+
width: decoded.width,
|
|
223
|
+
height: decoded.height,
|
|
224
|
+
format: decoded.format,
|
|
225
|
+
timestamp: decoded.timestamp
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** Zeroed motion result for a missed `frameHandle` read (dropped frame). */
|
|
229
|
+
emptyMotionResult(handle) {
|
|
230
|
+
return {
|
|
231
|
+
detected: false,
|
|
232
|
+
regionCount: 0,
|
|
233
|
+
regions: [],
|
|
234
|
+
rawRegions: [],
|
|
235
|
+
frameWidth: handle?.width ?? 0,
|
|
236
|
+
frameHeight: handle?.height ?? 0,
|
|
237
|
+
analysisMs: 0
|
|
238
|
+
};
|
|
189
239
|
}
|
|
190
240
|
async analyzeInternal(cameraId, frame) {
|
|
191
241
|
if (!this.detector) return {
|
|
@@ -347,6 +397,8 @@ var MotionWasmAddon = class MotionWasmAddon extends BaseAddon {
|
|
|
347
397
|
this.deviceConfigCache.clear();
|
|
348
398
|
for (const id of this.proxies.keys()) this.releaseProxy(id);
|
|
349
399
|
this.detector = null;
|
|
400
|
+
this.frameHandleReaders?.close();
|
|
401
|
+
this.frameHandleReaders = null;
|
|
350
402
|
}
|
|
351
403
|
deviceSettingsSchema() {
|
|
352
404
|
return this.schema({ sections: [{
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/shared/node-topology-platform.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pick a node's `{ platform, arch }` from the cluster topology. Returns an empty
|
|
4
|
+
* object when the topology is missing or the node is absent (caller falls back).
|
|
5
|
+
*/
|
|
6
|
+
function pickNodePlatformArch(topology, nodeId) {
|
|
7
|
+
const entry = topology?.find((n) => n.id === nodeId);
|
|
8
|
+
if (!entry) return {};
|
|
9
|
+
return {
|
|
10
|
+
platform: entry.platform,
|
|
11
|
+
arch: entry.arch
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { pickNodePlatformArch as t };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/shared/node-topology-platform.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pick a node's `{ platform, arch }` from the cluster topology. Returns an empty
|
|
4
|
+
* object when the topology is missing or the node is absent (caller falls back).
|
|
5
|
+
*/
|
|
6
|
+
function pickNodePlatformArch(topology, nodeId) {
|
|
7
|
+
const entry = topology?.find((n) => n.id === nodeId);
|
|
8
|
+
if (!entry) return {};
|
|
9
|
+
return {
|
|
10
|
+
platform: entry.platform,
|
|
11
|
+
arch: entry.arch
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
Object.defineProperty(exports, "pickNodePlatformArch", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function() {
|
|
18
|
+
return pickNodePlatformArch;
|
|
19
|
+
}
|
|
20
|
+
});
|