@camstack/addon-pipeline 1.1.42 → 1.1.44
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 +5 -4
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/chunk-D6vf50IK.js +28 -0
- package/dist/detection-pipeline/index.js +535 -115
- package/dist/detection-pipeline/index.mjs +529 -110
- package/dist/{dist-GFd8M6KO.mjs → dist-CgBGV988.mjs} +214 -186
- package/dist/{dist-NvwN60Fq.js → dist-CySHUkXY.js} +219 -185
- package/dist/{model-download-service-C-IHWnXx-DxM2DSns.js → model-download-service-C-IHWnXx-BLXzxpRB.js} +3 -29
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +122 -287
- package/dist/pipeline-runner/index.mjs +118 -285
- package/dist/recorder/index.js +78 -31
- package/dist/recorder/index.mjs +75 -29
- package/dist/{frame-handle-plane-E_AsR77T.mjs → remote-source-plane-B4hRwchu.mjs} +219 -2
- package/dist/{frame-handle-plane-B3Fxww8H.js → remote-source-plane-D9m0z1N9.js} +236 -1
- package/dist/session-decode/decode-worker-child.js +118 -12
- package/dist/session-decode/decode-worker-child.mjs +115 -12
- package/dist/stream-broker/_stub.js +550 -523
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CeielP5P.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-G6wtUb3G.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dlagq9X_.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-BUpF966l.mjs +26 -0
- package/dist/stream-broker/{hostInit-DmjqskOR.mjs → hostInit-BSzP3BIj.mjs} +2 -2
- package/dist/stream-broker/index.js +840 -196
- package/dist/stream-broker/index.mjs +831 -188
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CEsPjwzT.js → MaskShapeCanvas-DI4BY7W2-D2ML5TPi.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-D43fIRgB.js → MotionZonesSettings-NcxxQN8r-TTWGGaqv.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-C3wJxG8V.js → PrivacyMaskSettings-APgPLF7p-CJqHU2nU.js} +1 -1
- package/embed-dist/assets/index-C1oaL9s7.js +81 -0
- package/embed-dist/assets/{index-CSFtK41z.css → index-CA4kv43T.css} +1 -1
- package/embed-dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DqJvWBKS.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-dGka8yZY.mjs +0 -26
- package/embed-dist/assets/index-CX-rILhw.js +0 -81
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
-
key = keys[i];
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
-
get: ((k) => from[k]).bind(null, key),
|
|
13
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
-
value: mod,
|
|
20
|
-
enumerable: true
|
|
21
|
-
}) : target, mod));
|
|
1
|
+
const require_chunk = require("./chunk-D6vf50IK.js");
|
|
22
2
|
require("node:crypto");
|
|
23
3
|
let node_fs = require("node:fs");
|
|
24
|
-
node_fs = __toESM(node_fs, 1);
|
|
4
|
+
node_fs = require_chunk.__toESM(node_fs, 1);
|
|
25
5
|
let node_path = require("node:path");
|
|
26
|
-
node_path = __toESM(node_path, 1);
|
|
6
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
27
7
|
//#region ../system/dist/model-download-service-C-IHWnXx.mjs
|
|
28
8
|
/**
|
|
29
9
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
@@ -349,12 +329,6 @@ function deleteModelFromDisk(modelsDir, entry, format) {
|
|
|
349
329
|
return true;
|
|
350
330
|
}
|
|
351
331
|
//#endregion
|
|
352
|
-
Object.defineProperty(exports, "__toESM", {
|
|
353
|
-
enumerable: true,
|
|
354
|
-
get: function() {
|
|
355
|
-
return __toESM;
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
332
|
Object.defineProperty(exports, "createFileDataPlaneHandler", {
|
|
359
333
|
enumerable: true,
|
|
360
334
|
get: function() {
|
|
@@ -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-CySHUkXY.js");
|
|
6
6
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as DeviceType, E as motionDetectionCapability, L as BaseAddon, U as hydrateSchema, x as evaluateZoneRules } from "../dist-
|
|
1
|
+
import { B as DeviceType, E as motionDetectionCapability, L as BaseAddon, U as hydrateSchema, x as evaluateZoneRules } from "../dist-CgBGV988.mjs";
|
|
2
2
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const
|
|
5
|
+
const require_dist = require("../dist-CySHUkXY.js");
|
|
6
|
+
const require_remote_source_plane = require("../remote-source-plane-D9m0z1N9.js");
|
|
7
7
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
8
8
|
const require_worker_protocol = require("../worker-protocol-BCfO8gUF.js");
|
|
9
9
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
@@ -426,221 +426,6 @@ function startPolling(options, source, subscriptionId, resolvedMaxFps, lifecycle
|
|
|
426
426
|
};
|
|
427
427
|
}
|
|
428
428
|
//#endregion
|
|
429
|
-
//#region src/pipeline-runner/remote-source-plane.ts
|
|
430
|
-
/**
|
|
431
|
-
* Runner-side mode selection (pure): whether an attach payload routes this
|
|
432
|
-
* camera through the remote-source leg. Absent `frameSource` (a pre-P2c
|
|
433
|
-
* payload) and `local-broker` both take today's co-located broker path —
|
|
434
|
-
* the rollout-OFF safety invariant.
|
|
435
|
-
*/
|
|
436
|
-
function isRemoteRestream(frameSource) {
|
|
437
|
-
return frameSource !== void 0 && frameSource.kind === "remote-restream";
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Resolve WHICH broker profile a camStream id feeds for a device (pure).
|
|
441
|
-
*
|
|
442
|
-
* `getStreamWithCodec` targets a PROFILE (its assigned camStream), while the
|
|
443
|
-
* runner is dispatched with camStream ids (`motionStreamId` /
|
|
444
|
-
* `detectionStreamId`). The broker's profile slots carry the mapping
|
|
445
|
-
* (`sourceCamStreamId`); a stream no slot feeds returns `null` — the acquire
|
|
446
|
-
* fails fast and the poller's backoff retries (the assignment may still be
|
|
447
|
-
* propagating).
|
|
448
|
-
*/
|
|
449
|
-
function profileForStreamId(slots, deviceId, streamId) {
|
|
450
|
-
for (const slot of slots) if (slot.deviceId === deviceId && slot.sourceCamStreamId === streamId) return slot.profile;
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
var RemoteSourcePlane = class {
|
|
454
|
-
api;
|
|
455
|
-
logger;
|
|
456
|
-
deviceId;
|
|
457
|
-
ownNodeId;
|
|
458
|
-
hostname;
|
|
459
|
-
/** Single-flight entry creation per camStream id. */
|
|
460
|
-
entries = /* @__PURE__ */ new Map();
|
|
461
|
-
disposed = false;
|
|
462
|
-
constructor(options) {
|
|
463
|
-
this.api = options.api;
|
|
464
|
-
this.logger = options.logger.withTags({ deviceId: options.deviceId });
|
|
465
|
-
this.deviceId = options.deviceId;
|
|
466
|
-
this.ownNodeId = options.ownNodeId;
|
|
467
|
-
this.hostname = options.hostname;
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* A {@link FrameHandleSource} for one camStream id, pluggable into
|
|
471
|
-
* `startFrameHandlePoller`. Motion (`gray`) and detection (`rgb`)
|
|
472
|
-
* subscriptions of the same stream share ONE acquired restream and ONE
|
|
473
|
-
* satellite plane (per-format decoder sessions inside it — hub parity).
|
|
474
|
-
*/
|
|
475
|
-
makeSource(streamId) {
|
|
476
|
-
return {
|
|
477
|
-
subscribe: async (input) => {
|
|
478
|
-
if (this.disposed) throw new Error("RemoteSourcePlane: subscribe after dispose");
|
|
479
|
-
const entry = await this.ensureEntry(streamId);
|
|
480
|
-
const result = await entry.plane.subscribe({
|
|
481
|
-
format: input.format,
|
|
482
|
-
maxFps: input.maxFps,
|
|
483
|
-
tag: input.tag
|
|
484
|
-
});
|
|
485
|
-
entry.subscriptionIds.add(result.subscriptionId);
|
|
486
|
-
return result;
|
|
487
|
-
},
|
|
488
|
-
pullHandles: async (input) => {
|
|
489
|
-
const entry = await this.peekEntry(streamId);
|
|
490
|
-
if (!entry) return [];
|
|
491
|
-
return entry.plane.pullHandles(input.subscriptionId, input.maxCount);
|
|
492
|
-
},
|
|
493
|
-
unsubscribe: async (input) => {
|
|
494
|
-
const entry = await this.peekEntry(streamId);
|
|
495
|
-
if (!entry) return;
|
|
496
|
-
entry.subscriptionIds.delete(input.subscriptionId);
|
|
497
|
-
await entry.plane.unsubscribe(input.subscriptionId).catch(() => {});
|
|
498
|
-
if (entry.subscriptionIds.size === 0) {
|
|
499
|
-
this.entries.delete(streamId);
|
|
500
|
-
await this.teardownEntry(entry, streamId);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
/** Dispose every entry: satellite planes destroyed, restreams released. Idempotent. */
|
|
506
|
-
async dispose() {
|
|
507
|
-
if (this.disposed) return;
|
|
508
|
-
this.disposed = true;
|
|
509
|
-
const pending = [...this.entries.entries()];
|
|
510
|
-
this.entries.clear();
|
|
511
|
-
for (const [streamId, entryPromise] of pending) try {
|
|
512
|
-
const entry = await entryPromise;
|
|
513
|
-
await this.teardownEntry(entry, streamId);
|
|
514
|
-
} catch {}
|
|
515
|
-
}
|
|
516
|
-
/** The live entry for a stream, or `null` (never creates one). */
|
|
517
|
-
async peekEntry(streamId) {
|
|
518
|
-
const pending = this.entries.get(streamId);
|
|
519
|
-
if (!pending) return null;
|
|
520
|
-
try {
|
|
521
|
-
return await pending;
|
|
522
|
-
} catch {
|
|
523
|
-
return null;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
/** Single-flight: acquire the restream + build the satellite plane once per stream. */
|
|
527
|
-
ensureEntry(streamId) {
|
|
528
|
-
const existing = this.entries.get(streamId);
|
|
529
|
-
if (existing) return existing;
|
|
530
|
-
const creation = this.createEntry(streamId);
|
|
531
|
-
this.entries.set(streamId, creation);
|
|
532
|
-
creation.catch(() => {
|
|
533
|
-
if (this.entries.get(streamId) === creation) this.entries.delete(streamId);
|
|
534
|
-
});
|
|
535
|
-
return creation;
|
|
536
|
-
}
|
|
537
|
-
async createEntry(streamId) {
|
|
538
|
-
const { api, deviceId, ownNodeId } = this;
|
|
539
|
-
if (this.hostname === void 0) throw new Error("RemoteSourcePlane: owner hostname unresolvable — set frameSource.hubHostnameOverride or CAMSTACK_HUB_URL (a 127.0.0.1 restream URL would dial the wrong box)");
|
|
540
|
-
const profile = profileForStreamId(await api.streamBroker.listAllProfileSlots.query(), deviceId, streamId);
|
|
541
|
-
if (profile === null) throw new Error(`RemoteSourcePlane: no broker profile is fed by camStream '${streamId}' of device ${deviceId} — cannot acquire the owner restream`);
|
|
542
|
-
const rtpSource = await api.streamBroker.getStreamWithCodec.mutate({
|
|
543
|
-
deviceId,
|
|
544
|
-
video: "copy",
|
|
545
|
-
audio: "none",
|
|
546
|
-
profile,
|
|
547
|
-
hostname: this.hostname,
|
|
548
|
-
tag: `runner:${ownNodeId}:remote-src:${deviceId}/${streamId}`
|
|
549
|
-
});
|
|
550
|
-
const codec = rtpSource.videoCodec === "H265" ? "h265" : "h264";
|
|
551
|
-
const plane = new require_frame_handle_plane.FrameHandlePlane({
|
|
552
|
-
decoderApi: this.buildPinnedDecoderCapApi(),
|
|
553
|
-
logger: this.logger,
|
|
554
|
-
localNodeId: ownNodeId,
|
|
555
|
-
resolveStreamInfo: () => ({
|
|
556
|
-
codec,
|
|
557
|
-
numericDeviceId: deviceId,
|
|
558
|
-
tag: `runner-remote:${deviceId}/${streamId}`,
|
|
559
|
-
pullModeUrl: rtpSource.url
|
|
560
|
-
})
|
|
561
|
-
});
|
|
562
|
-
this.logger.info("remote-source plane: owner restream acquired", { meta: {
|
|
563
|
-
streamId,
|
|
564
|
-
profile,
|
|
565
|
-
codec,
|
|
566
|
-
pipelineKey: rtpSource.pipelineKey,
|
|
567
|
-
hostname: this.hostname
|
|
568
|
-
} });
|
|
569
|
-
return {
|
|
570
|
-
plane,
|
|
571
|
-
pipelineKey: rtpSource.pipelineKey,
|
|
572
|
-
subscriptionIds: /* @__PURE__ */ new Set(),
|
|
573
|
-
released: false
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
/** Destroy an entry's satellite plane and release its restream refcount once. */
|
|
577
|
-
async teardownEntry(entry, streamId) {
|
|
578
|
-
await entry.plane.dispose().catch((err) => {
|
|
579
|
-
this.logger.warn("remote-source plane: satellite plane dispose failed", { meta: {
|
|
580
|
-
streamId,
|
|
581
|
-
error: require_dist.errMsg(err)
|
|
582
|
-
} });
|
|
583
|
-
});
|
|
584
|
-
if (entry.released) return;
|
|
585
|
-
entry.released = true;
|
|
586
|
-
await this.api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey: entry.pipelineKey }).catch((err) => {
|
|
587
|
-
this.logger.debug("remote-source plane: releaseStreamWithCodec failed (benign on hub restart)", { meta: {
|
|
588
|
-
streamId,
|
|
589
|
-
pipelineKey: entry.pipelineKey,
|
|
590
|
-
error: require_dist.errMsg(err)
|
|
591
|
-
} });
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Every decoder call pinned to THIS runner's node — mirror of the hub
|
|
596
|
-
* broker's wrapper (`stream-broker-manager.buildDecoderCapApi`): the
|
|
597
|
-
* `decoder` cap is a singleton whose UNPINNED calls resolve to the
|
|
598
|
-
* cluster-elected node, which is generally NOT this agent. Pinning create
|
|
599
|
-
* AND every session op to the same local node keeps the session + its shm
|
|
600
|
-
* ring here, where this plane's readers live.
|
|
601
|
-
*/
|
|
602
|
-
buildPinnedDecoderCapApi() {
|
|
603
|
-
const api = this.api;
|
|
604
|
-
const pin = require_dist.nodePin(this.ownNodeId);
|
|
605
|
-
const toWireBytes = (bytes) => {
|
|
606
|
-
const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
|
|
607
|
-
copy.set(bytes);
|
|
608
|
-
return copy;
|
|
609
|
-
};
|
|
610
|
-
return {
|
|
611
|
-
supportsCodec: async (input) => api.decoder.supportsCodec.query({ ...input }, pin),
|
|
612
|
-
getInfo: async () => api.decoder.getInfo.query(void 0, pin),
|
|
613
|
-
createSession: async (config) => api.decoder.createSession.query({ ...config }, pin),
|
|
614
|
-
pushPacket: async (input) => {
|
|
615
|
-
await api.decoder.pushPacket.query({
|
|
616
|
-
sessionId: input.sessionId,
|
|
617
|
-
packet: {
|
|
618
|
-
...input.packet,
|
|
619
|
-
data: toWireBytes(input.packet.data)
|
|
620
|
-
}
|
|
621
|
-
}, pin);
|
|
622
|
-
},
|
|
623
|
-
pullFrames: async (input) => {
|
|
624
|
-
return (await api.decoder.pullFrames.query({ ...input }, pin)).map((frame) => ({
|
|
625
|
-
...frame,
|
|
626
|
-
data: Buffer.from(frame.data)
|
|
627
|
-
}));
|
|
628
|
-
},
|
|
629
|
-
pullHandles: async (input) => api.decoder.pullHandles.query({ ...input }, pin),
|
|
630
|
-
destroySession: async (input) => {
|
|
631
|
-
await api.decoder.destroySession.query({ ...input }, pin);
|
|
632
|
-
},
|
|
633
|
-
openStream: async (input) => {
|
|
634
|
-
await api.decoder.openStream.query({ ...input }, pin);
|
|
635
|
-
},
|
|
636
|
-
updateConfig: async (input) => {
|
|
637
|
-
await api.decoder.updateConfig.query({ ...input }, pin);
|
|
638
|
-
},
|
|
639
|
-
getStats: async (input) => api.decoder.getStats.query({ ...input }, pin)
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
//#endregion
|
|
644
429
|
//#region src/pipeline-runner/frame-queue.ts
|
|
645
430
|
/**
|
|
646
431
|
* Latest-frame-only buffer. Keeps only the most recent item, dropping all
|
|
@@ -1928,21 +1713,25 @@ function startSessionDecodePump(deps) {
|
|
|
1928
1713
|
const decoded = await toDecodedFrame(frame, format);
|
|
1929
1714
|
deps.onDecodedFrame(decoded);
|
|
1930
1715
|
} catch (err) {
|
|
1931
|
-
deps.logger.debug("session-decode pump: skipped a frame (decode/consume error)", {
|
|
1932
|
-
deviceId: deps.source.deviceId,
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1716
|
+
deps.logger.debug("session-decode pump: skipped a frame (decode/consume error)", {
|
|
1717
|
+
tags: { deviceId: deps.source.deviceId },
|
|
1718
|
+
meta: {
|
|
1719
|
+
format,
|
|
1720
|
+
error: require_dist.errMsg(err)
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1936
1723
|
} finally {
|
|
1937
1724
|
frame.image.close();
|
|
1938
1725
|
}
|
|
1939
1726
|
}
|
|
1940
1727
|
} catch (err) {
|
|
1941
|
-
deps.logger.warn("session-decode pump: frame loop ended with error", {
|
|
1942
|
-
deviceId: deps.source.deviceId,
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1728
|
+
deps.logger.warn("session-decode pump: frame loop ended with error", {
|
|
1729
|
+
tags: { deviceId: deps.source.deviceId },
|
|
1730
|
+
meta: {
|
|
1731
|
+
format,
|
|
1732
|
+
error: require_dist.errMsg(err)
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1946
1735
|
} finally {
|
|
1947
1736
|
releaseSpawnSlotOnce();
|
|
1948
1737
|
}
|
|
@@ -2064,10 +1853,96 @@ function resolveMotionFrameSource(config, sharedWithDetection) {
|
|
|
2064
1853
|
*/
|
|
2065
1854
|
function resolveSessionDecodeHostname(config, hubUrl) {
|
|
2066
1855
|
const frameSource = config.frameSource;
|
|
2067
|
-
if (!isRemoteRestream(frameSource)) return void 0;
|
|
1856
|
+
if (!require_remote_source_plane.isRemoteRestream(frameSource)) return void 0;
|
|
1857
|
+
return resolveOwnerRestreamHost(frameSource, hubUrl);
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* The camera-source OWNER node for a session-decode acquire, or `undefined`
|
|
1861
|
+
* when the source is node-local (co-located, pre-cross-node). Mirrors
|
|
1862
|
+
* `resolveSessionDecodeHostname`: only a `remote-restream` frame source names an
|
|
1863
|
+
* owner. Used to `nodePin` the broker calls at the actual owner.
|
|
1864
|
+
*/
|
|
1865
|
+
function resolveSessionDecodeOwnerNodeId(config) {
|
|
1866
|
+
const frameSource = config.frameSource;
|
|
1867
|
+
if (!require_remote_source_plane.isRemoteRestream(frameSource)) return void 0;
|
|
1868
|
+
return frameSource.ownerNodeId;
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* The host a cross-node consumer dials for the owner's compressed restream.
|
|
1872
|
+
* Precedence: the orchestrator-resolved `ownerReachableHost` (the per-node
|
|
1873
|
+
* Cluster-UI override) → the operator `hubHostnameOverride` escape hatch →
|
|
1874
|
+
* the `CAMSTACK_HUB_URL`-derived host (the auto-detected Moleculer address).
|
|
1875
|
+
* `undefined` only when none resolve (the acquire then fails loudly rather
|
|
1876
|
+
* than silently dialing `127.0.0.1` on a remote node).
|
|
1877
|
+
*/
|
|
1878
|
+
function resolveOwnerRestreamHost(frameSource, hubUrl) {
|
|
1879
|
+
const reachable = frameSource.ownerReachableHost?.trim();
|
|
1880
|
+
if (reachable !== void 0 && reachable.length > 0) return reachable;
|
|
2068
1881
|
return require_hub_hostname.resolveHubHostname(frameSource.hubHostnameOverride, hubUrl);
|
|
2069
1882
|
}
|
|
2070
1883
|
/**
|
|
1884
|
+
* Acquire the broker's COMPRESSED passthrough restream for one camStream and
|
|
1885
|
+
* wrap it as a {@link VideoFrameSource} the decode worker can dial. Mirrors
|
|
1886
|
+
* {@link RemoteSourcePlane}'s acquire: resolve the profile the camStream
|
|
1887
|
+
* feeds, then `getStreamWithCodec({ video: 'copy' })` (refcounted — rides the
|
|
1888
|
+
* broker's single source pull, never a second camera dial). No `hostname`:
|
|
1889
|
+
* the P1 flagged camera is HUB-LOCAL, so the returned `127.0.0.1` URL is
|
|
1890
|
+
* dialable from this hub-resident runner. Returns `null` (loudly logged) when
|
|
1891
|
+
* no profile is bound yet — the caller opens no subscription.
|
|
1892
|
+
*
|
|
1893
|
+
* Extracted as a standalone exported function (rather than a private method)
|
|
1894
|
+
* so the owner-node pin threading through the three `streamBroker` calls
|
|
1895
|
+
* (`listAllProfileSlots`, `getStreamWithCodec`, `releaseStreamWithCodec`) is
|
|
1896
|
+
* directly unit-testable without instantiating the addon class. The private
|
|
1897
|
+
* `acquireSessionDecodeRestream` method on {@link PipelineRunnerAddon} is a
|
|
1898
|
+
* thin delegator to this function — its two call sites are unchanged.
|
|
1899
|
+
*/
|
|
1900
|
+
async function acquireSessionDecodeRestreamFrom(api, logger, params) {
|
|
1901
|
+
const { deviceId, streamId, tag, hostname, ownerNodeId } = params;
|
|
1902
|
+
const pin = ownerNodeId !== void 0 ? require_dist.nodePin(ownerNodeId) : void 0;
|
|
1903
|
+
const profile = require_remote_source_plane.profileForStreamId(await api.streamBroker.listAllProfileSlots.query(void 0, pin), deviceId, streamId);
|
|
1904
|
+
if (profile === null) {
|
|
1905
|
+
logger.error("session-decode: no broker profile feeds camStream — cannot acquire restream", {
|
|
1906
|
+
tags: { deviceId },
|
|
1907
|
+
meta: { streamId }
|
|
1908
|
+
});
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
const rtpSource = await api.streamBroker.getStreamWithCodec.mutate({
|
|
1912
|
+
deviceId,
|
|
1913
|
+
video: "copy",
|
|
1914
|
+
audio: "none",
|
|
1915
|
+
profile,
|
|
1916
|
+
tag,
|
|
1917
|
+
...hostname !== void 0 ? { hostname } : {}
|
|
1918
|
+
}, pin);
|
|
1919
|
+
const codec = rtpSource.videoCodec === "H265" ? "h265" : "h264";
|
|
1920
|
+
const source = {
|
|
1921
|
+
restreamUrl: rtpSource.url,
|
|
1922
|
+
deviceId,
|
|
1923
|
+
codec
|
|
1924
|
+
};
|
|
1925
|
+
const pipelineKey = rtpSource.pipelineKey;
|
|
1926
|
+
let released = false;
|
|
1927
|
+
const release = async () => {
|
|
1928
|
+
if (released) return;
|
|
1929
|
+
released = true;
|
|
1930
|
+
await api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey }, pin).catch((err) => {
|
|
1931
|
+
logger.debug("session-decode: releaseStreamWithCodec failed (benign on hub restart)", {
|
|
1932
|
+
tags: { deviceId },
|
|
1933
|
+
meta: {
|
|
1934
|
+
pipelineKey,
|
|
1935
|
+
error: require_dist.errMsg(err)
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
});
|
|
1939
|
+
};
|
|
1940
|
+
return {
|
|
1941
|
+
source,
|
|
1942
|
+
release
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
2071
1946
|
* Interval for periodic metric snapshot emission. ~1 Hz strikes the
|
|
2072
1947
|
* balance between UI freshness (overlay phase / fps numbers feel live)
|
|
2073
1948
|
* and bus traffic (one snapshot per attached camera + one per node
|
|
@@ -2628,7 +2503,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2628
2503
|
if (config.motionSources.includes("analyzer")) attachment.motionUnsubscribe = await this.subscribeMotionFrames(config);
|
|
2629
2504
|
const stepsCount = config.steps?.length ?? 0;
|
|
2630
2505
|
const dispatch = stepsCount > 0 ? `runPipeline(${stepsCount}step${stepsCount === 1 ? "" : "s"})` : config.steps !== void 0 ? "skip(0steps)" : "runFrame(legacy)";
|
|
2631
|
-
const engineLabel = config.engine ? `${config.engine.runtime}+${config.engine.backend}/${config.engine.format}` : "default";
|
|
2632
2506
|
this.ctx.logger.info("attachCamera", {
|
|
2633
2507
|
tags: { deviceId: config.deviceId },
|
|
2634
2508
|
meta: {
|
|
@@ -2638,7 +2512,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2638
2512
|
detectionFps: config.detectionFps,
|
|
2639
2513
|
motionSources: config.motionSources,
|
|
2640
2514
|
dispatch,
|
|
2641
|
-
engine:
|
|
2515
|
+
engine: "node-local"
|
|
2642
2516
|
}
|
|
2643
2517
|
});
|
|
2644
2518
|
return { success: true };
|
|
@@ -2822,17 +2696,18 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2822
2696
|
*/
|
|
2823
2697
|
remoteSourceFor(config) {
|
|
2824
2698
|
const frameSource = config.frameSource;
|
|
2825
|
-
if (!isRemoteRestream(frameSource)) return null;
|
|
2699
|
+
if (!require_remote_source_plane.isRemoteRestream(frameSource)) return null;
|
|
2826
2700
|
const existing = this.remotePlanes.get(config.deviceId);
|
|
2827
2701
|
if (existing) return existing;
|
|
2828
2702
|
const api = this.ctx.api;
|
|
2829
2703
|
if (!api) return null;
|
|
2830
|
-
const hostname =
|
|
2831
|
-
const plane = new RemoteSourcePlane({
|
|
2704
|
+
const hostname = resolveOwnerRestreamHost(frameSource, process.env["CAMSTACK_HUB_URL"]);
|
|
2705
|
+
const plane = new require_remote_source_plane.RemoteSourcePlane({
|
|
2832
2706
|
api,
|
|
2833
2707
|
logger: this.ctx.logger,
|
|
2834
2708
|
deviceId: config.deviceId,
|
|
2835
2709
|
ownNodeId: this.nodeId,
|
|
2710
|
+
ownerNodeId: frameSource.ownerNodeId,
|
|
2836
2711
|
hostname
|
|
2837
2712
|
});
|
|
2838
2713
|
this.ctx.logger.info("remote-source leg armed for camera", {
|
|
@@ -2846,57 +2721,18 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2846
2721
|
return plane;
|
|
2847
2722
|
}
|
|
2848
2723
|
/**
|
|
2849
|
-
*
|
|
2850
|
-
*
|
|
2851
|
-
*
|
|
2852
|
-
* feeds, then `getStreamWithCodec({ video: 'copy' })` (refcounted — rides the
|
|
2853
|
-
* broker's single source pull, never a second camera dial). No `hostname`:
|
|
2854
|
-
* the P1 flagged camera is HUB-LOCAL, so the returned `127.0.0.1` URL is
|
|
2855
|
-
* dialable from this hub-resident runner. Returns `null` (loudly logged) when
|
|
2856
|
-
* no profile is bound yet — the caller opens no subscription.
|
|
2724
|
+
* Thin delegator to the module-level {@link acquireSessionDecodeRestreamFrom}
|
|
2725
|
+
* (see its doc for the acquire behavior) — supplies this addon's `ctx.logger`.
|
|
2726
|
+
* Kept as a method so the two call sites need no change.
|
|
2857
2727
|
*/
|
|
2858
|
-
async acquireSessionDecodeRestream(api, deviceId, streamId, tag, hostname) {
|
|
2859
|
-
|
|
2860
|
-
if (profile === null) {
|
|
2861
|
-
this.ctx.logger.error("session-decode: no broker profile feeds camStream — cannot acquire restream", {
|
|
2862
|
-
tags: { deviceId },
|
|
2863
|
-
meta: { streamId }
|
|
2864
|
-
});
|
|
2865
|
-
return null;
|
|
2866
|
-
}
|
|
2867
|
-
const rtpSource = await api.streamBroker.getStreamWithCodec.mutate({
|
|
2728
|
+
async acquireSessionDecodeRestream(api, deviceId, streamId, tag, hostname, ownerNodeId) {
|
|
2729
|
+
return acquireSessionDecodeRestreamFrom(api, this.ctx.logger, {
|
|
2868
2730
|
deviceId,
|
|
2869
|
-
|
|
2870
|
-
audio: "none",
|
|
2871
|
-
profile,
|
|
2731
|
+
streamId,
|
|
2872
2732
|
tag,
|
|
2873
|
-
|
|
2733
|
+
hostname,
|
|
2734
|
+
ownerNodeId
|
|
2874
2735
|
});
|
|
2875
|
-
const codec = rtpSource.videoCodec === "H265" ? "h265" : "h264";
|
|
2876
|
-
const source = {
|
|
2877
|
-
restreamUrl: rtpSource.url,
|
|
2878
|
-
deviceId,
|
|
2879
|
-
codec
|
|
2880
|
-
};
|
|
2881
|
-
const pipelineKey = rtpSource.pipelineKey;
|
|
2882
|
-
let released = false;
|
|
2883
|
-
const release = async () => {
|
|
2884
|
-
if (released) return;
|
|
2885
|
-
released = true;
|
|
2886
|
-
await api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey }).catch((err) => {
|
|
2887
|
-
this.ctx.logger.debug("session-decode: releaseStreamWithCodec failed (benign on hub restart)", {
|
|
2888
|
-
tags: { deviceId },
|
|
2889
|
-
meta: {
|
|
2890
|
-
pipelineKey,
|
|
2891
|
-
error: require_dist.errMsg(err)
|
|
2892
|
-
}
|
|
2893
|
-
});
|
|
2894
|
-
});
|
|
2895
|
-
};
|
|
2896
|
-
return {
|
|
2897
|
-
source,
|
|
2898
|
-
release
|
|
2899
|
-
};
|
|
2900
2736
|
}
|
|
2901
2737
|
/**
|
|
2902
2738
|
* Register this device's motion to ride the detection session-decode worker
|
|
@@ -2974,7 +2810,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2974
2810
|
if (!runner) return () => {};
|
|
2975
2811
|
return startGuardedSessionDecode({
|
|
2976
2812
|
logger: log,
|
|
2977
|
-
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"])),
|
|
2813
|
+
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
2978
2814
|
startPump: (source) => {
|
|
2979
2815
|
log.info("session-decode: detection routed through per-session decode worker (rgb)", { meta: {
|
|
2980
2816
|
streamId: config.detectionStreamId,
|
|
@@ -3013,7 +2849,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3013
2849
|
if (!runner) return () => {};
|
|
3014
2850
|
return startGuardedSessionDecode({
|
|
3015
2851
|
logger: log,
|
|
3016
|
-
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"])),
|
|
2852
|
+
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
3017
2853
|
startPump: (source) => {
|
|
3018
2854
|
log.info("session-decode: motion routed through per-session decode worker (gray)", { meta: {
|
|
3019
2855
|
streamId: config.motionStreamId,
|
|
@@ -3195,9 +3031,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3195
3031
|
log.error("runInference: this.ctx.api not available");
|
|
3196
3032
|
return null;
|
|
3197
3033
|
}
|
|
3198
|
-
const
|
|
3199
|
-
const steps = camConfig?.steps;
|
|
3200
|
-
const engine = camConfig?.engine;
|
|
3034
|
+
const steps = (this.attached.get(deviceId)?.config)?.steps;
|
|
3201
3035
|
if (!steps) {
|
|
3202
3036
|
log.warn("runInference: no steps in attach config — skipping frame (legacy attach?)");
|
|
3203
3037
|
return null;
|
|
@@ -3208,8 +3042,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3208
3042
|
return await api.pipelineExecutor.runPipeline.mutate({
|
|
3209
3043
|
steps: [...steps],
|
|
3210
3044
|
...useHandle ? { frameHandle: handle } : { frame },
|
|
3211
|
-
deviceId
|
|
3212
|
-
...engine ? { engine } : {}
|
|
3045
|
+
deviceId
|
|
3213
3046
|
});
|
|
3214
3047
|
} catch (err) {
|
|
3215
3048
|
const msg = require_dist.errMsg(err);
|
|
@@ -3572,9 +3405,11 @@ exports.FrameQueue = FrameQueue;
|
|
|
3572
3405
|
exports.PipelineRunner = PipelineRunner;
|
|
3573
3406
|
exports.PipelineTimingSampler = PipelineTimingSampler;
|
|
3574
3407
|
exports.Semaphore = Semaphore;
|
|
3408
|
+
exports.acquireSessionDecodeRestreamFrom = acquireSessionDecodeRestreamFrom;
|
|
3575
3409
|
exports.customActions = pipelineRunnerBenchActions;
|
|
3576
3410
|
exports.default = PipelineRunnerAddon;
|
|
3577
3411
|
exports.resolveMotionFrameSource = resolveMotionFrameSource;
|
|
3578
3412
|
exports.resolveSessionDecodeHostname = resolveSessionDecodeHostname;
|
|
3413
|
+
exports.resolveSessionDecodeOwnerNodeId = resolveSessionDecodeOwnerNodeId;
|
|
3579
3414
|
exports.rgbFrameToGrayFrame = rgbFrameToGrayFrame;
|
|
3580
3415
|
exports.shouldStartOnboardAnalyzer = shouldStartOnboardAnalyzer;
|