@camstack/addon-pipeline 1.2.369 → 1.2.371

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 (37) 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-uHzR2xWY.mjs → custom-model-catalog-C6C2cSwd.mjs} +2 -2
  4. package/dist/{custom-model-catalog-C3PYjN8c.js → custom-model-catalog-n9v8MUL6.js} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-DKSwif2M.mjs → dist-8AEiKOFO.mjs} +386 -30
  8. package/dist/{dist-D3PtiAhd.js → dist-DmuX7IFJ.js} +403 -29
  9. package/dist/{model-catalogs-CVd4TcTQ.mjs → model-catalogs-BSWAHZJh.mjs} +1 -1
  10. package/dist/{model-catalogs-kGqNkIuV.js → model-catalogs-DO4aA1qo.js} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-C_Fqti3A.mjs → node-Bd4va7c-.mjs} +1 -1
  14. package/dist/{node--15m1sN3.js → node-DqA6nD1z.js} +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-DELcKfDf.js → process-memory-2OQ2oxrz.js} +1 -1
  18. package/dist/{process-memory-Dq_Omilr.mjs → process-memory-BvS2Z88w.mjs} +1 -1
  19. package/dist/recorder/index.js +184 -54
  20. package/dist/recorder/index.mjs +184 -54
  21. package/dist/{segment-demux-js-DSYbEqvj.js → segment-demux-js-BkGK1rhu.js} +1 -1
  22. package/dist/{segment-demux-js-BTUbXt7b.mjs → segment-demux-js-By9MLn_g.mjs} +1 -1
  23. package/dist/stream-broker/_stub.js +1188 -861
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CMHYV8dG.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DjoAY42S.mjs} +3 -3
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C3ssSNKh.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-nua-KuDg.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-Cz1kTtGe.mjs → hostInit-DPiGNc5u.mjs} +3 -3
  30. package/dist/stream-broker/index.js +302 -90
  31. package/dist/stream-broker/index.mjs +302 -90
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/i18n/en.json +8 -0
  34. package/i18n/it.json +8 -0
  35. package/package.json +6 -1
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DBku2n1V.mjs +0 -26
  37. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DNC4tvrD.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-DKSwif2M.mjs";
3
- import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-C_Fqti3A.mjs";
2
+ import { $n as _enum, An as errMsg, At as gifFromMp4Args, Bt as memoizePinnedFfmpeg, D as G711_SCALE_CORRECTION_DB, Dn as isSoftwareDecode, Dt as ffmpegRate, En as invocationFromEncodeProfile, Ft as loadContributionCapability, Gn as makeProfileBrokerId, I as RATE_CONTROL_RELAXED, It as logChannelsCapability, J as addonWidgetsSourceCapability, Jn as nodePin, Kn as makeSourceBrokerId, L as RATE_CONTROL_TIGHT, Mn as BaseAddon, Nn as CAM_PROFILE_ORDER, On as DeviceFeature, Pn as CamProfileSchema, Qt as rankStreams, Rn as createEvent, Vn as expandAudioChunkToF32le, W as RingBuffer, Wn as isEvent, Yn as parseProfileBrokerId, an as resolveEgressDecodeHwAccel, ar as number, bn as webrtcSessionCapability, bt as egressTransportFromRequest, c as CLIP_STILL_REASON_CODE_HEADER, cr as record, dr as EventCategory, dt as declareLogChannel, er as array, gn as streamBrokerCapability, ir as literal, it as cameraStreamsCapability, jn as BOOT_RECOVERY_BACKOFF_MS, kn as DeviceType, kt as gifDeriveSpeed, lr as string, lt as createHwAccelCache, nr as discriminatedUnion, o as BatteryStatusSchema, or as object, ot as clipExportFailureOf, pt as deriveBatteryPresence, qn as maskUrlCredentials, s as CLIP_BROKER_PACED_RATES, tr as boolean, tt as batteryCapability, ur as union, ut as createLogChannelsProvider, w as EncodeProfileSchema, wn as buildFfmpegArgs, xn as AUDIO_PRESETS, yt as egressTranscodeSharingKey } from "../dist-8AEiKOFO.mjs";
3
+ import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-Bd4va7c-.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-BTUbXt7b.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-By9MLn_g.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";
@@ -2363,6 +2363,10 @@ var PacedRtpReplay = class {
2363
2363
  this.enqueuedLive++;
2364
2364
  return true;
2365
2365
  }
