@camstack/addon-pipeline 1.1.24 → 1.1.26

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 (45) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/audio-codec-ffmpeg/index.js +1 -1
  4. package/dist/audio-codec-ffmpeg/index.mjs +1 -1
  5. package/dist/decoder-ffmpeg/index.js +84 -12
  6. package/dist/decoder-ffmpeg/index.mjs +84 -12
  7. package/dist/detection-pipeline/index.js +149 -27
  8. package/dist/detection-pipeline/index.mjs +149 -27
  9. package/dist/{dist-pd_-3C0T.mjs → dist-CgEP_0OL.mjs} +794 -16
  10. package/dist/{dist-CE3a05qT.js → dist-DAIlCdAx.js} +799 -15
  11. package/dist/frame-handle-plane-Dq20KtKL.mjs +636 -0
  12. package/dist/frame-handle-plane-DtTRX_0n.js +647 -0
  13. package/dist/hub-hostname-DAJXlOgV.js +54 -0
  14. package/dist/hub-hostname-cCknRYKj.mjs +49 -0
  15. package/dist/{model-download-service-C-IHWnXx-BnQ_awK4.js → model-download-service-C-IHWnXx-DxM2DSns.js} +1 -2
  16. package/dist/motion-wasm/index.js +1 -1
  17. package/dist/motion-wasm/index.mjs +1 -1
  18. package/dist/pipeline-runner/index.js +341 -17
  19. package/dist/pipeline-runner/index.mjs +341 -17
  20. package/dist/recorder/index.js +267 -65
  21. package/dist/recorder/index.mjs +267 -65
  22. package/dist/stream-broker/_stub.js +39 -39
  23. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BVFAEkIB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-5tQlh9h4.mjs} +2 -2
  24. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CkOPfV8r.mjs +26 -0
  25. 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 +26 -0
  26. package/dist/stream-broker/{hostInit-DHej3Ep9.mjs → hostInit-DyLqyJaS.mjs} +2 -2
  27. package/dist/stream-broker/index.js +7 -639
  28. package/dist/stream-broker/index.mjs +5 -637
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-BqXU75it.js → MaskShapeCanvas-DI4BY7W2-BDLNwJ_F.js} +1 -1
  31. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-Ct2fpgCd.js → MotionZonesSettings-NcxxQN8r-CoLjNiUN.js} +1 -1
  32. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-a9eFVPPl.js → PrivacyMaskSettings-APgPLF7p-DJE3OU-q.js} +1 -1
  33. package/embed-dist/assets/index-C-pL8ETk.js +81 -0
  34. package/embed-dist/assets/index-DrJ0ee3f.css +2 -0
  35. package/embed-dist/index.html +2 -2
  36. package/package.json +1 -1
  37. package/python/inference_pool.py +522 -27
  38. package/python/test_inference_pool_backpressure.py +121 -0
  39. package/python/test_inference_pool_coreml_cache.py +416 -0
  40. package/python/test_inference_pool_device_selection.py +256 -0
  41. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BFeBlYRQ.mjs +0 -26
  42. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BC1Jt6G8.mjs +0 -26
  43. package/embed-dist/assets/index-C7SSikPl.css +0 -2
  44. package/embed-dist/assets/index-DhdVxWXL.js +0 -80
  45. package/dist/{model-download-service-C-IHWnXx-3Mmeob3l.mjs → model-download-service-C-IHWnXx-BPy6aoAx.mjs} +1 -1
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
6
- const require_dist = require("../dist-CE3a05qT.js");
5
+ const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-DxM2DSns.js");
6
+ const require_dist = require("../dist-DAIlCdAx.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { B as BaseAddon, E as mapAudioLabelToMacro, J as hydrateSchema, g as audioAnalyzerCapability, h as audioAnalysisCapability, l as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, z as errMsg } from "../dist-pd_-3C0T.mjs";
3
- import { r as downloadFile } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
2
+ import { B as errMsg, D as mapAudioLabelToMacro, V as BaseAddon, Y as hydrateSchema, _ as audioAnalyzerCapability, g as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, u as HF_BASE_URL } from "../dist-CgEP_0OL.mjs";
3
+ import { r as downloadFile } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
4
4
  import * as fs from "node:fs";
5
5
  import * as path$1 from "node:path";
6
6
  //#region src/audio-analyzer/audio-pipeline.ts
@@ -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-CE3a05qT.js");
5
+ const require_dist = require("../dist-DAIlCdAx.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/audio-codec-ffmpeg/ffmpeg-audio-process.ts
@@ -1,4 +1,4 @@
1
- import { B as BaseAddon, _ as audioCodecCapability, z as errMsg } from "../dist-pd_-3C0T.mjs";
1
+ import { B as errMsg, V as BaseAddon, v as audioCodecCapability } from "../dist-CgEP_0OL.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region src/audio-codec-ffmpeg/ffmpeg-audio-process.ts
@@ -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-CE3a05qT.js");
5
+ const require_dist = require("../dist-DAIlCdAx.js");
6
6
  const require_frame_dropper = require("../frame-dropper-7RTo_YyG.js");
7
7
  let _camstack_shm_ring = require("@camstack/shm-ring");
8
8
  let node_crypto = require("node:crypto");
@@ -145,8 +145,10 @@ function buildVideoFilter(hwaccel, pixel, outWidth, outHeight, maxW, gpuScaleFil
145
145
  * stream line from stderr to learn the exact output W×H (the broker does not
146
146
  * pass source geometry for the shm path). `-nostats` suppresses the periodic
147
147
  * `frame= …` counter while keeping the one-shot Output block.
148
- * - The input is `pipe:0` (Annex-B packets written to stdin); the output is raw
149
- * video on `pipe:1`.
148
+ * - The input is `pipe:0` (Annex-B packets written to stdin) in push mode, or
149
+ * `options.inputUrl` (the owner's restream, dialed by ffmpeg itself —
150
+ * `-rtsp_transport tcp` for rtsp URLs) in pull mode; the output is raw video
151
+ * on `pipe:1` either way.
150
152
  */
151
153
  function buildFfmpegDecodeArgs(config, options) {
152
154
  const inputFormat = codecToInputFormat(config.codec);
@@ -162,6 +164,16 @@ function buildFfmpegDecodeArgs(config, options) {
162
164
  if (needsHwDownload(hwaccel)) hwaccelArgs.push("-hwaccel_device", options.renderDevice ?? "/dev/dri/renderD128");
163
165
  if (keepFramesOnGpu(hwaccel, gpuScaleFilter)) hwaccelArgs.push("-hwaccel_output_format", hwaccel);
164
166
  }
167
+ const inputArgs = options.inputUrl !== void 0 ? [
168
+ ...options.inputUrl.startsWith("rtsp") ? ["-rtsp_transport", "tcp"] : [],
169
+ "-i",
170
+ options.inputUrl
171
+ ] : [
172
+ "-f",
173
+ inputFormat,
174
+ "-i",
175
+ "pipe:0"
176
+ ];
165
177
  return [
166
178
  ...require_frame_dropper.logBannerArgs("info"),
167
179
  "-nostats",
@@ -174,10 +186,7 @@ function buildFfmpegDecodeArgs(config, options) {
174
186
  "-analyzeduration",
175
187
  "0",
176
188
  ...hwaccelArgs,
177
- "-f",
178
- inputFormat,
179
- "-i",
180
- "pipe:0",
189
+ ...inputArgs,
181
190
  "-vf",
182
191
  vf,
183
192
  "-f",
@@ -650,6 +659,16 @@ var noopLogger = {
650
659
  * hwaccel init failure (missing render node, unsupported codec on GPU, the
651
660
  * Intel `qsv` `code=171`) surfaces this early. */
652
661
  var HWACCEL_FALLBACK_WINDOW_MS = 4e3;
662
+ /**
663
+ * Delay before a PULL-MODE session re-dials its input URL after the child
664
+ * exits (RTSP EOF, restream teardown, network blip). Push mode never
665
+ * self-respawns on a plain exit — the broker's packet feed drives recovery —
666
+ * but a pull-mode child owns its own input, so the session must re-dial or a
667
+ * single EOF would silence it forever. The delay turns a hard-down source
668
+ * into a slow probe instead of a spawn storm; the consumer tearing the
669
+ * session down (`destroy`) cancels the pending re-dial.
670
+ */
671
+ var PULL_REDIAL_DELAY_MS = 3e3;
653
672
  var FfmpegDecoderSession = class {
654
673
  config;
655
674
  logger;
@@ -680,6 +699,15 @@ var FfmpegDecoderSession = class {
680
699
  process = null;
681
700
  spawnedAtMs = 0;
682
701
  /**
702
+ * PULL-MODE input URL (P2b). `null` = push mode (packets via stdin — the
703
+ * default). Set by {@link openStream}; every (re)spawn since reads this URL
704
+ * directly instead of `pipe:0`. A session is push-mode or pull-mode for its
705
+ * whole life — `openStream` on a packet-fed session throws.
706
+ */
707
+ pullUrl = null;
708
+ /** Pending pull-mode re-dial timer (armed on a pull child exit). */
709
+ pullRedialTimer = null;
710
+ /**
683
711
  * Splits ffmpeg's raw stdout byte stream into frames, filling each DIRECTLY
684
712
  * into its shm slot (zero-copy) — created once the output geometry is known.
685
713
  */
@@ -733,7 +761,25 @@ var FfmpegDecoderSession = class {
733
761
  return this.frameRingSink;
734
762
  }
735
763
  get isPullMode() {
736
- return false;
764
+ return this.pullUrl !== null;
765
+ }
766
+ /**
767
+ * Switch the session to PULL MODE: (re)spawn ffmpeg reading `url` directly
768
+ * (`-rtsp_transport tcp -i <url>` for rtsp) instead of stdin. The whole
769
+ * output path (filtergraph, rawvideo pipe, shm ring sink) is unchanged.
770
+ *
771
+ * - Calling it again re-dials with the FRESH url (a hub restream bounce
772
+ * mints a new token URL — never cache the old one).
773
+ * - Calling it on a session already fed via `pushPacket` throws: a session
774
+ * is push-mode or pull-mode, never both (mixing inputs would corrupt the
775
+ * decode).
776
+ */
777
+ async openStream(url) {
778
+ if (this.destroyed) return;
779
+ if (this.pullUrl === null && this.inputPackets > 0) throw new Error("ffmpeg decoder: openStream on a push-fed session — a session is push-mode or pull-mode, never both");
780
+ this.pullUrl = url;
781
+ this.logger.info("ffmpeg decoder: pull-mode openStream — dialing input URL", { meta: { url } });
782
+ this.spawnFfmpeg();
737
783
  }
738
784
  ensureFrameRingSink() {
739
785
  if (this.frameRingSink === null) {
@@ -751,6 +797,7 @@ var FfmpegDecoderSession = class {
751
797
  }
752
798
  spawnFfmpeg() {
753
799
  if (this.destroyed) return;
800
+ this.clearPullRedial();
754
801
  this.killFfmpeg();
755
802
  this.abortOpenSlot();
756
803
  this.splitter = null;
@@ -767,7 +814,8 @@ var FfmpegDecoderSession = class {
767
814
  }, {
768
815
  hwaccel: this.hwaccel,
769
816
  renderDevice: this.renderDevice,
770
- gpuScaleFilter: this.activeGpuScaleFilter
817
+ gpuScaleFilter: this.activeGpuScaleFilter,
818
+ ...this.pullUrl !== null ? { inputUrl: this.pullUrl } : {}
771
819
  });
772
820
  let child;
773
821
  try {
@@ -854,6 +902,27 @@ var FfmpegDecoderSession = class {
854
902
  signal,
855
903
  outputFrames: this.outputFrames
856
904
  } });
905
+ if (this.pullUrl !== null) {
906
+ this.clearPullRedial();
907
+ const timer = setTimeout(() => {
908
+ this.pullRedialTimer = null;
909
+ this.spawnFfmpeg();
910
+ }, PULL_REDIAL_DELAY_MS);
911
+ timer.unref?.();
912
+ this.pullRedialTimer = timer;
913
+ this.logger.info("ffmpeg decoder: pull-mode child exited — re-dial scheduled", { meta: {
914
+ code,
915
+ signal,
916
+ redialInMs: PULL_REDIAL_DELAY_MS
917
+ } });
918
+ }
919
+ }
920
+ /** Cancel a pending pull-mode re-dial, if armed. */
921
+ clearPullRedial() {
922
+ if (this.pullRedialTimer !== null) {
923
+ clearTimeout(this.pullRedialTimer);
924
+ this.pullRedialTimer = null;
925
+ }
857
926
  }
858
927
  handleStderr(data) {
859
928
  const text = data.toString();
@@ -958,6 +1027,7 @@ var FfmpegDecoderSession = class {
958
1027
  }
959
1028
  pushPacket(packet) {
960
1029
  if (this.destroyed) return;
1030
+ if (this.pullUrl !== null) return;
961
1031
  const stdin = this.process?.stdin;
962
1032
  if (!stdin) return;
963
1033
  this.inputPackets++;
@@ -990,6 +1060,7 @@ var FfmpegDecoderSession = class {
990
1060
  async destroy() {
991
1061
  if (this.destroyed) return;
992
1062
  this.destroyed = true;
1063
+ this.clearPullRedial();
993
1064
  this.killFfmpeg();
994
1065
  this.abortOpenSlot();
995
1066
  this.splitter = null;
@@ -1235,7 +1306,7 @@ var DecoderFfmpegAddon = class extends require_dist.BaseAddon {
1235
1306
  return {
1236
1307
  id: "decoder-ffmpeg",
1237
1308
  name: "Decoder (ffmpeg)",
1238
- isPullMode: false,
1309
+ isPullMode: true,
1239
1310
  priority: 20
1240
1311
  };
1241
1312
  }
@@ -1346,8 +1417,9 @@ var DecoderFfmpegAddon = class extends require_dist.BaseAddon {
1346
1417
  });
1347
1418
  }
1348
1419
  async openStream(input) {
1349
- if (!this.sessions.get(input.sessionId)) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
1350
- input.url;
1420
+ const session = this.sessions.get(input.sessionId);
1421
+ if (!session) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
1422
+ await session.openStream(input.url);
1351
1423
  }
1352
1424
  async pullFrames(input) {
1353
1425
  if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
@@ -1,4 +1,4 @@
1
- import { B as BaseAddon, b as decoderCapability, f as RingBuffer, s as DEFAULT_DECODER_HWACCEL_CONFIG, u as HWACCEL_OPTIONS, z as errMsg } from "../dist-pd_-3C0T.mjs";
1
+ import { B as errMsg, V as BaseAddon, d as HWACCEL_OPTIONS, p as RingBuffer, s as DEFAULT_DECODER_HWACCEL_CONFIG, x as decoderCapability } from "../dist-CgEP_0OL.mjs";
2
2
  import { r as logBannerArgs, t as FrameDropper } from "../frame-dropper-CwkBTPGV.mjs";
3
3
  import { FrameRingReaderCache, FrameRingWriter, MIN_RING_SLOTS, computeSegmentSize, computeSlotByteLength, createSegment, deriveSlotCount } from "@camstack/shm-ring";
4
4
  import { randomUUID } from "node:crypto";
@@ -141,8 +141,10 @@ function buildVideoFilter(hwaccel, pixel, outWidth, outHeight, maxW, gpuScaleFil
141
141
  * stream line from stderr to learn the exact output W×H (the broker does not
142
142
  * pass source geometry for the shm path). `-nostats` suppresses the periodic
143
143
  * `frame= …` counter while keeping the one-shot Output block.
144
- * - The input is `pipe:0` (Annex-B packets written to stdin); the output is raw
145
- * video on `pipe:1`.
144
+ * - The input is `pipe:0` (Annex-B packets written to stdin) in push mode, or
145
+ * `options.inputUrl` (the owner's restream, dialed by ffmpeg itself —
146
+ * `-rtsp_transport tcp` for rtsp URLs) in pull mode; the output is raw video
147
+ * on `pipe:1` either way.
146
148
  */
147
149
  function buildFfmpegDecodeArgs(config, options) {
148
150
  const inputFormat = codecToInputFormat(config.codec);
@@ -158,6 +160,16 @@ function buildFfmpegDecodeArgs(config, options) {
158
160
  if (needsHwDownload(hwaccel)) hwaccelArgs.push("-hwaccel_device", options.renderDevice ?? "/dev/dri/renderD128");
159
161
  if (keepFramesOnGpu(hwaccel, gpuScaleFilter)) hwaccelArgs.push("-hwaccel_output_format", hwaccel);
160
162
  }
163
+ const inputArgs = options.inputUrl !== void 0 ? [
164
+ ...options.inputUrl.startsWith("rtsp") ? ["-rtsp_transport", "tcp"] : [],
165
+ "-i",
166
+ options.inputUrl
167
+ ] : [
168
+ "-f",
169
+ inputFormat,
170
+ "-i",
171
+ "pipe:0"
172
+ ];
161
173
  return [
162
174
  ...logBannerArgs("info"),
163
175
  "-nostats",
@@ -170,10 +182,7 @@ function buildFfmpegDecodeArgs(config, options) {
170
182
  "-analyzeduration",
171
183
  "0",
172
184
  ...hwaccelArgs,
173
- "-f",
174
- inputFormat,
175
- "-i",
176
- "pipe:0",
185
+ ...inputArgs,
177
186
  "-vf",
178
187
  vf,
179
188
  "-f",
@@ -646,6 +655,16 @@ var noopLogger = {
646
655
  * hwaccel init failure (missing render node, unsupported codec on GPU, the
647
656
  * Intel `qsv` `code=171`) surfaces this early. */
648
657
  var HWACCEL_FALLBACK_WINDOW_MS = 4e3;
658
+ /**
659
+ * Delay before a PULL-MODE session re-dials its input URL after the child
660
+ * exits (RTSP EOF, restream teardown, network blip). Push mode never
661
+ * self-respawns on a plain exit — the broker's packet feed drives recovery —
662
+ * but a pull-mode child owns its own input, so the session must re-dial or a
663
+ * single EOF would silence it forever. The delay turns a hard-down source
664
+ * into a slow probe instead of a spawn storm; the consumer tearing the
665
+ * session down (`destroy`) cancels the pending re-dial.
666
+ */
667
+ var PULL_REDIAL_DELAY_MS = 3e3;
649
668
  var FfmpegDecoderSession = class {
650
669
  config;
651
670
  logger;
@@ -676,6 +695,15 @@ var FfmpegDecoderSession = class {
676
695
  process = null;
677
696
  spawnedAtMs = 0;
678
697
  /**
698
+ * PULL-MODE input URL (P2b). `null` = push mode (packets via stdin — the
699
+ * default). Set by {@link openStream}; every (re)spawn since reads this URL
700
+ * directly instead of `pipe:0`. A session is push-mode or pull-mode for its
701
+ * whole life — `openStream` on a packet-fed session throws.
702
+ */
703
+ pullUrl = null;
704
+ /** Pending pull-mode re-dial timer (armed on a pull child exit). */
705
+ pullRedialTimer = null;
706
+ /**
679
707
  * Splits ffmpeg's raw stdout byte stream into frames, filling each DIRECTLY
680
708
  * into its shm slot (zero-copy) — created once the output geometry is known.
681
709
  */
@@ -729,7 +757,25 @@ var FfmpegDecoderSession = class {
729
757
  return this.frameRingSink;
730
758
  }
731
759
  get isPullMode() {
732
- return false;
760
+ return this.pullUrl !== null;
761
+ }
762
+ /**
763
+ * Switch the session to PULL MODE: (re)spawn ffmpeg reading `url` directly
764
+ * (`-rtsp_transport tcp -i <url>` for rtsp) instead of stdin. The whole
765
+ * output path (filtergraph, rawvideo pipe, shm ring sink) is unchanged.
766
+ *
767
+ * - Calling it again re-dials with the FRESH url (a hub restream bounce
768
+ * mints a new token URL — never cache the old one).
769
+ * - Calling it on a session already fed via `pushPacket` throws: a session
770
+ * is push-mode or pull-mode, never both (mixing inputs would corrupt the
771
+ * decode).
772
+ */
773
+ async openStream(url) {
774
+ if (this.destroyed) return;
775
+ if (this.pullUrl === null && this.inputPackets > 0) throw new Error("ffmpeg decoder: openStream on a push-fed session — a session is push-mode or pull-mode, never both");
776
+ this.pullUrl = url;
777
+ this.logger.info("ffmpeg decoder: pull-mode openStream — dialing input URL", { meta: { url } });
778
+ this.spawnFfmpeg();
733
779
  }
734
780
  ensureFrameRingSink() {
735
781
  if (this.frameRingSink === null) {
@@ -747,6 +793,7 @@ var FfmpegDecoderSession = class {
747
793
  }
748
794
  spawnFfmpeg() {
749
795
  if (this.destroyed) return;
796
+ this.clearPullRedial();
750
797
  this.killFfmpeg();
751
798
  this.abortOpenSlot();
752
799
  this.splitter = null;
@@ -763,7 +810,8 @@ var FfmpegDecoderSession = class {
763
810
  }, {
764
811
  hwaccel: this.hwaccel,
765
812
  renderDevice: this.renderDevice,
766
- gpuScaleFilter: this.activeGpuScaleFilter
813
+ gpuScaleFilter: this.activeGpuScaleFilter,
814
+ ...this.pullUrl !== null ? { inputUrl: this.pullUrl } : {}
767
815
  });
768
816
  let child;
769
817
  try {
@@ -850,6 +898,27 @@ var FfmpegDecoderSession = class {
850
898
  signal,
851
899
  outputFrames: this.outputFrames
852
900
  } });
901
+ if (this.pullUrl !== null) {
902
+ this.clearPullRedial();
903
+ const timer = setTimeout(() => {
904
+ this.pullRedialTimer = null;
905
+ this.spawnFfmpeg();
906
+ }, PULL_REDIAL_DELAY_MS);
907
+ timer.unref?.();
908
+ this.pullRedialTimer = timer;
909
+ this.logger.info("ffmpeg decoder: pull-mode child exited — re-dial scheduled", { meta: {
910
+ code,
911
+ signal,
912
+ redialInMs: PULL_REDIAL_DELAY_MS
913
+ } });
914
+ }
915
+ }
916
+ /** Cancel a pending pull-mode re-dial, if armed. */
917
+ clearPullRedial() {
918
+ if (this.pullRedialTimer !== null) {
919
+ clearTimeout(this.pullRedialTimer);
920
+ this.pullRedialTimer = null;
921
+ }
853
922
  }
854
923
  handleStderr(data) {
855
924
  const text = data.toString();
@@ -954,6 +1023,7 @@ var FfmpegDecoderSession = class {
954
1023
  }
955
1024
  pushPacket(packet) {
956
1025
  if (this.destroyed) return;
1026
+ if (this.pullUrl !== null) return;
957
1027
  const stdin = this.process?.stdin;
958
1028
  if (!stdin) return;
959
1029
  this.inputPackets++;
@@ -986,6 +1056,7 @@ var FfmpegDecoderSession = class {
986
1056
  async destroy() {
987
1057
  if (this.destroyed) return;
988
1058
  this.destroyed = true;
1059
+ this.clearPullRedial();
989
1060
  this.killFfmpeg();
990
1061
  this.abortOpenSlot();
991
1062
  this.splitter = null;
@@ -1231,7 +1302,7 @@ var DecoderFfmpegAddon = class extends BaseAddon {
1231
1302
  return {
1232
1303
  id: "decoder-ffmpeg",
1233
1304
  name: "Decoder (ffmpeg)",
1234
- isPullMode: false,
1305
+ isPullMode: true,
1235
1306
  priority: 20
1236
1307
  };
1237
1308
  }
@@ -1342,8 +1413,9 @@ var DecoderFfmpegAddon = class extends BaseAddon {
1342
1413
  });
1343
1414
  }
1344
1415
  async openStream(input) {
1345
- if (!this.sessions.get(input.sessionId)) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
1346
- input.url;
1416
+ const session = this.sessions.get(input.sessionId);
1417
+ if (!session) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);
1418
+ await session.openStream(input.url);
1347
1419
  }
1348
1420
  async pullFrames(input) {
1349
1421
  if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-ffmpeg: unknown sessionId ${input.sessionId}`);