@camstack/addon-pipeline 1.2.214 → 1.2.217

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 (39) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-C2vYSX3O.mjs → default-detection-model-BwNEV8Wl.mjs} +1 -1
  4. package/dist/{default-detection-model-f0PImZnh.js → default-detection-model-DzzuINe-.js} +1 -1
  5. package/dist/detection-pipeline/index.js +6 -7
  6. package/dist/detection-pipeline/index.mjs +5 -6
  7. package/dist/{dist-DoHcimdu.js → dist-D7hxiBnC.js} +237 -16
  8. package/dist/{dist-DttqKvsQ.mjs → dist-DgeXZfwn.mjs} +237 -16
  9. package/dist/{lazy-sharp-BeeQGCUu.js → lazy-sharp-BSkbiuz5.js} +1 -1
  10. package/dist/motion-wasm/index.js +2 -2
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-NtM7__8q.mjs → node-C9GcBrwk.mjs} +1 -1
  13. package/dist/{node-6uF72wri.js → node-DDhmPcOx.js} +1 -1
  14. package/dist/pipeline-runner/index.js +541 -26
  15. package/dist/pipeline-runner/index.mjs +539 -24
  16. package/dist/{process-memory-CddpCDWL.mjs → process-memory-Dw3CGS3D.mjs} +1 -1
  17. package/dist/{process-memory-BRdyl5fE.js → process-memory-ENqmiiis.js} +1 -1
  18. package/dist/recorder/index.js +158 -47
  19. package/dist/recorder/index.mjs +158 -47
  20. package/dist/{segment-demux-js-CsFtPVzH.mjs → segment-demux-js-BGbKlotF.mjs} +1 -1
  21. package/dist/{segment-demux-js-CkJPdyDH.js → segment-demux-js-sLs8mbZQ.js} +1 -1
  22. package/dist/session-decode/decode-worker-child.js +61 -180
  23. package/dist/session-decode/decode-worker-child.mjs +60 -179
  24. package/dist/stream-broker/_stub.js +2 -2
  25. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CbBa4iV-.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BULotDCb.mjs} +3 -3
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-yJ2CyixO.mjs +26 -0
  27. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Uxkwkgy5.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DICVQkNi.mjs} +1 -1
  28. package/dist/stream-broker/demux-worker-child.js +1 -1
  29. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  30. package/dist/stream-broker/{hostInit-CagSUJH7.mjs → hostInit-_RQxF72h.mjs} +3 -3
  31. package/dist/stream-broker/index.js +20 -21
  32. package/dist/stream-broker/index.mjs +20 -21
  33. package/dist/stream-broker/remoteEntry.js +1 -1
  34. package/dist/{worker-protocol-BWO2WNjw.js → worker-protocol-DGql9R4g.js} +1 -1
  35. package/dist/{worker-protocol-BKwkLZWT.mjs → worker-protocol-SzmaLNnP.mjs} +1 -1
  36. package/package.json +1 -1
  37. package/dist/event-loop-stall-monitor-DXLNBMkY.mjs +0 -138
  38. package/dist/event-loop-stall-monitor-DaUBcb13.js +0 -149
  39. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-0MF-KL1v.mjs +0 -26
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_worker_protocol = require("../worker-protocol-BWO2WNjw.js");
3
- const require_lazy_sharp = require("../lazy-sharp-BeeQGCUu.js");
2
+ const require_worker_protocol = require("../worker-protocol-DGql9R4g.js");
3
+ const require_lazy_sharp = require("../lazy-sharp-BSkbiuz5.js");
4
4
  //#region src/session-decode/color-conversion.ts