2366
+ /** Visit every still-queued item (probe time only; no allocation). */
2367
+ forEachQueued(visit) {
2368
+ for (const item of this.queue) visit(item);
2369
+ }
2366
2370
  /** Cancel the replay: pending packets are dropped, `onDone` fires with
2367
2371
  * `aborted: true`. Safe to call multiple times / before `start`. */
2368
2372
  abort() {
@@ -3082,6 +3086,133 @@ var RtpPacketizer = class {
3082
3086
  }
3083
3087
  };
3084
3088
  //#endregion
3089
+ //#region src/stream-broker/stream-broker/owned-buffer.ts
3090
+ /**
3091
+ * Buffers that enter a LONG-LIVED ring own their backing store.
3092
+ *
3093
+ * Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
3094
+ * below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
3095
+ * That is the right choice for the ingest path — a start code, a 4-byte
3096
+ * interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
3097
+ * within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
3098
+ * for 60 s in a clip ring pins its whole slab, and with it every short-lived
3099
+ * packet that happened to be allocated beside it. On the hub that held
3100
+ * 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
3101
+ * pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
3102
+ *
3103
+ * The ring byte caps counted payload bytes, so they never saw it. Both rings
3104
+ * now pass every entry through {@link detachFromPool} on the way in and
3105
+ * account it with {@link backingStoreBytes}, so the cap measures what the
3106
+ * entry costs. Short-lived traffic stays pooled.
3107
+ */
3108
+ /** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
3109
+ function ownsBackingStore(buf) {
3110
+ return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
3111
+ }
3112
+ /**
3113
+ * What retaining `buf` really costs: its own length when it owns its
3114
+ * ArrayBuffer, the whole shared slab when it is a view of one.
3115
+ */
3116
+ function backingStoreBytes(buf) {
3117
+ return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
3118
+ }
3119
+ /**
3120
+ * `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
3121
+ * buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
3122
+ * runtime detail), else a copy that does. One copy, never two: a buffer that is
3123
+ * already unpooled is returned as-is.
3124
+ */
3125
+ function detachFromPool(buf) {
3126
+ if (ownsBackingStore(buf)) return buf;
3127
+ const owned = Buffer.allocUnsafeSlow(buf.length);
3128
+ buf.copy(owned);
3129
+ return owned;
3130
+ }
3131
+ /**
3132
+ * A copy of `src[start, end)` that OWNS its backing store — the shape a reader
3133
+ * hands out when the packet it cuts from its socket buffer is going to be
3134
+ * retained anyway.
3135
+ *
3136
+ * Every source video packet is: `captureRtpForPreBuffer` keeps it for the life
3137
+ * of a GOP, and the restreamer's keyframe cache keeps the keyframe ones. With
3138
+ * the readers cutting packets as `Buffer.from(subarray)` — a pooled view — the
3139
+ * ring then had to copy each one a second time to own it: two copies of every
3140
+ * packet on the ingest path (measured 15.1 + 14.8 MB/s on the hub's load).
3141
+ * Cutting the packet owned up front makes the ring's and the cache's
3142
+ * `detachFromPool` an identity, so the packet is copied once and shared by the
3143
+ * reader's fan-out, the ring, the cache and every session's write queue.
3144
+ */
3145
+ function copyOwned(src, start, end) {
3146
+ const owned = Buffer.allocUnsafeSlow(end - start);
3147
+ src.copy(owned, 0, start, end);
3148
+ recordCopyOwned(end - start);
3149
+ return owned;
3150
+ }
3151
+ /** Accumulates entries; each distinct backing ArrayBuffer is summed once. */
3152
+ var BackingTally = class {
3153
+ seen = /* @__PURE__ */ new Set();
3154
+ entries = 0;
3155
+ payload = 0;
3156
+ naive = 0;
3157
+ distinct = 0;
3158
+ capped = false;
3159
+ add(buf) {
3160
+ if (this.entries >= 2e5) {
3161
+ this.capped = true;
3162
+ return;
3163
+ }
3164
+ this.entries += 1;
3165
+ this.payload += buf.length;
3166
+ const backing = backingStoreBytes(buf);
3167
+ this.naive += backing;
3168
+ if (!this.seen.has(buf.buffer)) {
3169
+ this.seen.add(buf.buffer);
3170
+ this.distinct += backing;
3171
+ }
3172
+ }
3173
+ totals() {
3174
+ return {
3175
+ entries: this.entries,
3176
+ payloadBytes: this.payload,
3177
+ naiveBackingBytes: this.naive,
3178
+ distinctBackingBytes: this.distinct,
3179
+ capped: this.capped
3180
+ };
3181
+ }
3182
+ };
3183
+ /** Add every populated slot of a sender's `rtpCache` (≤ RTP_HISTORY_SIZE). */
3184
+ function tallyRtpCache(tally, cache) {
3185
+ if (cache === void 0) return;
3186
+ for (let i = 0; i < cache.length; i++) {
3187
+ const slot = cache[i];
3188
+ if (slot !== void 0) tally.add(slot.payload);
3189
+ }
3190
+ }
3191
+ /** Distinct backing bytes a session pins across all four structures. */
3192
+ function sessionBackingBytes(r) {
3193
+ return r.rtpCache.distinctBackingBytes + r.pushQueue.distinctBackingBytes + r.rtpReplay.distinctBackingBytes + r.scrub.distinctBackingBytes;
3194
+ }
3195
+ function mbOf(bytes) {
3196
+ return Number((bytes / (1024 * 1024)).toFixed(1));
3197
+ }
3198
+ /** Flat log meta for the session open / close lines. */
3199
+ function sessionMemMeta(r) {
3200
+ return {
3201
+ rtpCacheEntries: r.rtpCache.entries,
3202
+ rtpCachePayloadMb: mbOf(r.rtpCache.payloadBytes),
3203
+ rtpCacheNaiveMb: mbOf(r.rtpCache.naiveBackingBytes),
3204
+ rtpCacheBackingMb: mbOf(r.rtpCache.distinctBackingBytes),
3205
+ pushQueueFrames: r.pushQueue.entries,
3206
+ pushQueueBackingMb: mbOf(r.pushQueue.distinctBackingBytes),
3207
+ rtpReplayPackets: r.rtpReplay.entries,
3208
+ rtpReplayBackingMb: mbOf(r.rtpReplay.distinctBackingBytes),
3209
+ scrubPayloadMb: mbOf(r.scrub.payloadBytes),
3210
+ scrubBackingMb: mbOf(r.scrub.distinctBackingBytes),
3211
+ sessionBackingMb: mbOf(sessionBackingBytes(r)),
3212
+ memCapped: r.rtpCache.capped || r.pushQueue.capped || r.rtpReplay.capped || r.scrub.capped
3213
+ };
3214
+ }
3215
+ //#endregion
3085
3216
  //#region src/stream-broker/stream-broker/broker-mem-probe.ts
