@blockcast/mmt-container 2.2.0-main.c4fa878f4ff4 → 2.2.0-main.e75bbd7576a6

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 (73) hide show
  1. package/README.md +29 -17
  2. package/dist/browser-runtime.js +703 -175
  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 +33 -1
  16. package/dist/fec-manager.d.ts.map +1 -1
  17. package/dist/fec-manager.js +222 -6
  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 +3 -3
  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 +9 -0
  27. package/dist/iso-block-locator.d.ts.map +1 -1
  28. package/dist/iso-block-locator.js +17 -0
  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 +37 -2
  36. package/dist/mmtp-container-parser.d.ts.map +1 -1
  37. package/dist/mmtp-container-parser.js +86 -9
  38. package/dist/mmtp-container-parser.js.map +2 -2
  39. package/dist/mmtp-container-parser.no-modules.js +89 -11
  40. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  41. package/dist/mmtp-decode-pipeline.js +85 -9
  42. package/dist/mmtp-decode-pipeline.js.map +2 -2
  43. package/dist/mmtp-router.d.ts +18 -1
  44. package/dist/mmtp-router.d.ts.map +1 -1
  45. package/dist/mmtp-router.js +304 -39
  46. package/dist/mmtp-router.js.map +2 -2
  47. package/dist/multicast-manager.js +526 -45
  48. package/dist/multicast-manager.js.map +2 -2
  49. package/dist/multicast-transport.js +26 -16
  50. package/dist/multicast-transport.js.map +2 -2
  51. package/dist/no-modules.js +85 -9
  52. package/dist/no-modules.js.map +2 -2
  53. package/dist/repair-layer-subscriptions.d.ts +6 -2
  54. package/dist/repair-layer-subscriptions.d.ts.map +1 -1
  55. package/dist/repair-layer-subscriptions.js +6 -3
  56. package/dist/repair-layer-subscriptions.js.map +2 -2
  57. package/dist/track-session.d.ts +1 -1
  58. package/dist/track-session.js.map +1 -1
  59. package/dist/wasm-mmtp-container-parser.js +85 -9
  60. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  61. package/package.json +8 -5
  62. package/schemas/catalog.schema.json +4 -0
  63. package/src/catalog-fec-params.ts +36 -1
  64. package/src/cmcd-producer.ts +189 -179
  65. package/src/fec-manager.ts +277 -6
  66. package/src/fec-worker-setup.ts +2 -2
  67. package/src/index.ts +7 -2
  68. package/src/iso-block-locator.ts +19 -0
  69. package/src/iso-bmff-timing.ts +40 -1
  70. package/src/mmtp-container-parser.ts +96 -8
  71. package/src/mmtp-router.ts +350 -13
  72. package/src/repair-layer-subscriptions.ts +13 -5
  73. package/src/track-session.ts +1 -1
@@ -5568,9 +5568,21 @@ var IsoBmffTimingResolver = class {
5568
5568
  }
5569
5569
  #track = null;
5570
5570
  #fragments = /* @__PURE__ */ new Map();
5571
+ /** First sample of each pending fragment, kept in `#delivered` with its sample count. */
5572
+ #firstSamples = /* @__PURE__ */ new Map();
5573
+ /**
5574
+ * First sample and sample count of the newest fragments whose every slot was
5575
+ * cleared. Abandonment counts: `releaseSample` clears a slot too, which
5576
+ * matches the combiner, since it retires an abandoned sample as well.
5577
+ */
5578
+ #delivered = /* @__PURE__ */ new Map();
5571
5579
  #pendingSamples = 0;
5572
5580
  #evictedSamples = 0;
5573
5581
  #lastMovieFragmentRejection = null;
5582
+ /** True once an FT=0 has bound the track that FT=1 timing is read against. */
5583
+ get initialized() {
5584
+ return this.#track !== null;
5585
+ }
5574
5586
  get pendingSampleCount() {
5575
5587
  return this.#pendingSamples;
5576
5588
  }
@@ -5594,7 +5606,14 @@ var IsoBmffTimingResolver = class {
5594
5606
  this.#track = timing;
5595
5607
  return true;
5596
5608
  }