5
5
  /**
6
6
  * Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
@@ -323,153 +323,6 @@ var FrameSlot = class {
323
323
  }
324
324
  };
325
325
  //#endregion
326
- //#region src/session-decode/media-clock.ts
327
- /**
328
- * Convert a container pts to milliseconds.
329
- *
330
- * Returns `null` — never a fabricated `0` — for a time base that cannot
331
- * describe a duration. A zero here would place every frame of the session at
332
- * the anchor, i.e. a whole burst of detections sharing one instant, which is
333
- * exactly the kind of plausible-looking wrong timestamp this module exists to
334
- * refuse. The caller degrades to the wall clock instead.
335
- */
336
- function ptsToMs(pts, timeBase) {
337
- if (!Number.isFinite(timeBase.num) || !Number.isFinite(timeBase.den)) return null;
338
- if (timeBase.num <= 0 || timeBase.den <= 0) return null;
339
- return Number(pts) * timeBase.num * 1e3 / timeBase.den;
340
- }
341
- /** Anchor the session's media clock on its first decoded frame. */
342
- function anchorMediaClock(input) {
343
- const preRoll = Number.isFinite(input.preRollMs) && input.preRollMs > 0 ? input.preRollMs : 0;
344
- return {
345
- firstPtsMs: input.firstPtsMs,
346
- wallAtFirstPts: input.nowWall - preRoll
347
- };
348
- }
349
- /**
350
- * The wall clock at which the frame with `ptsMs` was shot.
351
- *
352
- * Clamped to `nowWall`: a container whose pts runs past the anchor (a clock
353
- * jump, a source whose pts is not wall-clock-shaped, a re-dial that reset it)
354
- * must never produce a track that starts in the FUTURE. A future `firstSeen`
355
- * sorts a track ahead of everything, and no later pass can detect that it is
356
- * wrong. Clamping loses precision on a pathological source and keeps the
357
- * ordering property every consumer relies on.
358
- */
359
- function mediaWallClockOf(anchor, ptsMs, nowWall) {
360
- const shotAt = anchor.wallAtFirstPts + (ptsMs - anchor.firstPtsMs);
361
- return shotAt > nowWall ? nowWall : shotAt;
362
- }
363
- //#endregion
364
- //#region src/session-decode/pre-roll-burst.ts
365
- var ADMITTED = { admitted: true };
366
- /**
367
- * Choose the media stride for one burst.
368
- *
369
- * `plannedFrames` is what the stride actually yields, so a caller can log the
370
- * intent next to the outcome — a budget that is never reached and a budget that
371
- * is always hit are different problems and used to read the same.
372
- */
373
- function planPreRollBurst(input) {
374
- const span = Number.isFinite(input.spanMs) && input.spanMs > 0 ? input.spanMs : 0;
375
- const fps = Number.isFinite(input.detectionFps) && input.detectionFps > 0 ? input.detectionFps : 1;
376
- const maxFrames = Number.isFinite(input.maxFrames) && input.maxFrames > 0 ? input.maxFrames : 0;
377
- if (span === 0 || maxFrames === 0) return {
378
- strideMs: Number.POSITIVE_INFINITY,
379
- plannedFrames: 0,
380
- subsampled: false
381
- };
382
- const cadenceMs = 1e3 / fps;
383
- const budgetStrideMs = span / maxFrames;
384
- const subsampled = budgetStrideMs > cadenceMs;
385
- const strideMs = subsampled ? budgetStrideMs : cadenceMs;
386
- return {
387
- strideMs,
388
- plannedFrames: Math.min(maxFrames, Math.max(1, Math.floor(span / strideMs))),
389
- subsampled
390
- };
391
- }
392
- /**
393
- * The live, per-session budget. Stateful by necessity — a stride is a decision
394
- * about the PREVIOUS admission — and deliberately free of any clock of its own:
395
- * both times are passed in, so a test can drive a catch-up burst without
396
- * pretending 4 s of media takes 4 s to decode.
397
- */
398
- var PreRollBurst = class {
399
- budget;
400
- plan;
401
- startedAtWall;
402
- liveEdgeWall;
403
- lastAdmittedMediaWall = null;
404
- offered = 0;
405
- inferred = 0;
406
- done = false;
407
- drops = {
408
- stride: 0,
409
- "budget-frames": 0,
410
- "budget-wall-ms": 0,
411
- "live-edge": 0
412
- };
413
- constructor(input) {
414
- this.budget = input.budget;
415
- this.plan = input.plan;
416
- this.startedAtWall = input.startedAtWall;
417
- this.liveEdgeWall = input.liveEdgeWall;
418
- }
419
- /** True once the burst has ended — by budget, or by reaching the live edge. */
420
- get finished() {
421
- return this.done;
422
- }
423
- /**
424
- * Offer one decoded history frame.
425
- *
426
- * `mediaWall` is the frame's OWN moment (see `media-clock.ts`); `nowWall` is
427
- * the wall clock of the offer. The two are different by construction during
428
- * catch-up, which is the whole reason this class exists.
429
- */
430
- offer(mediaWall, nowWall) {
431
- this.offered++;
432
- if (this.done) return this.drop("budget-frames");
433
- if (mediaWall >= this.liveEdgeWall) {
434
- this.done = true;
435
- return this.drop("live-edge");
436
- }
437
- if (nowWall - this.startedAtWall >= this.budget.maxWallMs) {
438
- this.done = true;
439
- return this.drop("budget-wall-ms");
440
- }
441
- if (this.inferred >= this.budget.maxFrames) {
442
- this.done = true;
443
- return this.drop("budget-frames");
444
- }
445
- if (this.lastAdmittedMediaWall !== null && mediaWall - this.lastAdmittedMediaWall < this.plan.strideMs) return this.drop("stride");
446
- this.lastAdmittedMediaWall = mediaWall;
447
- this.inferred++;
448
- return ADMITTED;
449
- }
450
- /** End the burst without offering a frame (session torn down, decode ended). */
451
- finish() {
452
- this.done = true;
453
- }
454
- report() {
455
- return {
456
- offered: this.offered,
457
- inferred: this.inferred,
458
- dropped: this.offered - this.inferred,
459
- droppedByReason: { ...this.drops },
460
- strideMs: Math.round(this.plan.strideMs),
461
- subsampled: this.plan.subsampled
462
- };
463
- }
464
- drop(reason) {
465
- this.drops[reason]++;
466
- return {
467
- admitted: false,
468
- reason
469
- };
470
- }
471
- };
472
- //#endregion
473
326
  //#region src/session-decode/lease-activity-gate.ts
