@blockcast/mmt-container 2.2.0-main.efb4fa4521af → 2.2.0-main.fb5c7dfbe2de

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 (82) hide show
  1. package/README.md +29 -17
  2. package/dist/browser-runtime.js +1336 -302
  3. package/dist/browser-runtime.js.map +3 -3
  4. package/dist/catalog-fec-params.d.ts +17 -1
  5. package/dist/catalog-fec-params.d.ts.map +1 -1
  6. package/dist/catalog-fec-params.js +15 -0
  7. package/dist/catalog-fec-params.js.map +2 -2
  8. package/dist/cmcd-producer.d.ts +60 -47
  9. package/dist/cmcd-producer.d.ts.map +1 -1
  10. package/dist/cmcd-producer.js +138 -109
  11. package/dist/cmcd-producer.js.map +2 -2
  12. package/dist/delivery-catalog.js +26 -16
  13. package/dist/delivery-catalog.js.map +2 -2
  14. package/dist/delta-fallback-config.js.map +1 -1
  15. package/dist/fec-manager.d.ts +70 -12
  16. package/dist/fec-manager.d.ts.map +1 -1
  17. package/dist/fec-manager.js +403 -58
  18. package/dist/fec-manager.js.map +2 -2
  19. package/dist/fec-worker-setup.d.ts +1 -1
  20. package/dist/fec-worker-setup.js +1 -1
  21. package/dist/fec-worker-setup.js.map +1 -1
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -1
  25. package/dist/index.js.map +2 -2
  26. package/dist/iso-block-locator.d.ts +48 -13
  27. package/dist/iso-block-locator.d.ts.map +1 -1
  28. package/dist/iso-block-locator.js +97 -25
  29. package/dist/iso-block-locator.js.map +2 -2
  30. package/dist/iso-bmff-timing.d.ts +10 -1
  31. package/dist/iso-bmff-timing.d.ts.map +1 -1
  32. package/dist/iso-bmff-timing.js +36 -2
  33. package/dist/iso-bmff-timing.js.map +2 -2
  34. package/dist/media-router.js.map +1 -1
  35. package/dist/mmtp-container-parser.d.ts +79 -10
  36. package/dist/mmtp-container-parser.d.ts.map +1 -1
  37. package/dist/mmtp-container-parser.js +207 -26
  38. package/dist/mmtp-container-parser.js.map +2 -2
  39. package/dist/mmtp-container-parser.no-modules.js +209 -29
  40. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  41. package/dist/mmtp-decode-pipeline.js +206 -26
  42. package/dist/mmtp-decode-pipeline.js.map +2 -2
  43. package/dist/mmtp-router.d.ts +34 -8
  44. package/dist/mmtp-router.d.ts.map +1 -1
  45. package/dist/mmtp-router.js +555 -79
  46. package/dist/mmtp-router.js.map +2 -2
  47. package/dist/multicast-manager.d.ts +13 -3
  48. package/dist/multicast-manager.d.ts.map +1 -1
  49. package/dist/multicast-manager.js +980 -146
  50. package/dist/multicast-manager.js.map +2 -2
  51. package/dist/multicast-transport.js +28 -18
  52. package/dist/multicast-transport.js.map +2 -2
  53. package/dist/no-modules.js +206 -26
  54. package/dist/no-modules.js.map +2 -2
  55. package/dist/repair-fetch.d.ts +36 -8
  56. package/dist/repair-fetch.d.ts.map +1 -1
  57. package/dist/repair-fetch.js +285 -26
  58. package/dist/repair-fetch.js.map +3 -3
  59. package/dist/repair-layer-subscriptions.d.ts +6 -2
  60. package/dist/repair-layer-subscriptions.d.ts.map +1 -1
  61. package/dist/repair-layer-subscriptions.js +6 -3
  62. package/dist/repair-layer-subscriptions.js.map +2 -2
  63. package/dist/track-session.d.ts +1 -1
  64. package/dist/track-session.d.ts.map +1 -1
  65. package/dist/track-session.js +1 -0
  66. package/dist/track-session.js.map +2 -2
  67. package/dist/wasm-mmtp-container-parser.js +206 -26
  68. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  69. package/package.json +8 -5
  70. package/src/catalog-fec-params.ts +36 -1
  71. package/src/cmcd-producer.ts +189 -179
  72. package/src/fec-manager.ts +433 -32
  73. package/src/fec-worker-setup.ts +2 -2
  74. package/src/index.ts +9 -2
  75. package/src/iso-block-locator.ts +133 -31
  76. package/src/iso-bmff-timing.ts +40 -1
  77. package/src/mmtp-container-parser.ts +306 -29
  78. package/src/mmtp-router.ts +548 -38
  79. package/src/multicast-manager.ts +44 -6
  80. package/src/repair-fetch.ts +363 -49
  81. package/src/repair-layer-subscriptions.ts +13 -5
  82. package/src/track-session.ts +4 -1