3086
3217
  /**
3087
3218
  * broker-mem-probe — what owns the broker runner's NATIVE memory.
@@ -3325,8 +3456,58 @@ function formatBrokerMem(input) {
3325
3456
  const w = input.worker;
3326
3457
  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"}`);
3327
3458
  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}`);
3459
+ const sessions = input.sessions;
3460
+ if (sessions !== void 0) parts.push(...formatSessionMem(sessions, input));
3328
3461
  return parts.join(" ");
3329
3462
  }
3463
+ /**
3464
+ * Sums over every WebRTC session, each distinct backing store counted once PER
3465
+ * SESSION, and the three views of the NACK history so pinning shows:
3466
+ * `rtpCachePayloadMb` (what the packets say), `rtpCacheNaiveMb` (each entry's
3467
+ * own backing, a shared slab repeated) and `rtpCacheBackingMb` (each slab once).
3468
+ * `fragMb` is the brk heap NOT explained by v8 external memory.
3469
+ */
3470
+ function formatSessionMem(sessions, input) {
3471
+ let rtpEntries = 0;
3472
+ let rtpPayload = 0;
3473
+ let rtpNaive = 0;
3474
+ let rtpBacking = 0;
3475
+ let pushFrames = 0;
3476
+ let pushBacking = 0;
3477
+ let replayPackets = 0;
3478
+ let replayBacking = 0;
3479
+ let scrubPayload = 0;
3480
+ let scrubBacking = 0;
3481
+ for (const r of sessions) {
3482
+ rtpEntries += r.rtpCache.entries;
3483
+ rtpPayload += r.rtpCache.payloadBytes;
3484
+ rtpNaive += r.rtpCache.naiveBackingBytes;
3485
+ rtpBacking += r.rtpCache.distinctBackingBytes;
3486
+ pushFrames += r.pushQueue.entries;
3487
+ pushBacking += r.pushQueue.distinctBackingBytes;
3488
+ replayPackets += r.rtpReplay.entries;
3489
+ replayBacking += r.rtpReplay.distinctBackingBytes;
3490
+ scrubPayload += r.scrub.payloadBytes;
3491
+ scrubBacking += r.scrub.distinctBackingBytes;
3492
+ }
3493
+ const top = [...sessions].sort((a, b) => sessionBackingBytes(b) - sessionBackingBytes(a)).slice(0, 3).map((r) => `${r.deviceId ?? "x"}:${mb(sessionBackingBytes(r))}`).join(",");
3494
+ const parts = [
3495
+ `rtpCacheEntries=${rtpEntries}`,
3496
+ `rtpCachePayloadMb=${mb(rtpPayload)}`,
3497
+ `rtpCacheNaiveMb=${mb(rtpNaive)}`,
3498
+ `rtpCacheBackingMb=${mb(rtpBacking)}`,
3499
+ `pushQueueFrames=${pushFrames}`,
3500
+ `pushQueueBackingMb=${mb(pushBacking)}`,
3501
+ `rtpReplayPackets=${replayPackets}`,
3502
+ `rtpReplayBackingMb=${mb(replayBacking)}`,
3503
+ `scrubPayloadMb=${mb(scrubPayload)}`,
3504
+ `scrubBackingMb=${mb(scrubBacking)}`
3505
+ ];
3506
+ if (input.arrayBuffersBytes !== void 0) parts.push(`arrayBuffersMb=${mb(input.arrayBuffersBytes)}`);
3507
+ if (input.proc.maps !== null && input.v8 !== void 0 && input.v8 !== null) parts.push(`fragMb=${mb(input.proc.maps.heapBytes - input.v8.externalBytes)}`);
3508
+ parts.push(`topSessionMb=${top === "" ? "none" : top}`);
3509
+ return parts;
3510
+ }
3330
3511
  /** Count entries named `name` in `process.getActiveResourcesInfo()`. */
