@blockcast/mmt-container 2.1.0 → 2.2.0-main.02321582f1ca

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 (99) hide show
  1. package/README.md +29 -17
  2. package/dist/browser-runtime.js +1302 -249
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.js.map +1 -1
  5. package/dist/catalog-fec-params.d.ts +17 -1
  6. package/dist/catalog-fec-params.d.ts.map +1 -1
  7. package/dist/catalog-fec-params.js +15 -0
  8. package/dist/catalog-fec-params.js.map +2 -2
  9. package/dist/catalog-network-source.js.map +1 -1
  10. package/dist/cmcd-producer.d.ts +65 -47
  11. package/dist/cmcd-producer.d.ts.map +1 -1
  12. package/dist/cmcd-producer.js +138 -109
  13. package/dist/cmcd-producer.js.map +2 -2
  14. package/dist/delivery-catalog.js +26 -16
  15. package/dist/delivery-catalog.js.map +2 -2
  16. package/dist/delta-fallback-config.js.map +1 -1
  17. package/dist/fec-manager.d.ts +33 -1
  18. package/dist/fec-manager.d.ts.map +1 -1
  19. package/dist/fec-manager.js +213 -6
  20. package/dist/fec-manager.js.map +2 -2
  21. package/dist/fec-state.js.map +1 -1
  22. package/dist/fec-worker-setup.d.ts +1 -1
  23. package/dist/fec-worker-setup.js +1 -1
  24. package/dist/fec-worker-setup.js.map +1 -1
  25. package/dist/held-layer-admission.js.map +1 -1
  26. package/dist/index.d.ts +5 -4
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +9 -1
  29. package/dist/index.js.map +2 -2
  30. package/dist/iso-block-locator.d.ts +9 -0
  31. package/dist/iso-block-locator.d.ts.map +1 -1
  32. package/dist/iso-block-locator.js +17 -0
  33. package/dist/iso-block-locator.js.map +2 -2
  34. package/dist/iso-bmff-timing.d.ts +10 -1
  35. package/dist/iso-bmff-timing.d.ts.map +1 -1
  36. package/dist/iso-bmff-timing.js +36 -2
  37. package/dist/iso-bmff-timing.js.map +2 -2
  38. package/dist/media-router.js.map +1 -1
  39. package/dist/mfu-reassembler.js +12 -19
  40. package/dist/mfu-reassembler.js.map +2 -2
  41. package/dist/mmtp-container-parser.d.ts +37 -2
  42. package/dist/mmtp-container-parser.d.ts.map +1 -1
  43. package/dist/mmtp-container-parser.js +98 -28
  44. package/dist/mmtp-container-parser.js.map +2 -2
  45. package/dist/mmtp-container-parser.no-modules.js +222 -45
  46. package/dist/mmtp-container-parser.no-modules.js.map +3 -3
  47. package/dist/mmtp-decode-pipeline.js +97 -28
  48. package/dist/mmtp-decode-pipeline.js.map +2 -2
  49. package/dist/mmtp-fec-session.d.ts +41 -3
  50. package/dist/mmtp-fec-session.d.ts.map +1 -1
  51. package/dist/mmtp-fec-session.js +118 -13
  52. package/dist/mmtp-fec-session.js.map +3 -3
  53. package/dist/mmtp-router.d.ts +25 -2
  54. package/dist/mmtp-router.d.ts.map +1 -1
  55. package/dist/mmtp-router.js +1749 -56
  56. package/dist/mmtp-router.js.map +4 -4
  57. package/dist/multicast-auth-catalog.js.map +1 -1
  58. package/dist/multicast-input.js.map +1 -1
  59. package/dist/multicast-manager.d.ts +15 -2
  60. package/dist/multicast-manager.d.ts.map +1 -1
  61. package/dist/multicast-manager.js +15683 -14941
  62. package/dist/multicast-manager.js.map +4 -4
  63. package/dist/multicast-transport.d.ts +11 -3
  64. package/dist/multicast-transport.d.ts.map +1 -1
  65. package/dist/multicast-transport.js +281 -35
  66. package/dist/multicast-transport.js.map +4 -4
  67. package/dist/multicast-unwrap.js +12 -19
  68. package/dist/multicast-unwrap.js.map +2 -2
  69. package/dist/no-modules.js +215 -42
  70. package/dist/no-modules.js.map +3 -3
  71. package/dist/repair-fetch.js.map +1 -1
  72. package/dist/repair-layer-subscriptions.d.ts +82 -0
  73. package/dist/repair-layer-subscriptions.d.ts.map +1 -0
  74. package/dist/repair-layer-subscriptions.js +192 -0
  75. package/dist/repair-layer-subscriptions.js.map +7 -0
  76. package/dist/signaling.js.map +2 -2
  77. package/dist/timestamp-codec.js +12 -19
  78. package/dist/timestamp-codec.js.map +2 -2
  79. package/dist/track-session.d.ts +1 -1
  80. package/dist/track-session.js.map +1 -1
  81. package/dist/wasm-mfu-reassembler.js.map +1 -1
  82. package/dist/wasm-mmtp-container-parser.js +97 -28
  83. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  84. package/package.json +8 -5
  85. package/schemas/catalog.schema.json +4 -0
  86. package/src/catalog-fec-params.ts +36 -1
  87. package/src/cmcd-producer.ts +194 -179
  88. package/src/fec-manager.ts +256 -6
  89. package/src/fec-worker-setup.ts +2 -2
  90. package/src/index.ts +18 -2
  91. package/src/iso-block-locator.ts +19 -0
  92. package/src/iso-bmff-timing.ts +40 -1
  93. package/src/mmtp-container-parser.ts +96 -8
  94. package/src/mmtp-fec-session.ts +115 -15
  95. package/src/mmtp-router.ts +414 -29
  96. package/src/multicast-manager.ts +26 -5
  97. package/src/multicast-transport.ts +44 -20
  98. package/src/repair-layer-subscriptions.ts +261 -0
  99. package/src/track-session.ts +1 -1
