@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
@@ -1,10 +1,10 @@
1
1
  import { r as __toESM } from "../chunk-DnnnRqeS.mjs";
2
- import { $n as discriminatedUnion, An as BaseAddon, D as G711_SCALE_CORRECTION_DB, Dn as DeviceType, Dt as gifDeriveSpeed, En as DeviceFeature, Gn as maskUrlCredentials, Hn as isEvent, I as RATE_CONTROL_RELAXED, In as createEvent, J as addonWidgetsSourceCapability, Kn as nodePin, L as RATE_CONTROL_TIGHT, Mn as CamProfileSchema, Nt as loadContributionCapability, On as errMsg, Ot as gifFromMp4Args, Pt as logChannelsCapability, Qn as boolean, Rt as memoizePinnedFfmpeg, Sn as buildFfmpegArgs, Tn as isSoftwareDecode, Tt as ffmpegRate, Un as makeProfileBrokerId, W as RingBuffer, Wn as makeSourceBrokerId, Xn as _enum, Xt as rankStreams, Zn as array, _t as egressTranscodeSharingKey, ar as record, at as clipExportFailureOf, c as CLIP_STILL_REASON_CODE_HEADER, cr as EventCategory, ct as createHwAccelCache, et as batteryCapability, ft as deriveBatteryPresence, jn as CAM_PROFILE_ORDER, kn as BOOT_RECOVERY_BACKOFF_MS, lt as createLogChannelsProvider, mn as streamBrokerCapability, nr as number, o as BatteryStatusSchema, or as string, qn as parseProfileBrokerId, rn as resolveEgressDecodeHwAccel, rr as object, rt as cameraStreamsCapability, s as CLIP_BROKER_PACED_RATES, sr as union, tr as literal, ut as declareLogChannel, vn as webrtcSessionCapability, vt as egressTransportFromRequest, w as EncodeProfileSchema, wn as invocationFromEncodeProfile, yn as AUDIO_PRESETS, zn as expandAudioChunkToF32le } from "../dist-BshC0NZR.mjs";
3
- import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-Dqmrb990.mjs";
2
+ import { $n as discriminatedUnion, An as BaseAddon, D as G711_SCALE_CORRECTION_DB, Dn as DeviceType, Dt as gifDeriveSpeed, En as DeviceFeature, Gn as maskUrlCredentials, Hn as isEvent, I as RATE_CONTROL_RELAXED, In as createEvent, J as addonWidgetsSourceCapability, Kn as nodePin, L as RATE_CONTROL_TIGHT, Mn as CamProfileSchema, Nt as loadContributionCapability, On as errMsg, Ot as gifFromMp4Args, Pt as logChannelsCapability, Qn as boolean, Rt as memoizePinnedFfmpeg, Sn as buildFfmpegArgs, Tn as isSoftwareDecode, Tt as ffmpegRate, Un as makeProfileBrokerId, W as RingBuffer, Wn as makeSourceBrokerId, Xn as _enum, Xt as rankStreams, Zn as array, _t as egressTranscodeSharingKey, ar as record, at as clipExportFailureOf, c as CLIP_STILL_REASON_CODE_HEADER, cr as EventCategory, ct as createHwAccelCache, et as batteryCapability, ft as deriveBatteryPresence, jn as CAM_PROFILE_ORDER, kn as BOOT_RECOVERY_BACKOFF_MS, lt as createLogChannelsProvider, mn as streamBrokerCapability, nr as number, o as BatteryStatusSchema, or as string, qn as parseProfileBrokerId, rn as resolveEgressDecodeHwAccel, rr as object, rt as cameraStreamsCapability, s as CLIP_BROKER_PACED_RATES, sr as union, tr as literal, ut as declareLogChannel, vn as webrtcSessionCapability, vt as egressTransportFromRequest, w as EncodeProfileSchema, wn as invocationFromEncodeProfile, yn as AUDIO_PRESETS, zn as expandAudioChunkToF32le } from "../dist-5AhGOWWu.mjs";
3
+ import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-DqZJFOzR.mjs";
4
4
  import { a as parseRestreamPath, d as PREBUFFER_MAX_MS, f as capPreRollRequestMs, i as isLiveEdgeOnlyDial, m as resolvePrebuffer, n as NO_DECLARATION, p as prebufferRetentionMs, s as sdpVariantFor, u as withSentryIntent } from "../restream-intent-DyGmLAU9.mjs";
