@camstack/addon-pipeline 1.2.137 → 1.2.139

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 (40) hide show
  1. package/dist/audio-analyzer/index.js +38 -86
  2. package/dist/audio-analyzer/index.mjs +36 -84
  3. package/dist/detection-pipeline/index.js +12 -12
  4. package/dist/detection-pipeline/index.mjs +5 -5
  5. package/dist/{dist-g0Or5_pR.mjs → dist-CTmOaCwF.mjs} +163 -3
  6. package/dist/{dist-4EMKvxCp.js → dist-WUZH-lAM.js} +168 -2
  7. package/dist/{event-loop-stall-monitor-CskL9Lgu.mjs → event-loop-stall-monitor-BMtR9qZR.mjs} +1 -1
  8. package/dist/{event-loop-stall-monitor-DDm07Ei6.js → event-loop-stall-monitor-CoM21UjL.js} +1 -1
  9. package/dist/{lazy-sharp-BlA9PxWk.js → lazy-sharp-1WF7ZnxZ.js} +1 -1
  10. package/dist/motion-wasm/index.js +2 -2
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-BOHn8gpk.js → node-BAo2ivVu.js} +1 -1
  13. package/dist/{node-Bo1g8hLO.mjs → node-CQcgxO8l.mjs} +1 -1
  14. package/dist/pipeline-runner/index.js +55 -12
  15. package/dist/pipeline-runner/index.mjs +54 -11
  16. package/dist/{process-memory-DmrHWybb.mjs → process-memory-BPEKERfe.mjs} +1 -1
  17. package/dist/{process-memory-C3084DOt.js → process-memory-BZvkQCLP.js} +1 -1
  18. package/dist/recorder/index.js +6 -6
  19. package/dist/recorder/index.mjs +3 -3
  20. package/dist/{segment-demux-js-BrQ_A0oi.js → segment-demux-js-B1L1pfoc.js} +1 -1
  21. package/dist/{segment-demux-js-C-a2pghM.mjs → segment-demux-js-CubhFNwb.mjs} +1 -1
  22. package/dist/session-decode/decode-worker-child.js +115 -5
  23. package/dist/session-decode/decode-worker-child.mjs +114 -4
  24. package/dist/stream-broker/_stub.js +2 -2
  25. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-AH6OJ3hJ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DJ4k5ZBb.mjs} +3 -3
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-xdmO8wYp.mjs +26 -0
  27. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DPpl_mtg.mjs +26 -0
  28. package/dist/stream-broker/demux-worker-child.js +1 -1
  29. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  30. package/dist/stream-broker/{hostInit-XNyn-FTM.mjs → hostInit-oy-XDnw6.mjs} +3 -3
  31. package/dist/stream-broker/index.js +170 -62
  32. package/dist/stream-broker/index.mjs +170 -62
  33. package/dist/stream-broker/remoteEntry.js +1 -1
  34. package/dist/{worker-protocol-BLfaD5uC.js → worker-protocol-C-0LYdSt.js} +2 -1
  35. package/dist/{worker-protocol-vc8WWhjx.mjs → worker-protocol-DsuI4ifI.mjs} +2 -1
  36. package/package.json +5 -3
  37. package/dist/addon-utils-C6b5Tnia.js +0 -625
  38. package/dist/addon-utils-C8KTuPdE.mjs +0 -583
  39. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQyiEd0l.mjs +0 -26
  40. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BrTM_tsb.mjs +0 -26
@@ -1,7 +1,7 @@
1
- import { $ as loadContributionCapability, B as defineCustomActions, Ct as errMsg, Gt as boolean, H as deriveDetailCropRect, Jt as literal, L as customAction, Nt as createEvent, Qt as string, Rt as nodePin, Ut as _enum, Wt as array, Xt as object, Yt as number, ct as pickDetailCropConvention, dt as pipelineRunnerCapability, en as EventCategory, kt as BaseAddon, lt as pickNativeLeaseOverride, m as DEVICE_BACKEND_TO_FORMAT, p as DEFAULT_NATIVE_LEASE_SETTINGS, qt as lazy, u as DEFAULT_DETAIL_CROP_CONVENTION } from "../dist-g0Or5_pR.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-Bo1g8hLO.mjs";
3
- import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-CskL9Lgu.mjs";
4
- import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-vc8WWhjx.mjs";
1
+ import { $ as loadContributionCapability, $t as string, At as BaseAddon, B as defineCustomActions, Ct as errMsg, Gt as array, H as deriveDetailCropRect, Jt as lazy, Kt as boolean, L as customAction, Pt as createEvent, Wt as _enum, Xt as number, Yt as literal, Zt as object, ct as pickDetailCropConvention, dt as pipelineRunnerCapability, lt as pickNativeLeaseOverride, m as DEVICE_BACKEND_TO_FORMAT, p as DEFAULT_NATIVE_LEASE_SETTINGS, tn as EventCategory, u as DEFAULT_DETAIL_CROP_CONVENTION, zt as nodePin } from "../dist-CTmOaCwF.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CQcgxO8l.mjs";
3
+ import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-BMtR9qZR.mjs";
4
+ import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-DsuI4ifI.mjs";
5
5
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
6
6
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
7
7
  import { n as withDetectionIntent } from "../restream-intent-B4BXZra7.mjs";