3331
3512
  function countActiveResources(resources, name) {
3332
3513
  let n = 0;
@@ -3334,69 +3515,6 @@ function countActiveResources(resources, name) {
3334
3515
  return n;
3335
3516
  }
3336
3517
  //#endregion
3337
- //#region src/stream-broker/stream-broker/owned-buffer.ts
3338
- /**
3339
- * Buffers that enter a LONG-LIVED ring own their backing store.
3340
- *
3341
- * Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
3342
- * below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
3343
- * That is the right choice for the ingest path — a start code, a 4-byte
3344
- * interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
3345
- * within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
3346
- * for 60 s in a clip ring pins its whole slab, and with it every short-lived
3347
- * packet that happened to be allocated beside it. On the hub that held
3348
- * 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
3349
- * pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
3350
- *
3351
- * The ring byte caps counted payload bytes, so they never saw it. Both rings
3352
- * now pass every entry through {@link detachFromPool} on the way in and
3353
- * account it with {@link backingStoreBytes}, so the cap measures what the
3354
- * entry costs. Short-lived traffic stays pooled.
3355
- */
3356
- /** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
3357
- function ownsBackingStore(buf) {
3358
- return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
3359
- }
3360
- /**
3361
- * What retaining `buf` really costs: its own length when it owns its
3362
- * ArrayBuffer, the whole shared slab when it is a view of one.
3363
- */
3364
- function backingStoreBytes(buf) {
3365
- return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
3366
- }
3367
- /**
3368
- * `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
3369
- * buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
3370
- * runtime detail), else a copy that does. One copy, never two: a buffer that is
3371
- * already unpooled is returned as-is.
3372
- */
3373
- function detachFromPool(buf) {
3374
- if (ownsBackingStore(buf)) return buf;
3375
- const owned = Buffer.allocUnsafeSlow(buf.length);
3376
- buf.copy(owned);
3377
- return owned;
3378
- }
3379
- /**
3380
- * A copy of `src[start, end)` that OWNS its backing store — the shape a reader
3381
- * hands out when the packet it cuts from its socket buffer is going to be
3382
- * retained anyway.
3383
- *
3384
- * Every source video packet is: `captureRtpForPreBuffer` keeps it for the life
3385
- * of a GOP, and the restreamer's keyframe cache keeps the keyframe ones. With
3386
- * the readers cutting packets as `Buffer.from(subarray)` — a pooled view — the
3387
- * ring then had to copy each one a second time to own it: two copies of every
3388
- * packet on the ingest path (measured 15.1 + 14.8 MB/s on the hub's load).
3389
- * Cutting the packet owned up front makes the ring's and the cache's
3390
- * `detachFromPool` an identity, so the packet is copied once and shared by the
3391
- * reader's fan-out, the ring, the cache and every session's write queue.
3392
- */
3393
- function copyOwned(src, start, end) {
3394
- const owned = Buffer.allocUnsafeSlow(end - start);
3395
- src.copy(owned, 0, start, end);
3396
- recordCopyOwned(end - start);
3397
- return owned;
3398
- }
3399
- //#endregion
3400
3518
  //#region src/stream-broker/rtsp/sdp-builder.ts
3401
3519
  function buildSdp(options) {
3402
3520
  const { codec, parameterSets, streamName } = options;
@@ -24939,6 +25057,23 @@ var AdaptiveSession = class AdaptiveSession {
24939
25057
  } });