474
327
  /**
475
328
  * Demand-driven gate for the native-frame lease capture (see
@@ -654,6 +507,56 @@ var LeaseAdmissionLedger = class {
654
507
  }
655
508
  };
656
509
  //#endregion
510
+ //#region src/session-decode/media-clock.ts
511
+ /**
512
+ * Convert a container pts to milliseconds.
513
+ *
514
+ * Returns `null` — never a fabricated `0` — for a time base that cannot
515
+ * describe a duration. A zero here would place every frame of the session at
516
+ * the anchor, i.e. a whole burst of detections sharing one instant, which is
517
+ * exactly the kind of plausible-looking wrong timestamp this module exists to
518
+ * refuse. The caller degrades to the wall clock instead.
519
+ */
520
+ function ptsToMs(pts, timeBase) {
521
+ if (!Number.isFinite(timeBase.num) || !Number.isFinite(timeBase.den)) return null;
522
+ if (timeBase.num <= 0 || timeBase.den <= 0) return null;
523
+ return Number(pts) * timeBase.num * 1e3 / timeBase.den;
524
+ }
525
+ /** Anchor the session's media clock on its first decoded frame. */
526
+ function anchorMediaClock(input) {
527
+ const preRoll = Number.isFinite(input.preRollMs) && input.preRollMs > 0 ? input.preRollMs : 0;
528
+ return {
529
+ firstPtsMs: input.firstPtsMs,
530
+ wallAtFirstPts: input.nowWall - preRoll
531
+ };
532
+ }
533
+ /**
534
+ * The wall clock at which the frame with `ptsMs` was shot.
535
+ *
536
+ * Clamped to `nowWall`: a container whose pts runs past the anchor (a clock
537
+ * jump, a source whose pts is not wall-clock-shaped, a re-dial that reset it)
538
+ * must never produce a track that starts in the FUTURE. A future `firstSeen`
539
+ * sorts a track ahead of everything, and no later pass can detect that it is
540
+ * wrong. Clamping loses precision on a pathological source and keeps the
541
+ * ordering property every consumer relies on.
542
+ */
543
+ function mediaWallClockOf(anchor, ptsMs, nowWall) {
544
+ const shotAt = anchor.wallAtFirstPts + (ptsMs - anchor.firstPtsMs);
545
+ return shotAt > nowWall ? nowWall : shotAt;
546
+ }
547
+ /**
548
+ * The anchor for a frame of dial `dialSeq`: the current one while the dial is
549
+ * the same, a fresh one — anchored on THIS frame — the moment the dial changes.
550
+ * Pure; the caller keeps the returned value and passes it back next frame.
551
+ */
552
+ function mediaClockForDial(current, dialSeq, input) {
553
+ if (current !== null && current.dialSeq === dialSeq) return current;
554
+ return {
555
+ dialSeq,
556
+ anchor: anchorMediaClock(input)
557
+ };
558
+ }
559
+ //#endregion
657
560
  //#region src/session-decode/native-frame-ring.ts