@@ -885,10 +885,29 @@ var DEFAULT_FIRST_FRAME_TIMEOUT_MS = 2e4;
885
885
  *
886
886
  * The first decodable frame is not a fixed cost: it is one IDR interval plus
887
887
  * every GOP the decoder has to discard. Measured on device 618 (2560x1440
888
- * h264, 8.0 s IDR interval, `Invalid NAL unit` / `corrupt decoded frame` in its
889
- * bitstream), dial → first decoded frame over the broker's own muted restream
890
- * was 1.8 / 6.5 / 7.0 / 7.2 / 9.2 / 10.0 / 10.8 / 12.9 / 14.9 / 20.0 / 30.3 /
891
- * >40 s across 12 trials — against a healthy 587's 2.0 s ± 0.02.
888
+ * h264, `Invalid NAL unit` / `corrupt decoded frame` in its bitstream), dial →
889
+ * first decoded frame over the broker's own muted restream was 1.8 / 6.5 / 7.0
890
+ * / 7.2 / 9.2 / 10.0 / 10.8 / 12.9 / 14.9 / 20.0 / 30.3 / >40 s across 12
891
+ * trials — against a healthy 587's 2.0 s ± 0.02.
892
+ *
893
+ * **The "8.0 s IDR interval" this used to attribute 618's spread to was wrong,
894
+ * and is corrected here rather than deleted** (2026-08-28): a stated number
895
+ * nobody re-derived reads as verification, and this one was used to argue that
896
+ * 618 was an encoder outlier. The fleet runs 100-frame GOPs at 25 fps — a
897
+ * **4 000 ms** IDR interval — and two independent readings of 6 h of live logs
898
+ * agree with the operator rather than with this docblock. First, the join-burst
899
+ * span logged on `restream prime (lat-probe)` is the time since the last IDR,
900
+ * uniform in `[0, IDR]` and censored at `PRIME_MAX_SPAN_MS`: its median is
901
+ * ~1 000 ms on eleven cameras, which is what `uniform[0, 4000]` conditioned on
902
+ * `≤2000` predicts. Second, `detectionIntentServed` over detection dials puts
903
+ * the SERVED fraction at 44-61 % on the bulk of the fleet, i.e. the ~50 %
904
+ * `P(span > 2000)` a 4 000 ms interval implies. 618 measures 51 %, which is the
905
+ * fleet norm. The genuine long-GOP outlier is **637** at 30 % served (⇒ ~6.7 s).
906
+ *
907
+ * So the spread is not this camera's encoder. It is D57's fixed 2 000 ms bound
908
+ * withholding the join burst WHOLE on roughly half of every 4 s-GOP camera's
909
+ * dials — see `join-burst-bound.ts`, which states the arithmetic, and
910
+ * `docs/architecture/load-ledger.md` (2026-08-28) for the measurement.
892
911
  *
893
912
  * Abandoning at 20 s was therefore not a timeout, it was a coin flip, and the
894
913
  * recovery made it worse: ending the session RE-DIALS, which throws the