24940
25058
  }
24941
25059
  /** Whether the session has an active feed (vs idle/pooled). */
25060
+ /**
25061
+ * What this session pins outside the heap, by backing store: werift's NACK
25062
+ * history (video + audio, one tally so a slab shared by both counts once) and
25063
+ * the paced replay queue. Probe / lifecycle-line time only — it walks
25064
+ * ≤ 2 × RTP_HISTORY_SIZE slots and the replay tail, never per packet.
25065
+ */
25066
+ readMemTotals() {
25067
+ const cache = new BackingTally();
25068
+ tallyRtpCache(cache, this.videoSender?.rtpCache);
25069
+ tallyRtpCache(cache, this.audioSender?.rtpCache);
25070
+ const replay = new BackingTally();
25071
+ this.activeRtpReplay?.forEachQueued((pkt) => replay.add(pkt));
25072
+ return {
25073
+ rtpCache: cache.totals(),
25074
+ rtpReplay: replay.totals()
25075
+ };
25076
+ }
24942
25077
  get isFeeding() {
24943
25078
  return this.feedAbort !== null && !this.feedAbort.signal.aborted;
24944
25079
  }
@@ -28202,6 +28337,13 @@ var ScrubFragmentCache = class {
28202
28337
  get size() {
28203
28338
  return this.map.size;
28204
28339
  }
28340
+ /**
28341
+ * Feed every cached access unit to `tally` — the BACKING-store view of what
28342
+ * `byteSize` counts by `.length`. Probe time only (`session-mem.ts`).
28343
+ */
28344
+ tallyBacking(tally) {
28345
+ for (const frag of this.map.values()) for (const au of frag.aus) tally.add(au.data);
28346
+ }
28205
28347
  };
