@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.
Files changed (36) hide show
  1. package/dist/audio-analyzer/index.js +5 -4
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/chunk-D6vf50IK.js +28 -0
  4. package/dist/detection-pipeline/index.js +535 -115
  5. package/dist/detection-pipeline/index.mjs +529 -110
  6. package/dist/{dist-GFd8M6KO.mjs → dist-CgBGV988.mjs} +214 -186
  7. package/dist/{dist-NvwN60Fq.js → dist-CySHUkXY.js} +219 -185
  8. package/dist/{model-download-service-C-IHWnXx-DxM2DSns.js → model-download-service-C-IHWnXx-BLXzxpRB.js} +3 -29
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +122 -287
  12. package/dist/pipeline-runner/index.mjs +118 -285
  13. package/dist/recorder/index.js +78 -31
  14. package/dist/recorder/index.mjs +75 -29
  15. package/dist/{frame-handle-plane-E_AsR77T.mjs → remote-source-plane-B4hRwchu.mjs} +219 -2
  16. package/dist/{frame-handle-plane-B3Fxww8H.js → remote-source-plane-D9m0z1N9.js} +236 -1
  17. package/dist/session-decode/decode-worker-child.js +118 -12
  18. package/dist/session-decode/decode-worker-child.mjs +115 -12
  19. package/dist/stream-broker/_stub.js +550 -523
  20. 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
  21. 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
  22. 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
  23. package/dist/stream-broker/{hostInit-DmjqskOR.mjs → hostInit-BSzP3BIj.mjs} +2 -2
  24. package/dist/stream-broker/index.js +840 -196
  25. package/dist/stream-broker/index.mjs +831 -188
  26. package/dist/stream-broker/remoteEntry.js +1 -1
  27. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CEsPjwzT.js → MaskShapeCanvas-DI4BY7W2-D2ML5TPi.js} +1 -1
  28. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-D43fIRgB.js → MotionZonesSettings-NcxxQN8r-TTWGGaqv.js} +1 -1
  29. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-C3wJxG8V.js → PrivacyMaskSettings-APgPLF7p-CJqHU2nU.js} +1 -1
  30. package/embed-dist/assets/index-C1oaL9s7.js +81 -0
  31. package/embed-dist/assets/{index-CSFtK41z.css → index-CA4kv43T.css} +1 -1
  32. package/embed-dist/index.html +2 -2
  33. package/package.json +2 -2
  34. 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
  35. 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
  36. package/embed-dist/assets/index-CX-rILhw.js +0 -81
@@ -1,5 +1,5 @@
1
- import { D as nodePin, G as makeSourceBrokerId, H as createEvent, I as errMsg, L as BaseAddon, Q as boolean, V as EventCategory, X as _enum, Z as array, _ as customAction, at as string, et as lazy, k as pipelineRunnerCapability, nt as number, rt as object, y as defineCustomActions } from "../dist-GFd8M6KO.mjs";
2
- import { t as FrameHandlePlane } from "../frame-handle-plane-E_AsR77T.mjs";
1
+ import { $ as boolean, D as nodePin, H as createEvent, I as errMsg, K as makeSourceBrokerId, L as BaseAddon, Q as array, V as EventCategory, Z as _enum, _ as customAction, it as object, k as pipelineRunnerCapability, ot as string, rt as number, tt as lazy, y as defineCustomActions } from "../dist-CgBGV988.mjs";
2
+ import { n as isRemoteRestream, r as profileForStreamId, t as RemoteSourcePlane } from "../remote-source-plane-B4hRwchu.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { t as isWorkerReply } from "../worker-protocol-pk7qdYXt.mjs";
5
5
  import { FrameRingReaderCache } from "@camstack/shm-ring";
@@ -422,221 +422,6 @@ function startPolling(options, source, subscriptionId, resolvedMaxFps, lifecycle
422
422
  };
423
423
  }
424
424
  //#endregion