5
5
  import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
6
6
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
7
- import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-COoPqx93.mjs";
7
+ import { a as require_mp4box_all, c as parseNalLengthSize, i as runFfmpegOnce, n as demuxSegmentPreferJs, o as avccSampleToAnnexB, r as FfmpegFallbackRefusedError, s as buildKeyframeAnnexB, t as demuxSegmentFullJs } from "../segment-demux-js-dlhO3Q8W.mjs";
8
8
  import { createRequire } from "node:module";
9
9
  import * as os$1 from "node:os";
10
10
  import { networkInterfaces, tmpdir } from "node:os";
@@ -18,6 +18,7 @@ import { spawn } from "node:child_process";
18
18
  import { readFile } from "node:fs/promises";
19
19
  import { fileURLToPath, pathToFileURL } from "node:url";
20
20
  import { renderCourtesyJpeg, renderCourtesyOverlayJpeg } from "@camstack/system/addon-utils";
21
+ import { getHeapSpaceStatistics, getHeapStatistics } from "node:v8";
21
22
  import * as net$1 from "node:net";
22
23
  import net from "node:net";
23
24
  import { createSocket } from "node:dgram";
@@ -3082,6 +3083,25 @@ var RtpPacketizer = class {
3082
3083
  };
3083
3084
  //#endregion
3084
3085
  //#region src/stream-broker/stream-broker/broker-mem-probe.ts
3086
+ /**
3087
+ * broker-mem-probe — what owns the broker runner's NATIVE memory.
3088
+ *
3089
+ * The runner's `[mem]` heartbeat (`kernel/heap-watch.ts`) says how big the
3090
+ * process is; it cannot say who holds it. On the hub, `stream-broker` grew
3091
+ * 250 -> 1015 MB in steps while `heapUsed` stayed at ~90 MB and `arrayBuffers`
3092
+ * at ~150 MB, so ~600 MB sat outside everything the heartbeat reports. This
3093
+ * module is the attribution half: one `[broker-mem]` line every five minutes
3094
+ * carrying the kernel's view of the process (RssAnon / RssFile, the `[heap]`
3095
+ * brk mapping, the count of anonymous mappings) NEXT TO the broker's own
3096
+ * structures (rings, write queues, consumers by kind, egress children), so a
3097
+ * step in RSS can be laid against what changed.
3098
+ *
3099
+ * It is a probe, not a fix: no caps, no allocator tuning, no snapshot. Cost is
3100
+ * O(brokers + sessions) per line and O(1) per packet (two integer adds in
3101
+ * `copyOwned`). Everything here is a pure function over plain data so the
3102
+ * aggregation and the `/proc` parsers are testable on fixture text.
3103
+ */
3104
+ var MB = 1024 * 1024;
3085
3105
  /** Cadence of the `[broker-mem]` line — aligned with the runner's `[mem]`. */
3086
3106
  var BROKER_MEM_INTERVAL_MS = 5 * 6e4;
3087
3107
  var copiedPackets = 0;