@@ -924,6 +943,7 @@ var SessionDecodeSession = class {
924
943
  firstFrameTimeoutMs;
925
944
  frameTimeoutMs;
926
945
  firstFrameMaxWaitMs;
946
+ onDialTiming;
927
947
  pendingPull = null;
928
948
  pendingToBuffers = /* @__PURE__ */ new Map();
929
949
  /** Outstanding `nativeCrop` requests, keyed by the monotonic `requestId`. */
@@ -937,13 +957,14 @@ var SessionDecodeSession = class {
937
957
  firstFrameReceived = false;
938
958
  /** `Date.now()` of the FIRST `pull` — the origin of the cold-start ceiling. */
939
959
  firstPullAt = 0;
940
- constructor(child, logger, deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs) {
960
+ constructor(child, logger, deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs, onDialTiming) {
941
961
  this.child = child;
942
962
  this.logger = logger;
943
963
  this.deviceId = deviceId;
944
964
  this.firstFrameTimeoutMs = firstFrameTimeoutMs;
945
965
  this.frameTimeoutMs = frameTimeoutMs;
946
966
  this.firstFrameMaxWaitMs = firstFrameMaxWaitMs;
967
+ this.onDialTiming = onDialTiming;
947
968
  this.child.on("message", (m) => this.handleMessage(m));
948
969
  this.child.on("exit", (m) => this.handleExit(toWorkerExitInfo(m)));
949
970
  }
@@ -1119,6 +1140,15 @@ var SessionDecodeSession = class {
1119
1140
  case "log":
1120
1141
  this.logger[raw.level](raw.message);
1121
1142
  return;
1143
+ case "dialTiming":
1144
+ this.onDialTiming?.({
1145
+ hwContextMs: raw.hwContextMs,
1146
+ demuxerOpenMs: raw.demuxerOpenMs,
1147
+ decoderCreateMs: raw.decoderCreateMs,
1148
+ firstDecodeMs: raw.firstDecodeMs,
1149
+ streamInfoProbed: raw.streamInfoProbed
1150
+ });
1151
+ return;
1122
1152
  }
1123
1153
  }
1124
1154
  handleExit(info) {
@@ -1343,7 +1373,7 @@ function createSessionDecodeCoordinator(deps) {
1343
1373
  const frameTimeoutMs = deps.frameTimeoutMs ?? DEFAULT_FRAME_TIMEOUT_MS;
1344
1374
  const firstFrameMaxWaitMs = Math.max(deps.firstFrameMaxWaitMs ?? DEFAULT_FIRST_FRAME_MAX_WAIT_MS, firstFrameTimeoutMs);
1345
1375
  return { async *generateVideoFrames(source, opts) {
1346
- const session = new SessionDecodeSession(deps.fork(source), deps.logger, source.deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs);
1376
+ const session = new SessionDecodeSession(deps.fork(source), deps.logger, source.deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs, deps.onDialTiming);
1347
1377
  session.start(source, opts);
1348
1378
  try {
1349
1379
  while (true) {
@@ -1397,9 +1427,13 @@ async function toDecodedFrame(frame, format, viewSpec) {
1397
1427
  */
1398
1428
  function startSessionDecodePump(deps) {
1399
1429
  const format = deps.opts.format ?? "rgb";
1430
+ let dialTiming = null;
1400
1431
  const iterator = createSessionDecodeCoordinator({
1401
1432
  fork: deps.fork,
1402
1433
  logger: deps.logger,
1434
+ onDialTiming: (timing) => {
1435
+ dialTiming = timing;
1436
+ },
1403
1437
  ...deps.firstFrameTimeoutMs !== void 0 ? { firstFrameTimeoutMs: deps.firstFrameTimeoutMs } : {},
1404
1438
  ...deps.frameTimeoutMs !== void 0 ? { frameTimeoutMs: deps.frameTimeoutMs } : {}
1405
1439
  }).generateVideoFrames(deps.source, deps.opts);
@@ -1430,9 +1464,11 @@ function startSessionDecodePump(deps) {
1430
1464
  if (!firstFrameSeen) {
1431
1465
  firstFrameSeen = true;
1432
1466
  releaseSpawnSlotOnce();
1467
+ const dial = dialTiming;
1433
1468
  deps.onFirstFrame?.({
1434
1469
  spawnWaitMs: spawnGrantedAt - pumpStartedAt,
1435
- forkToFrameMs: Date.now() - spawnGrantedAt
1470
+ forkToFrameMs: Date.now() - spawnGrantedAt,
1471
+ ...dial !== null ? { dial } : {}
1436
1472
  });
1437
1473
  }
1438
1474
  if (stopped) {
@@ -7339,6 +7375,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
7339
7375
  dialMs,
7340
7376
  spawnWaitMs: timing.spawnWaitMs,
7341
7377
  forkToFrameMs: timing.forkToFrameMs,
7378
+ ...timing.dial ? {
7379
+ hwContextMs: timing.dial.hwContextMs,
7380
+ demuxerOpenMs: timing.dial.demuxerOpenMs,
7381
+ decoderCreateMs: timing.dial.decoderCreateMs,
7382
+ firstDecodeMs: timing.dial.firstDecodeMs,
7383
+ streamInfoProbed: timing.dial.streamInfoProbed
7384
+ } : {},
7342
7385
  firstFrameMs: Date.now() - requestedAt,
7343
7386
  ...phaseActiveAt !== void 0 ? { sincePhaseActiveMs: Date.now() - phaseActiveAt } : {}
7344
7387
  }
@@ -1,4 +1,4 @@
1
- import { at as parseProcStatus } from "./dist-g0Or5_pR.mjs";
1
+ import { at as parseProcStatus } from "./dist-CTmOaCwF.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
4
  //#region src/shared/node-topology-platform.ts
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-4EMKvxCp.js");
1
+ const require_dist = require("./dist-WUZH-lAM.js");
2
2
  let node_fs = require("node:fs");
3
3
  node_fs = require_dist.__toESM(node_fs);
4
4
  let node_child_process = require("node:child_process");
@@ -1,8 +1,7 @@
1
- const require_dist = require("../dist-4EMKvxCp.js");
2
- const require_node = require("../node-BOHn8gpk.js");
1
+ const require_dist = require("../dist-WUZH-lAM.js");
2
+ const require_node = require("../node-BAo2ivVu.js");
3
3
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
4
4
  const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
5
- const require_addon_utils = require("../addon-utils-C6b5Tnia.js");
6
5
  const require_retire_root_keys = require("../retire-root-keys-KE6D6Xh_.js");
7
6
  let node_crypto = require("node:crypto");
8
7
  let node_fs = require("node:fs");
@@ -10,6 +9,7 @@ let node_path = require("node:path");
10
9
  node_path = require_dist.__toESM(node_path);
11
10
  let node_child_process = require("node:child_process");
12
11
  let node_fs_promises = require("node:fs/promises");
12
+ let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
13
13
  //#region src/recorder/event-capture.ts
14
14
  function markerFromMotion(data, pad) {
15
15
  if (!data.detected) return null;
@@ -12972,7 +12972,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
12972
12972
  source: this.calendar,
12973
12973
  mfraFor: (deviceId, profile, startMs) => this.mfraTables.get(deviceId, profile, startMs),
12974
12974
  onLog: (msg, meta) => this.ctx.logger.debug(msg, { meta })
12975
- }, require_addon_utils.createFileDataPlaneHandler({ getRoots: () => this.playbackRoots() }));
12975
+ }, (0, _camstack_system_addon_utils.createFileDataPlaneHandler)({ getRoots: () => this.playbackRoots() }));
12976
12976
  const served = await this.ctx.dataPlane?.serve({
12977
12977
  prefix: PLAYBACK_PREFIX,
12978
12978
  access: "authenticated",
@@ -13816,11 +13816,11 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
13816
13816
  return;
13817
13817
  }
13818
13818
  const baseHeaders = {
13819
- "content-type": require_addon_utils.contentTypeFor(absPath),
13819
+ "content-type": (0, _camstack_system_addon_utils.contentTypeFor)(absPath),
13820
13820
  "accept-ranges": "bytes",
13821
13821
  "cache-control": "no-cache"
13822
13822
  };
13823
- const range = require_addon_utils.parseRangeHeader(req.headers.range, size);
13823
+ const range = (0, _camstack_system_addon_utils.parseRangeHeader)(req.headers.range, size);
13824
13824
  const servesFinalByte = range ? range.end === size - 1 : true;
13825
13825
  const recordDownloadOnFinish = () => {
13826
13826
  if (req.method !== "GET" || !servesFinalByte) return;
@@ -1,14 +1,14 @@
1
- import { $ as loadContributionCapability, B as defineCustomActions, Ct as errMsg, E as RecordingConfigSchema, Gt as boolean, J as evaluateSensorEdge, L as customAction, Mt as DeviceType, N as batteryCapability, Pt as hydrateSchema, Q as isFirstLevelMacroClass, Qt as string, Rt as nodePin, T as RECORDING_EXPORT_MAX_READ_BYTES, U as deriveRecordingMode, V as deriveBatteryPresence, Vt as selectAssignedProfileSlots, Wt as array, Xt as object, Yt as number, Zt as record, _ as ExportRecordSchema, _t as resolveRecordingProfiles, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, en as EventCategory, ft as recordingCapability, h as EVENT_PAD_MS, i as BatteryStatusSchema, jt as DeviceFeature, kt as BaseAddon, pt as recordingExportCapability, x as OpsLogEntrySchema, yt as storageEvictableCapability } from "../dist-g0Or5_pR.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-Bo1g8hLO.mjs";
1
+ import { $ as loadContributionCapability, $t as string, At as BaseAddon, B as defineCustomActions, Ct as errMsg, E as RecordingConfigSchema, Ft as hydrateSchema, Gt as array, Ht as selectAssignedProfileSlots, J as evaluateSensorEdge, Kt as boolean, L as customAction, Mt as DeviceFeature, N as batteryCapability, Nt as DeviceType, Q as isFirstLevelMacroClass, Qt as record, T as RECORDING_EXPORT_MAX_READ_BYTES, U as deriveRecordingMode, V as deriveBatteryPresence, Xt as number, Zt as object, _ as ExportRecordSchema, _t as resolveRecordingProfiles, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, ft as recordingCapability, h as EVENT_PAD_MS, i as BatteryStatusSchema, pt as recordingExportCapability, tn as EventCategory, x as OpsLogEntrySchema, yt as storageEvictableCapability, zt as nodePin } from "../dist-CTmOaCwF.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CQcgxO8l.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { r as withRecordingIntent } from "../restream-intent-B4BXZra7.mjs";
5
- import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../addon-utils-C8KTuPdE.mjs";
6
5
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DMolfhsP.mjs";
7
6
  import { randomUUID } from "node:crypto";
8
7
  import { createReadStream, promises, realpathSync } from "node:fs";
9
8
  import path from "node:path";
10
9
  import { spawn } from "node:child_process";
11
10
  import { open } from "node:fs/promises";
11
+ import { contentTypeFor, createFileDataPlaneHandler, parseRangeHeader } from "@camstack/system/addon-utils";
12
12
  //#region src/recorder/event-capture.ts
13
13
  function markerFromMotion(data, pad) {
14
14
  if (!data.detected) return null;
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-4EMKvxCp.js");
1
+ const require_dist = require("./dist-WUZH-lAM.js");
2
2
  let node_child_process = require("node:child_process");
3
3
  //#region ../../node_modules/mp4box/dist/mp4box.all.js
4
4
  var require_mp4box_all = /* @__PURE__ */ require_dist.__commonJSMin(((exports) => {
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
2
- import { Ct as errMsg } from "./dist-g0Or5_pR.mjs";
2
+ import { Ct as errMsg } from "./dist-CTmOaCwF.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region ../../node_modules/mp4box/dist/mp4box.all.js
5
5
  var require_mp4box_all = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_worker_protocol = require("../worker-protocol-BLfaD5uC.js");
3
- const require_lazy_sharp = require("../lazy-sharp-BlA9PxWk.js");
2
+ const require_worker_protocol = require("../worker-protocol-C-0LYdSt.js");
3
+ const require_lazy_sharp = require("../lazy-sharp-1WF7ZnxZ.js");
4
4
  //#region src/session-decode/color-conversion.ts
5
5
  /**
6
6
  * Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
@@ -1092,6 +1092,56 @@ function resolveRgbCropSource(plane, planeStride, region) {
1092
1092
  };
1093
1093
  }
1094
1094
  //#endregion
1095
+ //#region src/session-decode/pull-demuxer-open.ts
1096
+ /**
1097
+ * Open the pull input, skipping `avformat_find_stream_info` when the opened
1098
+ * container already describes its video stream.
1099
+ *
1100
+ * The demuxer is returned even when no video stream was found: disposing it is
1101
+ * the caller's existing `closeInput()` responsibility, and taking that over
1102
+ * here would double-free.
1103
+ */
1104
+ async function openPullDemuxer(params) {
1105
+ const startedAt = Date.now();
1106
+ if (params.forceStreamInfoProbe) return {
1107
+ demuxer: await params.open(params.url, {
1108
+ options: params.demuxerOptions,
1109
+ signal: params.signal,
1110
+ skipStreamInfo: false
1111
+ }),
1112
+ demuxerOpenMs: Date.now() - startedAt,
1113
+ streamInfoProbed: true
1114
+ };
1115
+ const demuxer = await params.open(params.url, {
1116
+ options: params.demuxerOptions,
1117
+ signal: params.signal,
1118
+ skipStreamInfo: true
1119
+ });
1120
+ const videoStream = demuxer.video();
1121
+ if (videoStream && videoStream.codecpar.extradataSize > 0) {
1122
+ const parsed = videoStream.codecpar.parseExtradata();
1123
+ if (parsed < 0) params.note(`decode-worker-child: SPS parse from SDP extradata failed (${parsed}) — decoding anyway, geometry comes from the first decoded frame
1124
+ `);
1125
+ return {
1126
+ demuxer,
1127
+ demuxerOpenMs: Date.now() - startedAt,
1128
+ streamInfoProbed: false
1129
+ };
1130
+ }
1131
+ params.note(`decode-worker-child: opened input did not describe a video stream (videoStream=${videoStream ? "present" : "absent"}, extradataSize=${videoStream?.codecpar.extradataSize ?? 0}) — re-opening WITH the stream-info probe
1132
+ `);
1133
+ demuxer[Symbol.dispose]?.();
1134
+ return {
1135
+ demuxer: await params.open(params.url, {
1136
+ options: params.demuxerOptions,
1137
+ signal: params.signal,
1138
+ skipStreamInfo: false
1139
+ }),
1140
+ demuxerOpenMs: Date.now() - startedAt,
1141
+ streamInfoProbed: true
1142
+ };
1143
+ }
1144
+ //#endregion
1095
1145
  //#region src/session-decode/scene-tile.ts
1096
1146
  /**
1097
1147
  * The whole frame, in the same normalized space every other cut uses. Identical
@@ -1517,6 +1567,17 @@ function errMessage(err) {
1517
1567
  * addon consumer of that package). `analyzeduration`/`probesize` stay small
1518
1568
  * but non-zero — zeroing them starves the Reolink rfc4571 restream demuxer.
1519
1569
  */
1570
+ /**
1571
+ * Restore the unconditional `avformat_find_stream_info` probe on the pull dial.
1572
+ *
1573
+ * Inline rollback (no deploy) for the probe skip, and the A/B that MEASURES it:
1574
+ * `demuxerOpenMs` with this set minus `demuxerOpenMs` with it clear is the
1575
+ * probe's cost on that camera. Read once at module load — a knob that can change
1576
+ * mid-session is a knob with two values during a session
1577
+ * (`native-lease-config.ts` makes the same argument for the lease knobs).
1578
+ * See `pull-demuxer-open.ts`.
1579
+ */
1580
+ var FORCE_STREAM_INFO_PROBE = process.env["CAMSTACK_DECODE_PROBE_STREAM_INFO"] === "1";
1520
1581
  function buildDemuxerOptions() {
1521
1582
  return {
1522
1583
  rtsp_transport: "tcp",
@@ -1753,6 +1814,21 @@ var DecodeWorkerChild = class {
1753
1814
  leaseDownloadFilterBuiltAt = 0;
1754
1815
  dialStartedAt = 0;
1755
1816
  /**
1817
+ * `HardwareContext.create` cost, measured once per worker.
1818
+ *
1819
+ * Reported rather than assumed. The hw device is resolved BEFORE the first
1820
+ * dial and reused across re-dials, so if it is cheap there is nothing to
1821
+ * overlap with `Demuxer.open`; if it is not, this is the number that says so.
1822
+ * Nothing else in this path could answer that question.
1823
+ */
1824
+ hwContextMs = 0;
1825
+ /**
1826
+ * Guards {@link WorkerDialTiming} to ONE report per worker: it describes the
1827
+ * dial that produced the first decoded frame, and a re-dial's numbers would
1828
+ * silently overwrite the cold start the operator is asking about.
1829
+ */
1830
+ dialTimingSent = false;
1831
+ /**
1756
1832
  * Download failures within the CURRENT dial. Forensics (2026-07-16 late,
1757
1833
  * dev 618): failures cluster in the FIRST seconds of a dial after 4-10
1758
1834
  * successful uses — the decoder re-creates its hw_frames_ctx shortly after
@@ -2597,7 +2673,9 @@ var DecodeWorkerChild = class {
2597
2673
  this.nav = nav;
2598
2674
  this.consts = C;
2599
2675
  nav.Log.setLevel(C.AV_LOG_FATAL);
2676
+ const hwContextStartedAt = Date.now();
2600
2677
  await this.ensureHwContext(nav, C);
2678
+ this.hwContextMs = Date.now() - hwContextStartedAt;
2601
2679
  while (!this.stopped) {
2602
2680
  try {
2603
2681
  await this.dialAndDecode(nav, C, url);
@@ -2638,10 +2716,15 @@ var DecodeWorkerChild = class {
2638
2716
  this.dialStartedAt = Date.now();
2639
2717
  this.leaseDownloadFailuresThisDial = 0;
2640
2718
  this.abortController = new AbortController();
2641
- const demuxer = await nav.Demuxer.open(url, {
2642
- options: buildDemuxerOptions(),
2643
- signal: this.abortController.signal
2719
+ const opened = await openPullDemuxer({
2720
+ url,
2721
+ demuxerOptions: buildDemuxerOptions(),
2722
+ signal: this.abortController.signal,
2723
+ open: (target, options) => nav.Demuxer.open(target, options),
2724
+ forceStreamInfoProbe: FORCE_STREAM_INFO_PROBE,
2725
+ note: (message) => this.emitStderr(message)
2644
2726
  });
2727
+ const demuxer = opened.demuxer;
2645
2728
  if (this.stopped) {
2646
2729
  demuxer[Symbol.dispose]?.();
2647
2730
  return;
@@ -2650,24 +2733,51 @@ var DecodeWorkerChild = class {
2650
2733
  const videoStream = demuxer.video();
2651
2734
  if (!videoStream) throw new Error("decode-worker-child: pull input has no video stream");
2652
2735
  const wantHwFrames = this.hwContext !== null && this.hwFilterDecision !== "software";
2736
+ const decoderStartedAt = Date.now();
2653
2737
  const decoder = await nav.Decoder.create(videoStream, {
2654
2738
  ...this.hwContext ? { hardware: this.hwContext } : {},
2655
2739
  ...wantHwFrames ? {} : { rescale: { pixelFormat: C.AV_PIX_FMT_YUV420P } },
2656
2740
  exitOnError: false
2657
2741
  });
2742
+ const decoderCreateMs = Date.now() - decoderStartedAt;
2658
2743
  if (this.stopped) {
2659
2744
  decoder[Symbol.dispose]?.();
2660
2745
  return;
2661
2746
  }
2662
2747
  this.decoder = decoder;
2748
+ const decodeStartedAt = Date.now();
2663
2749
  for await (const frame of decoder.frames(demuxer.packets(videoStream.index))) {
2664
2750
  if (this.stopped) break;
2665
2751
  if (!frame) continue;
2752
+ this.reportDialTiming({
2753
+ hwContextMs: this.hwContextMs,
2754
+ demuxerOpenMs: opened.demuxerOpenMs,
2755
+ decoderCreateMs,
2756
+ firstDecodeMs: Date.now() - decodeStartedAt,
2757
+ streamInfoProbed: opened.streamInfoProbed
2758
+ });
2666
2759
  this.consumeDecodedFrame(frame);
2667
2760
  if (this.redialAsSoftware) break;
2668
2761
  }
2669
2762
  }
2670
2763
  /**
2764
+ * Send the cold-start split ONCE — for the dial that produced the first
2765
+ * decoded frame of this worker's life.
2766
+ *
2767
+ * Not sent on later dials on purpose: a re-dial's numbers are a different
2768
+ * question (a degrade-to-software rebuild, a reconnect), and letting them
2769
+ * overwrite the cold start would make the one line the operator reads mean
2770
+ * two different things depending on what happened afterwards.
2771
+ */
2772
+ reportDialTiming(timing) {
2773
+ if (this.dialTimingSent) return;
2774
+ this.dialTimingSent = true;
2775
+ this.send({
2776
+ kind: "dialTiming",
2777
+ ...timing
2778
+ });
2779
+ }
2780
+ /**
2671
2781
  * Route a decoded frame according to the session's {@link hwFilterDecision}.
2672
2782
  * On the software path a frame is published straight to the latest-wins slot;
2673
2783
  * on the HW path the FIRST GPU surface is probed through the GPU filtergraph
@@ -1,4 +1,4 @@
1
- import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-vc8WWhjx.mjs";
1
+ import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-DsuI4ifI.mjs";
2
2
  import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  //#region src/session-decode/color-conversion.ts
4
4
  /**
@@ -1091,6 +1091,56 @@ function resolveRgbCropSource(plane, planeStride, region) {
1091
1091
  };
1092
1092
  }
1093
1093
  //#endregion
1094
+ //#region src/session-decode/pull-demuxer-open.ts
1095
+ /**
1096
+ * Open the pull input, skipping `avformat_find_stream_info` when the opened
1097
+ * container already describes its video stream.
1098
+ *
1099
+ * The demuxer is returned even when no video stream was found: disposing it is
1100
+ * the caller's existing `closeInput()` responsibility, and taking that over
1101
+ * here would double-free.
1102
+ */
1103
+ async function openPullDemuxer(params) {
1104
+ const startedAt = Date.now();
1105
+ if (params.forceStreamInfoProbe) return {
1106
+ demuxer: await params.open(params.url, {
1107
+ options: params.demuxerOptions,
1108
+ signal: params.signal,
1109
+ skipStreamInfo: false
1110
+ }),
1111
+ demuxerOpenMs: Date.now() - startedAt,
1112
+ streamInfoProbed: true
1113
+ };
1114
+ const demuxer = await params.open(params.url, {
1115
+ options: params.demuxerOptions,
1116
+ signal: params.signal,
1117
+ skipStreamInfo: true
1118
+ });
1119
+ const videoStream = demuxer.video();
1120
+ if (videoStream && videoStream.codecpar.extradataSize > 0) {
1121
+ const parsed = videoStream.codecpar.parseExtradata();
1122
+ if (parsed < 0) params.note(`decode-worker-child: SPS parse from SDP extradata failed (${parsed}) — decoding anyway, geometry comes from the first decoded frame
1123
+ `);
1124
+ return {
1125
+ demuxer,
1126
+ demuxerOpenMs: Date.now() - startedAt,
1127
+ streamInfoProbed: false
1128
+ };
1129
+ }
1130
+ params.note(`decode-worker-child: opened input did not describe a video stream (videoStream=${videoStream ? "present" : "absent"}, extradataSize=${videoStream?.codecpar.extradataSize ?? 0}) — re-opening WITH the stream-info probe
1131
+ `);
1132
+ demuxer[Symbol.dispose]?.();
1133
+ return {
1134
+ demuxer: await params.open(params.url, {
1135
+ options: params.demuxerOptions,
1136
+ signal: params.signal,
1137
+ skipStreamInfo: false
1138
+ }),
1139
+ demuxerOpenMs: Date.now() - startedAt,
1140
+ streamInfoProbed: true
1141
+ };
1142
+ }
1143
+ //#endregion
1094
1144
  //#region src/session-decode/scene-tile.ts
1095
1145
  /**
1096
1146
  * The whole frame, in the same normalized space every other cut uses. Identical
@@ -1516,6 +1566,17 @@ function errMessage(err) {
1516
1566
  * addon consumer of that package). `analyzeduration`/`probesize` stay small
1517
1567
  * but non-zero — zeroing them starves the Reolink rfc4571 restream demuxer.
1518
1568
  */
1569
+ /**
1570
+ * Restore the unconditional `avformat_find_stream_info` probe on the pull dial.
1571
+ *
1572
+ * Inline rollback (no deploy) for the probe skip, and the A/B that MEASURES it:
1573
+ * `demuxerOpenMs` with this set minus `demuxerOpenMs` with it clear is the
1574
+ * probe's cost on that camera. Read once at module load — a knob that can change
1575
+ * mid-session is a knob with two values during a session
1576
+ * (`native-lease-config.ts` makes the same argument for the lease knobs).
1577
+ * See `pull-demuxer-open.ts`.
1578
+ */
1579
+ var FORCE_STREAM_INFO_PROBE = process.env["CAMSTACK_DECODE_PROBE_STREAM_INFO"] === "1";
1519
1580
  function buildDemuxerOptions() {
1520
1581
  return {
1521
1582
  rtsp_transport: "tcp",
@@ -1752,6 +1813,21 @@ var DecodeWorkerChild = class {
1752
1813
  leaseDownloadFilterBuiltAt = 0;
1753
1814
  dialStartedAt = 0;
1754
1815
  /**
1816
+ * `HardwareContext.create` cost, measured once per worker.
1817
+ *
1818
+ * Reported rather than assumed. The hw device is resolved BEFORE the first
1819
+ * dial and reused across re-dials, so if it is cheap there is nothing to
1820
+ * overlap with `Demuxer.open`; if it is not, this is the number that says so.
1821
+ * Nothing else in this path could answer that question.
1822
+ */
1823
+ hwContextMs = 0;
1824
+ /**
1825
+ * Guards {@link WorkerDialTiming} to ONE report per worker: it describes the
1826
+ * dial that produced the first decoded frame, and a re-dial's numbers would
1827
+ * silently overwrite the cold start the operator is asking about.
1828
+ */
1829
+ dialTimingSent = false;
1830
+ /**
1755
1831
  * Download failures within the CURRENT dial. Forensics (2026-07-16 late,
1756
1832
  * dev 618): failures cluster in the FIRST seconds of a dial after 4-10
1757
1833
  * successful uses — the decoder re-creates its hw_frames_ctx shortly after
@@ -2596,7 +2672,9 @@ var DecodeWorkerChild = class {
2596
2672
  this.nav = nav;
2597
2673
  this.consts = C;
2598
2674
  nav.Log.setLevel(C.AV_LOG_FATAL);
2675
+ const hwContextStartedAt = Date.now();
2599
2676
  await this.ensureHwContext(nav, C);
2677
+ this.hwContextMs = Date.now() - hwContextStartedAt;
2600
2678
  while (!this.stopped) {
2601
2679
  try {
2602
2680
  await this.dialAndDecode(nav, C, url);
@@ -2637,10 +2715,15 @@ var DecodeWorkerChild = class {
2637
2715
  this.dialStartedAt = Date.now();
2638
2716
  this.leaseDownloadFailuresThisDial = 0;
2639
2717
  this.abortController = new AbortController();
2640
- const demuxer = await nav.Demuxer.open(url, {
2641
- options: buildDemuxerOptions(),
2642
- signal: this.abortController.signal
2718
+ const opened = await openPullDemuxer({
2719
+ url,
2720
+ demuxerOptions: buildDemuxerOptions(),
2721
+ signal: this.abortController.signal,
2722
+ open: (target, options) => nav.Demuxer.open(target, options),
2723
+ forceStreamInfoProbe: FORCE_STREAM_INFO_PROBE,
2724
+ note: (message) => this.emitStderr(message)
2643
2725
  });
2726
+ const demuxer = opened.demuxer;
2644
2727
  if (this.stopped) {
2645
2728
  demuxer[Symbol.dispose]?.();
2646
2729
  return;
@@ -2649,24 +2732,51 @@ var DecodeWorkerChild = class {
2649
2732
  const videoStream = demuxer.video();
2650
2733
  if (!videoStream) throw new Error("decode-worker-child: pull input has no video stream");
2651
2734
  const wantHwFrames = this.hwContext !== null && this.hwFilterDecision !== "software";
2735
+ const decoderStartedAt = Date.now();
2652
2736
  const decoder = await nav.Decoder.create(videoStream, {
2653
2737
  ...this.hwContext ? { hardware: this.hwContext } : {},
2654
2738
  ...wantHwFrames ? {} : { rescale: { pixelFormat: C.AV_PIX_FMT_YUV420P } },
2655
2739
  exitOnError: false
2656
2740
  });
2741
+ const decoderCreateMs = Date.now() - decoderStartedAt;
2657
2742
  if (this.stopped) {
2658
2743
  decoder[Symbol.dispose]?.();
2659
2744
  return;
2660
2745
  }
2661
2746
  this.decoder = decoder;
2747
+ const decodeStartedAt = Date.now();
2662
2748
  for await (const frame of decoder.frames(demuxer.packets(videoStream.index))) {
2663
2749
  if (this.stopped) break;
2664
2750
  if (!frame) continue;
2751
+ this.reportDialTiming({
2752
+ hwContextMs: this.hwContextMs,
2753
+ demuxerOpenMs: opened.demuxerOpenMs,
2754
+ decoderCreateMs,
2755
+ firstDecodeMs: Date.now() - decodeStartedAt,
2756
+ streamInfoProbed: opened.streamInfoProbed
2757
+ });
2665
2758
  this.consumeDecodedFrame(frame);
2666
2759
  if (this.redialAsSoftware) break;
2667
2760
  }
2668
2761
  }
2669
2762
  /**
2763
+ * Send the cold-start split ONCE — for the dial that produced the first
2764
+ * decoded frame of this worker's life.
2765
+ *
2766
+ * Not sent on later dials on purpose: a re-dial's numbers are a different
2767
+ * question (a degrade-to-software rebuild, a reconnect), and letting them
2768
+ * overwrite the cold start would make the one line the operator reads mean
2769
+ * two different things depending on what happened afterwards.
2770
+ */
2771
+ reportDialTiming(timing) {
2772
+ if (this.dialTimingSent) return;
2773
+ this.dialTimingSent = true;
2774
+ this.send({
2775
+ kind: "dialTiming",
2776
+ ...timing
2777
+ });
2778
+ }
2779
+ /**
2670
2780
  * Route a decoded frame according to the session's {@link hwFilterDecision}.
2671
2781
  * On the software path a frame is published straight to the latest-wins slot;
2672
2782
  * on the HW path the FIRST GPU surface is probed through the GPU filtergraph
@@ -1,7 +1,7 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BrTM_tsb.mjs";
1
+ import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DPpl_mtg.mjs";
2
2
  import { a as p, i as m, n as h, o as g, r as _, t as v } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
3
3
  import { n as y, r as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
4
- import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQyiEd0l.mjs";
4
+ import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-xdmO8wYp.mjs";
5
5
  import { n as C, t as w } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
6
6
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
7
7
  var T = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), E = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), D = (e) => {
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.39",
6
+ version: "1.2.41",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_stream_broker_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.117",
21
+ version: "1.2.119",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.80",
36
+ version: "1.2.82",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",