28206
28348
  //#endregion
28207
28349
  //#region src/stream-broker/recorded/segment-pts-repair.ts
@@ -35256,7 +35398,8 @@ var BrokerWebrtcServer = class {
35256
35398
  iceServers: setup.iceServerCount,
35257
35399
  codec: setup.sessionCodec,
35258
35400
  useRtpRepacketizer: setup.useRtpRepacketizer,
35259
- relayOnly: opts.relayOnly === true
35401
+ relayOnly: opts.relayOnly === true,
35402
+ ...this.sessionMemMetaFor(setup.sessionId)
35260
35403
  } });
35261
35404
  return {
35262
35405
  sessionId: setup.sessionId,
@@ -35306,7 +35449,8 @@ var BrokerWebrtcServer = class {
35306
35449
  setupMs,
35307
35450
  negotiateMs,
35308
35451
  totalMs: Date.now() - offerStart,
35309
- ...setup.setupTimings
35452
+ ...setup.setupTimings,
35453
+ ...this.sessionMemMetaFor(setup.sessionId)
35310
35454
  } });
35311
35455
  return {
35312
35456
  sessionId: setup.sessionId,
@@ -35415,7 +35559,7 @@ var BrokerWebrtcServer = class {
35415
35559
  let framesDroppedSinceLog = 0;
35416
35560
  let purgesSinceLog = 0;
35417
35561
  let unalignedPurgesSinceLog = 0;
35418
- const { source, pushFrame, close: closeSource } = createPushFrameSource((count, keyframeAligned) => {
35562
+ const { source, pushFrame, close: closeSource, forEachQueued: forEachQueuedFrame } = createPushFrameSource((count, keyframeAligned) => {
35419
35563
  framesDroppedSinceLog += count;
35420
35564
  purgesSinceLog += 1;
35421
35565
  if (!keyframeAligned) unalignedPurgesSinceLog += 1;
@@ -35694,6 +35838,7 @@ var BrokerWebrtcServer = class {
35694
35838
  unsubSdpParams,
35695
35839
  unsubSourceRestart,
35696
35840
  closeSource,
35841
+ forEachQueuedFrame,
35697
35842
  ...talkback !== void 0 ? { closeTalkback: () => void talkback.close() } : {},
35698
35843
  rebind: {
35699
35844
  deviceId,
@@ -35986,6 +36131,7 @@ var BrokerWebrtcServer = class {
35986
36131
  })
35987
36132
  });
35988
36133
  entry.timeline = timeline;
36134
+ entry.scrubCache = scrubCache;
35989
36135
  sessionLogger.info("recorded: TimelineSession wired (control accessors present)", { meta: {
35990
36136
  sessionId,
35991
36137
  deviceId
@@ -36117,6 +36263,55 @@ var BrokerWebrtcServer = class {
36117
36263
  return this.sessions.size;
36118
36264
  }
36119
36265
  /**
36266
+ * One session's pinned-memory reading, by backing store (`session-mem.ts`).
36267
+ * Walks the session's NACK history, push queue, replay tail and scrub cache:
36268
+ * call at probe / lifecycle-line time only.
36269
+ */
36270
+ readSessionMem(sessionId, entry) {
36271
+ const own = entry.session.readMemTotals();
36272
+ const push = new BackingTally();
36273
+ entry.forEachQueuedFrame((mf) => push.add(mf.frame.data));
36274
+ const scrub = new BackingTally();
36275
+ entry.scrubCache?.tallyBacking(scrub);
36276
+ return {
36277
+ sessionId,
36278
+ deviceId: deviceIdFromBrokerId(entry.brokerId),
36279
+ rtpCache: own.rtpCache,
36280
+ rtpReplay: own.rtpReplay,
36281
+ pushQueue: push.totals(),
36282
+ scrub: scrub.totals()
36283
+ };
36284
+ }
36285
+ /** Every live session's reading, for the `[broker-mem]` line. */
36286
+ getSessionMemReadings() {
36287
+ const readings = [];
36288
+ for (const [sessionId, entry] of this.sessions) try {
36289
+ readings.push(this.readSessionMem(sessionId, entry));
36290
+ } catch (err) {
36291
+ this.logger.warn("session memory reading failed", {
36292
+ tags: { deviceId: deviceIdFromBrokerId(entry.brokerId) },
36293
+ meta: {
36294
+ sessionId,
36295
+ error: errMsg(err)
36296
+ }
36297
+ });
36298
+ }
36299
+ return readings;
36300
+ }
36301
+ /** The counters a lifecycle line carries; empty when the session is gone. */
36302
+ sessionMemMetaFor(sessionId) {
36303
+ const entry = this.sessions.get(sessionId);
36304
+ if (entry === void 0) return {};
36305
+ try {
36306
+ return sessionMemMeta(this.readSessionMem(sessionId, entry));
36307
+ } catch (err) {
36308
+ return {
36309
+ memReadFailed: true,
36310
+ memReadError: errMsg(err)
36311
+ };
36312
+ }
36313
+ }
36314
+ /**
36120
36315
  * People watching LIVE through this server, per camera (`live-viewers.ts`):
36121
36316
  * sessions of a viewer kind whose track is on the live edge. A session on a
36122
36317
  * recorded playhead is not watching live; the system's own subscriptions
@@ -36925,6 +37120,15 @@ var BrokerWebrtcServer = class {
36925
37120
  cleanupSession(sessionId) {
36926
37121
  const entry = this.sessions.get(sessionId);
36927
37122
  if (!entry) return;
37123
+ const deviceId = deviceIdFromBrokerId(entry.brokerId);
37124
+ this.logger.info("WebRTC session closing: pinned-memory counters", {
37125
+ tags: { deviceId },
37126
+ meta: {
37127
+ sessionId,
37128
+ deviceId,
37129
+ ...this.sessionMemMetaFor(sessionId)
37130
+ }
37131
+ });
36928
37132
  this.sessions.delete(sessionId);
36929
37133
  reportConsumerEvent(this.logger, "detach", `webrtc:${entry.consumerKind}`, entry.rebind?.deviceId, this.sessions.size);
36930
37134
  entry.pendingSwitch?.cancel();
@@ -37119,28 +37323,33 @@ function createPushFrameSource(onFramesDropped) {
37119
37323
  });
37120
37324
  }
37121
37325
  };
37122
- return {
37123
- source: (async function* () {
37124
- try {
37125
- while (true) {
37126
- const item = queue.shift();
37127
- if (item) {
37128
- yield item;
37129
- continue;
37130
- }
37131
- if (done) return;
37132
- const result = await new Promise((r) => {
37133
- resolve = r;
37134
- });
37135
- if (result.done) return;
37136
- yield result.value;
37326
+ const source = (async function* () {
37327
+ try {
37328
+ while (true) {
37329
+ const item = queue.shift();
37330
+ if (item) {
37331
+ yield item;
37332
+ continue;
37137
37333
  }
37138
- } finally {
37139
- done = true;
37334
+ if (done) return;
37335
+ const result = await new Promise((r) => {
37336
+ resolve = r;
37337
+ });
37338
+ if (result.done) return;
37339
+ yield result.value;
37140
37340
  }
37141
- })(),
37341
+ } finally {
37342
+ done = true;
37343
+ }
37344
+ })();
37345
+ const forEachQueued = (visit) => {
37346
+ for (const frame of queue) visit(frame);
37347
+ };
37348
+ return {
37349
+ source,
37142
37350
  pushFrame,
37143
- close
37351
+ close,
37352
+ forEachQueued
37144
37353
  };
37145
37354
  }
37146
37355
  //#endregion
@@ -37674,7 +37883,9 @@ async function emitOnce(deps) {
37674
37883
  churn: consumerChurn.drain(),
37675
37884
  copied: readCopyOwnedTotals(),
37676
37885
  v8: readV8Heap(),
37677
- worker
37886
+ worker,
37887
+ sessions: deps.sessionMem(),
37888
+ arrayBuffersBytes: process.memoryUsage().arrayBuffers
37678
37889
  });
37679
37890
  deps.logger.info(line);
37680
37891
  }
@@ -38225,7 +38436,8 @@ var StreamBrokerAddon = class extends BaseAddon {
38225
38436
  this.stopBrokerMemReport = startBrokerMemReport({
38226
38437
  logger: this.ctx.logger,
38227
38438
  manager: this.brokerManager,
38228
- webrtcSessions: () => webrtcServer.getSessionCount()
38439
+ webrtcSessions: () => webrtcServer.getSessionCount(),
38440
+ sessionMem: () => webrtcServer.getSessionMemReadings()
38229
38441
  });
38230
38442
  const widgetsProvider = { listWidgets: async () => [{
38231
38443
  tab: "device-tab",
@@ -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-CMHYV8dG.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DjoAY42S.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/i18n/en.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "globalSettings.s_cluster-recording.title": "Cluster recording",
3
+ "globalSettings.s_cluster-recording.description": "Which node runs recording (continuous + events write path). A SINGLE node — no multi-node balancing; every other node stays fully inert. Default: hub.",
4
+ "globalSettings.s_cluster-recording.recordingNodeId.label": "Recording node",
5
+ "globalSettings.s_cluster-recording.recordingNodeId.description": "Node id that records + serves playback (e.g. \"hub\"). Leave \"hub\" unless you intentionally move recording to an agent. Takes effect on addon restart.",
6
+ "globalSettings.s_cluster-recording.recordingHubHostname.label": "Hub host (cross-node)",
7
+ "globalSettings.s_cluster-recording.recordingHubHostname.description": "Advanced: only used when the recording node is an AGENT. The hub's host the agent dials to pull broker streams (the RTSP restream port must be reachable there). Leave empty to auto-derive from the agent's hub connection."
8
+ }
package/i18n/it.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "globalSettings.s_cluster-recording.title": "Registrazione del cluster",
3
+ "globalSettings.s_cluster-recording.description": "Quale nodo esegue la registrazione (percorso di scrittura continuo + eventi). UN SOLO nodo — nessun bilanciamento tra più nodi; tutti gli altri restano del tutto inattivi. Predefinito: hub.",
4
+ "globalSettings.s_cluster-recording.recordingNodeId.label": "Nodo di registrazione",
5
+ "globalSettings.s_cluster-recording.recordingNodeId.description": "Id del nodo che registra e serve la riproduzione (es. \"hub\"). Lascia \"hub\" a meno che tu non voglia spostare di proposito la registrazione su un agent. Ha effetto al riavvio dell'addon.",
6
+ "globalSettings.s_cluster-recording.recordingHubHostname.label": "Host dell'hub (tra nodi)",
7
+ "globalSettings.s_cluster-recording.recordingHubHostname.description": "Avanzato: usato solo quando il nodo di registrazione è un AGENT. L'host dell'hub che l'agent contatta per prelevare i flussi del broker (la porta di restream RTSP deve essere raggiungibile lì). Lascia vuoto per ricavarlo dalla connessione dell'agent all'hub."
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.369",
3
+ "version": "1.2.371",
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",
@@ -47,6 +47,7 @@
47
47
  },
48
48
  "files": [
49
49
  "dist",
50
+ "i18n",
50
51
  "python",
51
52
  "swift",
52
53
  "wasm",
@@ -223,6 +224,10 @@
223
224
  "rssBudgetMb": 3584
224
225
  },
225
226
  "capabilities": [
227
+ {
228
+ "name": "device-removal-hook",
229
+ "optional": true
230
+ },
226
231
  {
227
232
  "name": "recording"
228
233
  },