@camstack/addon-pipeline 1.2.362 → 1.2.364

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 (34) 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-C77XAnt9.mjs → custom-model-catalog-DHFPSu0E.mjs} +2 -2
  4. package/dist/{custom-model-catalog-CL9Tcn2Y.js → custom-model-catalog-nx2TXAGS.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-D629dz5x.mjs → dist-BX9VTm12.mjs} +177 -3
  8. package/dist/{dist-tclsRHCk.js → dist-CGOb--t-.js} +177 -3
  9. package/dist/{model-catalogs-Y1WJrGyS.js → model-catalogs-DHV48cdY.js} +1 -1
  10. package/dist/{model-catalogs-BxQBJBt4.mjs → model-catalogs-DNL4ydAg.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-D2D0N7_h.mjs → node-C8bbmV3c.mjs} +1 -1
  14. package/dist/{node-BYrYuZdh.js → node-UBMRK8SK.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-eM4qLFdC.js → process-memory-1BwIq2Ks.js} +1 -1
  18. package/dist/{process-memory-BR1e6q_Q.mjs → process-memory-BHG2uCeq.mjs} +1 -1
  19. package/dist/recorder/index.js +3 -3
  20. package/dist/recorder/index.mjs +3 -3
  21. package/dist/{segment-demux-js-C5IOs4lZ.mjs → segment-demux-js-BhnT__dA.mjs} +1 -1
  22. package/dist/{segment-demux-js-B9EqRfBi.js → segment-demux-js-PkDP-xQg.js} +1 -1
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C4Cp_OAY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-vdmrukBk.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-CICWhpXS.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ScPS8ZWM.mjs} +1 -1
  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-Cq5NPjBX.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-CZ_HBwki.mjs → hostInit-D_DBY5wu.mjs} +2 -2
  30. package/dist/stream-broker/index.js +357 -71
  31. package/dist/stream-broker/index.mjs +357 -71
  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_ui_mf_2_library__loadShare__.js-7VuNrr9l.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-D629dz5x.mjs";