@@ -73,6 +73,8 @@ var BlockcastMmt = (() => {
73
73
  var PACKET_TYPE_REPAIR = 3;
74
74
  var MMTP_SIGNALING_PACKET_ID = 0;
75
75
  var MMTP_PACKET_ID_MAX = 65535;
76
+ var FEC_CODE_REED_SOLOMON = 1;
77
+ var FEC_CODE_RAPTORQ = 3;
76
78
  function parseMMTPHeader(buffer) {
77
79
  if (buffer.length < MMTP_HEADER_SIZE) {
78
80
  throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
@@ -213,30 +215,26 @@ var BlockcastMmt = (() => {
213
215
  const authLength = view2.getUint16(2, false);
214
216
  return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
215
217
  }
216
- function resolveDeclaredTrailers(payload, declaredEnd, fecType, options, rapFlag = false) {
218
+ function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
217
219
  if (declaredEnd > payload.length)
218
220
  return null;
219
221
  if (options?.skipFecId) {
220
- return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false, hasOverlayFecId: false } : null;
222
+ return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
221
223
  }
222
- const overlay = options?.overlayDeclared === true && rapFlag;
223
224
  if (fecType === 1) {
224
- const fecStart = payload.length - sourceFecTrailerSize(overlay);
225
+ const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
225
226
  if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
226
227
  return null;
227
228
  }
228
- return { hasFecId: true, hasOverlayFecId: overlay };
229
+ return { hasFecId: true };
229
230
  }
230
- if (fecType !== 0 || overlay)
231
+ if (fecType !== 0)
231
232
  return null;
232
233
  if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
233
- return { hasFecId: false, hasOverlayFecId: false };
234
+ return { hasFecId: false };
234
235
  }
235
236
  return null;
236
237
  }
237
- function sourceFecTrailerSize(overlay) {
238
- return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
239
- }
240
238
  function planMfuExtraction(mmtpPacket, options, parsedHeader) {
241
239
  if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
242
240
  return {
@@ -289,11 +287,11 @@ var BlockcastMmt = (() => {
289
287
  if ("rejection" in layoutResult)
290
288
  return layoutResult;
291
289
  const { layout } = layoutResult;
292
- const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options, header.rapFlag);
290
+ const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
293
291
  if (!trailerLayout) {
294
292
  if (!options?.skipFecId && header.fecType === 1) {
295
293
  const observed = payload.length - layout.dataEnd;
296
- const expected = sourceFecTrailerSize(options?.overlayDeclared === true && header.rapFlag);
294
+ const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
297
295
  if (observed !== expected) {
298
296
  return {
299
297
  rejection: {
@@ -325,13 +323,10 @@ var BlockcastMmt = (() => {
325
323
  const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
326
324
  const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
327
325
  const data = payload.subarray(mfuOffset, dataEnd);
328
- const trailerStart = mmtpPacket.length - sourceFecTrailerSize(trailerLayout.hasOverlayFecId);
329
- const ssIdAt = (offset) => ({
330
- ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + offset, 4).getUint32(0, false)
331
- });
332
- const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
333
- const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
334
- return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
326
+ const fecPayloadId = trailerLayout.hasFecId ? {
327
+ ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
328
+ } : null;
329
+ return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
335
330
  }
336
331
  function sourceFecPayloadIdOf(mmtpPacket, header) {
337
332
  if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
@@ -423,6 +418,50 @@ var BlockcastMmt = (() => {
423
418
  var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
424
419
  var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
425
420
 
421
+ // ../mmt-base/dist/signalling.js
422
+ var FEC_CODING_STRUCTURE_ONE_STAGE = 1;
423
+ var SSBG_MODE_1 = 1;
424
+ var RAPTORQ_MAX_SOURCE_SYMBOLS = 56403;
425
+ var REPAIR_SYMBOL_ID_MAX = 16777215;
426
+ var MIN_REPAIR_SYMBOL_SIZE = 14;
427
+ var REED_SOLOMON_MAX_BLOCK = 255;
428
+ function alFecRaptorQFlowConfig(flow) {
429
+ if (flow.fecCodingStructure !== FEC_CODING_STRUCTURE_ONE_STAGE || flow.repairFlows.length !== 1)
430
+ return null;
431
+ if (flow.ssbgMode !== SSBG_MODE_1 || flow.ffsrptsFlag !== 0 || flow.fecPayloadIdMode !== 0)
432
+ return null;
433
+ if (flow.packetIds.length !== 1)
434
+ return null;
435
+ const repair = flow.repairFlows[0];
436
+ const { fecCodeId, maximumK: k, maximumP: p } = repair;
437
+ const t = flow.repairSymbolSize;
438
+ if (t < MIN_REPAIR_SYMBOL_SIZE || k < 1 || p < 1 || k + p - 1 > REPAIR_SYMBOL_ID_MAX)
439
+ return null;
440
+ if (fecCodeId === FEC_CODE_RAPTORQ) {
441
+ if (t % 8 !== 0 || k > RAPTORQ_MAX_SOURCE_SYMBOLS)
442
+ return null;
443
+ } else if (fecCodeId === FEC_CODE_REED_SOLOMON) {
444
+ if (k + p > REED_SOLOMON_MAX_BLOCK)
445
+ return null;
446
+ } else {
447
+ return null;
448
+ }
449
+ if (repair.repairFlowId === MMTP_SIGNALING_PACKET_ID || repair.repairFlowId === flow.packetIds[0])
450
+ return null;
451
+ return {
452
+ sourcePacketIds: [...flow.packetIds],
453
+ repairFlowId: repair.repairFlowId,
454
+ fecCodeId,
455
+ ssbgMode: flow.ssbgMode,
456
+ fecPayloadIdMode: flow.fecPayloadIdMode,
457
+ repairSymbolSize: t,
458
+ maximumK: k,
459
+ maximumP: p,
460
+ protectionWindowTime: repair.protectionWindowTime,
461
+ protectionWindowSize: repair.protectionWindowSize
462
+ };
463
+ }
464
+
426
465
  // ../mmt-base/dist/serial-number.js
427
466
  var SERIAL32_SPAN = 4294967296;
428
467
  var SERIAL32_HALF = 2147483648;
@@ -5594,13 +5633,21 @@ var BlockcastMmt = (() => {
5594
5633
  function sameSample(a, b) {
5595
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;
5596
5635
  }
5597
- 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;
5598
5637
  var IsoBmffTimingResolver = class {
5599
5638
  constructor(maxPendingSamples) {
5600
5639
  this.maxPendingSamples = maxPendingSamples;
5601
5640
  __privateAdd(this, _IsoBmffTimingResolver_instances);
5602
5641
  __privateAdd(this, _track, null);
5603
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());
5604
5651
  __privateAdd(this, _pendingSamples, 0);
5605
5652
  __privateAdd(this, _evictedSamples, 0);
5606
5653
  __privateAdd(this, _lastMovieFragmentRejection, null);
@@ -5608,6 +5655,10 @@ var BlockcastMmt = (() => {
5608
5655
  throw new Error("[IsoBmffTimingResolver] maxPendingSamples must be a positive integer");
5609
5656
  }
5610
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
+ }
5611
5662
  get pendingSampleCount() {
5612
5663
  return __privateGet(this, _pendingSamples);
5613
5664
  }
@@ -5631,7 +5682,14 @@ var BlockcastMmt = (() => {
5631
5682
  __privateSet(this, _track, timing);
5632
5683
  return true;
5633
5684
  }
5634
- 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) {
5635
5693
  __privateSet(this, _lastMovieFragmentRejection, null);
5636
5694
  if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
5637
5695
  __privateSet(this, _lastMovieFragmentRejection, "invalid");
@@ -5657,6 +5715,8 @@ var BlockcastMmt = (() => {
5657
5715
  (sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
5658
5716
  );
5659
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;
5660
5720
  let freed = 0;
5661
5721
  while (__privateGet(this, _fragments).size > 0 && (__privateGet(this, _pendingSamples) + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
5662
5722
  freed += __privateMethod(this, _IsoBmffTimingResolver_instances, evictOldestFragment_fn).call(this);
@@ -5670,6 +5730,7 @@ var BlockcastMmt = (() => {
5670
5730
  return false;
5671
5731
  }
5672
5732
  __privateGet(this, _fragments).set(key, [...parsed.samples]);
5733
+ __privateGet(this, _firstSamples).set(key, parsed.samples[0]);
5673
5734
  __privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) + parsed.samples.length);
5674
5735
  return true;
5675
5736
  }
@@ -5716,11 +5777,15 @@ var BlockcastMmt = (() => {
5716
5777
  reset() {
5717
5778
  __privateSet(this, _track, null);
5718
5779
  __privateGet(this, _fragments).clear();
5780
+ __privateGet(this, _firstSamples).clear();
5781
+ __privateGet(this, _delivered).clear();
5719
5782
  __privateSet(this, _pendingSamples, 0);
5720
5783
  }
5721
5784
  };
5722
5785
  _track = new WeakMap();
5723
5786
  _fragments = new WeakMap();
5787
+ _firstSamples = new WeakMap();
5788
+ _delivered = new WeakMap();
5724
5789
  _pendingSamples = new WeakMap();
5725
5790
  _evictedSamples = new WeakMap();
5726
5791
  _lastMovieFragmentRejection = new WeakMap();
@@ -5739,6 +5804,7 @@ var BlockcastMmt = (() => {
5739
5804
  let live = 0;
5740
5805
  for (const sample of samples) if (sample !== null) live++;
5741
5806
  __privateGet(this, _fragments).delete(oldest);
5807
+ __privateGet(this, _firstSamples).delete(oldest);
5742
5808
  __privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) - live);
5743
5809
  __privateSet(this, _evictedSamples, __privateGet(this, _evictedSamples) + live);
5744
5810
  return live;
@@ -5754,7 +5820,16 @@ var BlockcastMmt = (() => {
5754
5820
  if (!samples || !samples[index]) return false;
5755
5821
  samples[index] = null;
5756
5822
  __privateWrapper(this, _pendingSamples)._--;
5757
- 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
+ }
5758
5833
  return true;
5759
5834
  };
5760
5835
  function fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber) {
@@ -6216,7 +6291,7 @@ var BlockcastMmt = (() => {
6216
6291
  return FMPU_BOX_TYPES.has(type);
6217
6292
  }
6218
6293
  var MAX_AWAITING_TIMING_HEADS = 8;
6219
- 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;
6294
+ 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;
6220
6295
  var _MmtpContainerParser = class _MmtpContainerParser {
6221
6296
  constructor(config) {
6222
6297
  __privateAdd(this, _MmtpContainerParser_instances);
@@ -6252,6 +6327,12 @@ var BlockcastMmt = (() => {
6252
6327
  __privateAdd(this, _reclaimedTimingSamples, 0);
6253
6328
  __privateAdd(this, _evictedTimingSamples, 0);
6254
6329
  __privateAdd(this, _rejectedMovieFragments, 0);
6330
+ __privateAdd(this, _recoveredHeadsAwaitingTiming, {
6331
+ held: 0,
6332
+ expired: 0,
6333
+ discarded: 0,
6334
+ superseded: 0
6335
+ });
6255
6336
  __privateAdd(this, _sequencers, /* @__PURE__ */ new Map());
6256
6337
  __privateAdd(this, _fecGate, null);
6257
6338
  __privateAdd(this, _fecSequencer, null);
@@ -6394,6 +6475,14 @@ var BlockcastMmt = (() => {
6394
6475
  rejectedMovieFragments: __privateGet(this, _rejectedMovieFragments)
6395
6476
  };
6396
6477
  }
6478
+ /** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
6479
+ get recoveredHeadsAwaitingTiming() {
6480
+ let pending = 0;
6481
+ for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
6482
+ for (const head of awaitingTiming) if (head.options?.recovered) pending++;
6483
+ }
6484
+ return { ...__privateGet(this, _recoveredHeadsAwaitingTiming), pending };
6485
+ }
6397
6486
  get reorderStats() {
6398
6487
  let bufferedFrames = __privateGet(this, _fecSequencer)?.buffered ?? 0;
6399
6488
  for (const { sequencer } of __privateGet(this, _sequencers).values()) bufferedFrames += sequencer.buffered;
@@ -6545,7 +6634,7 @@ var BlockcastMmt = (() => {
6545
6634
  const now = Date.now();
6546
6635
  for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
6547
6636
  while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
6548
- awaitingTiming.shift();
6637
+ __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, awaitingTiming.shift(), "expired");
6549
6638
  }
6550
6639
  }
6551
6640
  }
@@ -6750,6 +6839,10 @@ var BlockcastMmt = (() => {
6750
6839
  }
6751
6840
  __privateMethod(this, _MmtpContainerParser_instances, emitFrame_fn2).call(this, data, meta);
6752
6841
  }
6842
+ /** Whether FT=1 metadata already names this sample, without claiming it. */
6843
+ hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
6844
+ 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;
6845
+ }
6753
6846
  /**
6754
6847
  * Claim and return the authored timing for a recovered sample. Recovery does
6755
6848
  * not pass through the ordinary `feed()` completion path, so it must retire
@@ -6794,6 +6887,9 @@ var BlockcastMmt = (() => {
6794
6887
  for (const { sequencer } of __privateGet(this, _sequencers).values()) sequencer.discard();
6795
6888
  __privateGet(this, _sequencers).clear();
6796
6889
  __privateGet(this, _fecSequencer)?.discard();
6890
+ for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
6891
+ for (const head of awaitingTiming) __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, head, "discarded");
6892
+ }
6797
6893
  __privateGet(this, _timingStreams).clear();
6798
6894
  __privateSet(this, _pendingTimingSamples, 0);
6799
6895
  __privateGet(this, _reliableMoqStartupGate).clear();
@@ -6822,6 +6918,7 @@ var BlockcastMmt = (() => {
6822
6918
  _reclaimedTimingSamples = new WeakMap();
6823
6919
  _evictedTimingSamples = new WeakMap();
6824
6920
  _rejectedMovieFragments = new WeakMap();
6921
+ _recoveredHeadsAwaitingTiming = new WeakMap();
6825
6922
  _sequencers = new WeakMap();
6826
6923
  _fecGate = new WeakMap();
6827
6924
  _fecSequencer = new WeakMap();
@@ -6980,11 +7077,16 @@ var BlockcastMmt = (() => {
6980
7077
  if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
6981
7078
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-unexpected-fragmentation");
6982
7079
  const resolver2 = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, false);
6983
- if (!resolver2) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-no-resolver");
7080
+ if (!resolver2?.initialized) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-no-resolver");
6984
7081
  const pendingBefore = resolver2.pendingSampleCount;
6985
7082
  const evictedBefore = resolver2.evictedSampleCount;
6986
7083
  const remaining = __privateGet(this, _maxPendingTimingSamples) - __privateGet(this, _pendingTimingSamples);
6987
- const admitted = resolver2.addMovieFragment(mpu.mpuSequenceNumber, data, remaining);
7084
+ const admitted = resolver2.addMovieFragment(
7085
+ mpu.mpuSequenceNumber,
7086
+ data,
7087
+ remaining,
7088
+ options?.recovered === true
7089
+ );
6988
7090
  __privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) + (resolver2.pendingSampleCount - pendingBefore));
6989
7091
  __privateSet(this, _evictedTimingSamples, __privateGet(this, _evictedTimingSamples) + (resolver2.evictedSampleCount - evictedBefore));
6990
7092
  if (!admitted) {
@@ -7004,7 +7106,7 @@ var BlockcastMmt = (() => {
7004
7106
  };
7005
7107
  }
7006
7108
  if (fragmentType !== 2) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "unknown-fragment-type");
7007
- const resolver = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, false);
7109
+ const resolver = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, true);
7008
7110
  if (!resolver) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-no-resolver");
7009
7111
  if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
7010
7112
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-empty-payload");
@@ -7025,6 +7127,7 @@ var BlockcastMmt = (() => {
7025
7127
  mpuSequenceNumber: mpu.mpuSequenceNumber,
7026
7128
  movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
7027
7129
  sampleNumber: mfuDuHeader.sampleNumber,
7130
+ sequenceNumber: info.sequenceNumber,
7028
7131
  arrivedAt: Date.now()
7029
7132
  });
7030
7133
  return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-sample-timing-missing");
@@ -7206,10 +7309,23 @@ var BlockcastMmt = (() => {
7206
7309
  return resolver;
7207
7310
  };
7208
7311
  awaitTiming_fn = function(streamKey, head) {
7209
- const awaitingTiming = __privateGet(this, _timingStreams).get(streamKey)?.awaitingTiming;
7210
- if (!awaitingTiming) return;
7312
+ const state = __privateGet(this, _timingStreams).get(streamKey);
7313
+ if (!state) return;
7314
+ if (!head.options?.recovered) {
7315
+ const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
7316
+ __privateGet(this, _recoveredHeadsAwaitingTiming).superseded += state.awaitingTiming.length - kept.length;
7317
+ state.awaitingTiming = kept;
7318
+ }
7319
+ const awaitingTiming = state.awaitingTiming;
7211
7320
  awaitingTiming.push(head);
7212
- if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) awaitingTiming.shift();
7321
+ if (head.options?.recovered) __privateGet(this, _recoveredHeadsAwaitingTiming).held++;
7322
+ if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
7323
+ __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, awaitingTiming.shift(), "expired");
7324
+ }
7325
+ };
7326
+ /** Count a recovered head leaving the queue other than by replay or its source copy. */
7327
+ leaveAwaitingTiming_fn = function(head, outcome) {
7328
+ if (head.options?.recovered) __privateGet(this, _recoveredHeadsAwaitingTiming)[outcome]++;
7213
7329
  };
7214
7330
  /**
7215
7331
  * Feed again the heads whose timing the FT=1 just admitted declares. That
@@ -7237,6 +7353,7 @@ var BlockcastMmt = (() => {
7237
7353
  const state = __privateGet(this, _timingStreams).get(streamKey);
7238
7354
  if (!state) return;
7239
7355
  __privateGet(this, _timingStreams).delete(streamKey);
7356
+ for (const head of state.awaitingTiming) __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, head, "discarded");
7240
7357
  __privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) - state.resolver.pendingSampleCount);
7241
7358
  if (__privateGet(this, _pendingTimingSamples) < 0) {
7242
7359
  throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
@@ -8217,7 +8334,7 @@ var BlockcastMmt = (() => {
8217
8334
  // src/mmtp-fec-session.ts
8218
8335
  var PATH_BITS = { moq: 1, multicast: 2 };
8219
8336
  var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
8220
- var _decoder, _blocks, _settled, _have, _newest, _prunedAt, _repairPacketId, _newestSource, _heldLayers, _anchorHorizon, _stats3, _MmtpFecSession_instances, drop_fn2, noteNewest_fn, blockStartOf_fn, countHeld_fn, noteMixed_fn, behindHorizon_fn, settle_fn, settleClean_fn, settleDecoded_fn, retireBehindHorizon_fn;
8337
+ var _decoder, _repairSymbols, _message, _blocks, _settled, _have, _newest, _prunedAt, _repairPacketId, _newestSource, _heldLayers, _anchorHorizon, _stats3, _MmtpFecSession_instances, trackFor_fn, drop_fn2, noteNewest_fn, blockStartOf_fn, countHeld_fn, noteMixed_fn, behindHorizon_fn, settle_fn, settleClean_fn, settleDecoded_fn, retireBehindHorizon_fn;
8221
8338
  var MmtpFecSession = class {
8222
8339
  /**
8223
8340
  * @throws when the instance lacks or misstates a field the session needs:
@@ -8227,6 +8344,9 @@ var BlockcastMmt = (() => {
8227
8344
  constructor(config) {
8228
8345
  __privateAdd(this, _MmtpFecSession_instances);
8229
8346
  __privateAdd(this, _decoder);
8347
+ __privateAdd(this, _repairSymbols);
8348
+ /** The message entry a catalog-less session was built from; undefined under a catalog. */
8349
+ __privateAdd(this, _message);
8230
8350
  /** Blocks a repair has defined and that are not settled yet, by SS_Start. */
8231
8351
  __privateAdd(this, _blocks, /* @__PURE__ */ new Map());
8232
8352
  /** SS_Starts of settled blocks, so a later repair for one is refused. */
@@ -8258,6 +8378,24 @@ var BlockcastMmt = (() => {
8258
8378
  mixedPath: { clean: 0, recovered: 0, failed: 0, released: 0 },
8259
8379
  failClosed: null
8260
8380
  });
8381
+ if (config && "alFecFlow" in config) {
8382
+ const message = alFecRaptorQFlowConfig(config.alFecFlow);
8383
+ if (!message) {
8384
+ throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
8385
+ }
8386
+ __privateSet(this, _message, message);
8387
+ this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
8388
+ this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
8389
+ this.sourceSymbols = message.maximumK;
8390
+ __privateSet(this, _repairSymbols, message.maximumP);
8391
+ this.symbolSize = message.repairSymbolSize;
8392
+ this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
8393
+ __privateSet(this, _repairPacketId, message.repairFlowId);
8394
+ __privateSet(this, _heldLayers, layerZero(message.maximumP));
8395
+ __privateSet(this, _anchorHorizon, heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols));
8396
+ __privateSet(this, _decoder, buildDecoder(config.Decoder, this));
8397
+ return;
8398
+ }
8261
8399
  const fec = config?.fec;
8262
8400
  if (!fec || typeof fec !== "object") {
8263
8401
  throw new Error("MmtpFecSession: the catalog fec instance is required");
@@ -8268,7 +8406,7 @@ var BlockcastMmt = (() => {
8268
8406
  }
8269
8407
  this.algorithm = fec.scheme;
8270
8408
  this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
8271
- this.repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
8409
+ __privateSet(this, _repairSymbols, requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols"));
8272
8410
  this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
8273
8411
  const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
8274
8412
  const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
@@ -8288,17 +8426,34 @@ var BlockcastMmt = (() => {
8288
8426
  config.heldLayers
8289
8427
  ));
8290
8428
  __privateSet(this, _anchorHorizon, heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols));
8291
- if (typeof config.Decoder !== "function") {
8292
- throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
8293
- }
8294
- const decoder = new config.Decoder(this.retentionBlocks, this.blockProfile, this.algorithm);
8295
- try {
8296
- decoder.configure_params(this.sourceSymbols, this.symbolSize);
8297
- } catch (error) {
8298
- decoder.free();
8299
- throw error;
8300
- }
8301
- __privateSet(this, _decoder, decoder);
8429
+ __privateSet(this, _decoder, buildDecoder(config.Decoder, this));
8430
+ }
8431
+ /** P_0 = B_0, the held total every repair packet is checked against. */
8432
+ get repairSymbols() {
8433
+ return __privateGet(this, _repairSymbols);
8434
+ }
8435
+ /**
8436
+ * A later version of the AL-FEC message, catalog-less sessions only. A
8437
+ * maximum_p_for_repair_flow above the held total raises it (`raised`): the
8438
+ * anchored check restarts at the new total, so a block generated under the
8439
+ * previous one stays valid before the first block carrying the new total
8440
+ * (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
8441
+ * (`unchanged`): no publisher lowers it, and the check catches a wire that
8442
+ * does. An entry with another asset, repair flow, code point, K or T, or
8443
+ * one outside 8.3, is `refused`: that is a new source encoding, which needs
8444
+ * a new session. A latched fail-closed stays latched either way.
8445
+ */
8446
+ updateAlFecFlow(flow) {
8447
+ const held = __privateGet(this, _message);
8448
+ if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
8449
+ const next = alFecRaptorQFlowConfig(flow);
8450
+ if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
8451
+ return "refused";
8452
+ }
8453
+ if (next.maximumP <= __privateGet(this, _repairSymbols)) return "unchanged";
8454
+ __privateSet(this, _repairSymbols, next.maximumP);
8455
+ __privateSet(this, _heldLayers, layerZero(next.maximumP));
8456
+ return "raised";
8302
8457
  }
8303
8458
  /** True once release() ran. */
8304
8459
  get released() {
@@ -8393,7 +8548,7 @@ var BlockcastMmt = (() => {
8393
8548
  }
8394
8549
  if (admission.reject) {
8395
8550
  const verdict2 = __privateGet(this, _heldLayers).failsClosed(
8396
- repairTrackName,
8551
+ __privateMethod(this, _MmtpFecSession_instances, trackFor_fn).call(this, repairTrackName),
8397
8552
  frame.ssStart,
8398
8553
  frame.rsbLength,
8399
8554
  __privateGet(this, _anchorHorizon),
@@ -8408,7 +8563,7 @@ var BlockcastMmt = (() => {
8408
8563
  __privateGet(this, _repairPacketId) ?? __privateSet(this, _repairPacketId, frame.packetId);
8409
8564
  if (frame.ssbLength > this.sourceSymbols) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:ssb-length");
8410
8565
  const verdict = __privateGet(this, _heldLayers).admit(
8411
- repairTrackName,
8566
+ __privateMethod(this, _MmtpFecSession_instances, trackFor_fn).call(this, repairTrackName),
8412
8567
  frame.ssStart,
8413
8568
  frame.rsbLength,
8414
8569
  __privateGet(this, _anchorHorizon),
@@ -8469,6 +8624,8 @@ var BlockcastMmt = (() => {
8469
8624
  }
8470
8625
  };
8471
8626
  _decoder = new WeakMap();
8627
+ _repairSymbols = new WeakMap();
8628
+ _message = new WeakMap();
8472
8629
  _blocks = new WeakMap();
8473
8630
  _settled = new WeakMap();
8474
8631
  _have = new WeakMap();
@@ -8480,6 +8637,10 @@ var BlockcastMmt = (() => {
8480
8637
  _anchorHorizon = new WeakMap();
8481
8638
  _stats3 = new WeakMap();
8482
8639
  _MmtpFecSession_instances = new WeakSet();
8640
+ /** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
8641
+ trackFor_fn = function(repairTrackName) {
8642
+ return __privateGet(this, _message) ? void 0 : repairTrackName;
8643
+ };
8483
8644
  drop_fn2 = function(reason) {
8484
8645
  __privateGet(this, _stats3).dropped[reason] = (__privateGet(this, _stats3).dropped[reason] ?? 0) + 1;
8485
8646
  return [];
@@ -8600,6 +8761,22 @@ var BlockcastMmt = (() => {
8600
8761
  if (__privateMethod(this, _MmtpFecSession_instances, behindHorizon_fn).call(this, start)) __privateGet(this, _settled).delete(start);
8601
8762
  }
8602
8763
  };
8764
+ function layerZero(total) {
8765
+ return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
8766
+ }
8767
+ function buildDecoder(Decoder, session) {
8768
+ if (typeof Decoder !== "function") {
8769
+ throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
8770
+ }
8771
+ const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
8772
+ try {
8773
+ decoder.configure_params(session.sourceSymbols, session.symbolSize);
8774
+ } catch (error) {
8775
+ decoder.free();
8776
+ throw error;
8777
+ }
8778
+ return decoder;
8779
+ }
8603
8780
  function requirePositiveInteger(value, path) {
8604
8781
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
8605
8782
  throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);