@camstack/addon-pipeline 1.2.366 → 1.2.368

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 (35) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-C34VqcPo.js → custom-model-catalog-BY-aEI2s.js} +2 -2
  4. package/dist/{custom-model-catalog-CtpK3FKL.mjs → custom-model-catalog-Bnel86hH.mjs} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-BshC0NZR.mjs → dist-5AhGOWWu.mjs} +20270 -1345
  8. package/dist/{dist-i4kS_3O7.js → dist-DDXsZaRH.js} +20270 -1345
  9. package/dist/{model-catalogs-Cy7mZRWh.js → model-catalogs-DJN58c_U.js} +1 -1
  10. package/dist/{model-catalogs-DNx5ATgm.mjs → model-catalogs-DWR9EsGI.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-TzgGsCvW.js → node-CiciZWts.js} +1 -1
  14. package/dist/{node-Dqmrb990.mjs → node-DqZJFOzR.mjs} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-DLCPFTGC.js → process-memory-BRu19Dgr.js} +1 -1
  18. package/dist/{process-memory-DyfrbFY4.mjs → process-memory-Batknjjt.mjs} +1 -1
  19. package/dist/recorder/index.js +3 -2
  20. package/dist/recorder/index.mjs +3 -2
  21. package/dist/{segment-demux-js-C9AYdKkD.js → segment-demux-js-0CRf7GAb.js} +2 -2
  22. package/dist/{segment-demux-js-COoPqx93.mjs → segment-demux-js-dlhO3Q8W.mjs} +2 -2
  23. package/dist/stream-broker/_stub.js +83 -83
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVgnss_e.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CSajlHKC.mjs} +2 -2
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BfVREmhg.mjs +26 -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-5KL24-a_.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-ZvA7Cl_1.mjs → hostInit-KJ1eka58.mjs} +2 -2
  30. package/dist/stream-broker/index.js +240 -65
  31. package/dist/stream-broker/index.mjs +240 -65
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DWI1BCfa.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-BUBmyDde.mjs +0 -26
@@ -3,12 +3,12 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-i4kS_3O7.js");
7
- const require_node = require("../node-TzgGsCvW.js");
6
+ const require_dist = require("../dist-DDXsZaRH.js");
7
+ const require_node = require("../node-CiciZWts.js");
8
8
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
9
9
  const require_remote_restream = require("../remote-restream-CO36Sr30.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
11
- const require_segment_demux_js = require("../segment-demux-js-C9AYdKkD.js");
11
+ const require_segment_demux_js = require("../segment-demux-js-0CRf7GAb.js");
12
12
  let node_os = require("node:os");
13
13
  node_os = require_chunk.__toESM(node_os);
14
14
  let node_crypto = require("node:crypto");
@@ -22,6 +22,7 @@ let node_fs_promises = require("node:fs/promises");
22
22
  let node_url = require("node:url");
23
23
  let node_module = require("node:module");
24
24
  let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
25
+ let node_v8 = require("node:v8");
25
26
  let node_net = require("node:net");
26
27
  node_net = require_chunk.__toESM(node_net);
27
28
  let node_dgram = require("node:dgram");
@@ -3086,6 +3087,25 @@ var RtpPacketizer = class {
3086
3087
  };
3087
3088
  //#endregion
3088
3089
  //#region src/stream-broker/stream-broker/broker-mem-probe.ts
3090
+ /**
3091
+ * broker-mem-probe — what owns the broker runner's NATIVE memory.
3092
+ *
3093
+ * The runner's `[mem]` heartbeat (`kernel/heap-watch.ts`) says how big the
3094
+ * process is; it cannot say who holds it. On the hub, `stream-broker` grew
3095
+ * 250 -> 1015 MB in steps while `heapUsed` stayed at ~90 MB and `arrayBuffers`
3096
+ * at ~150 MB, so ~600 MB sat outside everything the heartbeat reports. This
3097
+ * module is the attribution half: one `[broker-mem]` line every five minutes
3098
+ * carrying the kernel's view of the process (RssAnon / RssFile, the `[heap]`
3099
+ * brk mapping, the count of anonymous mappings) NEXT TO the broker's own
3100
+ * structures (rings, write queues, consumers by kind, egress children), so a
3101
+ * step in RSS can be laid against what changed.
3102
+ *
3103
+ * It is a probe, not a fix: no caps, no allocator tuning, no snapshot. Cost is
3104
+ * O(brokers + sessions) per line and O(1) per packet (two integer adds in
3105
+ * `copyOwned`). Everything here is a pure function over plain data so the
3106
+ * aggregation and the `/proc` parsers are testable on fixture text.
3107
+ */
3108
+ var MB = 1024 * 1024;
3089
3109
  /** Cadence of the `[broker-mem]` line — aligned with the runner's `[mem]`. */
3090
3110
  var BROKER_MEM_INTERVAL_MS = 5 * 6e4;
3091
3111
  var copiedPackets = 0;
@@ -3124,6 +3144,7 @@ function parseProcMaps(text) {
3124
3144
  let heapBytes = 0;
3125
3145
  let anonMappings = 0;
3126
3146
  let anonBytes = 0;
3147
+ let v8PageMaps = 0;
3127
3148
  let from = 0;
3128
3149
  while (from < text.length) {
3129
3150
  let to = text.indexOf("\n", from);
@@ -3139,14 +3160,50 @@ function parseProcMaps(text) {
3139
3160
  if (path === void 0 || path === "") {
3140
3161
  anonMappings += 1;
3141
3162
  anonBytes += size;
3163
+ if (size === 262144) v8PageMaps += 1;
3142
3164
  } else if (path === "[heap]") heapBytes += size;
3143
3165
  }
3144
3166
  return {
3145
3167
  heapBytes,
3146
3168
  anonMappings,
3147
- anonBytes
3169
+ anonBytes,
3170
+ v8PageMaps
3148
3171
  };
3149
3172
  }
3173
+ /** Read the main isolate. Probe tick only: `getHeapSpaceStatistics` walks the spaces. */
3174
+ function readV8Heap() {
3175
+ const h = (0, node_v8.getHeapStatistics)();
3176
+ return {
3177
+ totalHeapBytes: h.total_heap_size,
3178
+ physicalBytes: h.total_physical_size,
3179
+ mallocedBytes: h.malloced_memory,
3180
+ peakMallocedBytes: h.peak_malloced_memory,
3181
+ externalBytes: h.external_memory,
3182
+ spaces: (0, node_v8.getHeapSpaceStatistics)().map((x) => ({
3183
+ name: x.space_name,
3184
+ committedBytes: x.space_size
3185
+ }))
3186
+ };
3187
+ }
3188
+ var SPACE_ABBREVIATIONS = {
3189
+ read_only_space: "ro",
3190
+ old_space: "old",
3191
+ new_space: "new",
3192
+ large_object_space: "lo",
3193
+ new_large_object_space: "nlo",
3194
+ code_space: "code",
3195
+ code_large_object_space: "clo",
3196
+ map_space: "map",
3197
+ shared_space: "shared",
3198
+ shared_large_object_space: "slo",
3199
+ trusted_space: "trusted",
3200
+ trusted_large_object_space: "tlo"
3201
+ };
3202
+ /** `old:120,new:24,lo:8` — committed MB per space, empty spaces left out. */
3203
+ function formatHeapSpaces(spaces) {
3204
+ const parts = spaces.filter((x) => x.committedBytes > 0).map((x) => `${SPACE_ABBREVIATIONS[x.name] ?? x.name}:${Math.round(x.committedBytes / MB)}`);
3205
+ return parts.length === 0 ? "none" : parts.join(",");
3206
+ }
3150
3207
  /** Opened/closed consumers since the last reading; `drain()` resets. */
3151
3208
  var ConsumerChurn = class {
3152
3209
  opened = 0;
@@ -3224,7 +3281,6 @@ function reportConsumerEvent(logger, event, kind, deviceId, total, now = Date.no
3224
3281
  const suppressed = verdict.suppressedBefore > 0 ? ` suppressedBefore=${verdict.suppressedBefore}` : "";
3225
3282
  logger.info(`consumer ${event} kind=${kind} total=${total}${suppressed}`, { ...deviceId !== void 0 ? { tags: { deviceId } } : {} });
3226
3283
  }
3227
- var MB = 1024 * 1024;
3228
3284
  function ringBytesOf(s) {
3229
3285
  return s.preBufferBytes + s.clipBytes + s.rtpRingBytes + s.keyframeCacheBytes;
3230
3286
  }
@@ -3267,7 +3323,11 @@ function formatBrokerMem(input) {
3267
3323
  const top = [...samples].filter((s) => s.deviceId !== void 0).sort((a, b) => ringBytesOf(b) - ringBytesOf(a)).slice(0, 5).map((s) => `${s.deviceId}:${mb(ringBytesOf(s))}`).join(",");
3268
3324
  const parts = ["[broker-mem]"];
3269
3325
  if (proc.status !== null) parts.push(`rssAnonMb=${(proc.status.rssAnonKb / 1024).toFixed(0)}`, `rssFileMb=${(proc.status.rssFileKb / 1024).toFixed(0)}`);
3270
- if (proc.maps !== null) parts.push(`heapMapMb=${mb(proc.maps.heapBytes)}`, `anonMaps=${proc.maps.anonMappings}`, `anonMapsMb=${mb(proc.maps.anonBytes)}`);
3326
+ if (proc.maps !== null) parts.push(`heapMapMb=${mb(proc.maps.heapBytes)}`, `anonMaps=${proc.maps.anonMappings}`, `anonMapsMb=${mb(proc.maps.anonBytes)}`, `v8PageMaps=${proc.maps.v8PageMaps}`);
3327
+ const v8 = input.v8;
3328
+ if (v8 !== void 0 && v8 !== null) parts.push(`v8TotalHeapMb=${mb(v8.totalHeapBytes)}`, `v8PhysicalMb=${mb(v8.physicalBytes)}`, `v8MallocedMb=${mb(v8.mallocedBytes)}`, `v8PeakMallocedMb=${mb(v8.peakMallocedBytes)}`, `v8ExternalMb=${mb(v8.externalBytes)}`, `spaces=${formatHeapSpaces(v8.spaces)}`);
3329
+ const w = input.worker;
3330
+ if (w !== void 0 && w !== null) parts.push(`workers=${w.workers}`, `workerHeapMb=${mb(w.heapBytes)}`, `workerPhysicalMb=${mb(w.physicalBytes)}`, `workerUsedMb=${mb(w.usedBytes)}`, `workerAgeS=${Math.round(w.maxAgeMs / 1e3)}`, `workerIdleExit=${w.idleTerminates ? "yes" : "never"}`);
3271
3331
  parts.push(`brokers=${samples.length}`, `ingesting=${ingesting}`, `annexBPlanes=${planes}`, `rtsp=${rtsp}`, `webrtc=${input.webrtcSessions}`, `encoded=${kindList(consumers)}`, `audioSubs=${audio}`, `pipeClients=${pipe}`, `egress=${input.egressTranscodes}`, `children=${input.childProcesses}`, `opened=${churn.opened}`, `closed=${churn.closed}`, `openedKinds=${kindList(churn.openedByKind)}`, `closedKinds=${kindList(churn.closedByKind)}`, `preBufferMb=${mb(pre)}`, `clipMb=${mb(clip)}`, `rtpRingMb=${mb(rtp)}`, `keyframeMb=${mb(kf)}`, `writeQueueSumMb=${mb(qSum)}`, `writeQueueMaxMb=${mb(qMax)}`, `copiedPackets=${copied.packets}`, `copiedMb=${mb(copied.bytes)}`, `topRingMb=${top === "" ? "none" : top}`);
3272
3332
  return parts.join(" ");
3273
3333
  }
@@ -5087,6 +5147,17 @@ var RtspSession = class {
5087
5147
  //#endregion
5088
5148
  //#region src/stream-broker/rtsp/rtsp-listen-server.ts
5089
5149
  /**
5150
+ * Attribution of the synthetic consumer a {@link BrokerWarmUp} subscribes:
5151
+ * the RTSP listen server holding a cold broker open for a DESCRIBE. Named
5152
+ * after that real consumer — it used to subscribe with no attribution and
5153
+ * surfaced as `unknown` (45 attach/detach pairs per 5 min on the hub).
5154
+ */
5155
+ var RTSP_DESCRIBE_WARMUP_ATTRIBUTION = {
5156
+ kind: "rtsp-listen",
5157
+ label: "describe-warmup",
5158
+ media: "both"
5159
+ };
5160
+ /**
5090
5161
  * Single TCP server for all RTSP restream connections.
5091
5162
  * Routes clients to the correct RtspRestreamer by a random token path
5092
5163
  * (not the predictable brokerId) for security.
@@ -19933,7 +20004,7 @@ var StreamBrokerManager = class StreamBrokerManager {
19933
20004
  const sourceBroker = this.brokers.get(sourceBrokerId);
19934
20005
  if (!sourceBroker) continue;
19935
20006
  const existingToken = this.persistedTokens.get(profileBrokerId);
19936
- const warmUp = () => sourceBroker.onEncodedData(() => {});
20007
+ const warmUp = () => sourceBroker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
19937
20008
  const openPrestreamWindow = () => sourceBroker.openPrestreamWindow();
19938
20009
  this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
19939
20010
  this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId);
@@ -21592,7 +21663,7 @@ var StreamBrokerManager = class StreamBrokerManager {
21592
21663
  this.brokers.set(brokerId, broker);
21593
21664
  this.applyClipRetention(deviceId);
21594
21665
  const existingToken = this.persistedTokens.get(brokerId);
21595
- const warmUp = () => broker.onEncodedData(() => {});
21666
+ const warmUp = () => broker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
21596
21667
  const openPrestreamWindow = () => broker.openPrestreamWindow();
21597
21668
  this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
21598
21669
  if (!existingToken) this.rtspProvider.persistTokens();
@@ -33870,23 +33941,37 @@ function createHttpClipStreamOpener(options = {}) {
33870
33941
  req.end();
33871
33942
  });
33872
33943
  }
33873
- //#endregion
33874
- //#region src/stream-broker/recorded/demux-off-thread.ts
33875
33944
  /**
33876
- * Off-thread JS demux client — mp4box parse on a `worker_threads` Worker
33877
- * inside the stream-broker runner (D228). Same addon, same WebRTC track;
33878
- * the live restream event loop is not the one walking boxes.
33879
- *
33880
- * The worker file is a separate vite entry (`dist/stream-broker/demux-worker-child.js`).
33881
- * This module is inlined into `dist/stream-broker/index.js`, so
33882
- * `import.meta.url` is THAT file and the child is a sibling — the same
33883
- * resolution trick as `fork-decode-worker.ts` / `decode-worker-main.js`.
33884
- *
33885
- * Missing worker file (vitest from `src/`, a botched deploy) falls back to
33886
- * {@link demuxSegmentFullJs} on this thread. A worker that errors a job
33887
- * returns null so {@link demuxSegmentPreferJs} takes the ffmpeg subprocess
33888
- * (already off-loop) instead of re-running mp4box here.
33889
- */
33945
+ * Heap shaping for the demux isolate. The runner's process-wide V8 flags
33946
+ * (`--initial-old-space-size=512`, `--max-semi-space-size=8`, from the
33947
+ * stream-broker manifest) are sized for the MAIN isolate and leak into every
33948
+ * Worker: with a 512 MB initial old space the worker grew to 144 MB of
33949
+ * garbage without a single major GC. Explicit `resourceLimits` replace the
33950
+ * inherited sizing (measured under those flags with the same allocation
33951
+ * churn: worker heap 474 MB without limits, 88 MB with them; see
33952
+ * docs/history/sessions.md).
33953
+ *
33954
+ * 128 MB old generation: parsing a recorded fragment retains about 1 MB
33955
+ * after GC for an 86 KB input (fixture bench), so even a 10 MB fragment (a
33956
+ * long 4K segment) holds well under 30 MB live; 128 MB is more than 4x that.
33957
+ * A parse that still exceeds it fails the worker cleanly
33958
+ * (`ERR_WORKER_OUT_OF_MEMORY`): in-flight parses resolve null (ffmpeg
33959
+ * fallback) and the next parse spawns a fresh worker.
33960
+ */
33961
+ var DEMUX_WORKER_RESOURCE_LIMITS = {
33962
+ maxOldGenerationSizeMb: 128,
33963
+ maxYoungGenerationSizeMb: 16
33964
+ };
33965
+ var defaultTimers = {
33966
+ set: (fn, ms) => {
33967
+ const t = setTimeout(fn, ms);
33968
+ t.unref();
33969
+ return t;
33970
+ },
33971
+ clear: (handle) => {
33972
+ if (handle !== void 0) clearTimeout(handle);
33973
+ }
33974
+ };
33890
33975
  function isCodec(value) {
33891
33976
  return value === "H264" || value === "H265";
33892
33977
  }
@@ -33921,55 +34006,93 @@ function parseWorkerMessage(message) {
33921
34006
  function defaultWorkerFilename() {
33922
34007
  return (0, node_url.fileURLToPath)(new URL("./demux-worker-child.js", require("url").pathToFileURL(__filename).href));
33923
34008
  }
33924
- /**
33925
- * JS parse that posts each fragment to one process-wide Worker. Concurrent
33926
- * callers are matched by request id; the worker's own event loop serializes
33927
- * mp4box. Live RTP on the parent thread is not waiting on box parsing.
33928
- */
33929
- function createOffThreadJsParse(deps) {
34009
+ /** {@link createOffThreadJsParse} plus the heap accessor the memory probe reads. */
34010
+ function createOffThreadDemuxClient(deps) {
33930
34011
  const Ctor = deps.WorkerCtor;
33931
- let worker = null;
34012
+ const idleMs = deps.idleMs ?? 6e4;
34013
+ const timers = deps.timers ?? defaultTimers;
34014
+ const now = deps.now ?? Date.now;
34015
+ let current = null;
33932
34016
  let nextId = 1;
33933
- const pending = /* @__PURE__ */ new Map();
33934
- const failAll = () => {
33935
- const waiting = [...pending.values()];
33936
- pending.clear();
33937
- for (const resolve of waiting) resolve(null);
34017
+ const failAll = (slot, why) => {
34018
+ const waiting = [...slot.pending.values()];
34019
+ slot.pending.clear();
34020
+ if (waiting.length > 0 && deps.logger) for (const deviceId of new Set(waiting.map((p) => p.deviceId))) deps.logger.warn(`demux worker lost with parses in flight (${why}) — ffmpeg fallback`, {
34021
+ ...deviceId !== void 0 ? { tags: { deviceId } } : {},
34022
+ meta: { inFlight: waiting.length }
34023
+ });
34024
+ for (const p of waiting) p.resolve(null);
34025
+ };
34026
+ const retire = (slot) => {
34027
+ timers.clear(slot.idleTimer);
34028
+ slot.idleTimer = void 0;
34029
+ if (current === slot) current = null;
34030
+ };
34031
+ const onIdle = async (slot) => {
34032
+ slot.idleTimer = void 0;
34033
+ if (current !== slot || slot.pending.size > 0) return;
34034
+ current = null;
34035
+ const heapBytes = (await slot.worker.getHeapStatistics?.().catch(() => null))?.total_heap_size ?? slot.lastHeapBytes;
34036
+ deps.logger?.info("demux worker idle — terminating", { meta: {
34037
+ ageMs: Math.max(0, now() - slot.spawnedAt),
34038
+ idleMs,
34039
+ lastHeapMb: Math.round(heapBytes / 1048576)
34040
+ } });
34041
+ slot.worker.terminate();
34042
+ };
34043
+ const armIdle = (slot) => {
34044
+ timers.clear(slot.idleTimer);
34045
+ slot.idleTimer = timers.set(() => void onIdle(slot), idleMs);
33938
34046
  };
33939
- const attach = (w) => {
34047
+ const attach = (slot) => {
34048
+ const w = slot.worker;
33940
34049
  w.on("message", (message) => {
33941
34050
  const parsed = parseWorkerMessage(message);
33942
34051
  if (!parsed) return;
33943
- const resolve = pending.get(parsed.id);
33944
- if (!resolve) return;
33945
- pending.delete(parsed.id);
33946
- resolve(parsed.aus);
34052
+ const pending = slot.pending.get(parsed.id);
34053
+ if (!pending) return;
34054
+ slot.pending.delete(parsed.id);
34055
+ pending.resolve(parsed.aus);
34056
+ if (slot.pending.size === 0 && current === slot) armIdle(slot);
33947
34057
  });
33948
- w.on("error", () => {
33949
- worker = null;
33950
- failAll();
34058
+ w.on("error", (err) => {
34059
+ retire(slot);
34060
+ failAll(slot, String(err));
33951
34061
  });
33952
34062
  w.on("exit", () => {
33953
- worker = null;
33954
- failAll();
34063
+ retire(slot);
34064
+ failAll(slot, "exit");
33955
34065
  });
33956
34066
  };
33957
34067
  const ensureWorker = () => {
33958
- if (worker) return worker;
34068
+ if (current) return current;
33959
34069
  if (!Ctor) return null;
33960
34070
  try {
33961
- const spawned = new Ctor(deps.workerFilename);
33962
- attach(spawned);
33963
- worker = spawned;
33964
- return spawned;
33965
- } catch {
34071
+ const slot = {
34072
+ worker: new Ctor(deps.workerFilename, { resourceLimits: DEMUX_WORKER_RESOURCE_LIMITS }),
34073
+ pending: /* @__PURE__ */ new Map(),
34074
+ spawnedAt: now(),
34075
+ idleTimer: void 0,
34076
+ lastHeapBytes: 0
34077
+ };
34078
+ attach(slot);
34079
+ current = slot;
34080
+ deps.logger?.info("demux worker spawned", { meta: {
34081
+ resourceLimits: DEMUX_WORKER_RESOURCE_LIMITS,
34082
+ idleMs
34083
+ } });
34084
+ return slot;
34085
+ } catch (err) {
34086
+ deps.logger?.warn(`demux worker spawn failed: ${String(err)} — parsing in-process`);
33966
34087
  return null;
33967
34088
  }
33968
34089
  };
33969
- return async (bytes) => {
34090
+ const parse = async (bytes, ctx) => {
33970
34091
  if (!deps.existsSync(deps.workerFilename)) return deps.parseInProcess(bytes);
33971
- const w = ensureWorker();
33972
- if (!w) return deps.parseInProcess(bytes);
34092
+ const slot = ensureWorker();
34093
+ if (!slot) return deps.parseInProcess(bytes);
34094
+ timers.clear(slot.idleTimer);
34095
+ slot.idleTimer = void 0;
33973
34096
  const id = nextId;
33974
34097
  nextId += 1;
33975
34098
  const copy = new Uint8Array(bytes);
@@ -33978,25 +34101,73 @@ function createOffThreadJsParse(deps) {
33978
34101
  bytes: copy
33979
34102
  };
33980
34103
  return new Promise((resolve) => {
33981
- pending.set(id, resolve);
33982
- w.postMessage(request, [copy.buffer]);
34104
+ slot.pending.set(id, {
34105
+ resolve,
34106
+ deviceId: ctx?.deviceId
34107
+ });
34108
+ slot.worker.postMessage(request, [copy.buffer]);
33983
34109
  });
33984
34110
  };
34111
+ const readHeaps = async (at = Date.now()) => {
34112
+ const live = current;
34113
+ const empty = {
34114
+ workers: 0,
34115
+ heapBytes: 0,
34116
+ physicalBytes: 0,
34117
+ usedBytes: 0,
34118
+ maxAgeMs: 0,
34119
+ idleTerminates: true
34120
+ };
34121
+ if (live === null) return empty;
34122
+ const stats = await live.worker.getHeapStatistics?.().catch(() => null);
34123
+ if (stats) live.lastHeapBytes = stats.total_heap_size;
34124
+ return {
34125
+ ...empty,
34126
+ workers: 1,
34127
+ maxAgeMs: Math.max(0, at - live.spawnedAt),
34128
+ ...stats ? {
34129
+ heapBytes: stats.total_heap_size,
34130
+ physicalBytes: stats.total_physical_size,
34131
+ usedBytes: stats.used_heap_size
34132
+ } : {}
34133
+ };
34134
+ };
34135
+ return {
34136
+ parse,
34137
+ readHeaps
34138
+ };
33985
34139
  }
33986
- function defaultOffThreadJsParse() {
33987
- return createOffThreadJsParse({
34140
+ function defaultOffThreadClient() {
34141
+ return createOffThreadDemuxClient({
33988
34142
  workerFilename: defaultWorkerFilename(),
33989
34143
  existsSync: node_fs.existsSync,
33990
34144
  parseInProcess: require_segment_demux_js.demuxSegmentFullJs,
33991
- WorkerCtor: node_worker_threads.Worker
34145
+ WorkerCtor: node_worker_threads.Worker,
34146
+ ...sharedLogger ? { logger: sharedLogger } : {}
33992
34147
  });
33993
34148
  }
33994
34149
  var shared;
33995
34150
  /** Process-wide JS parse: one Worker, shared by every WebRTC session. */
33996
- var offThreadJsParse = (bytes) => {
33997
- shared ??= defaultOffThreadJsParse();
33998
- return shared(bytes);
34151
+ var offThreadJsParse = (bytes, ctx) => {
34152
+ shared ??= defaultOffThreadClient();
34153
+ return shared.parse(bytes, ctx);
33999
34154
  };
34155
+ var sharedLogger;
34156
+ /** Give the process-wide client a logger; call before the first parse. */
34157
+ function setDemuxWorkerLogger(logger) {
34158
+ sharedLogger = logger;
34159
+ }
34160
+ /** Heap of the shared demux worker, for the `[broker-mem]` probe (tick only). */
34161
+ async function readDemuxWorkerHeaps(now) {
34162
+ return shared ? shared.readHeaps(now) : {
34163
+ workers: 0,
34164
+ heapBytes: 0,
34165
+ physicalBytes: 0,
34166
+ usedBytes: 0,
34167
+ maxAgeMs: 0,
34168
+ idleTerminates: true
34169
+ };
34170
+ }
34000
34171
  /**
34001
34172
  * Drop-in for the session `demuxVideo` hook: JS parse off the live loop,
34002
34173
  * ffmpeg subprocess fallback unchanged.
@@ -34897,6 +35068,7 @@ var BrokerWebrtcServer = class {
34897
35068
  stopped = false;
34898
35069
  constructor(options) {
34899
35070
  this.logger = options.logger;
35071
+ setDemuxWorkerLogger(options.logger.child("demux-worker"));
34900
35072
  this.getIceServersFn = options.getIceServers;
34901
35073
  this.staticIceServers = options.iceServers;
34902
35074
  this.icePortRange = options.icePortRange;
@@ -35562,6 +35734,7 @@ var BrokerWebrtcServer = class {
35562
35734
  if (locateRecorded && readRecorded) {
35563
35735
  const demuxVideo = (b) => demuxSegmentPreferJsOffThread(b, {
35564
35736
  ffmpegPath: resolveFfmpeg(),
35737
+ deviceId,
35565
35738
  onDemuxPath: (outcome) => {
35566
35739
  const report = demuxTally.record(outcome, Date.now());
35567
35740
  if (!report) return;
@@ -37494,7 +37667,7 @@ async function readProc() {
37494
37667
  }
37495
37668
  async function emitOnce(deps) {
37496
37669
  const now = Date.now();
37497
- const proc = await readProc();
37670
+ const [proc, worker] = await Promise.all([readProc(), readDemuxWorkerHeaps(now)]);
37498
37671
  const resources = process.getActiveResourcesInfo();
37499
37672
  const line = formatBrokerMem({
37500
37673
  samples: deps.manager.getBrokerInstances().map((b) => b.getMemSample(now)),
@@ -37503,7 +37676,9 @@ async function emitOnce(deps) {
37503
37676
  childProcesses: countActiveResources(resources, "ProcessWrap"),
37504
37677
  proc,
37505
37678
  churn: consumerChurn.drain(),
37506
- copied: readCopyOwnedTotals()
37679
+ copied: readCopyOwnedTotals(),
37680
+ v8: readV8Heap(),
37681
+ worker
37507
37682
  });
37508
37683
  deps.logger.info(line);
37509
37684
  }