5597
- addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples) {
5609
+ /**
5610
+ * `recovered` marks an FEC-recovered copy. A recovered block hands back
5611
+ * every FT=1 it holds, the received ones included, so a recovered copy of a
5612
+ * fragment whose samples are all done is a duplicate: admitting it again
5613
+ * would charge stale timing and could displace the live MPU's. A source
5614
+ * copy is still admitted again (a rendition switch re-arms timing that way).
5615
+ */
5616
+ addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
5598
5617
  this.#lastMovieFragmentRejection = null;
5599
5618
  if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
5600
5619
  this.#lastMovieFragmentRejection = "invalid";
@@ -5620,6 +5639,8 @@ var IsoBmffTimingResolver = class {
5620
5639
  (sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
5621
5640
  );
5622
5641
  }
5642
+ const delivered = recovered ? this.#delivered.get(key) : void 0;
5643
+ if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
5623
5644
  let freed = 0;
5624
5645
  while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
5625
5646
  freed += this.#evictOldestFragment();
@@ -5633,6 +5654,7 @@ var IsoBmffTimingResolver = class {
5633
5654
  return false;
5634
5655
  }
5635
5656
  this.#fragments.set(key, [...parsed.samples]);
5657
+ this.#firstSamples.set(key, parsed.samples[0]);
5636
5658
  this.#pendingSamples += parsed.samples.length;
5637
5659
  return true;
5638
5660
  }
@@ -5690,6 +5712,7 @@ var IsoBmffTimingResolver = class {
5690
5712
  let live = 0;
5691
5713
  for (const sample of samples) if (sample !== null) live++;
5692
5714
  this.#fragments.delete(oldest);
5715
+ this.#firstSamples.delete(oldest);
5693
5716
  this.#pendingSamples -= live;
5694
5717
  this.#evictedSamples += live;
5695
5718
  return live;
@@ -5705,12 +5728,23 @@ var IsoBmffTimingResolver = class {
5705
5728
  if (!samples || !samples[index]) return false;
5706
5729
  samples[index] = null;
5707
5730
  this.#pendingSamples--;
5708
- if (samples.every((value) => value === null)) this.#fragments.delete(key);
5731
+ if (samples.every((value) => value === null)) {
5732
+ this.#fragments.delete(key);
5733
+ const first = this.#firstSamples.get(key);
5734
+ this.#firstSamples.delete(key);
5735
+ this.#delivered.delete(key);
5736
+ this.#delivered.set(key, { first, sampleCount: samples.length });
5737
+ if (this.#delivered.size > this.maxPendingSamples) {
5738
+ this.#delivered.delete(this.#delivered.keys().next().value);
5739
+ }
5740
+ }
5709
5741
  return true;
5710
5742
  }
5711
5743
  reset() {
5712
5744
  this.#track = null;
5713
5745
  this.#fragments.clear();
5746
+ this.#firstSamples.clear();
5747
+ this.#delivered.clear();
5714
5748
  this.#pendingSamples = 0;
5715
5749
  }
5716
5750
  };
@@ -6192,6 +6226,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
6192
6226
  this.#reclaimedTimingSamples = 0;
6193
6227
  this.#evictedTimingSamples = 0;
6194
6228
  this.#rejectedMovieFragments = 0;
6229
+ this.#recoveredHeadsAwaitingTiming = {
6230
+ held: 0,
6231
+ expired: 0,
6232
+ discarded: 0,
6233
+ superseded: 0
6234
+ };
6195
6235
  this.#sequencers = /* @__PURE__ */ new Map();
6196
6236
  this.#fecGate = null;
6197
6237
  this.#fecSequencer = null;
@@ -6300,6 +6340,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6300
6340
  #reclaimedTimingSamples;
6301
6341
  #evictedTimingSamples;
6302
6342
  #rejectedMovieFragments;
6343
+ #recoveredHeadsAwaitingTiming;
6303
6344
  #sequencers;
6304
6345
  #fecGate;
6305
6346
  #fecSequencer;
@@ -6420,6 +6461,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
6420
6461
  rejectedMovieFragments: this.#rejectedMovieFragments
6421
6462
  };
6422
6463
  }