425
- //#region src/pipeline-runner/remote-source-plane.ts
426
- /**
427
- * Runner-side mode selection (pure): whether an attach payload routes this
428
- * camera through the remote-source leg. Absent `frameSource` (a pre-P2c
429
- * payload) and `local-broker` both take today's co-located broker path —
430
- * the rollout-OFF safety invariant.
431
- */
432
- function isRemoteRestream(frameSource) {
433
- return frameSource !== void 0 && frameSource.kind === "remote-restream";
434
- }
435
- /**
436
- * Resolve WHICH broker profile a camStream id feeds for a device (pure).
437
- *
438
- * `getStreamWithCodec` targets a PROFILE (its assigned camStream), while the
439
- * runner is dispatched with camStream ids (`motionStreamId` /
440
- * `detectionStreamId`). The broker's profile slots carry the mapping
441
- * (`sourceCamStreamId`); a stream no slot feeds returns `null` — the acquire
442
- * fails fast and the poller's backoff retries (the assignment may still be
443
- * propagating).
444
- */
445
- function profileForStreamId(slots, deviceId, streamId) {
446
- for (const slot of slots) if (slot.deviceId === deviceId && slot.sourceCamStreamId === streamId) return slot.profile;
447
- return null;
448
- }
449
- var RemoteSourcePlane = class {
450
- api;
451
- logger;
452
- deviceId;
453
- ownNodeId;
454
- hostname;
455
- /** Single-flight entry creation per camStream id. */
456
- entries = /* @__PURE__ */ new Map();
457
- disposed = false;
458
- constructor(options) {
459
- this.api = options.api;
460
- this.logger = options.logger.withTags({ deviceId: options.deviceId });
461
- this.deviceId = options.deviceId;
462
- this.ownNodeId = options.ownNodeId;
463
- this.hostname = options.hostname;
464
- }
465
- /**
466
- * A {@link FrameHandleSource} for one camStream id, pluggable into
467
- * `startFrameHandlePoller`. Motion (`gray`) and detection (`rgb`)
468
- * subscriptions of the same stream share ONE acquired restream and ONE
469
- * satellite plane (per-format decoder sessions inside it — hub parity).
470
- */
471
- makeSource(streamId) {
472
- return {
473
- subscribe: async (input) => {
474
- if (this.disposed) throw new Error("RemoteSourcePlane: subscribe after dispose");
475
- const entry = await this.ensureEntry(streamId);
476
- const result = await entry.plane.subscribe({
477
- format: input.format,
478
- maxFps: input.maxFps,
479
- tag: input.tag
480
- });
481
- entry.subscriptionIds.add(result.subscriptionId);
482
- return result;
483
- },
484
- pullHandles: async (input) => {
485
- const entry = await this.peekEntry(streamId);
486
- if (!entry) return [];
487
- return entry.plane.pullHandles(input.subscriptionId, input.maxCount);
488
- },
489
- unsubscribe: async (input) => {
490
- const entry = await this.peekEntry(streamId);
491
- if (!entry) return;
492
- entry.subscriptionIds.delete(input.subscriptionId);
493
- await entry.plane.unsubscribe(input.subscriptionId).catch(() => {});
494
- if (entry.subscriptionIds.size === 0) {
495
- this.entries.delete(streamId);
496
- await this.teardownEntry(entry, streamId);
497
- }
498
- }
499
- };
500
- }
501
- /** Dispose every entry: satellite planes destroyed, restreams released. Idempotent. */
502
- async dispose() {
503
- if (this.disposed) return;
504
- this.disposed = true;
505
- const pending = [...this.entries.entries()];
506
- this.entries.clear();
507
- for (const [streamId, entryPromise] of pending) try {
508
- const entry = await entryPromise;
509
- await this.teardownEntry(entry, streamId);
510
- } catch {}
511
- }
512
- /** The live entry for a stream, or `null` (never creates one). */
513
- async peekEntry(streamId) {
514
- const pending = this.entries.get(streamId);
515
- if (!pending) return null;
516
- try {
517
- return await pending;
518
- } catch {
519
- return null;
520
- }
521
- }
522
- /** Single-flight: acquire the restream + build the satellite plane once per stream. */
523
- ensureEntry(streamId) {
524
- const existing = this.entries.get(streamId);
525
- if (existing) return existing;
526
- const creation = this.createEntry(streamId);
527
- this.entries.set(streamId, creation);
528
- creation.catch(() => {
529
- if (this.entries.get(streamId) === creation) this.entries.delete(streamId);
530
- });
531
- return creation;
532
- }
533
- async createEntry(streamId) {
534
- const { api, deviceId, ownNodeId } = this;
535
- 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)");
536
- const profile = profileForStreamId(await api.streamBroker.listAllProfileSlots.query(), deviceId, streamId);
537
- if (profile === null) throw new Error(`RemoteSourcePlane: no broker profile is fed by camStream '${streamId}' of device ${deviceId} — cannot acquire the owner restream`);
538
- const rtpSource = await api.streamBroker.getStreamWithCodec.mutate({
539
- deviceId,
540
- video: "copy",
541
- audio: "none",
542
- profile,
543
- hostname: this.hostname,
544
- tag: `runner:${ownNodeId}:remote-src:${deviceId}/${streamId}`
545
- });
546
- const codec = rtpSource.videoCodec === "H265" ? "h265" : "h264";
547
- const plane = new FrameHandlePlane({
548
- decoderApi: this.buildPinnedDecoderCapApi(),
549
- logger: this.logger,
550
- localNodeId: ownNodeId,
551
- resolveStreamInfo: () => ({
552
- codec,
553
- numericDeviceId: deviceId,
554
- tag: `runner-remote:${deviceId}/${streamId}`,
555
- pullModeUrl: rtpSource.url
556
- })
557
- });
558
- this.logger.info("remote-source plane: owner restream acquired", { meta: {
559
- streamId,
560
- profile,
561
- codec,
562
- pipelineKey: rtpSource.pipelineKey,
563
- hostname: this.hostname
564
- } });
565
- return {
566
- plane,
567
- pipelineKey: rtpSource.pipelineKey,
568
- subscriptionIds: /* @__PURE__ */ new Set(),
569
- released: false
570
- };
571
- }
572
- /** Destroy an entry's satellite plane and release its restream refcount once. */
573
- async teardownEntry(entry, streamId) {
574
- await entry.plane.dispose().catch((err) => {
575
- this.logger.warn("remote-source plane: satellite plane dispose failed", { meta: {
576
- streamId,
577
- error: errMsg(err)
578
- } });
579
- });
580
- if (entry.released) return;
581
- entry.released = true;
582
- await this.api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey: entry.pipelineKey }).catch((err) => {
583
- this.logger.debug("remote-source plane: releaseStreamWithCodec failed (benign on hub restart)", { meta: {
584
- streamId,
585
- pipelineKey: entry.pipelineKey,
586
- error: errMsg(err)
587
- } });
588
- });
589
- }
590
- /**
591
- * Every decoder call pinned to THIS runner's node — mirror of the hub
592
- * broker's wrapper (`stream-broker-manager.buildDecoderCapApi`): the
593
- * `decoder` cap is a singleton whose UNPINNED calls resolve to the
594
- * cluster-elected node, which is generally NOT this agent. Pinning create
595
- * AND every session op to the same local node keeps the session + its shm
596
- * ring here, where this plane's readers live.
597
- */
598
- buildPinnedDecoderCapApi() {
599
- const api = this.api;
600
- const pin = nodePin(this.ownNodeId);
601
- const toWireBytes = (bytes) => {
602
- const copy = new Uint8Array(new ArrayBuffer(bytes.byteLength));
603
- copy.set(bytes);
604
- return copy;
605
- };
606
- return {
607
- supportsCodec: async (input) => api.decoder.supportsCodec.query({ ...input }, pin),
608
- getInfo: async () => api.decoder.getInfo.query(void 0, pin),
609
- createSession: async (config) => api.decoder.createSession.query({ ...config }, pin),
610
- pushPacket: async (input) => {
611
- await api.decoder.pushPacket.query({
612
- sessionId: input.sessionId,
613
- packet: {
614
- ...input.packet,
615
- data: toWireBytes(input.packet.data)
616
- }
617
- }, pin);
618
- },
619
- pullFrames: async (input) => {
620
- return (await api.decoder.pullFrames.query({ ...input }, pin)).map((frame) => ({
621
- ...frame,
622
- data: Buffer.from(frame.data)
623
- }));
624
- },
625
- pullHandles: async (input) => api.decoder.pullHandles.query({ ...input }, pin),
626
- destroySession: async (input) => {
627
- await api.decoder.destroySession.query({ ...input }, pin);
628
- },
629
- openStream: async (input) => {
630
- await api.decoder.openStream.query({ ...input }, pin);
631
- },
632
- updateConfig: async (input) => {
633
- await api.decoder.updateConfig.query({ ...input }, pin);
634
- },
635
- getStats: async (input) => api.decoder.getStats.query({ ...input }, pin)
636
- };
637
- }
638
- };
639
- //#endregion
640
425
  //#region src/pipeline-runner/frame-queue.ts