@@ -3120,6 +3140,7 @@ function parseProcMaps(text) {
3120
3140
  let heapBytes = 0;
3121
3141
  let anonMappings = 0;
3122
3142
  let anonBytes = 0;
3143
+ let v8PageMaps = 0;
3123
3144
  let from = 0;
3124
3145
  while (from < text.length) {
3125
3146
  let to = text.indexOf("\n", from);
@@ -3135,14 +3156,50 @@ function parseProcMaps(text) {
3135
3156
  if (path === void 0 || path === "") {
3136
3157
  anonMappings += 1;
3137
3158
  anonBytes += size;
3159
+ if (size === 262144) v8PageMaps += 1;
3138
3160
  } else if (path === "[heap]") heapBytes += size;
3139
3161
  }
3140
3162
  return {
3141
3163
  heapBytes,
3142
3164
  anonMappings,
3143
- anonBytes
3165
+ anonBytes,
3166
+ v8PageMaps
3144
3167
  };
3145
3168
  }
3169
+ /** Read the main isolate. Probe tick only: `getHeapSpaceStatistics` walks the spaces. */
3170
+ function readV8Heap() {
3171
+ const h = getHeapStatistics();
3172
+ return {
3173
+ totalHeapBytes: h.total_heap_size,
3174
+ physicalBytes: h.total_physical_size,
3175
+ mallocedBytes: h.malloced_memory,
3176
+ peakMallocedBytes: h.peak_malloced_memory,
3177
+ externalBytes: h.external_memory,
3178
+ spaces: getHeapSpaceStatistics().map((x) => ({
3179
+ name: x.space_name,
3180
+ committedBytes: x.space_size
3181
+ }))
3182
+ };
3183
+ }
3184
+ var SPACE_ABBREVIATIONS = {
3185
+ read_only_space: "ro",
3186
+ old_space: "old",
3187
+ new_space: "new",
3188
+ large_object_space: "lo",
3189
+ new_large_object_space: "nlo",
3190
+ code_space: "code",
3191
+ code_large_object_space: "clo",
3192
+ map_space: "map",
3193
+ shared_space: "shared",
3194
+ shared_large_object_space: "slo",
3195
+ trusted_space: "trusted",
3196
+ trusted_large_object_space: "tlo"
3197
+ };
3198
+ /** `old:120,new:24,lo:8` — committed MB per space, empty spaces left out. */
3199
+ function formatHeapSpaces(spaces) {
3200
+ const parts = spaces.filter((x) => x.committedBytes > 0).map((x) => `${SPACE_ABBREVIATIONS[x.name] ?? x.name}:${Math.round(x.committedBytes / MB)}`);
3201
+ return parts.length === 0 ? "none" : parts.join(",");
3202
+ }
3146
3203
  /** Opened/closed consumers since the last reading; `drain()` resets. */
3147
3204
  var ConsumerChurn = class {
3148
3205
  opened = 0;
@@ -3220,7 +3277,6 @@ function reportConsumerEvent(logger, event, kind, deviceId, total, now = Date.no
3220
3277
  const suppressed = verdict.suppressedBefore > 0 ? ` suppressedBefore=${verdict.suppressedBefore}` : "";
3221
3278
  logger.info(`consumer ${event} kind=${kind} total=${total}${suppressed}`, { ...deviceId !== void 0 ? { tags: { deviceId } } : {} });
3222
3279
  }
3223
- var MB = 1024 * 1024;
3224
3280
  function ringBytesOf(s) {
3225
3281
  return s.preBufferBytes + s.clipBytes + s.rtpRingBytes + s.keyframeCacheBytes;
3226
3282
  }
@@ -3263,7 +3319,11 @@ function formatBrokerMem(input) {
3263
3319
  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(",");
3264
3320
  const parts = ["[broker-mem]"];
3265
3321
  if (proc.status !== null) parts.push(`rssAnonMb=${(proc.status.rssAnonKb / 1024).toFixed(0)}`, `rssFileMb=${(proc.status.rssFileKb / 1024).toFixed(0)}`);
3266
- if (proc.maps !== null) parts.push(`heapMapMb=${mb(proc.maps.heapBytes)}`, `anonMaps=${proc.maps.anonMappings}`, `anonMapsMb=${mb(proc.maps.anonBytes)}`);
3322
+ if (proc.maps !== null) parts.push(`heapMapMb=${mb(proc.maps.heapBytes)}`, `anonMaps=${proc.maps.anonMappings}`, `anonMapsMb=${mb(proc.maps.anonBytes)}`, `v8PageMaps=${proc.maps.v8PageMaps}`);
3323
+ const v8 = input.v8;
3324
+ 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)}`);
3325
+ const w = input.worker;
3326
+ 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"}`);
3267
3327
  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}`);
3268
3328
  return parts.join(" ");
3269
3329
  }
@@ -5083,6 +5143,17 @@ var RtspSession = class {
5083
5143
  //#endregion
5084
5144
  //#region src/stream-broker/rtsp/rtsp-listen-server.ts
5085
5145
  /**
5146
+ * Attribution of the synthetic consumer a {@link BrokerWarmUp} subscribes:
5147
+ * the RTSP listen server holding a cold broker open for a DESCRIBE. Named
5148
+ * after that real consumer — it used to subscribe with no attribution and
5149
+ * surfaced as `unknown` (45 attach/detach pairs per 5 min on the hub).
5150
+ */
5151
+ var RTSP_DESCRIBE_WARMUP_ATTRIBUTION = {
5152
+ kind: "rtsp-listen",
5153
+ label: "describe-warmup",
5154
+ media: "both"
5155
+ };
5156
+ /**
5086
5157
  * Single TCP server for all RTSP restream connections.
5087
5158
  * Routes clients to the correct RtspRestreamer by a random token path
5088
5159
  * (not the predictable brokerId) for security.
@@ -19929,7 +20000,7 @@ var StreamBrokerManager = class StreamBrokerManager {
19929
20000
  const sourceBroker = this.brokers.get(sourceBrokerId);
19930
20001
  if (!sourceBroker) continue;
19931
20002
  const existingToken = this.persistedTokens.get(profileBrokerId);
19932
- const warmUp = () => sourceBroker.onEncodedData(() => {});
20003
+ const warmUp = () => sourceBroker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
19933
20004
  const openPrestreamWindow = () => sourceBroker.openPrestreamWindow();
19934
20005
  this.rtspServer.registerRestreamer(profileBrokerId, sourceBroker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
19935
20006
  this.profileRestreamerBindings.set(profileBrokerId, sourceBrokerId);
@@ -21588,7 +21659,7 @@ var StreamBrokerManager = class StreamBrokerManager {
21588
21659
  this.brokers.set(brokerId, broker);
21589
21660
  this.applyClipRetention(deviceId);
21590
21661
  const existingToken = this.persistedTokens.get(brokerId);
21591
- const warmUp = () => broker.onEncodedData(() => {});
21662
+ const warmUp = () => broker.onEncodedData(() => {}, RTSP_DESCRIBE_WARMUP_ATTRIBUTION);
21592
21663
  const openPrestreamWindow = () => broker.openPrestreamWindow();
21593
21664
  this.rtspServer.registerRestreamer(brokerId, broker.getRtspRestreamer(), warmUp, existingToken, openPrestreamWindow);
21594
21665
  if (!existingToken) this.rtspProvider.persistTokens();
@@ -33866,23 +33937,37 @@ function createHttpClipStreamOpener(options = {}) {
33866
33937
  req.end();
33867
33938
  });
33868
33939
  }
33869
- //#endregion
33870
- //#region src/stream-broker/recorded/demux-off-thread.ts
33871
33940
  /**
33872
- * Off-thread JS demux client — mp4box parse on a `worker_threads` Worker
33873
- * inside the stream-broker runner (D228). Same addon, same WebRTC track;
33874
- * the live restream event loop is not the one walking boxes.
33875
- *
33876
- * The worker file is a separate vite entry (`dist/stream-broker/demux-worker-child.js`).
33877
- * This module is inlined into `dist/stream-broker/index.js`, so
33878
- * `import.meta.url` is THAT file and the child is a sibling — the same
33879
- * resolution trick as `fork-decode-worker.ts` / `decode-worker-main.js`.
33880
- *
33881
- * Missing worker file (vitest from `src/`, a botched deploy) falls back to
33882
- * {@link demuxSegmentFullJs} on this thread. A worker that errors a job
33883
- * returns null so {@link demuxSegmentPreferJs} takes the ffmpeg subprocess
33884
- * (already off-loop) instead of re-running mp4box here.
33885
- */
33941
+ * Heap shaping for the demux isolate. The runner's process-wide V8 flags
33942
+ * (`--initial-old-space-size=512`, `--max-semi-space-size=8`, from the
33943
+ * stream-broker manifest) are sized for the MAIN isolate and leak into every
33944
+ * Worker: with a 512 MB initial old space the worker grew to 144 MB of
33945
+ * garbage without a single major GC. Explicit `resourceLimits` replace the
33946
+ * inherited sizing (measured under those flags with the same allocation
33947
+ * churn: worker heap 474 MB without limits, 88 MB with them; see
33948
+ * docs/history/sessions.md).
33949
+ *
33950
+ * 128 MB old generation: parsing a recorded fragment retains about 1 MB
33951
+ * after GC for an 86 KB input (fixture bench), so even a 10 MB fragment (a
33952
+ * long 4K segment) holds well under 30 MB live; 128 MB is more than 4x that.
33953
+ * A parse that still exceeds it fails the worker cleanly
33954
+ * (`ERR_WORKER_OUT_OF_MEMORY`): in-flight parses resolve null (ffmpeg
33955
+ * fallback) and the next parse spawns a fresh worker.
33956
+ */
33957
+ var DEMUX_WORKER_RESOURCE_LIMITS = {
33958
+ maxOldGenerationSizeMb: 128,
33959
+ maxYoungGenerationSizeMb: 16
33960
+ };
33961
+ var defaultTimers = {
33962
+ set: (fn, ms) => {
33963
+ const t = setTimeout(fn, ms);
33964
+ t.unref();
33965
+ return t;
33966
+ },
33967
+ clear: (handle) => {
33968
+ if (handle !== void 0) clearTimeout(handle);
33969
+ }
33970
+ };
33886
33971
  function isCodec(value) {
33887
33972
  return value === "H264" || value === "H265";
33888
33973
  }
@@ -33917,55 +34002,93 @@ function parseWorkerMessage(message) {
33917
34002
  function defaultWorkerFilename() {
33918
34003
  return fileURLToPath(new URL("./demux-worker-child.js", import.meta.url));
33919
34004
  }
33920
- /**
33921
- * JS parse that posts each fragment to one process-wide Worker. Concurrent
33922
- * callers are matched by request id; the worker's own event loop serializes
33923
- * mp4box. Live RTP on the parent thread is not waiting on box parsing.
33924
- */
33925
- function createOffThreadJsParse(deps) {
34005
+ /** {@link createOffThreadJsParse} plus the heap accessor the memory probe reads. */
34006
+ function createOffThreadDemuxClient(deps) {
33926
34007
  const Ctor = deps.WorkerCtor;
33927
- let worker = null;
34008
+ const idleMs = deps.idleMs ?? 6e4;
34009
+ const timers = deps.timers ?? defaultTimers;
34010
+ const now = deps.now ?? Date.now;
34011
+ let current = null;
33928
34012
  let nextId = 1;
33929
- const pending = /* @__PURE__ */ new Map();
33930
- const failAll = () => {
33931
- const waiting = [...pending.values()];
33932
- pending.clear();
33933
- for (const resolve of waiting) resolve(null);
34013
+ const failAll = (slot, why) => {
34014
+ const waiting = [...slot.pending.values()];
34015
+ slot.pending.clear();
34016
+ 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`, {
34017
+ ...deviceId !== void 0 ? { tags: { deviceId } } : {},
34018
+ meta: { inFlight: waiting.length }
34019
+ });
34020
+ for (const p of waiting) p.resolve(null);
34021
+ };
34022
+ const retire = (slot) => {
34023
+ timers.clear(slot.idleTimer);
34024
+ slot.idleTimer = void 0;
34025
+ if (current === slot) current = null;
34026
+ };
34027
+ const onIdle = async (slot) => {
34028
+ slot.idleTimer = void 0;
34029
+ if (current !== slot || slot.pending.size > 0) return;
34030
+ current = null;
34031
+ const heapBytes = (await slot.worker.getHeapStatistics?.().catch(() => null))?.total_heap_size ?? slot.lastHeapBytes;
34032
+ deps.logger?.info("demux worker idle — terminating", { meta: {
34033
+ ageMs: Math.max(0, now() - slot.spawnedAt),
34034
+ idleMs,
34035
+ lastHeapMb: Math.round(heapBytes / 1048576)
34036
+ } });
34037
+ slot.worker.terminate();
34038
+ };
34039
+ const armIdle = (slot) => {
34040
+ timers.clear(slot.idleTimer);
34041
+ slot.idleTimer = timers.set(() => void onIdle(slot), idleMs);
33934
34042
  };