@@ -5633,13 +5633,21 @@ var BlockcastMmt = (() => {
5633
5633
  function sameSample(a, b) {
5634
5634
  return a.decodeTime === b.decodeTime && a.presentationTime === b.presentationTime && a.duration === b.duration && a.timescale === b.timescale && a.size === b.size && a.bytePosition === b.bytePosition;
5635
5635
  }
5636
- var _track, _fragments, _pendingSamples, _evictedSamples, _lastMovieFragmentRejection, _IsoBmffTimingResolver_instances, evictOldestFragment_fn, clearSlot_fn;
5636
+ var _track, _fragments, _firstSamples, _delivered, _pendingSamples, _evictedSamples, _lastMovieFragmentRejection, _IsoBmffTimingResolver_instances, evictOldestFragment_fn, clearSlot_fn;
5637
5637
  var IsoBmffTimingResolver = class {
5638
5638
  constructor(maxPendingSamples) {
5639
5639
  this.maxPendingSamples = maxPendingSamples;
5640
5640
  __privateAdd(this, _IsoBmffTimingResolver_instances);
5641
5641
  __privateAdd(this, _track, null);
5642
5642
  __privateAdd(this, _fragments, /* @__PURE__ */ new Map());
5643
+ /** First sample of each pending fragment, kept in `#delivered` with its sample count. */
5644
+ __privateAdd(this, _firstSamples, /* @__PURE__ */ new Map());
5645
+ /**
5646
+ * First sample and sample count of the newest fragments whose every slot was
5647
+ * cleared. Abandonment counts: `releaseSample` clears a slot too, which
5648
+ * matches the combiner, since it retires an abandoned sample as well.
5649
+ */
5650
+ __privateAdd(this, _delivered, /* @__PURE__ */ new Map());
5643
5651
  __privateAdd(this, _pendingSamples, 0);
5644
5652
  __privateAdd(this, _evictedSamples, 0);
5645
5653
  __privateAdd(this, _lastMovieFragmentRejection, null);
@@ -5647,6 +5655,10 @@ var BlockcastMmt = (() => {
5647
5655
  throw new Error("[IsoBmffTimingResolver] maxPendingSamples must be a positive integer");
5648
5656
  }
5649
5657
  }
5658
+ /** True once an FT=0 has bound the track that FT=1 timing is read against. */
5659
+ get initialized() {
5660
+ return __privateGet(this, _track) !== null;
5661
+ }
5650
5662
  get pendingSampleCount() {
5651
5663
  return __privateGet(this, _pendingSamples);
5652
5664
  }
@@ -5670,7 +5682,14 @@ var BlockcastMmt = (() => {
5670
5682
  __privateSet(this, _track, timing);
5671
5683
  return true;
5672
5684
  }
5673
- addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples) {
5685
+ /**
5686
+ * `recovered` marks an FEC-recovered copy. A recovered block hands back
5687
+ * every FT=1 it holds, the received ones included, so a recovered copy of a
5688
+ * fragment whose samples are all done is a duplicate: admitting it again
5689
+ * would charge stale timing and could displace the live MPU's. A source
5690
+ * copy is still admitted again (a rendition switch re-arms timing that way).
5691
+ */
5692
+ addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
5674
5693
  __privateSet(this, _lastMovieFragmentRejection, null);
5675
5694
  if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
5676
5695
  __privateSet(this, _lastMovieFragmentRejection, "invalid");
@@ -5696,6 +5715,8 @@ var BlockcastMmt = (() => {
5696
5715
  (sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
5697
5716
  );
5698
5717
  }
5718
+ const delivered = recovered ? __privateGet(this, _delivered).get(key) : void 0;
5719
+ if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
5699
5720
  let freed = 0;
5700
5721
  while (__privateGet(this, _fragments).size > 0 && (__privateGet(this, _pendingSamples) + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
5701
5722
  freed += __privateMethod(this, _IsoBmffTimingResolver_instances, evictOldestFragment_fn).call(this);
@@ -5709,6 +5730,7 @@ var BlockcastMmt = (() => {
5709
5730
  return false;
5710
5731
  }
5711
5732
  __privateGet(this, _fragments).set(key, [...parsed.samples]);
5733
+ __privateGet(this, _firstSamples).set(key, parsed.samples[0]);
5712
5734
  __privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) + parsed.samples.length);
5713
5735
  return true;
5714
5736
  }
@@ -5755,11 +5777,15 @@ var BlockcastMmt = (() => {
5755
5777
  reset() {
5756
5778
  __privateSet(this, _track, null);
5757
5779
  __privateGet(this, _fragments).clear();
5780
+ __privateGet(this, _firstSamples).clear();
5781
+ __privateGet(this, _delivered).clear();
5758
5782
  __privateSet(this, _pendingSamples, 0);
5759
5783
  }
5760
5784
  };
5761
5785
  _track = new WeakMap();
5762
5786
  _fragments = new WeakMap();
5787
+ _firstSamples = new WeakMap();
5788
+ _delivered = new WeakMap();
5763
5789
  _pendingSamples = new WeakMap();
5764
5790
  _evictedSamples = new WeakMap();
5765
5791
  _lastMovieFragmentRejection = new WeakMap();
@@ -5778,6 +5804,7 @@ var BlockcastMmt = (() => {
5778
5804
  let live = 0;
5779
5805
  for (const sample of samples) if (sample !== null) live++;
5780
5806
  __privateGet(this, _fragments).delete(oldest);
5807
+ __privateGet(this, _firstSamples).delete(oldest);
5781
5808
  __privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) - live);
5782
5809
  __privateSet(this, _evictedSamples, __privateGet(this, _evictedSamples) + live);
5783
5810
  return live;
@@ -5793,7 +5820,16 @@ var BlockcastMmt = (() => {
5793
5820
  if (!samples || !samples[index]) return false;
5794
5821
  samples[index] = null;
5795
5822
  __privateWrapper(this, _pendingSamples)._--;
5796
- if (samples.every((value) => value === null)) __privateGet(this, _fragments).delete(key);
5823
+ if (samples.every((value) => value === null)) {
5824
+ __privateGet(this, _fragments).delete(key);
5825
+ const first = __privateGet(this, _firstSamples).get(key);
5826
+ __privateGet(this, _firstSamples).delete(key);
5827
+ __privateGet(this, _delivered).delete(key);
5828
+ __privateGet(this, _delivered).set(key, { first, sampleCount: samples.length });
5829
+ if (__privateGet(this, _delivered).size > this.maxPendingSamples) {
5830
+ __privateGet(this, _delivered).delete(__privateGet(this, _delivered).keys().next().value);
5831
+ }
5832
+ }
5797
5833
  return true;
5798
5834
  };
5799
5835
  function fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber) {
@@ -5969,7 +6005,10 @@ var BlockcastMmt = (() => {
5969
6005
  function ssIdAfter(a, b) {
5970
6006
  return (a - b | 0) > 0;
5971
6007
  }
5972
- var _streams2, _gate, _emit2, _recordStat2, _deferDepth, _deferredStreams, _FecReleaseSequencer_instances, drainUnlessDeferred_fn, streamFor_fn2, drain_fn, release_fn;
6008
+ function holdsRebuilt(stream) {
6009
+ return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
6010
+ }
6011
+ var _streams2, _gate, _emit2, _recordStat2, _deferDepth, _deferredStreams, _FecReleaseSequencer_instances, drainUnlessDeferred_fn, streamFor_fn2, drain_fn, drainLoop_fn, cursorPassed_fn, release_fn;
5973
6012
  var FecReleaseSequencer = class {
5974
6013
  constructor(gate, emit, recordStat) {
5975
6014
  __privateAdd(this, _FecReleaseSequencer_instances);
@@ -6029,6 +6068,7 @@ var BlockcastMmt = (() => {
6029
6068
  stream.lastSbn = void 0;
6030
6069
  stream.discontinuity = false;
6031
6070
  stream.rapSsId = void 0;
6071
+ stream.sourced = false;
6032
6072
  }
6033
6073
  if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
6034
6074
  __privateGet(this, _recordStat2).call(this, "droppedStale");
@@ -6055,6 +6095,7 @@ var BlockcastMmt = (() => {
6055
6095
  if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
6056
6096
  else hi = mid;
6057
6097
  }
6098
+ if (stream.buf[lo]?.passed === true) frame.passed = true;
6058
6099
  stream.buf.splice(lo, 0, frame);
6059
6100
  __privateMethod(this, _FecReleaseSequencer_instances, drainUnlessDeferred_fn).call(this, stream);
6060
6101
  }
@@ -6090,6 +6131,41 @@ var BlockcastMmt = (() => {
6090
6131
  }
6091
6132
  return false;
6092
6133
  }
6134
+ /**
6135
+ * These streams' sink is being replaced by one that has seen no source
6136
+ * frame yet. First, from a stream that holds a frame the overlay rebuilt,
6137
+ * release to the sink being replaced the frames the unanchored path had
6138
+ * already released to it (#cursorPassed): there they waited in its queue,
6139
+ * which its replacement flushes. A stream that holds none is not drained:
6140
+ * it holds nothing the anchor kept back, and a drain would still force out
6141
+ * a frame held past the hold limit, which before the anchor waited for the
6142
+ * next push. Then treat the streams as unsourced until a source frame is
6143
+ * released.
6144
+ */
6145
+ unsource(packetId, catalogTrackName) {
6146
+ for (const stream of __privateGet(this, _streams2).values()) {
6147
+ if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
6148
+ if (stream.draining === 0 && holdsRebuilt(stream)) {
6149
+ if (__privateGet(this, _deferDepth) > 0) __privateGet(this, _deferredStreams).add(stream);
6150
+ else __privateMethod(this, _FecReleaseSequencer_instances, drain_fn).call(this, stream, true);
6151
+ }
6152
+ stream.sourced = false;
6153
+ }
6154
+ }
6155
+ }
6156
+ /**
6157
+ * Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
6158
+ * (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
6159
+ * packetId.
6160
+ */
6161
+ holdsRebuilt(packetId, catalogTrackName) {
6162
+ for (const stream of __privateGet(this, _streams2).values()) {
6163
+ if (stream.packetId !== packetId) continue;
6164
+ if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
6165
+ if (holdsRebuilt(stream)) return true;
6166
+ }
6167
+ return false;
6168
+ }
6093
6169
  /** Re-evaluate held gaps after FEC state changed. */
6094
6170
  poke(streamKey) {
6095
6171
  if (streamKey !== void 0) {
@@ -6193,16 +6269,32 @@ var BlockcastMmt = (() => {
6193
6269
  last: null,
6194
6270
  lastSbn: void 0,
6195
6271
  discontinuity: false,
6196
- rapSsId: void 0
6272
+ rapSsId: void 0,
6273
+ sourced: false,
6274
+ draining: 0
6197
6275
  };
6198
6276
  __privateGet(this, _streams2).set(streamKey, stream);
6199
6277
  }
6200
6278
  return stream;
6201
6279
  };
6202
- drain_fn = function(stream) {
6280
+ /** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
6281
+ drain_fn = function(stream, asUnanchored = false) {
6282
+ stream.draining++;
6283
+ try {
6284
+ __privateMethod(this, _FecReleaseSequencer_instances, drainLoop_fn).call(this, stream, asUnanchored);
6285
+ } finally {
6286
+ stream.draining--;
6287
+ }
6288
+ };
6289
+ drainLoop_fn = function(stream, asUnanchored) {
6203
6290
  while (stream.buf.length > 0 && stream.last !== null) {
6204
6291
  const head = stream.buf[0];
6205
6292
  const last = stream.last;
6293
+ if (compareFrameOrder(head, last) <= 0) {
6294
+ stream.buf.shift();
6295
+ __privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, void 0);
6296
+ continue;
6297
+ }
6206
6298
  if (stream.discontinuity) {
6207
6299
  stream.discontinuity = false;
6208
6300
  stream.buf.shift();
@@ -6216,9 +6308,10 @@ var BlockcastMmt = (() => {
6216
6308
  }
6217
6309
  const headSbn = head.meta.fecSbn;
6218
6310
  const from = stream.lastSbn ?? headSbn;
6219
- const to = headSbn ?? stream.lastSbn;
6311
+ const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
6220
6312
  let lost = [];
6221
6313
  const unanchored = from === void 0 && to === void 0;
6314
+ let unresolved = false;
6222
6315
  if (from !== void 0 && to !== void 0) {
6223
6316
  const resolved = __privateGet(this, _gate).resolvedLoss(
6224
6317
  stream.packetId,
@@ -6227,7 +6320,8 @@ var BlockcastMmt = (() => {
6227
6320
  Math.max(from, to),
6228
6321
  from
6229
6322
  );
6230
- if (resolved === null) {
6323
+ unresolved = resolved === null;
6324
+ if (unresolved && !__privateMethod(this, _FecReleaseSequencer_instances, cursorPassed_fn).call(this, stream, asUnanchored)) {
6231
6325
  const heldMs = performance.now() - head.arrivedAtMs;
6232
6326
  if (heldMs <= __privateGet(this, _gate).holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
6233
6327
  __privateGet(this, _recordStat2).call(this, "forcedWindowFlushes");
@@ -6235,18 +6329,52 @@ var BlockcastMmt = (() => {
6235
6329
  __privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, __privateGet(this, _gate).decide(last, head, [], true));
6236
6330
  continue;
6237
6331
  }
6238
- lost = resolved;
6332
+ lost = resolved ?? [];
6239
6333
  }
6240
6334
  stream.buf.shift();
6241
- __privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? __privateGet(this, _gate).decide(last, head, lost, false) : void 0);
6335
+ __privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? __privateGet(this, _gate).decide(last, head, lost, false) : void 0);
6242
6336
  }
6243
6337
  };
6338
+ /**
6339
+ * An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
6340
+ * source cursor holds it unanchored, and neither does a later frame behind
6341
+ * it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
6342
+ * fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
6343
+ * like each frame that arrived in front of it since (stale there). The head
6344
+ * then goes, decided as it would be unanchored, once a frame the base path
6345
+ * delivered (a source frame, or one base FEC recovered) is buffered after a
6346
+ * marked one: the base path has passed that one, so a frame still missing
6347
+ * before it can only come from a repair. It also goes while no source frame
6348
+ * has been released, when the direct path shows a recovered frame at once.
6349
+ * And it goes at once while a staged switch decides on when frames arrive
6350
+ * (FecReleaseGate.releaseUnanchored), and when unsource releases into a
6351
+ * sink being replaced: unanchored, it had already reached the sink.
6352
+ * The scan also writes: the `passed` marks it sets stay on the frames,
6353
+ * and push marks a frame it inserts in front of a marked one from them.
6354
+ */
6355
+ cursorPassed_fn = function(stream, asUnanchored) {
6356
+ let at = -1;
6357
+ for (let i = 0; i < stream.buf.length; i++) {
6358
+ const frame = stream.buf[i];
6359
+ if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
6360
+ frame.passed = true;
6361
+ if (at < 0) at = i;
6362
+ } else if (frame.passed !== true) break;
6363
+ }
6364
+ if (at < 0) return false;
6365
+ return asUnanchored || !stream.sourced || __privateGet(this, _gate).releaseUnanchored?.(stream.packetId, stream.catalogTrackName) === true || stream.buf.some(({ meta }, i) => i > at && meta.fecSbnFirst === void 0 && (meta.recovered !== true || meta.fecSbn !== void 0));
6366
+ };
6244
6367
  release_fn = function(stream, frame, decision) {
6368
+ const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
6369
+ if (stale) __privateGet(this, _recordStat2).call(this, "droppedStale");
6245
6370
  if (decision) frame.meta.gapBefore = decision;
6246
6371
  stream.last = frame;
6247
- if (frame.meta.fecSbn !== void 0) stream.lastSbn = frame.meta.fecSbn;
6372
+ if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
6373
+ stream.lastSbn = frame.meta.fecSbn;
6374
+ }
6375
+ if (frame.meta.recovered !== true) stream.sourced = true;
6248
6376
  __privateGet(this, _gate).onReleased?.(frame.data, frame.meta);
6249
- __privateGet(this, _emit2).call(this, frame.data, frame.meta);
6377
+ if (!stale) __privateGet(this, _emit2).call(this, frame.data, frame.meta);
6250
6378
  };
6251
6379
  var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
6252
6380
  function isFmp4Box(data) {
@@ -6255,7 +6383,7 @@ var BlockcastMmt = (() => {
6255
6383
  return FMPU_BOX_TYPES.has(type);
6256
6384
  }
6257
6385
  var MAX_AWAITING_TIMING_HEADS = 8;
6258
- var _tracks, _defaultTrack, _reassembler, _sampleCombiners, _sampleCombinerOptions, _retiredSampleCombinerStats, _timingStreams, _maxPendingTimingSamples, _pendingTimingSamples, _reclaimedTimingSamples, _evictedTimingSamples, _rejectedMovieFragments, _sequencers, _fecGate, _fecSequencer, _reorderWindow, _reassemblyPolicy, _frameCb, _initCb, _sourceSymbolCb, _trackFrameCbs, _reorderStats, _reliableMoqStartupGate, _disposed, _replayingAwaitingTiming, _fedObjects, _reassemblerFragmentsFed, _reassemblerDataUnitsReturned, _feedOutcomes, _deferredFeedOutcomes, _lastOutcome, _lastOutcomeCount, _MmtpContainerParser_instances, streamKey_fn, sampleCombinerFor_fn, releaseTimingSlot_fn, dropSampleCombiner_fn, countOutcome_fn, countDeferredOutcome_fn, drop_fn, feedCounted_fn, feed_fn, emitCombinedSample_fn, passReliableMoqStartupGate_fn, timingResolver_fn, awaitTiming_fn, replayAwaitingTiming_fn, dropTimingStream_fn, validateReassemblyPolicy_fn, buildFrameMeta_fn, sequencerFor_fn, flushSequencers_fn, emitFrame_fn2, _MmtpContainerParser_static, scanForKeyframe_fn, scanLengthPrefixedHevcForKeyframe_fn, scanAnnexBForKeyframe_fn;
6386
+ var _tracks, _defaultTrack, _reassembler, _sampleCombiners, _sampleCombinerOptions, _retiredSampleCombinerStats, _timingStreams, _maxPendingTimingSamples, _pendingTimingSamples, _reclaimedTimingSamples, _evictedTimingSamples, _rejectedMovieFragments, _recoveredHeadsAwaitingTiming, _sequencers, _fecGate, _fecSequencer, _reorderWindow, _reassemblyPolicy, _frameCb, _initCb, _sourceSymbolCb, _trackFrameCbs, _reorderStats, _reliableMoqStartupGate, _disposed, _replayingAwaitingTiming, _fedObjects, _reassemblerFragmentsFed, _reassemblerDataUnitsReturned, _feedOutcomes, _deferredFeedOutcomes, _lastOutcome, _lastOutcomeCount, _MmtpContainerParser_instances, streamKey_fn, sampleCombinerFor_fn, releaseTimingSlot_fn, dropSampleCombiner_fn, countOutcome_fn, countDeferredOutcome_fn, drop_fn, feedCounted_fn, feed_fn, emitCombinedSample_fn, passReliableMoqStartupGate_fn, timingResolver_fn, awaitTiming_fn, leaveAwaitingTiming_fn, replayAwaitingTiming_fn, dropTimingStream_fn, validateReassemblyPolicy_fn, buildFrameMeta_fn, sequencerFor_fn, flushSequencers_fn, emitFrame_fn2, _MmtpContainerParser_static, scanForKeyframe_fn, scanLengthPrefixedHevcForKeyframe_fn, scanAnnexBForKeyframe_fn;
6259
6387
  var _MmtpContainerParser = class _MmtpContainerParser {
6260
6388
  constructor(config) {
6261
6389
  __privateAdd(this, _MmtpContainerParser_instances);
@@ -6291,6 +6419,12 @@ var BlockcastMmt = (() => {
6291
6419
  __privateAdd(this, _reclaimedTimingSamples, 0);
6292
6420
  __privateAdd(this, _evictedTimingSamples, 0);
6293
6421
  __privateAdd(this, _rejectedMovieFragments, 0);
6422
+ __privateAdd(this, _recoveredHeadsAwaitingTiming, {
6423
+ held: 0,
6424
+ expired: 0,
6425
+ discarded: 0,
6426
+ superseded: 0
6427
+ });
6294
6428
  __privateAdd(this, _sequencers, /* @__PURE__ */ new Map());
6295
6429
  __privateAdd(this, _fecGate, null);
6296
6430
  __privateAdd(this, _fecSequencer, null);
@@ -6418,11 +6552,12 @@ var BlockcastMmt = (() => {
6418
6552
  /**
6419
6553
  * Typed view of the ISO-BMFF timing-sample budget.
6420
6554
  *
6421
- * This is the acceptance signal for the budget ratchet: `pending` walking
6422
- * to `capacity` while `rejectedMovieFragments` climbs is the stream wedging,
6423
- * and it is observable here rather than only as media silently stopping.
6424
- * Per repo policy this is a producer-owned counter -- never infer budget
6425
- * state by parsing logs.
6555
+ * Each stream admits timing against its own window of `capacity` slots
6556
+ * (BLO-52085). A stream at capacity displaces its own oldest timing to
6557
+ * admit the moof in hand, and `evicted` counts the slots so displaced:
6558
+ * timing a stream declared and never delivered shows up there. `pending`
6559
+ * sums every stream, so it can exceed `capacity`. Per repo policy this is
6560
+ * a producer-owned counter -- never infer budget state by parsing logs.
6426
6561
  */
6427
6562
  get timingBudgetStats() {
6428
6563
  return {
@@ -6433,6 +6568,14 @@ var BlockcastMmt = (() => {
6433
6568
  rejectedMovieFragments: __privateGet(this, _rejectedMovieFragments)
6434
6569
  };
6435
6570
  }
6571
+ /** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
6572
+ get recoveredHeadsAwaitingTiming() {
6573
+ let pending = 0;
6574
+ for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
6575
+ for (const head of awaitingTiming) if (head.options?.recovered) pending++;
6576
+ }
6577
+ return { ...__privateGet(this, _recoveredHeadsAwaitingTiming), pending };
6578
+ }
6436
6579
  get reorderStats() {
6437
6580
  let bufferedFrames = __privateGet(this, _fecSequencer)?.buffered ?? 0;
6438
6581
  for (const { sequencer } of __privateGet(this, _sequencers).values()) bufferedFrames += sequencer.buffered;
@@ -6474,6 +6617,14 @@ var BlockcastMmt = (() => {
6474
6617
  fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
6475
6618
  return __privateGet(this, _fecSequencer)?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
6476
6619
  }
6620
+ /** See FecReleaseSequencer.unsource. */
6621
+ unsourceFecRelease(packetId, catalogTrackName) {
6622
+ __privateGet(this, _fecSequencer)?.unsource(packetId, catalogTrackName);
6623
+ }
6624
+ /** See FecReleaseSequencer.holdsRebuilt. */
6625
+ fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
6626
+ return __privateGet(this, _fecSequencer)?.holdsRebuilt(packetId, catalogTrackName) ?? false;
6627
+ }
6477
6628
  /** Re-evaluate held gaps after FEC block state changed. */
6478
6629
  pokeFecRelease(packetId, catalogTrackName) {
6479
6630
  if (!__privateGet(this, _fecSequencer)) return;
@@ -6584,7 +6735,7 @@ var BlockcastMmt = (() => {
6584
6735
  const now = Date.now();
6585
6736
  for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
6586
6737
  while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
6587
- awaitingTiming.shift();
6738
+ __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, awaitingTiming.shift(), "expired");
6588
6739
  }
6589
6740
  }
6590
6741
  }
@@ -6789,6 +6940,10 @@ var BlockcastMmt = (() => {
6789
6940
  }
6790
6941
  __privateMethod(this, _MmtpContainerParser_instances, emitFrame_fn2).call(this, data, meta);
6791
6942
  }
6943
+ /** Whether FT=1 metadata already names this sample, without claiming it. */
6944
+ hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
6945
+ return __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, __privateMethod(this, _MmtpContainerParser_instances, streamKey_fn).call(this, packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
6946
+ }
6792
6947
  /**
6793
6948
  * Claim and return the authored timing for a recovered sample. Recovery does
6794
6949
  * not pass through the ordinary `feed()` completion path, so it must retire
@@ -6833,6 +6988,9 @@ var BlockcastMmt = (() => {
6833
6988
  for (const { sequencer } of __privateGet(this, _sequencers).values()) sequencer.discard();
6834
6989
  __privateGet(this, _sequencers).clear();
6835
6990
  __privateGet(this, _fecSequencer)?.discard();
6991
+ for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
6992
+ for (const head of awaitingTiming) __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, head, "discarded");
6993
+ }
6836
6994
  __privateGet(this, _timingStreams).clear();
6837
6995
  __privateSet(this, _pendingTimingSamples, 0);
6838
6996
  __privateGet(this, _reliableMoqStartupGate).clear();
@@ -6861,6 +7019,7 @@ var BlockcastMmt = (() => {
6861
7019
  _reclaimedTimingSamples = new WeakMap();
6862
7020
  _evictedTimingSamples = new WeakMap();
6863
7021
  _rejectedMovieFragments = new WeakMap();
7022
+ _recoveredHeadsAwaitingTiming = new WeakMap();
6864
7023
  _sequencers = new WeakMap();
6865
7024
  _fecGate = new WeakMap();
6866
7025
  _fecSequencer = new WeakMap();
@@ -7019,11 +7178,16 @@ var BlockcastMmt = (() => {
7019
7178
  if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
7020
7179
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-unexpected-fragmentation");
7021
7180
  const resolver2 = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, false);
7022
- if (!resolver2) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-no-resolver");
7181
+ if (!resolver2?.initialized) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-no-resolver");
7023
7182
  const pendingBefore = resolver2.pendingSampleCount;
7024
7183
  const evictedBefore = resolver2.evictedSampleCount;
7025
- const remaining = __privateGet(this, _maxPendingTimingSamples) - __privateGet(this, _pendingTimingSamples);
7026
- const admitted = resolver2.addMovieFragment(mpu.mpuSequenceNumber, data, remaining);
7184
+ const remaining = __privateGet(this, _maxPendingTimingSamples) - resolver2.pendingSampleCount;
7185
+ const admitted = resolver2.addMovieFragment(
7186
+ mpu.mpuSequenceNumber,
7187
+ data,
7188
+ remaining,
7189
+ options?.recovered === true
7190
+ );
7027
7191
  __privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) + (resolver2.pendingSampleCount - pendingBefore));
7028
7192
  __privateSet(this, _evictedTimingSamples, __privateGet(this, _evictedTimingSamples) + (resolver2.evictedSampleCount - evictedBefore));
7029
7193
  if (!admitted) {
@@ -7043,7 +7207,7 @@ var BlockcastMmt = (() => {
7043
7207
  };
7044
7208
  }
7045
7209
  if (fragmentType !== 2) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "unknown-fragment-type");
7046
- const resolver = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, false);
7210
+ const resolver = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, true);
7047
7211
  if (!resolver) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-no-resolver");
7048
7212
  if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
7049
7213
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-empty-payload");
@@ -7064,6 +7228,7 @@ var BlockcastMmt = (() => {
7064
7228
  mpuSequenceNumber: mpu.mpuSequenceNumber,
7065
7229
  movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
7066
7230
  sampleNumber: mfuDuHeader.sampleNumber,
7231
+ sequenceNumber: info.sequenceNumber,
7067
7232
  arrivedAt: Date.now()
7068
7233
  });
7069
7234
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-sample-timing-missing");
@@ -7084,7 +7249,7 @@ var BlockcastMmt = (() => {
7084
7249
  headTiming.size
7085
7250
  );
7086
7251
  if (readyOnRegister) {
7087
- const deferred = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, readyOnRegister, info, resolver, options?.recovered === true, options?.fecSbn);
7252
+ const deferred = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, readyOnRegister, info, resolver, options?.recovered === true, options?.fecSbn, options?.fecSbnFirst);
7088
7253
  if (deferred.record === void 0) __privateMethod(this, _MmtpContainerParser_instances, countDeferredOutcome_fn).call(this, deferred.reason);
7089
7254
  }
7090
7255
  }
@@ -7171,7 +7336,7 @@ var BlockcastMmt = (() => {
7171
7336
  fragmentCounter: mpu.fragmentCounter
7172
7337
  };
7173
7338
  }
7174
- const outcome = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, combined, info, resolver, options?.recovered === true, options?.fecSbn);
7339
+ const outcome = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, combined, info, resolver, options?.recovered === true, options?.fecSbn, options?.fecSbnFirst);
7175
7340
  if (outcome.record !== void 0) return outcome.record;
7176
7341
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, outcome.reason);
7177
7342
  };
@@ -7190,7 +7355,7 @@ var BlockcastMmt = (() => {
7190
7355
  * attribution exists to keep readable. Leaving the choice at the call site
7191
7356
  * is what keeps both true.
7192
7357
  */
7193
- emitCombinedSample_fn = function(combined, info, resolver, recovered, fecSbn) {
7358
+ emitCombinedSample_fn = function(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
7194
7359
  const timing = resolver.sampleTiming(
7195
7360
  combined.mpuSequenceNumber,
7196
7361
  combined.movieFragmentSequenceNumber,
@@ -7201,6 +7366,7 @@ var BlockcastMmt = (() => {
7201
7366
  const meta = __privateMethod(this, _MmtpContainerParser_instances, buildFrameMeta_fn).call(this, combined, info.packetId, info.sequenceNumber, timing, recovered);
7202
7367
  if (!meta) return { reason: "combined-meta-build-failed" };
7203
7368
  if (fecSbn !== void 0) meta.fecSbn = fecSbn;
7369
+ if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
7204
7370
  if (!resolver.consumeSample(
7205
7371
  combined.mpuSequenceNumber,
7206
7372
  combined.movieFragmentSequenceNumber,
@@ -7245,10 +7411,23 @@ var BlockcastMmt = (() => {
7245
7411
  return resolver;
7246
7412
  };
7247
7413
  awaitTiming_fn = function(streamKey, head) {
7248
- const awaitingTiming = __privateGet(this, _timingStreams).get(streamKey)?.awaitingTiming;
7249
- if (!awaitingTiming) return;
7414
+ const state = __privateGet(this, _timingStreams).get(streamKey);
7415
+ if (!state) return;
7416
+ if (!head.options?.recovered) {
7417
+ const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
7418
+ __privateGet(this, _recoveredHeadsAwaitingTiming).superseded += state.awaitingTiming.length - kept.length;
7419
+ state.awaitingTiming = kept;
7420
+ }
7421
+ const awaitingTiming = state.awaitingTiming;
7250
7422
  awaitingTiming.push(head);
7251
- if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) awaitingTiming.shift();
7423
+ if (head.options?.recovered) __privateGet(this, _recoveredHeadsAwaitingTiming).held++;
7424
+ if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
7425
+ __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, awaitingTiming.shift(), "expired");
7426
+ }
7427
+ };
7428
+ /** Count a recovered head leaving the queue other than by replay or its source copy. */
7429
+ leaveAwaitingTiming_fn = function(head, outcome) {
7430
+ if (head.options?.recovered) __privateGet(this, _recoveredHeadsAwaitingTiming)[outcome]++;
7252
7431
  };
7253
7432
  /**
7254
7433
  * Feed again the heads whose timing the FT=1 just admitted declares. That
@@ -7276,6 +7455,7 @@ var BlockcastMmt = (() => {
7276
7455
  const state = __privateGet(this, _timingStreams).get(streamKey);
7277
7456
  if (!state) return;
7278
7457
  __privateGet(this, _timingStreams).delete(streamKey);
7458
+ for (const head of state.awaitingTiming) __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, head, "discarded");
7279
7459
  __privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) - state.resolver.pendingSampleCount);
7280
7460
  if (__privateGet(this, _pendingTimingSamples) < 0) {
7281
7461
  throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");