641
426
  /**
642
427
  * Latest-frame-only buffer. Keeps only the most recent item, dropping all
@@ -1924,21 +1709,25 @@ function startSessionDecodePump(deps) {
1924
1709
  const decoded = await toDecodedFrame(frame, format);
1925
1710
  deps.onDecodedFrame(decoded);
1926
1711
  } catch (err) {
1927
- deps.logger.debug("session-decode pump: skipped a frame (decode/consume error)", { meta: {
1928
- deviceId: deps.source.deviceId,
1929
- format,
1930
- error: errMsg(err)
1931
- } });
1712
+ deps.logger.debug("session-decode pump: skipped a frame (decode/consume error)", {
1713
+ tags: { deviceId: deps.source.deviceId },
1714
+ meta: {
1715
+ format,
1716
+ error: errMsg(err)
1717
+ }
1718
+ });
1932
1719
  } finally {
1933
1720
  frame.image.close();
1934
1721
  }
1935
1722
  }
1936
1723
  } catch (err) {
1937
- deps.logger.warn("session-decode pump: frame loop ended with error", { meta: {
1938
- deviceId: deps.source.deviceId,
1939
- format,
1940
- error: errMsg(err)
1941
- } });
1724
+ deps.logger.warn("session-decode pump: frame loop ended with error", {
1725
+ tags: { deviceId: deps.source.deviceId },
1726
+ meta: {
1727
+ format,
1728
+ error: errMsg(err)
1729
+ }
1730
+ });
1942
1731
  } finally {
1943
1732
  releaseSpawnSlotOnce();
1944
1733
  }
@@ -2061,9 +1850,95 @@ function resolveMotionFrameSource(config, sharedWithDetection) {
2061
1850
  function resolveSessionDecodeHostname(config, hubUrl) {
2062
1851
  const frameSource = config.frameSource;
2063
1852
  if (!isRemoteRestream(frameSource)) return void 0;
1853
+ return resolveOwnerRestreamHost(frameSource, hubUrl);
1854
+ }
1855
+ /**
1856
+ * The camera-source OWNER node for a session-decode acquire, or `undefined`
1857
+ * when the source is node-local (co-located, pre-cross-node). Mirrors
1858
+ * `resolveSessionDecodeHostname`: only a `remote-restream` frame source names an
1859
+ * owner. Used to `nodePin` the broker calls at the actual owner.
1860
+ */
1861
+ function resolveSessionDecodeOwnerNodeId(config) {
1862
+ const frameSource = config.frameSource;
1863
+ if (!isRemoteRestream(frameSource)) return void 0;
1864
+ return frameSource.ownerNodeId;
1865
+ }
1866
+ /**
1867
+ * The host a cross-node consumer dials for the owner's compressed restream.
1868
+ * Precedence: the orchestrator-resolved `ownerReachableHost` (the per-node
1869
+ * Cluster-UI override) → the operator `hubHostnameOverride` escape hatch →
1870
+ * the `CAMSTACK_HUB_URL`-derived host (the auto-detected Moleculer address).
1871
+ * `undefined` only when none resolve (the acquire then fails loudly rather
1872
+ * than silently dialing `127.0.0.1` on a remote node).
1873
+ */
1874
+ function resolveOwnerRestreamHost(frameSource, hubUrl) {
1875
+ const reachable = frameSource.ownerReachableHost?.trim();
1876
+ if (reachable !== void 0 && reachable.length > 0) return reachable;
2064
1877
  return resolveHubHostname(frameSource.hubHostnameOverride, hubUrl);
2065
1878
  }