658
561
  /**
659
562
  * A hard-capped, insertion-ordered map from worker frameId → retained frame.
@@ -1674,18 +1577,6 @@ var REDIAL_MS = 3e3;
1674
1577
  /** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
1675
1578
  var METRICS_INTERVAL_MS = 1e4;
1676
1579
  /**
1677
- * Wall-clock ceiling on one pre-roll catch-up burst.
1678
- *
1679
- * A burst is a race against the live edge it is trying to get ahead of. Two
1680
- * seconds in, the decoder has either caught up (the burst ended on its own) or
1681
- * something is wrong — a cold GPU, a node under load, a camera whose GOP is
1682
- * longer than its history — and continuing to spend inference on footage that
1683
- * is now three or four seconds stale buys an operator nothing while a live
1684
- * frame waits. The frame budget bounds the WORK; this bounds the WINDOW in
1685
- * which that work is still worth doing.
1686
- */
1687
- var PRE_ROLL_BURST_MAX_WALL_MS = 2e3;
1688
- /**
1689
1580
  * Hard cap on the native-frame retention ring (native-res crop feature). Kept
1690
1581
  * DELIBERATELY TINY — its only job is to cover the detection→crop latency
1691
1582
  * (typically 1-4 frames), NOT the full shm ring depth. On the HW path every
@@ -2257,7 +2148,7 @@ var DecodeWorkerChild = class {
2257
2148
  lastColorProvenanceKeyByPath = /* @__PURE__ */ new Map();
2258
2149
  /** Per-frame color provenance logging (opt-in; the transition marker is always on). */
2259
2150
  colorDebug = process.env["CAMSTACK_DECODE_COLOR_DEBUG"] === "1";
2260
- /** Scrypted-style throughput counters (plain integers, no per-frame allocation). */
2151
+ /** Throughput counters (plain integers, no per-frame allocation). */
2261
2152
  framesDecoded = 0;
2262
2153
  /** Releases that found a held frame / that found nothing to release. */
2263
2154
  leaseReleased = 0;
@@ -2323,6 +2214,7 @@ var DecodeWorkerChild = class {
2323
2214
  * frame arrives, or forever on a source whose pts cannot be read — in which
2324
2215
  * case every frame falls back to the wall clock, exactly as before.
2325
2216
  */
2217
+ /** Anchored per DIAL (see `mediaClockForDial`): a re-dial is a new pts epoch. */
2326
2218
  mediaClock = null;
2327
2219
  /** History this dial asked the broker for; `0` ⇒ there is no burst. */
2328
2220
  preRollMs = 0;
@@ -2338,7 +2230,7 @@ var DecodeWorkerChild = class {
2338
2230
  /**
2339
2231
  * The latest-wins decoded-frame slot + reserved-for-`toBuffer` holder +
2340
2232
  * pending-pull flag — extracted to `FrameSlot` (Epic C P2 Task 2 / I2) so
2341
- * the reserve-on-pull invariant (`e3c78a9b`) is unit-tested without
2233
+ * the reserve-on-pull invariant is unit-tested without
2342
2234
  * node-av. See that file's doc comment for the full semantics.
2343
2235
  */
2344
2236
  frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.captureNativeFrame(frameId, frame) });