3
- import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-D2D0N7_h.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-BX9VTm12.mjs";
3
+ import { i as NO_COST_CLAIM, n as Fmp4FragmentChild, o as probeFfmpegBinary, r as Fmp4FragmentPlane, t as ChildCostRegistry } from "../node-C8bbmV3c.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-C5IOs4lZ.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-BhnT__dA.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";
@@ -3727,7 +3727,12 @@ var RtspRestreamer = class {
3727
3727
  * Forwarded directly to all connected RTSP sessions — zero re-packetization.
3728
3728
  *
3729
3729
  * @param rtpData Complete RTP packet (header + payload)
3730
- * @param isKeyframe True if this packet is part of a keyframe access unit
3730
+ * @param isKeyframe True if this packet is part of a keyframe ACCESS UNIT —
3731
+ * judged per picture by the broker's `RtpAccessUnitTracker`: every
3732
+ * fragment of a fragmented IDR and a prefix SEI inside the picture carry
3733
+ * it, not only the packet that completed an IRAP NAL. The keyframe cache
3734
+ * below collects exactly that run, and the transition into it is the
3735
+ * once-per-picture `keyframeAuStart` the sessions resume and count on.
3731
3736
  * @param isParameterSet True if this packet contains SPS/PPS/VPS
3732
3737
  */
3733
3738
  pushRtpPassthrough(rtpData, isKeyframe, isParameterSet) {
@@ -3743,14 +3748,14 @@ var RtspRestreamer = class {
3743
3748
  this.lastKeyframeRtpBytes = 0;
3744
3749
  }
3745
3750
  }
3751
+ if (this.collectingKeyframeRtp && !isKeyframe && !isParameterSet) this.collectingKeyframeRtp = false;
3746
3752
  if (this.collectingKeyframeRtp) {
3747
3753
  this.lastKeyframeRtpPackets.push({
3748
3754
  data: Buffer.from(rtpData),
3749
3755
  marker: rtpPacket.marker
3750
3756
  });
3751
3757
  this.lastKeyframeRtpBytes += rtpData.length;
3752
- if (!isKeyframe && !isParameterSet) this.collectingKeyframeRtp = false;
3753
- else if (this.lastKeyframeRtpBytes > MAX_KEYFRAME_CACHE_BYTES) {
3758
+ if (this.lastKeyframeRtpBytes > MAX_KEYFRAME_CACHE_BYTES) {
3754
3759
  this.warnCacheCapExceeded("rtp", this.lastKeyframeRtpBytes, this.lastKeyframeRtpPackets.length);
3755
3760
  this.lastKeyframeRtpPackets = [];
3756
3761
  this.lastKeyframeRtpBytes = 0;
@@ -3763,17 +3768,16 @@ var RtspRestreamer = class {
3763
3768
  if (!session.isPlaying()) continue;
3764
3769
  this.pendingKeyframe.delete(session.getSessionId());
3765
3770
  }
3766
- const keyframeAu = isKeyframe || isParameterSet;
3767
3771
  for (const session of this.sessions.values()) {
3768
3772
  const sessionId = session.getSessionId();
3769
3773
  if (this.activeReplays.get(sessionId)?.enqueue({
3770
3774
  packet: rtpPacket,
3771
3775
  prime: false,
3772
- keyframeAuStart: keyframeAu
3776
+ keyframeAuStart
3773
3777
  })) continue;
3774
3778
  if (this.pendingKeyframe.has(sessionId)) continue;
3775
3779
  if (!session.isPlaying()) continue;
3776
- this.recordLiveOutcome(session.sendRtp(rtpPacket, false, keyframeAu));
3780
+ this.recordLiveOutcome(session.sendRtp(rtpPacket, false, keyframeAuStart));
3777
3781
  }
3778
3782
  }
3779
3783
  /**
@@ -4626,15 +4630,16 @@ var RtspSession = class {
4626
4630
  *
4627
4631
  * ## Why sharing the payload is safe, and what would make it unsafe
4628
4632
  *
4629
- * The payload buffer handed here is ALREADY retained, by reference, in the
4630
- * broker's `rtpRing` for the life of a GOP (`captureRtpForPreBuffer` pushes
4631
- * the same object). A pending `socket.write` cannot outlive that, so the
4632
- * lifetime requirement this adds is strictly weaker than one the broker
4633
- * already meets. Its provenance is a private allocation per packet —
4633
+ * The payload buffer handed here is a private allocation per packet —
4634
4634
  * `Buffer.from(readBuffer.subarray(...))` in `rtsp-client.ts`'s `onData`, for
4635
4635
  * video and audio alike — never a view of the reader's socket buffer, which
4636
4636
  * advances on the next read. Nothing in the broker writes into a captured
4637
4637
  * packet: every mutation in this path targets a freshly allocated buffer.
4638
+ * (The broker's `rtpRing` keeps its OWN copy of a pooled packet —
4639
+ * `detachFromPool`, so a ring entry never pins a shared pool slab — so the
4640
+ * ring no longer vouches for THIS object's lifetime, and it does not need
4641
+ * to: a pending write holds its own reference, and the rule below is about
4642
+ * mutation, not retention.)
4638
4643
  *
4639
4644
  * The rule to keep: **`socket.write` forbids MUTATION before the write
4640
4645
  * completes, not retention.** So the header, which IS built per call, must
@@ -10123,6 +10128,16 @@ function allocAnnexBNal(byteLength) {
10123
10128
  buf[3] = 1;
10124
10129
  return buf;
10125
10130
  }
10131
+ var NO_PAYLOAD_CLASS = {
10132
+ keyframe: false,
10133
+ parameterSet: false
10134
+ };
10135
+ function isH265IrapType(type) {
10136
+ return type >= H265_NAL_IRAP_MIN && type <= H265_NAL_IRAP_MAX;
10137
+ }
10138
+ function isH265ParameterSetType(type) {
10139
+ return type === H265_NAL_VPS || type === H265_NAL_SPS || type === H265_NAL_PPS;
10140
+ }
10126
10141
  /** Total byte length of the reassembly fragments, without allocating a closure. */
10127
10142
  function totalFragmentBytes(fragments) {
10128
10143
  let total = 0;
@@ -10168,16 +10183,92 @@ var RtpDepacketizer = class {
10168
10183
  }
10169
10184
  /**
10170
10185
  * Process a raw RTP packet (including 12-byte RTP header).
10171
- * Emits zero or more complete NAL units via the callback.
10186
+ * Emits zero or more complete NAL units via the callback and returns what
10187
+ * THIS packet carried ({@link RtpPacketClass}) — `null` for a packet too
10188
+ * short to be RTP with a payload.
10172
10189
  */
10173
10190
  processPacket(rtpData) {
10174
- if (rtpData.length < 12) return;
10191
+ if (rtpData.length < 12) return null;
10175
10192
  const marker = (rtpData[1] & 128) !== 0;
10176
10193
  const timestamp = rtpData.readUInt32BE(4);
10177
10194
  const payload = rtpData.subarray(12);
10178
- if (payload.length === 0) return;
10195
+ if (payload.length === 0) return null;
10196
+ const carried = this.codec === "h264" ? this.classifyH264Payload(payload) : this.classifyH265Payload(payload);
10179
10197
  if (this.codec === "h264") this.processH264(payload, timestamp, marker);
10180
10198
  else this.processH265(payload, timestamp, marker);
10199
+ return {
10200
+ timestamp,
10201
+ marker,
10202
+ keyframe: carried.keyframe,
10203
+ parameterSet: carried.parameterSet
10204
+ };
10205
+ }
10206
+ /** H.264: single NAL by its type; STAP-A by every aggregated type; FU-A by the FU header. */
10207
+ classifyH264Payload(payload) {
10208
+ const nalType = payload[0] & 31;
10209
+ if (nalType === H264_NAL_STAP_A) {
10210
+ let keyframe = false;
10211
+ let parameterSet = false;
10212
+ let offset = 1;
10213
+ while (offset + 2 <= payload.length) {
10214
+ const nalSize = payload.readUInt16BE(offset);
10215
+ offset += 2;
10216
+ if (nalSize === 0 || offset + nalSize > payload.length) break;
10217
+ const type = payload[offset] & 31;
10218
+ keyframe ||= type === H264_NAL_IDR;
10219
+ parameterSet ||= type === H264_NAL_SPS || type === H264_NAL_PPS;
10220
+ offset += nalSize;
10221
+ }
10222
+ return {
10223
+ keyframe,
10224
+ parameterSet
10225
+ };
10226
+ }
10227
+ if (nalType === 28) {
10228
+ if (payload.length < 2) return NO_PAYLOAD_CLASS;
10229
+ return {
10230
+ keyframe: (payload[1] & 31) === H264_NAL_IDR,
10231
+ parameterSet: false
10232
+ };
10233
+ }
10234
+ return {
10235
+ keyframe: nalType === H264_NAL_IDR,
10236
+ parameterSet: nalType === H264_NAL_SPS || nalType === H264_NAL_PPS
10237
+ };
10238
+ }
10239
+ /** H.265: single NAL by its type; AP by every aggregated type; FU by the FU header. */
10240
+ classifyH265Payload(payload) {
10241
+ if (payload.length < 2) return NO_PAYLOAD_CLASS;
10242
+ const nalType = payload[0] >> 1 & 63;
10243
+ if (nalType === H265_NAL_AP) {
10244
+ let keyframe = false;
10245
+ let parameterSet = false;
10246
+ let offset = 2;
10247
+ while (offset + 2 <= payload.length) {
10248
+ const nalSize = payload.readUInt16BE(offset);
10249
+ offset += 2;
10250
+ if (nalSize === 0 || offset + nalSize > payload.length) break;
10251
+ const type = payload[offset] >> 1 & 63;
10252
+ keyframe ||= isH265IrapType(type);
10253
+ parameterSet ||= isH265ParameterSetType(type);
10254
+ offset += nalSize;
10255
+ }
10256
+ return {
10257
+ keyframe,
10258
+ parameterSet
10259
+ };
10260
+ }
10261
+ if (nalType === 49) {
10262
+ if (payload.length < 3) return NO_PAYLOAD_CLASS;
10263
+ return {
10264
+ keyframe: isH265IrapType(payload[2] & 63),
10265
+ parameterSet: false
10266
+ };
10267
+ }
10268
+ return {
10269
+ keyframe: isH265IrapType(nalType),
10270
+ parameterSet: isH265ParameterSetType(nalType)
10271
+ };
10181
10272
  }
10182
10273
  processH264(payload, timestamp, marker) {
10183
10274
  const nalType = payload[0] & 31;
@@ -10323,6 +10414,138 @@ var RtpDepacketizer = class {
10323
10414
  }
10324
10415
  };
10325
10416
  //#endregion
10417
+ //#region src/stream-broker/rtsp/rtp-access-unit-tracker.ts
10418
+ /**
10419
+ * Tracks access-unit membership across the source RTP packets of ONE stream.
10420
+ *
10421
+ * The RTSP restreamer forwards source RTP untouched and must know, per
10422
+ * packet, whether it is part of a keyframe picture: that is what its keyframe
10423
+ * cache collects and what a backlog-latched session resumes on. The
10424
+ * depacketizer can only say so about a NAL that COMPLETED, so the fragments of
10425
+ * a fragmented IDR before the last one, and a prefix SEI between the parameter
10426
+ * sets and the slice, were reported as plain packets: the cache latch closed
10427
+ * on them and re-opened on the final fragment, and a backlogged session
10428
+ * counted every IRAP slice as a missed keyframe. Here a packet is classified
10429
+ * by what it carries ({@link RtpPacketClass}) and the verdict is carried
10430
+ * across the whole picture, bounded by the RTP timestamp and marker bit.
10431
+ *
10432
+ * A parameter-set-only run is not a picture: a camera that stamps SPS/PPS
10433
+ * with the previous timestamp still announces ONE keyframe, so the keyframe
10434
+ * verdict carries over into the slice that follows instead of starting twice.
10435
+ */
10436
+ var RtpAccessUnitTracker = class {
10437
+ timestamp = -1;
10438
+ previousMarker = true;
10439
+ keyframeAu = false;
10440
+ /** The picture in progress has received something other than a parameter set. */
10441
+ pictureSeen = false;
10442
+ classify(packet) {
10443
+ const auStart = this.previousMarker || packet.timestamp !== this.timestamp;
10444
+ if (auStart && this.pictureSeen) this.keyframeAu = false;
10445
+ if (auStart) this.pictureSeen = false;
10446
+ this.timestamp = packet.timestamp;
10447
+ this.previousMarker = packet.marker;
10448
+ const keyframeContent = packet.keyframe || packet.parameterSet;
10449
+ const keyframeAuStart = keyframeContent && !this.keyframeAu;
10450
+ if (keyframeContent) this.keyframeAu = true;
10451
+ if (!packet.parameterSet) this.pictureSeen = true;
10452
+ return {
10453
+ auStart,
10454
+ keyframeAu: this.keyframeAu,
10455
+ keyframeAuStart,
10456
+ parameterSet: packet.parameterSet
10457
+ };
10458
+ }
10459
+ /** A redial re-bases the clock; nothing carries over. */
10460
+ reset() {
10461
+ this.timestamp = -1;
10462
+ this.previousMarker = true;
10463
+ this.keyframeAu = false;
10464
+ this.pictureSeen = false;
10465
+ }
10466
+ };
10467
+ //#endregion
10468
+ //#region src/stream-broker/stream-broker/access-unit-assembler.ts
10469
+ /**
10470
+ * Groups depacketized NALs into ACCESS UNITS — one per picture.
10471
+ *
10472
+ * The broker's `EncodedPacket` is one access unit, and every consumer judges it
10473
+ * that way: `keyframe` gates where a clip ring widens to, where a late joiner
10474
+ * can start, how the pre-roll window is cut; the stats count it as one picture
10475
+ * and time the GOP from one `keyframe` to the next. A multi-slice camera (a
10476
+ * Hikvision G2 main stream is 3 slices per picture) used to hand each slice —
10477
+ * and each prefix SEI — down as its own "frame": 75 input fps on a 25 fps
10478
+ * camera, an 8–34 ms "GOP", a clip ring that widened to the LAST IRAP slice
10479
+ * (a third of the picture, without its parameter sets), and a prefix SEI that
10480
+ * inherited the welded VPS/SPS/PPS instead of the IDR.
10481
+ *
10482
+ * Boundary rule (RFC 3550 / 6184 / 7798): the RTP marker bit closes the
10483
+ * picture it ends — flushed on the spot, so a single-slice stream pays no
10484
+ * latency — and a timestamp change closes the one before it, for cameras that
10485
+ * never set the marker. Parameter sets are held and welded onto the picture
10486
+ * that follows them, so a keyframe access unit is self-contained
10487
+ * (VPS/SPS/PPS + IDR) exactly as before; a prefix SEI stays inside its
10488
+ * picture and is never a frame of its own.
10489
+ */
10490
+ var AccessUnitAssembler = class {
10491
+ onAccessUnit;
10492
+ /** Parameter sets seen since the last picture closed; welded onto the next one. */
10493
+ pendingParamNals = [];
10494
+ /** NALs of the picture in progress (parameter sets first when welded). */
10495
+ pictureNals = [];
10496
+ pictureTimestamp = 0;
10497
+ pictureKeyframe = false;
10498
+ constructor(onAccessUnit) {
10499
+ this.onAccessUnit = onAccessUnit;
10500
+ }
10501
+ push(depacketized) {
10502
+ const { annexB, keyframe, parameterSet, timestamp, marker } = depacketized;
10503
+ if (this.pictureNals.length > 0 && timestamp !== this.pictureTimestamp) this.flush();
10504
+ if (parameterSet) {
10505
+ this.pendingParamNals.push(annexB);
10506
+ return;
10507
+ }
10508
+ if (this.pictureNals.length === 0) {
10509
+ this.pictureTimestamp = timestamp;
10510
+ this.pictureKeyframe = false;
10511
+ }
10512
+ if (this.pendingParamNals.length > 0) {
10513
+ for (const paramNal of this.pendingParamNals) this.pictureNals.push(paramNal);
10514
+ this.pendingParamNals = [];
10515
+ this.pictureKeyframe = true;
10516
+ }
10517
+ this.pictureNals.push(annexB);
10518
+ if (keyframe) this.pictureKeyframe = true;
10519
+ if (marker) this.flush();
10520
+ }
10521
+ /**
10522
+ * Emit the picture in progress, if any. Called on the marker bit, on a
10523
+ * timestamp change, and by the broker when the stream stops or redials so
10524
+ * the last picture of a marker-less stream is not lost.
10525
+ */
10526
+ flush() {
10527
+ if (this.pictureNals.length === 0) return;
10528
+ const nals = this.pictureNals;
10529
+ const timestamp = this.pictureTimestamp;
10530
+ const keyframe = this.pictureKeyframe;
10531
+ this.pictureNals = [];
10532
+ this.pictureKeyframe = false;
10533
+ const annexB = nals.length === 1 ? nals[0] : Buffer.concat(nals);
10534
+ this.onAccessUnit({
10535
+ annexB,
10536
+ timestamp,
10537
+ keyframe
10538
+ });
10539
+ }
10540
+ /** Drop everything held — a redial re-bases the clock, nothing carries over. */
10541
+ reset() {
10542
+ this.pendingParamNals = [];
10543
+ this.pictureNals = [];
10544
+ this.pictureTimestamp = 0;
10545
+ this.pictureKeyframe = false;
10546
+ }
10547
+ };
10548
+ //#endregion
10326
10549
  //#region src/stream-broker/rtsp/rtsp-client.ts
10327
10550
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
10328
10551
  var DEFAULT_KEEPALIVE_INTERVAL_MS = 3e4;
@@ -11291,6 +11514,49 @@ var AudioCodecSession = class {
11291
11514
  }
11292
11515
  };
11293
11516
  //#endregion
11517
+ //#region src/stream-broker/stream-broker/owned-buffer.ts
11518
+ /**
11519
+ * Buffers that enter a LONG-LIVED ring own their backing store.
11520
+ *
11521
+ * Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
11522
+ * below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
11523
+ * That is the right choice for the ingest path — a start code, a 4-byte
11524
+ * interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
11525
+ * within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
11526
+ * for 60 s in a clip ring pins its whole slab, and with it every short-lived
11527
+ * packet that happened to be allocated beside it. On the hub that held
11528
+ * 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
11529
+ * pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
11530
+ *
11531
+ * The ring byte caps counted payload bytes, so they never saw it. Both rings
11532
+ * now pass every entry through {@link detachFromPool} on the way in and
11533
+ * account it with {@link backingStoreBytes}, so the cap measures what the
11534
+ * entry costs. Short-lived traffic stays pooled.
11535
+ */
11536
+ /** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
11537
+ function ownsBackingStore(buf) {
11538
+ return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
11539
+ }
11540
+ /**
11541
+ * What retaining `buf` really costs: its own length when it owns its
11542
+ * ArrayBuffer, the whole shared slab when it is a view of one.
11543
+ */
11544
+ function backingStoreBytes(buf) {
11545
+ return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
11546
+ }
11547
+ /**
11548
+ * `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
11549
+ * buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
11550
+ * runtime detail), else a copy that does. One copy, never two: a buffer that is
11551
+ * already unpooled is returned as-is.
11552
+ */
11553
+ function detachFromPool(buf) {
11554
+ if (ownsBackingStore(buf)) return buf;
11555
+ const owned = Buffer.allocUnsafeSlow(buf.length);
11556
+ buf.copy(owned);
11557
+ return owned;
11558
+ }
11559
+ //#endregion
11294
11560
  //#region src/stream-broker/stream-broker/encoded-ring-buffer.ts
11295
11561
  /**
11296
11562
  * Time-based ring buffer for encoded video packets.
@@ -11301,6 +11567,12 @@ var AudioCodecSession = class {
11301
11567
  *
11302
11568
  * The buffer always starts from the most recent keyframe to ensure
11303
11569
  * consumers can decode the returned data.
11570
+ *
11571
+ * Every entry OWNS its backing store (`owned-buffer.ts`): a sub-stream NAL
11572
+ * arrives as a view of a shared 8 KiB pool slab, and a ring that kept it for
11573
+ * 60 s pinned the slab and every short-lived packet beside it — the clip
11574
+ * rings of ~70 brokers were the largest holder of the hub broker's
11575
+ * 550–980 MB of ArrayBuffers. The byte budget charges what an entry pins.
11304
11576
  */
11305
11577
  var EncodedRingBuffer = class {
11306
11578
  packets = [];
@@ -11332,13 +11604,25 @@ var EncodedRingBuffer = class {
11332
11604
  getDuration() {
11333
11605
  return this.maxDurationMs / 1e3;
11334
11606
  }
11335
- /** Push a new packet into the buffer and evict expired ones. */
11607
+ /**
11608
+ * Push a new packet into the buffer and evict expired ones.
11609
+ *
11610
+ * A pooled payload is copied into its own allocation on the way in; one
11611
+ * that already owns its ArrayBuffer is stored as-is. The consumers fanned
11612
+ * out from the same packet keep the original, so the copy is the ring's
11613
+ * alone and is freed with it.
11614
+ */
11336
11615
  push(packet) {
11337
- this.packets.push(packet);
11338
- this.bytes += packet.data.length;
11616
+ const data = detachFromPool(packet.data);
11617
+ const stored = data === packet.data ? packet : {
11618
+ ...packet,
11619
+ data
11620
+ };
11621
+ this.packets.push(stored);
11622
+ this.bytes += backingStoreBytes(data);
11339
11623
  this.evict();
11340
11624
  }
11341
- /** What the retained window currently costs, in bytes. */
11625
+ /** What the retained window currently costs, in bytes of backing store. */
11342
11626
  getBufferedBytes() {
11343
11627
  return this.bytes;
11344
11628
  }
@@ -11416,14 +11700,14 @@ var EncodedRingBuffer = class {
11416
11700
  let removeCount = 0;
11417
11701
  while (removeCount < this.packets.length && (this.packets[removeCount].pts < cutoff || this.packets[removeCount].pts > newest)) removeCount++;
11418
11702
  if (removeCount > 0) {
11419
- for (let i = 0; i < removeCount; i++) this.bytes -= this.packets[i].data.length;
11703
+ for (let i = 0; i < removeCount; i++) this.bytes -= backingStoreBytes(this.packets[i].data);
11420
11704
  this.packets.splice(0, removeCount);
11421
11705
  }
11422
11706
  if (this.maxBytes > 0 && this.bytes > this.maxBytes) {
11423
11707
  this.clampedByBytes = true;
11424
11708
  let dropped = 0;
11425
11709
  while (dropped < this.packets.length && this.bytes > this.maxBytes) {
11426
- this.bytes -= this.packets[dropped].data.length;
11710
+ this.bytes -= backingStoreBytes(this.packets[dropped].data);
11427
11711
  dropped++;
11428
11712
  }
11429
11713
  if (dropped > 0) this.packets.splice(0, dropped);
@@ -13087,7 +13371,7 @@ var StreamBroker = class StreamBroker {
13087
13371
  this.rtspRestreamer.destroy();
13088
13372
  this.preBuffer.clear();
13089
13373
  this.clipBuffer?.clear();
13090
- this.pendingParamNals = [];
13374
+ this.annexBAssembler.reset();
13091
13375
  this.encodedCallbacks.clear();
13092
13376
  this.audioChunkPlane.dispose();
13093
13377
  }
@@ -13550,7 +13834,7 @@ var StreamBroker = class StreamBroker {
13550
13834
  this.reconcilePrestreamWindow();
13551
13835
  this.preBuffer.clear();
13552
13836
  this.clipBuffer?.clear();
13553
- this.pendingParamNals = [];
13837
+ this.annexBAssembler.reset();
13554
13838
  this.rtspRestreamer.resetStreamState();
13555
13839
  this.totalBytes = 0;
13556
13840
  this.bytesInWindow = 0;
@@ -13859,8 +14143,9 @@ var StreamBroker = class StreamBroker {
13859
14143
  captureRtpForPreBuffer(rtpData) {
13860
14144
  const marker = rtpData.length > 1 && (rtpData[1] & 128) !== 0;
13861
14145
  if (this.rtpRingPrevMarker || this.rtpRing.length === 0) this.rtpRingCurAuStart = this.rtpRing.length;
13862
- this.rtpRing.push(rtpData);
13863
- this.rtpRingBytes += rtpData.length;
14146
+ const owned = detachFromPool(rtpData);
14147
+ this.rtpRing.push(owned);
14148
+ this.rtpRingBytes += backingStoreBytes(owned);
13864
14149
  if (this._lastNalParamSet || this._lastNalKeyframe) {
13865
14150
  const auStart = this.rtpRingCurAuStart;
13866
14151
  const lastMark = this.rtpRingKeyframeMarks[this.rtpRingKeyframeMarks.length - 1];
@@ -14038,18 +14323,18 @@ var StreamBroker = class StreamBroker {
14038
14323
  this.reportPreRollClamp(budget, retentionMs);
14039
14324
  const historyEnd = this.rtpRingKeyframeMarks[this.rtpRingKeyframeMarks.length - 1].index;
14040
14325
  let retained = 0;
14041
- for (let i = cut; i < historyEnd; i++) retained += this.rtpRing[i].length;
14326
+ for (let i = cut; i < historyEnd; i++) retained += backingStoreBytes(this.rtpRing[i]);
14042
14327
  let markIdx = this.rtpRingKeyframeMarks.findIndex((m) => m.index === cut);
14043
14328
  while (retained > budget.bytes && markIdx >= 0 && markIdx < this.rtpRingKeyframeMarks.length - 1) {
14044
14329
  const nextCut = this.rtpRingKeyframeMarks[markIdx + 1].index;
14045
- for (let i = cut; i < nextCut; i++) retained -= this.rtpRing[i].length;
14330
+ for (let i = cut; i < nextCut; i++) retained -= backingStoreBytes(this.rtpRing[i]);
14046
14331
  cut = nextCut;
14047
14332
  markIdx++;
14048
14333
  }
14049
14334
  }
14050
14335
  if (cut <= 0) return;
14051
14336
  const dropped = this.rtpRing.splice(0, cut);
14052
- for (const b of dropped) this.rtpRingBytes -= b.length;
14337
+ for (const b of dropped) this.rtpRingBytes -= backingStoreBytes(b);
14053
14338
  this.rtpRingKeyframeMarks = this.rtpRingKeyframeMarks.filter((mark) => mark.index >= cut).map((mark) => ({
14054
14339
  index: mark.index - cut,
14055
14340
  ts: mark.ts
@@ -14389,11 +14674,15 @@ var StreamBroker = class StreamBroker {
14389
14674
  if (clamped === this.clipRetentionSec) return;
14390
14675
  const previous = this.clipRetentionSec;
14391
14676
  this.clipRetentionSec = clamped;
14392
- this.logger?.info(clamped === 0 ? "Clip retention off" : "Clip retention set", { meta: {
14393
- seconds: clamped,
14394
- requested: seconds,
14395
- previousSeconds: previous
14396
- } });
14677
+ this.logger?.info(clamped === 0 ? "Clip retention off" : "Clip retention set", {
14678
+ tags: { deviceId: this.numericDeviceId },
14679
+ meta: {
14680
+ seconds: clamped,
14681
+ requested: seconds,
14682
+ previousSeconds: previous,
14683
+ capBytes: StreamBroker.CLIP_RING_MAX_BYTES
14684
+ }
14685
+ });
14397
14686
  if (clamped === 0) {
14398
14687
  this.clipBuffer = null;
14399
14688
  return;
@@ -14626,8 +14915,9 @@ var StreamBroker = class StreamBroker {
14626
14915
  } });
14627
14916
  this._lastNalKeyframe = false;
14628
14917
  this._lastNalParamSet = false;
14629
- this.rtpDepacketizer?.processPacket(rtpData);
14630
- this.rtspRestreamer.pushRtpPassthrough(rtpData, this._lastNalKeyframe, this._lastNalParamSet);
14918
+ const carried = this.rtpDepacketizer?.processPacket(rtpData) ?? null;
14919
+ const auFlags = carried ? this.rtpAuTracker.classify(carried) : null;
14920
+ this.rtspRestreamer.pushRtpPassthrough(rtpData, auFlags?.keyframeAu ?? false, auFlags?.parameterSet ?? false);
14631
14921
  this.captureRtpForPreBuffer(rtpData);
14632
14922
  if (this.rtpVideoCallbacks.size > 0) for (const [cb, entry] of this.rtpVideoCallbacks) try {
14633
14923
  cb(rtpData);
@@ -14823,51 +15113,44 @@ var StreamBroker = class StreamBroker {
14823
15113
  * RTSP restreamer's pushRtpPassthrough.
14824
15114
  */
14825
15115
  /**
14826
- * Pending parameter-set NALs (SPS/PPS for H.264, VPS/SPS/PPS for H.265).
14827
- * Cameras emit these as separate RTP packets before the IDR/IRAP frame.
14828
- * We buffer them here and prepend to the next VCL NAL so downstream
14829
- * consumers (WebRTC, prebuffer, restream) receive complete access units
14830
- * with a single `EncodedPacket` that contains SPS+PPS+IDR together.
15116
+ * Groups the depacketizer's NALs into access units — ONE `EncodedPacket`
15117
+ * per picture, parameter sets welded onto the keyframe picture they
15118
+ * precede. See `access-unit-assembler.ts` for the boundary rule and the
15119
+ * per-NAL emit it replaced.
14831
15120
  */
14832
- pendingParamNals = [];
15121
+ annexBAssembler = new AccessUnitAssembler((accessUnit) => this.emitAccessUnit(accessUnit));
15122
+ /** Access-unit membership of the source RTP packets, for the restreamer's flags. */
15123
+ rtpAuTracker = new RtpAccessUnitTracker();
14833
15124
  /**
14834
15125
  * handleDepacketizedNal receives individual NALs from the RTP depacketizer
14835
- * and emits them as EncodedPackets.
14836
- *
14837
- * Parameter-set NALs (SPS/PPS type 7/8 for H.264, VPS/SPS/PPS type 32/33/34
14838
- * for H.265) are buffered and prepended to the next VCL NAL (slice/IDR).
14839
- * This guarantees every keyframe EncodedPacket is a self-contained access
14840
- * unit that a decoder can initialise from — critical for WebRTC (Chrome
14841
- * can't decode an IDR without preceding SPS/PPS).
15126
+ * and hands them to the access-unit assembler, which emits one
15127
+ * `EncodedPacket` per PICTURE (`emitAccessUnit`) — never per NAL. A
15128
+ * multi-slice IDR, its parameter sets and its prefix SEI are one keyframe
15129
+ * packet; three P slices are one P packet.
14842
15130
  *
14843
- * The RTSP restreamer handles its own RTP passthrough separately and is
14844
- * not affected by this merging.
15131
+ * The RTSP restreamer forwards source RTP separately (`onVideoRtp`) and is
15132
+ * not fed from here.
14845
15133
  */
14846
15134
  handleDepacketizedNal(depacketized) {
14847
- const { nal, keyframe, parameterSet, timestamp } = depacketized;
15135
+ if (depacketized.keyframe) this._lastNalKeyframe = true;
15136
+ if (depacketized.parameterSet) this._lastNalParamSet = true;
15137
+ this.annexBAssembler.push(depacketized);
15138
+ }
15139
+ /**
15140
+ * One assembled access unit → one `EncodedPacket` for every Annex-B
15141
+ * consumer (prebuffer, clip ring, pipe, WebRTC). A keyframe packet is
15142
+ * self-contained (VPS/SPS/PPS + every IDR slice), which is what a decoder
15143
+ * initialises from and what a clip or a pre-roll window may start at.
15144
+ */
15145
+ emitAccessUnit(accessUnit) {
14848
15146
  const codec = this.detectedCodec ?? "h264";
14849
- if (keyframe) this._lastNalKeyframe = true;
14850
- if (parameterSet) this._lastNalParamSet = true;
14851
- const isH264 = codec === "h264";
14852
- const nalType = isH264 ? nal[0] & 31 : (nal[0] & 126) >> 1;
14853
- if (isH264 ? nalType === 7 || nalType === 8 : nalType === 32 || nalType === 33 || nalType === 34) {
14854
- this.pendingParamNals.push(depacketized.annexB);
14855
- return;
14856
- }
14857
- const hadParamSets = this.pendingParamNals.length > 0;
14858
- let annexBData;
14859
- if (hadParamSets) {
14860
- this.pendingParamNals.push(depacketized.annexB);
14861
- annexBData = Buffer.concat(this.pendingParamNals);
14862
- this.pendingParamNals = [];
14863
- } else annexBData = depacketized.annexB;
14864
- const pts = Math.floor(timestamp / 90);
15147
+ const pts = Math.floor(accessUnit.timestamp / 90);
14865
15148
  const packet = {
14866
15149
  type: "video",
14867
- data: annexBData,
15150
+ data: accessUnit.annexB,
14868
15151
  pts,
14869
15152
  dts: pts,
14870
- keyframe: keyframe || hadParamSets,
15153
+ keyframe: accessUnit.keyframe,
14871
15154
  codec
14872
15155
  };
14873
15156
  this.pushEncodedPacket(packet);
@@ -15271,6 +15554,9 @@ var StreamBroker = class StreamBroker {
15271
15554
  this.rtpDepacketizer.reset();
15272
15555
  this.rtpDepacketizer = null;
15273
15556
  }
15557
+ this.annexBAssembler.flush();
15558
+ this.annexBAssembler.reset();
15559
+ this.rtpAuTracker.reset();
15274
15560
  if (this.pushRtpFlagDepacketizer) {
15275
15561
  this.pushRtpFlagDepacketizer.reset();
15276
15562
  this.pushRtpFlagDepacketizer = null;
@@ -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-C4Cp_OAY.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-vdmrukBk.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.362",
3
+ "version": "1.2.364",
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",