2066
1879
  /**
1880
+ * Acquire the broker's COMPRESSED passthrough restream for one camStream and
1881
+ * wrap it as a {@link VideoFrameSource} the decode worker can dial. Mirrors
1882
+ * {@link RemoteSourcePlane}'s acquire: resolve the profile the camStream
1883
+ * feeds, then `getStreamWithCodec({ video: 'copy' })` (refcounted — rides the
1884
+ * broker's single source pull, never a second camera dial). No `hostname`:
1885
+ * the P1 flagged camera is HUB-LOCAL, so the returned `127.0.0.1` URL is
1886
+ * dialable from this hub-resident runner. Returns `null` (loudly logged) when
1887
+ * no profile is bound yet — the caller opens no subscription.
1888
+ *
1889
+ * Extracted as a standalone exported function (rather than a private method)
1890
+ * so the owner-node pin threading through the three `streamBroker` calls
1891
+ * (`listAllProfileSlots`, `getStreamWithCodec`, `releaseStreamWithCodec`) is
1892
+ * directly unit-testable without instantiating the addon class. The private
1893
+ * `acquireSessionDecodeRestream` method on {@link PipelineRunnerAddon} is a
1894
+ * thin delegator to this function — its two call sites are unchanged.
1895
+ */
1896
+ async function acquireSessionDecodeRestreamFrom(api, logger, params) {
1897
+ const { deviceId, streamId, tag, hostname, ownerNodeId } = params;
1898
+ const pin = ownerNodeId !== void 0 ? nodePin(ownerNodeId) : void 0;
1899
+ const profile = profileForStreamId(await api.streamBroker.listAllProfileSlots.query(void 0, pin), deviceId, streamId);
1900
+ if (profile === null) {
1901
+ logger.error("session-decode: no broker profile feeds camStream — cannot acquire restream", {
1902
+ tags: { deviceId },
1903
+ meta: { streamId }
1904
+ });
1905
+ return null;
1906
+ }
1907
+ const rtpSource = await api.streamBroker.getStreamWithCodec.mutate({
1908
+ deviceId,
1909
+ video: "copy",
1910
+ audio: "none",
1911
+ profile,
1912
+ tag,
1913
+ ...hostname !== void 0 ? { hostname } : {}
1914
+ }, pin);
1915
+ const codec = rtpSource.videoCodec === "H265" ? "h265" : "h264";
1916
+ const source = {
1917
+ restreamUrl: rtpSource.url,
1918
+ deviceId,
1919
+ codec
1920
+ };
1921
+ const pipelineKey = rtpSource.pipelineKey;
1922
+ let released = false;
1923
+ const release = async () => {
1924
+ if (released) return;
1925
+ released = true;
1926
+ await api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey }, pin).catch((err) => {
1927
+ logger.debug("session-decode: releaseStreamWithCodec failed (benign on hub restart)", {
1928
+ tags: { deviceId },
1929
+ meta: {
1930
+ pipelineKey,
1931
+ error: errMsg(err)
1932
+ }
1933
+ });
1934
+ });
1935
+ };
1936
+ return {
1937
+ source,
1938
+ release
1939
+ };
1940
+ }
1941
+ /**
2067
1942
  * Interval for periodic metric snapshot emission. ~1 Hz strikes the
2068
1943
  * balance between UI freshness (overlay phase / fps numbers feel live)
2069
1944
  * and bus traffic (one snapshot per attached camera + one per node
@@ -2624,7 +2499,6 @@ var PipelineRunnerAddon = class extends BaseAddon {
2624
2499
  if (config.motionSources.includes("analyzer")) attachment.motionUnsubscribe = await this.subscribeMotionFrames(config);
2625
2500
  const stepsCount = config.steps?.length ?? 0;
2626
2501
  const dispatch = stepsCount > 0 ? `runPipeline(${stepsCount}step${stepsCount === 1 ? "" : "s"})` : config.steps !== void 0 ? "skip(0steps)" : "runFrame(legacy)";
2627
- const engineLabel = config.engine ? `${config.engine.runtime}+${config.engine.backend}/${config.engine.format}` : "default";
2628
2502
  this.ctx.logger.info("attachCamera", {
2629
2503
  tags: { deviceId: config.deviceId },
2630
2504
  meta: {
@@ -2634,7 +2508,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
2634
2508
  detectionFps: config.detectionFps,
2635
2509
  motionSources: config.motionSources,
2636
2510
  dispatch,
2637
- engine: engineLabel
2511
+ engine: "node-local"
2638
2512
  }
2639
2513
  });
2640
2514
  return { success: true };
@@ -2823,12 +2697,13 @@ var PipelineRunnerAddon = class extends BaseAddon {
2823
2697
  if (existing) return existing;
2824
2698
  const api = this.ctx.api;
2825
2699
  if (!api) return null;
2826
- const hostname = resolveHubHostname(frameSource.hubHostnameOverride, process.env["CAMSTACK_HUB_URL"]);
2700
+ const hostname = resolveOwnerRestreamHost(frameSource, process.env["CAMSTACK_HUB_URL"]);
2827
2701
  const plane = new RemoteSourcePlane({
2828
2702
  api,
2829
2703
  logger: this.ctx.logger,
2830
2704
  deviceId: config.deviceId,
2831
2705
  ownNodeId: this.nodeId,
2706
+ ownerNodeId: frameSource.ownerNodeId,
2832
2707
  hostname
2833
2708
  });
2834
2709
  this.ctx.logger.info("remote-source leg armed for camera", {
@@ -2842,57 +2717,18 @@ var PipelineRunnerAddon = class extends BaseAddon {
2842
2717
  return plane;
2843
2718
  }
2844
2719
  /**
2845
- * Acquire the broker's COMPRESSED passthrough restream for one camStream and
2846
- * wrap it as a {@link VideoFrameSource} the decode worker can dial. Mirrors
2847
- * {@link RemoteSourcePlane}'s acquire: resolve the profile the camStream
2848
- * feeds, then `getStreamWithCodec({ video: 'copy' })` (refcounted — rides the
2849
- * broker's single source pull, never a second camera dial). No `hostname`:
2850
- * the P1 flagged camera is HUB-LOCAL, so the returned `127.0.0.1` URL is
2851
- * dialable from this hub-resident runner. Returns `null` (loudly logged) when
2852
- * no profile is bound yet — the caller opens no subscription.
2720
+ * Thin delegator to the module-level {@link acquireSessionDecodeRestreamFrom}
2721
+ * (see its doc for the acquire behavior) supplies this addon's `ctx.logger`.
2722
+ * Kept as a method so the two call sites need no change.
2853
2723
  */
