@blockcast/mmt-container 2.2.0-main.da2e7cd1dd5d → 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 (46) hide show
  1. package/dist/browser-runtime.js +472 -36
  2. package/dist/browser-runtime.js.map +2 -2
  3. package/dist/delivery-catalog.js +2 -1
  4. package/dist/delivery-catalog.js.map +2 -2
  5. package/dist/fec-manager.d.ts +30 -0
  6. package/dist/fec-manager.d.ts.map +1 -1
  7. package/dist/fec-manager.js +196 -2
  8. package/dist/fec-manager.js.map +2 -2
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js.map +2 -2
  12. package/dist/iso-block-locator.d.ts +9 -0
  13. package/dist/iso-block-locator.d.ts.map +1 -1
  14. package/dist/iso-block-locator.js +17 -0
  15. package/dist/iso-block-locator.js.map +2 -2
  16. package/dist/iso-bmff-timing.d.ts +10 -1
  17. package/dist/iso-bmff-timing.d.ts.map +1 -1
  18. package/dist/iso-bmff-timing.js +36 -2
  19. package/dist/iso-bmff-timing.js.map +2 -2
  20. package/dist/mmtp-container-parser.d.ts +35 -2
  21. package/dist/mmtp-container-parser.d.ts.map +1 -1
  22. package/dist/mmtp-container-parser.js +82 -9
  23. package/dist/mmtp-container-parser.js.map +2 -2
  24. package/dist/mmtp-container-parser.no-modules.js +85 -11
  25. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  26. package/dist/mmtp-decode-pipeline.js +81 -9
  27. package/dist/mmtp-decode-pipeline.js.map +2 -2
  28. package/dist/mmtp-router.d.ts +18 -1
  29. package/dist/mmtp-router.d.ts.map +1 -1
  30. package/dist/mmtp-router.js +276 -34
  31. package/dist/mmtp-router.js.map +2 -2
  32. package/dist/multicast-manager.js +472 -36
  33. package/dist/multicast-manager.js.map +2 -2
  34. package/dist/multicast-transport.js +2 -1
  35. package/dist/multicast-transport.js.map +2 -2
  36. package/dist/no-modules.js +81 -9
  37. package/dist/no-modules.js.map +2 -2
  38. package/dist/wasm-mmtp-container-parser.js +81 -9
  39. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  40. package/package.json +5 -5
  41. package/src/fec-manager.ts +251 -2
  42. package/src/index.ts +1 -0
  43. package/src/iso-block-locator.ts +19 -0
  44. package/src/iso-bmff-timing.ts +40 -1
  45. package/src/mmtp-container-parser.ts +84 -8
  46. package/src/mmtp-router.ts +346 -30
@@ -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");
@@ -7144,6 +7199,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
7144
7199
  for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
7145
7200
  this.#sequencers.clear();
7146
7201
  this.#fecSequencer?.discard();
7202
+ for (const { awaitingTiming } of this.#timingStreams.values()) {
7203
+ for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
7204
+ }
7147
7205
  this.#timingStreams.clear();
7148
7206
  this.#pendingTimingSamples = 0;
7149
7207
  this.#reliableMoqStartupGate.clear();
@@ -7162,10 +7220,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
7162
7220
  return resolver;
7163
7221
  }
7164
7222
  #awaitTiming(streamKey, head) {
7165
- const awaitingTiming = this.#timingStreams.get(streamKey)?.awaitingTiming;
7166
- 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;
7167
7231
  awaitingTiming.push(head);
7168
- 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]++;
7169
7240
  }
7170
7241
  /**
7171
7242
  * Feed again the heads whose timing the FT=1 just admitted declares. That
@@ -7193,6 +7264,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
7193
7264
  const state = this.#timingStreams.get(streamKey);
7194
7265
  if (!state) return;
7195
7266
  this.#timingStreams.delete(streamKey);
7267
+ for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
7196
7268
  this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
7197
7269
  if (this.#pendingTimingSamples < 0) {
7198
7270
  throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");