33935
- const attach = (w) => {
34043
+ const attach = (slot) => {
34044
+ const w = slot.worker;
33936
34045
  w.on("message", (message) => {
33937
34046
  const parsed = parseWorkerMessage(message);
33938
34047
  if (!parsed) return;
33939
- const resolve = pending.get(parsed.id);
33940
- if (!resolve) return;
33941
- pending.delete(parsed.id);
33942
- resolve(parsed.aus);
34048
+ const pending = slot.pending.get(parsed.id);
34049
+ if (!pending) return;
34050
+ slot.pending.delete(parsed.id);
34051
+ pending.resolve(parsed.aus);
34052
+ if (slot.pending.size === 0 && current === slot) armIdle(slot);
33943
34053
  });
33944
- w.on("error", () => {
33945
- worker = null;
33946
- failAll();
34054
+ w.on("error", (err) => {
34055
+ retire(slot);
34056
+ failAll(slot, String(err));
33947
34057
  });
33948
34058
  w.on("exit", () => {
33949
- worker = null;
33950
- failAll();
34059
+ retire(slot);
34060
+ failAll(slot, "exit");
33951
34061
  });
33952
34062
  };
33953
34063
  const ensureWorker = () => {
33954
- if (worker) return worker;
34064
+ if (current) return current;
33955
34065
  if (!Ctor) return null;
33956
34066
  try {
33957
- const spawned = new Ctor(deps.workerFilename);
33958
- attach(spawned);
33959
- worker = spawned;
33960
- return spawned;
33961
- } catch {
34067
+ const slot = {
34068
+ worker: new Ctor(deps.workerFilename, { resourceLimits: DEMUX_WORKER_RESOURCE_LIMITS }),
34069
+ pending: /* @__PURE__ */ new Map(),
34070
+ spawnedAt: now(),
34071
+ idleTimer: void 0,
34072
+ lastHeapBytes: 0
34073
+ };
34074
+ attach(slot);
34075
+ current = slot;
34076
+ deps.logger?.info("demux worker spawned", { meta: {
34077
+ resourceLimits: DEMUX_WORKER_RESOURCE_LIMITS,
34078
+ idleMs
34079
+ } });
34080
+ return slot;
34081
+ } catch (err) {
34082
+ deps.logger?.warn(`demux worker spawn failed: ${String(err)} — parsing in-process`);
33962
34083
  return null;
33963
34084
  }
33964
34085
  };
33965
- return async (bytes) => {
34086
+ const parse = async (bytes, ctx) => {
33966
34087
  if (!deps.existsSync(deps.workerFilename)) return deps.parseInProcess(bytes);
33967
- const w = ensureWorker();
33968
- if (!w) return deps.parseInProcess(bytes);
34088
+ const slot = ensureWorker();
34089
+ if (!slot) return deps.parseInProcess(bytes);
34090
+ timers.clear(slot.idleTimer);
34091
+ slot.idleTimer = void 0;
33969
34092
  const id = nextId;
33970
34093
  nextId += 1;
33971
34094
  const copy = new Uint8Array(bytes);
@@ -33974,25 +34097,73 @@ function createOffThreadJsParse(deps) {
33974
34097
  bytes: copy
33975
34098
  };
33976
34099
  return new Promise((resolve) => {
33977
- pending.set(id, resolve);
33978
- w.postMessage(request, [copy.buffer]);
34100
+ slot.pending.set(id, {
34101
+ resolve,
34102
+ deviceId: ctx?.deviceId
34103
+ });
34104
+ slot.worker.postMessage(request, [copy.buffer]);
33979
34105
  });
33980
34106
  };
34107
+ const readHeaps = async (at = Date.now()) => {
34108
+ const live = current;
34109
+ const empty = {
34110
+ workers: 0,
34111
+ heapBytes: 0,
34112
+ physicalBytes: 0,
34113
+ usedBytes: 0,
34114
+ maxAgeMs: 0,
34115
+ idleTerminates: true
34116
+ };
34117
+ if (live === null) return empty;
34118
+ const stats = await live.worker.getHeapStatistics?.().catch(() => null);
34119
+ if (stats) live.lastHeapBytes = stats.total_heap_size;
34120
+ return {
34121
+ ...empty,
34122
+ workers: 1,
34123
+ maxAgeMs: Math.max(0, at - live.spawnedAt),
34124
+ ...stats ? {
34125
+ heapBytes: stats.total_heap_size,
34126
+ physicalBytes: stats.total_physical_size,
34127
+ usedBytes: stats.used_heap_size
34128
+ } : {}
34129
+ };
34130
+ };
34131
+ return {
34132
+ parse,
34133
+ readHeaps
34134
+ };
33981
34135
  }
33982
- function defaultOffThreadJsParse() {
33983
- return createOffThreadJsParse({
34136
+ function defaultOffThreadClient() {
34137
+ return createOffThreadDemuxClient({
33984
34138
  workerFilename: defaultWorkerFilename(),
33985
34139
  existsSync,
33986
34140
  parseInProcess: demuxSegmentFullJs,
33987
- WorkerCtor: Worker
34141
+ WorkerCtor: Worker,
34142
+ ...sharedLogger ? { logger: sharedLogger } : {}
33988
34143
  });
33989
34144
  }
33990
34145
  var shared;
33991
34146
  /** Process-wide JS parse: one Worker, shared by every WebRTC session. */
33992
- var offThreadJsParse = (bytes) => {
33993
- shared ??= defaultOffThreadJsParse();
33994
- return shared(bytes);
34147
+ var offThreadJsParse = (bytes, ctx) => {
34148
+ shared ??= defaultOffThreadClient();
34149
+ return shared.parse(bytes, ctx);
33995
34150
  };
34151
+ var sharedLogger;
34152
+ /** Give the process-wide client a logger; call before the first parse. */
34153
+ function setDemuxWorkerLogger(logger) {
34154
+ sharedLogger = logger;
34155
+ }
34156
+ /** Heap of the shared demux worker, for the `[broker-mem]` probe (tick only). */
34157
+ async function readDemuxWorkerHeaps(now) {
34158
+ return shared ? shared.readHeaps(now) : {
34159
+ workers: 0,
34160
+ heapBytes: 0,
34161
+ physicalBytes: 0,
34162
+ usedBytes: 0,
34163
+ maxAgeMs: 0,
34164
+ idleTerminates: true
34165
+ };
34166
+ }
33996
34167
  /**
33997
34168
  * Drop-in for the session `demuxVideo` hook: JS parse off the live loop,
33998
34169
  * ffmpeg subprocess fallback unchanged.
@@ -34893,6 +35064,7 @@ var BrokerWebrtcServer = class {
34893
35064
  stopped = false;
34894
35065
  constructor(options) {
34895
35066
  this.logger = options.logger;
35067
+ setDemuxWorkerLogger(options.logger.child("demux-worker"));
34896
35068
  this.getIceServersFn = options.getIceServers;
34897
35069
  this.staticIceServers = options.iceServers;
34898
35070
  this.icePortRange = options.icePortRange;
@@ -35558,6 +35730,7 @@ var BrokerWebrtcServer = class {
35558
35730
  if (locateRecorded && readRecorded) {
35559
35731
  const demuxVideo = (b) => demuxSegmentPreferJsOffThread(b, {
35560
35732
  ffmpegPath: resolveFfmpeg(),
35733
+ deviceId,
35561
35734
  onDemuxPath: (outcome) => {
35562
35735
  const report = demuxTally.record(outcome, Date.now());
35563
35736
  if (!report) return;
@@ -37490,7 +37663,7 @@ async function readProc() {
37490
37663
  }
37491
37664
  async function emitOnce(deps) {
37492
37665
  const now = Date.now();
37493
- const proc = await readProc();
37666
+ const [proc, worker] = await Promise.all([readProc(), readDemuxWorkerHeaps(now)]);
37494
37667
  const resources = process.getActiveResourcesInfo();
37495
37668
  const line = formatBrokerMem({
37496
37669
  samples: deps.manager.getBrokerInstances().map((b) => b.getMemSample(now)),
@@ -37499,7 +37672,9 @@ async function emitOnce(deps) {
37499
37672
  childProcesses: countActiveResources(resources, "ProcessWrap"),
37500
37673
  proc,
37501
37674
  churn: consumerChurn.drain(),
37502
- copied: readCopyOwnedTotals()
37675
+ copied: readCopyOwnedTotals(),
37676
+ v8: readV8Heap(),
37677
+ worker
37503
37678
  });
37504
37679
  deps.logger.info(line);
37505
37680
  }
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVgnss_e.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CSajlHKC.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.366",
3
+ "version": "1.2.368",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",