6464
+ /** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
6465
+ get recoveredHeadsAwaitingTiming() {
6466
+ let pending = 0;
6467
+ for (const { awaitingTiming } of this.#timingStreams.values()) {
6468
+ for (const head of awaitingTiming) if (head.options?.recovered) pending++;
6469
+ }
6470
+ return { ...this.#recoveredHeadsAwaitingTiming, pending };
6471
+ }
6423
6472
  get reorderStats() {
6424
6473
  let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
6425
6474
  for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
@@ -6593,7 +6642,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6593
6642
  const now = Date.now();
6594
6643
  for (const { awaitingTiming } of this.#timingStreams.values()) {
6595
6644
  while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
6596
- awaitingTiming.shift();
6645
+ this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
6597
6646
  }
6598
6647
  }
6599
6648
  }
@@ -6711,11 +6760,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
6711
6760
  if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
6712
6761
  return this.#drop("fragment-unexpected-fragmentation");
6713
6762
  const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
6714
- if (!resolver2) return this.#drop("fragment-no-resolver");
6763
+ if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
6715
6764
  const pendingBefore = resolver2.pendingSampleCount;
6716
6765
  const evictedBefore = resolver2.evictedSampleCount;
6717
6766
  const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
6718
- const admitted = resolver2.addMovieFragment(mpu.mpuSequenceNumber, data, remaining);
6767
+ const admitted = resolver2.addMovieFragment(
6768
+ mpu.mpuSequenceNumber,
6769
+ data,
6770
+ remaining,
6771
+ options?.recovered === true
6772
+ );
6719
6773
  this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
6720
6774
  this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
6721
6775
  if (!admitted) {
@@ -6735,7 +6789,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6735
6789
  };
6736
6790
  }
6737
6791
  if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
6738
- const resolver = this.#timingResolver(streamKey, info.packetId, false);
6792
+ const resolver = this.#timingResolver(streamKey, info.packetId, true);
6739
6793
  if (!resolver) return this.#drop("mfu-no-resolver");
6740
6794
  if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
6741
6795
  return this.#drop("mfu-empty-payload");
@@ -6756,6 +6810,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6756
6810
  mpuSequenceNumber: mpu.mpuSequenceNumber,
6757
6811
  movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
6758
6812
  sampleNumber: mfuDuHeader.sampleNumber,
6813
+ sequenceNumber: info.sequenceNumber,
6759
6814
  arrivedAt: Date.now()
6760
6815
  });
6761
6816
  return this.#drop("mfu-sample-timing-missing");
@@ -7079,6 +7134,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
7079
7134
  }
7080
7135
  this.#emitFrame(data, meta);
7081
7136
  }
7137
+ /** Whether FT=1 metadata already names this sample, without claiming it. */
7138
+ hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
7139
+ return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
7140
+ }
7082
7141
  /**
7083
7142
  * Claim and return the authored timing for a recovered sample. Recovery does
7084
7143
  * not pass through the ordinary `feed()` completion path, so it must retire
@@ -7140,6 +7199,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
7140
7199
  for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
7141
7200
  this.#sequencers.clear();
7142
7201
  this.#fecSequencer?.discard();
7202
+ for (const { awaitingTiming } of this.#timingStreams.values()) {
7203
+ for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
7204
+ }
7143
7205
  this.#timingStreams.clear();
7144
7206
  this.#pendingTimingSamples = 0;
7145
7207
  this.#reliableMoqStartupGate.clear();
@@ -7158,10 +7220,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
7158
7220
  return resolver;
7159
7221
  }
7160
7222
  #awaitTiming(streamKey, head) {
7161
- const awaitingTiming = this.#timingStreams.get(streamKey)?.awaitingTiming;
7162
- if (!awaitingTiming) return;
7223
+ const state = this.#timingStreams.get(streamKey);
7224
+ if (!state) return;
7225
+ if (!head.options?.recovered) {
7226
+ const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
7227
+ this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
7228
+ state.awaitingTiming = kept;
7229
+ }
7230
+ const awaitingTiming = state.awaitingTiming;
7163
7231
  awaitingTiming.push(head);
7164
- if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) awaitingTiming.shift();
7232
+ if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
7233
+ if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
7234
+ this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
7235
+ }
7236
+ }
7237
+ /** Count a recovered head leaving the queue other than by replay or its source copy. */
7238
+ #leaveAwaitingTiming(head, outcome) {
7239
+ if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
7165
7240
  }
7166
7241
  /**
7167
7242
  * Feed again the heads whose timing the FT=1 just admitted declares. That
@@ -7189,6 +7264,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
7189
7264
  const state = this.#timingStreams.get(streamKey);
7190
7265
  if (!state) return;
7191
7266
  this.#timingStreams.delete(streamKey);
7267
+ for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
7192
7268
  this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
7193
7269
  if (this.#pendingTimingSamples < 0) {
7194
7270
  throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");