2854
- async acquireSessionDecodeRestream(api, deviceId, streamId, tag, hostname) {
2855
- const profile = profileForStreamId(await api.streamBroker.listAllProfileSlots.query(), deviceId, streamId);
2856
- if (profile === null) {
2857
- this.ctx.logger.error("session-decode: no broker profile feeds camStream — cannot acquire restream", {
2858
- tags: { deviceId },
2859
- meta: { streamId }
2860
- });
2861
- return null;
2862
- }
2863
- const rtpSource = await api.streamBroker.getStreamWithCodec.mutate({
2724
+ async acquireSessionDecodeRestream(api, deviceId, streamId, tag, hostname, ownerNodeId) {
2725
+ return acquireSessionDecodeRestreamFrom(api, this.ctx.logger, {
2864
2726
  deviceId,
2865
- video: "copy",
2866
- audio: "none",
2867
- profile,
2727
+ streamId,
2868
2728
  tag,
2869
- ...hostname !== void 0 ? { hostname } : {}
2729
+ hostname,
2730
+ ownerNodeId
2870
2731
  });
2871
- const codec = rtpSource.videoCodec === "H265" ? "h265" : "h264";
2872
- const source = {
2873
- restreamUrl: rtpSource.url,
2874
- deviceId,
2875
- codec
2876
- };
2877
- const pipelineKey = rtpSource.pipelineKey;
2878
- let released = false;
2879
- const release = async () => {
2880
- if (released) return;
2881
- released = true;
2882
- await api.streamBroker.releaseStreamWithCodec.mutate({ pipelineKey }).catch((err) => {
2883
- this.ctx.logger.debug("session-decode: releaseStreamWithCodec failed (benign on hub restart)", {
2884
- tags: { deviceId },
2885
- meta: {
2886
- pipelineKey,
2887
- error: errMsg(err)
2888
- }
2889
- });
2890
- });
2891
- };
2892
- return {
2893
- source,
2894
- release
2895
- };
2896
2732
  }
2897
2733
  /**
2898
2734
  * Register this device's motion to ride the detection session-decode worker
@@ -2970,7 +2806,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
2970
2806
  if (!runner) return () => {};
2971
2807
  return startGuardedSessionDecode({
2972
2808
  logger: log,
2973
- acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"])),
2809
+ acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
2974
2810
  startPump: (source) => {
2975
2811
  log.info("session-decode: detection routed through per-session decode worker (rgb)", { meta: {
2976
2812
  streamId: config.detectionStreamId,
@@ -3009,7 +2845,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
3009
2845
  if (!runner) return () => {};
3010
2846
  return startGuardedSessionDecode({
3011
2847
  logger: log,
3012
- acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"])),
2848
+ acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.motionStreamId, `session-decode:motion:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
3013
2849
  startPump: (source) => {
3014
2850
  log.info("session-decode: motion routed through per-session decode worker (gray)", { meta: {
3015
2851
  streamId: config.motionStreamId,
@@ -3191,9 +3027,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
3191
3027
  log.error("runInference: this.ctx.api not available");
3192
3028
  return null;
3193
3029
  }
3194
- const camConfig = this.attached.get(deviceId)?.config;
3195
- const steps = camConfig?.steps;
3196
- const engine = camConfig?.engine;
3030
+ const steps = (this.attached.get(deviceId)?.config)?.steps;
3197
3031
  if (!steps) {
3198
3032
  log.warn("runInference: no steps in attach config — skipping frame (legacy attach?)");
3199
3033
  return null;
@@ -3204,8 +3038,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
3204
3038
  return await api.pipelineExecutor.runPipeline.mutate({
3205
3039
  steps: [...steps],
3206
3040
  ...useHandle ? { frameHandle: handle } : { frame },
3207
- deviceId,
3208
- ...engine ? { engine } : {}
3041
+ deviceId
3209
3042
  });
3210
3043
  } catch (err) {
3211
3044
  const msg = errMsg(err);
@@ -3563,4 +3396,4 @@ var PipelineRunnerAddon = class extends BaseAddon {
3563
3396
  }
3564
3397
  };
3565
3398
  //#endregion
3566
- export { DEFAULT_CONFIG, FrameQueue, PipelineRunner, PipelineTimingSampler, Semaphore, pipelineRunnerBenchActions as customActions, PipelineRunnerAddon as default, resolveMotionFrameSource, resolveSessionDecodeHostname, rgbFrameToGrayFrame, shouldStartOnboardAnalyzer };
3399
+ export { DEFAULT_CONFIG, FrameQueue, PipelineRunner, PipelineTimingSampler, Semaphore, acquireSessionDecodeRestreamFrom, pipelineRunnerBenchActions as customActions, PipelineRunnerAddon as default, resolveMotionFrameSource, resolveSessionDecodeHostname, resolveSessionDecodeOwnerNodeId, rgbFrameToGrayFrame, shouldStartOnboardAnalyzer };