@@ -3346,27 +3238,16 @@ var DecodeWorkerChild = class {
3346
3238
  mediaWallClockOfFrame(frame, now) {
3347
3239
  const ptsMs = this.frameMediaPtsMs(frame);
3348
3240
  if (ptsMs === null) return now;
3349
- if (!this.mediaClock) {
3350
- this.mediaClock = anchorMediaClock({
3241
+ if (this.mediaClock === null || this.mediaClock.dialSeq !== this.dialSeq) {
3242
+ const servedPreRollMs = 0;
3243
+ if (this.preRollMs > 0) this.emitStderr(`[decode] pre-roll requested ${this.preRollMs}ms but the SERVED span does not reach this worker — burst suppressed, clock anchored at now (D411 follow-up)`);
3244
+ this.mediaClock = mediaClockForDial(this.mediaClock, this.dialSeq, {
3351
3245
  firstPtsMs: ptsMs,
3352
- preRollMs: this.preRollMs,
3246
+ preRollMs: servedPreRollMs,
3353
3247
  nowWall: now
3354
3248
  });
3355
- if (this.preRollMs > 0 && this.preRollMaxFrames > 0) this.preRollBurst = new PreRollBurst({
3356
- budget: {
3357
- maxFrames: this.preRollMaxFrames,
3358
- maxWallMs: PRE_ROLL_BURST_MAX_WALL_MS
3359
- },
3360
- plan: planPreRollBurst({
3361
- spanMs: this.preRollMs,
3362
- detectionFps: this.minIntervalMs > 0 ? 1e3 / this.minIntervalMs : 1,
3363
- maxFrames: this.preRollMaxFrames
3364
- }),
3365
- startedAtWall: now,
3366
- liveEdgeWall: now
3367
- });
3368
3249
  }
3369
- return mediaWallClockOf(this.mediaClock, ptsMs, now);
3250
+ return mediaWallClockOf(this.mediaClock.anchor, ptsMs, now);
3370
3251
  }
3371
3252
  /** This frame's pts in ms, or `null` when the source cannot express one. */
3372
3253
  frameMediaPtsMs(frame) {
@@ -1,4 +1,4 @@
1
- import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-BKwkLZWT.mjs";
1
+ import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-SzmaLNnP.mjs";
2
2
  import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  //#region src/session-decode/color-conversion.ts
4
4
  /**
@@ -322,153 +322,6 @@ var FrameSlot = class {
322
322
  }
323
323
  };
324
324
  //#endregion
325
- //#region src/session-decode/media-clock.ts
326
- /**
327
- * Convert a container pts to milliseconds.
328
- *
329
- * Returns `null` — never a fabricated `0` — for a time base that cannot
330
- * describe a duration. A zero here would place every frame of the session at
331
- * the anchor, i.e. a whole burst of detections sharing one instant, which is
332
- * exactly the kind of plausible-looking wrong timestamp this module exists to
333
- * refuse. The caller degrades to the wall clock instead.
334
- */
335
- function ptsToMs(pts, timeBase) {
336
- if (!Number.isFinite(timeBase.num) || !Number.isFinite(timeBase.den)) return null;
337
- if (timeBase.num <= 0 || timeBase.den <= 0) return null;
338
- return Number(pts) * timeBase.num * 1e3 / timeBase.den;
339
- }
340
- /** Anchor the session's media clock on its first decoded frame. */
341
- function anchorMediaClock(input) {
342
- const preRoll = Number.isFinite(input.preRollMs) && input.preRollMs > 0 ? input.preRollMs : 0;
343
- return {
344
- firstPtsMs: input.firstPtsMs,
345
- wallAtFirstPts: input.nowWall - preRoll
346
- };
347
- }
348
- /**
349
- * The wall clock at which the frame with `ptsMs` was shot.
350
- *
351
- * Clamped to `nowWall`: a container whose pts runs past the anchor (a clock
352
- * jump, a source whose pts is not wall-clock-shaped, a re-dial that reset it)
353
- * must never produce a track that starts in the FUTURE. A future `firstSeen`
354
- * sorts a track ahead of everything, and no later pass can detect that it is
355
- * wrong. Clamping loses precision on a pathological source and keeps the
356
- * ordering property every consumer relies on.
357
- */
358
- function mediaWallClockOf(anchor, ptsMs, nowWall) {
359
- const shotAt = anchor.wallAtFirstPts + (ptsMs - anchor.firstPtsMs);
360
- return shotAt > nowWall ? nowWall : shotAt;
361
- }
362
- //#endregion
363
- //#region src/session-decode/pre-roll-burst.ts
364
- var ADMITTED = { admitted: true };
365
- /**
366
- * Choose the media stride for one burst.
367
- *
368
- * `plannedFrames` is what the stride actually yields, so a caller can log the
369
- * intent next to the outcome — a budget that is never reached and a budget that
370
- * is always hit are different problems and used to read the same.
371
- */
372
- function planPreRollBurst(input) {
373
- const span = Number.isFinite(input.spanMs) && input.spanMs > 0 ? input.spanMs : 0;
374
- const fps = Number.isFinite(input.detectionFps) && input.detectionFps > 0 ? input.detectionFps : 1;
375
- const maxFrames = Number.isFinite(input.maxFrames) && input.maxFrames > 0 ? input.maxFrames : 0;
376
- if (span === 0 || maxFrames === 0) return {
377
- strideMs: Number.POSITIVE_INFINITY,
378
- plannedFrames: 0,
379
- subsampled: false
380
- };
381
- const cadenceMs = 1e3 / fps;
382
- const budgetStrideMs = span / maxFrames;
383
- const subsampled = budgetStrideMs > cadenceMs;
384
- const strideMs = subsampled ? budgetStrideMs : cadenceMs;
385
- return {
386
- strideMs,
387
- plannedFrames: Math.min(maxFrames, Math.max(1, Math.floor(span / strideMs))),
388
- subsampled
389
- };
390
- }
391
- /**
392
- * The live, per-session budget. Stateful by necessity — a stride is a decision
393
- * about the PREVIOUS admission — and deliberately free of any clock of its own:
394
- * both times are passed in, so a test can drive a catch-up burst without
395
- * pretending 4 s of media takes 4 s to decode.
396
- */
397
- var PreRollBurst = class {
398
- budget;
399
- plan;
400
- startedAtWall;
401
- liveEdgeWall;
402
- lastAdmittedMediaWall = null;
403
- offered = 0;
404
- inferred = 0;
405
- done = false;
406
- drops = {
407
- stride: 0,
408
- "budget-frames": 0,
409
- "budget-wall-ms": 0,
410
- "live-edge": 0
411
- };
412
- constructor(input) {
413
- this.budget = input.budget;
414
- this.plan = input.plan;
415
- this.startedAtWall = input.startedAtWall;
416
- this.liveEdgeWall = input.liveEdgeWall;
417
- }
418
- /** True once the burst has ended — by budget, or by reaching the live edge. */
419
- get finished() {
420
- return this.done;
421
- }
422
- /**
423
- * Offer one decoded history frame.
424
- *
425
- * `mediaWall` is the frame's OWN moment (see `media-clock.ts`); `nowWall` is
426
- * the wall clock of the offer. The two are different by construction during
427
- * catch-up, which is the whole reason this class exists.
428
- */
429
- offer(mediaWall, nowWall) {
430
- this.offered++;
431
- if (this.done) return this.drop("budget-frames");
432
- if (mediaWall >= this.liveEdgeWall) {
433
- this.done = true;
434
- return this.drop("live-edge");
435
- }
436
- if (nowWall - this.startedAtWall >= this.budget.maxWallMs) {
437
- this.done = true;
438
- return this.drop("budget-wall-ms");
439
- }
440
- if (this.inferred >= this.budget.maxFrames) {
441
- this.done = true;
442
- return this.drop("budget-frames");
443
- }
444
- if (this.lastAdmittedMediaWall !== null && mediaWall - this.lastAdmittedMediaWall < this.plan.strideMs) return this.drop("stride");
445
- this.lastAdmittedMediaWall = mediaWall;
446
- this.inferred++;
447
- return ADMITTED;
448
- }
449
- /** End the burst without offering a frame (session torn down, decode ended). */
450
- finish() {
451
- this.done = true;
452
- }
453
- report() {
454
- return {
455
- offered: this.offered,
456
- inferred: this.inferred,
457
- dropped: this.offered - this.inferred,
458
- droppedByReason: { ...this.drops },
459
- strideMs: Math.round(this.plan.strideMs),
460
- subsampled: this.plan.subsampled
461
- };
462
- }
463
- drop(reason) {
464
- this.drops[reason]++;
465
- return {
466
- admitted: false,
467
- reason
468
- };
469
- }
470
- };
471
- //#endregion
472
325
  //#region src/session-decode/lease-activity-gate.ts
473
326
  /**
474
327
  * Demand-driven gate for the native-frame lease capture (see
@@ -653,6 +506,56 @@ var LeaseAdmissionLedger = class {
653
506
  }
654
507
  };
655
508
  //#endregion
509
+ //#region src/session-decode/media-clock.ts
510
+ /**
511
+ * Convert a container pts to milliseconds.
512
+ *
513
+ * Returns `null` — never a fabricated `0` — for a time base that cannot
514
+ * describe a duration. A zero here would place every frame of the session at
515
+ * the anchor, i.e. a whole burst of detections sharing one instant, which is
516
+ * exactly the kind of plausible-looking wrong timestamp this module exists to
517
+ * refuse. The caller degrades to the wall clock instead.
518
+ */
519
+ function ptsToMs(pts, timeBase) {
520
+ if (!Number.isFinite(timeBase.num) || !Number.isFinite(timeBase.den)) return null;
521
+ if (timeBase.num <= 0 || timeBase.den <= 0) return null;
522
+ return Number(pts) * timeBase.num * 1e3 / timeBase.den;
523
+ }
524
+ /** Anchor the session's media clock on its first decoded frame. */
525
+ function anchorMediaClock(input) {
526
+ const preRoll = Number.isFinite(input.preRollMs) && input.preRollMs > 0 ? input.preRollMs : 0;
527
+ return {
528
+ firstPtsMs: input.firstPtsMs,
529
+ wallAtFirstPts: input.nowWall - preRoll
530
+ };
531
+ }
532
+ /**
533
+ * The wall clock at which the frame with `ptsMs` was shot.
534
+ *
535
+ * Clamped to `nowWall`: a container whose pts runs past the anchor (a clock
536
+ * jump, a source whose pts is not wall-clock-shaped, a re-dial that reset it)
537
+ * must never produce a track that starts in the FUTURE. A future `firstSeen`
538
+ * sorts a track ahead of everything, and no later pass can detect that it is
539
+ * wrong. Clamping loses precision on a pathological source and keeps the
540
+ * ordering property every consumer relies on.
541
+ */
542
+ function mediaWallClockOf(anchor, ptsMs, nowWall) {
543
+ const shotAt = anchor.wallAtFirstPts + (ptsMs - anchor.firstPtsMs);
544
+ return shotAt > nowWall ? nowWall : shotAt;
545
+ }
546
+ /**
547
+ * The anchor for a frame of dial `dialSeq`: the current one while the dial is
548
+ * the same, a fresh one — anchored on THIS frame — the moment the dial changes.
549
+ * Pure; the caller keeps the returned value and passes it back next frame.
550
+ */
551
+ function mediaClockForDial(current, dialSeq, input) {
552
+ if (current !== null && current.dialSeq === dialSeq) return current;
553
+ return {
554
+ dialSeq,
555
+ anchor: anchorMediaClock(input)
556
+ };
557
+ }
558
+ //#endregion
656
559
  //#region src/session-decode/native-frame-ring.ts
657
560
  /**
658
561
  * A hard-capped, insertion-ordered map from worker frameId → retained frame.
@@ -1673,18 +1576,6 @@ var REDIAL_MS = 3e3;
1673
1576
  /** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
1674
1577
  var METRICS_INTERVAL_MS = 1e4;
1675
1578
  /**
1676
- * Wall-clock ceiling on one pre-roll catch-up burst.
1677
- *
1678
- * A burst is a race against the live edge it is trying to get ahead of. Two
1679
- * seconds in, the decoder has either caught up (the burst ended on its own) or
1680
- * something is wrong — a cold GPU, a node under load, a camera whose GOP is
1681
- * longer than its history — and continuing to spend inference on footage that
1682
- * is now three or four seconds stale buys an operator nothing while a live
1683
- * frame waits. The frame budget bounds the WORK; this bounds the WINDOW in
1684
- * which that work is still worth doing.
1685
- */
1686
- var PRE_ROLL_BURST_MAX_WALL_MS = 2e3;
1687
- /**
1688
1579
  * Hard cap on the native-frame retention ring (native-res crop feature). Kept
1689
1580
  * DELIBERATELY TINY — its only job is to cover the detection→crop latency
1690
1581
  * (typically 1-4 frames), NOT the full shm ring depth. On the HW path every
@@ -2256,7 +2147,7 @@ var DecodeWorkerChild = class {
2256
2147
  lastColorProvenanceKeyByPath = /* @__PURE__ */ new Map();
2257
2148
  /** Per-frame color provenance logging (opt-in; the transition marker is always on). */
2258
2149
  colorDebug = process.env["CAMSTACK_DECODE_COLOR_DEBUG"] === "1";
2259
- /** Scrypted-style throughput counters (plain integers, no per-frame allocation). */
2150
+ /** Throughput counters (plain integers, no per-frame allocation). */
2260
2151
  framesDecoded = 0;
2261
2152
  /** Releases that found a held frame / that found nothing to release. */
2262
2153
  leaseReleased = 0;
@@ -2322,6 +2213,7 @@ var DecodeWorkerChild = class {
2322
2213
  * frame arrives, or forever on a source whose pts cannot be read — in which
2323
2214
  * case every frame falls back to the wall clock, exactly as before.
2324
2215
  */
2216
+ /** Anchored per DIAL (see `mediaClockForDial`): a re-dial is a new pts epoch. */
2325
2217
  mediaClock = null;
2326
2218
  /** History this dial asked the broker for; `0` ⇒ there is no burst. */
2327
2219
  preRollMs = 0;
@@ -2337,7 +2229,7 @@ var DecodeWorkerChild = class {
2337
2229
  /**
2338
2230
  * The latest-wins decoded-frame slot + reserved-for-`toBuffer` holder +
2339
2231
  * pending-pull flag — extracted to `FrameSlot` (Epic C P2 Task 2 / I2) so
2340
- * the reserve-on-pull invariant (`e3c78a9b`) is unit-tested without
2232
+ * the reserve-on-pull invariant is unit-tested without
2341
2233
  * node-av. See that file's doc comment for the full semantics.
2342
2234
  */
2343
2235
  frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.captureNativeFrame(frameId, frame) });
@@ -3345,27 +3237,16 @@ var DecodeWorkerChild = class {
3345
3237
  mediaWallClockOfFrame(frame, now) {
3346
3238
  const ptsMs = this.frameMediaPtsMs(frame);
3347
3239
  if (ptsMs === null) return now;
3348
- if (!this.mediaClock) {
3349
- this.mediaClock = anchorMediaClock({
3240
+ if (this.mediaClock === null || this.mediaClock.dialSeq !== this.dialSeq) {
3241
+ const servedPreRollMs = 0;
3242
+ if (this.preRollMs > 0) this.emitStderr(`[decode] pre-roll requested ${this.preRollMs}ms but the SERVED span does not reach this worker — burst suppressed, clock anchored at now (D411 follow-up)`);
3243
+ this.mediaClock = mediaClockForDial(this.mediaClock, this.dialSeq, {
3350
3244
  firstPtsMs: ptsMs,
3351
- preRollMs: this.preRollMs,
3245
+ preRollMs: servedPreRollMs,
3352
3246
  nowWall: now
3353
3247
  });
3354
- if (this.preRollMs > 0 && this.preRollMaxFrames > 0) this.preRollBurst = new PreRollBurst({
3355
- budget: {
3356
- maxFrames: this.preRollMaxFrames,
3357
- maxWallMs: PRE_ROLL_BURST_MAX_WALL_MS
3358
- },
3359
- plan: planPreRollBurst({
3360
- spanMs: this.preRollMs,
3361
- detectionFps: this.minIntervalMs > 0 ? 1e3 / this.minIntervalMs : 1,
3362
- maxFrames: this.preRollMaxFrames
3363
- }),
3364
- startedAtWall: now,
3365
- liveEdgeWall: now
3366
- });
3367
3248
  }
3368
- return mediaWallClockOf(this.mediaClock, ptsMs, now);
3249
+ return mediaWallClockOf(this.mediaClock.anchor, ptsMs, now);
3369
3250
  }
3370
3251
  /** This frame's pts in ms, or `null` when the source cannot express one. */
3371
3252
  frameMediaPtsMs(frame) {
@@ -1,7 +1,7 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Uxkwkgy5.mjs";
1
+ import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DICVQkNi.mjs";
2
2
  import { a as p, i as m, n as h, o as g, r as _, t as v } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
3
3
  import { n as y, r as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
4
- import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-0MF-KL1v.mjs";
4
+ import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-yJ2CyixO.mjs";
5
5
  import { n as C, t as w } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
6
6
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
7
7
  var T = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), E = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), D = (e) => {
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.92",
6
+ version: "1.2.95",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_stream_broker_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.172",
21
+ version: "1.2.175",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.140",
36
+ version: "1.2.143",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",