@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
@@ -4851,9 +4851,21 @@ var IsoBmffTimingResolver = class {
4851
4851
  }
4852
4852
  #track = null;
4853
4853
  #fragments = /* @__PURE__ */ new Map();
4854
+ /** First sample of each pending fragment, kept in `#delivered` with its sample count. */
4855
+ #firstSamples = /* @__PURE__ */ new Map();
4856
+ /**
4857
+ * First sample and sample count of the newest fragments whose every slot was
4858
+ * cleared. Abandonment counts: `releaseSample` clears a slot too, which
4859
+ * matches the combiner, since it retires an abandoned sample as well.
4860
+ */
4861
+ #delivered = /* @__PURE__ */ new Map();
4854
4862
  #pendingSamples = 0;
4855
4863
  #evictedSamples = 0;
4856
4864
  #lastMovieFragmentRejection = null;
4865
+ /** True once an FT=0 has bound the track that FT=1 timing is read against. */
4866
+ get initialized() {
4867
+ return this.#track !== null;
4868
+ }
4857
4869
  get pendingSampleCount() {
4858
4870
  return this.#pendingSamples;
4859
4871
  }
@@ -4877,7 +4889,14 @@ var IsoBmffTimingResolver = class {
4877
4889
  this.#track = timing;
4878
4890
  return true;
4879
4891
  }
4880
- addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples) {
4892
+ /**
4893
+ * `recovered` marks an FEC-recovered copy. A recovered block hands back
4894
+ * every FT=1 it holds, the received ones included, so a recovered copy of a
4895
+ * fragment whose samples are all done is a duplicate: admitting it again
4896
+ * would charge stale timing and could displace the live MPU's. A source
4897
+ * copy is still admitted again (a rendition switch re-arms timing that way).
4898
+ */
4899
+ addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
4881
4900
  this.#lastMovieFragmentRejection = null;
4882
4901
  if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
4883
4902
  this.#lastMovieFragmentRejection = "invalid";
@@ -4903,6 +4922,8 @@ var IsoBmffTimingResolver = class {
4903
4922
  (sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
4904
4923
  );
4905
4924
  }
4925
+ const delivered = recovered ? this.#delivered.get(key) : void 0;
4926
+ if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
4906
4927
  let freed = 0;
4907
4928
  while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
4908
4929
  freed += this.#evictOldestFragment();
@@ -4916,6 +4937,7 @@ var IsoBmffTimingResolver = class {
4916
4937
  return false;
4917
4938
  }
4918
4939
  this.#fragments.set(key, [...parsed.samples]);
4940
+ this.#firstSamples.set(key, parsed.samples[0]);
4919
4941
  this.#pendingSamples += parsed.samples.length;
4920
4942
  return true;
4921
4943
  }
@@ -4973,6 +4995,7 @@ var IsoBmffTimingResolver = class {
4973
4995
  let live = 0;
4974
4996
  for (const sample of samples) if (sample !== null) live++;
4975
4997
  this.#fragments.delete(oldest);
4998
+ this.#firstSamples.delete(oldest);
4976
4999
  this.#pendingSamples -= live;
4977
5000
  this.#evictedSamples += live;
4978
5001
  return live;
@@ -4988,12 +5011,23 @@ var IsoBmffTimingResolver = class {
4988
5011
  if (!samples || !samples[index]) return false;
4989
5012
  samples[index] = null;
4990
5013
  this.#pendingSamples--;
4991
- if (samples.every((value) => value === null)) this.#fragments.delete(key);
5014
+ if (samples.every((value) => value === null)) {
5015
+ this.#fragments.delete(key);
5016
+ const first = this.#firstSamples.get(key);
5017
+ this.#firstSamples.delete(key);
5018
+ this.#delivered.delete(key);
5019
+ this.#delivered.set(key, { first, sampleCount: samples.length });
5020
+ if (this.#delivered.size > this.maxPendingSamples) {
5021
+ this.#delivered.delete(this.#delivered.keys().next().value);
5022
+ }
5023
+ }
4992
5024
  return true;
4993
5025
  }
4994
5026
  reset() {
4995
5027
  this.#track = null;
4996
5028
  this.#fragments.clear();
5029
+ this.#firstSamples.clear();
5030
+ this.#delivered.clear();
4997
5031
  this.#pendingSamples = 0;
4998
5032
  }
4999
5033
  };
@@ -5475,6 +5509,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
5475
5509
  this.#reclaimedTimingSamples = 0;
5476
5510
  this.#evictedTimingSamples = 0;
5477
5511
  this.#rejectedMovieFragments = 0;
5512
+ this.#recoveredHeadsAwaitingTiming = {
5513
+ held: 0,
5514
+ expired: 0,
5515
+ discarded: 0,
5516
+ superseded: 0
5517
+ };
5478
5518
  this.#sequencers = /* @__PURE__ */ new Map();
5479
5519
  this.#fecGate = null;
5480
5520
  this.#fecSequencer = null;
@@ -5583,6 +5623,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
5583
5623
  #reclaimedTimingSamples;
5584
5624
  #evictedTimingSamples;
5585
5625
  #rejectedMovieFragments;
5626
+ #recoveredHeadsAwaitingTiming;
5586
5627
  #sequencers;
5587
5628
  #fecGate;
5588
5629
  #fecSequencer;
@@ -5703,6 +5744,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
5703
5744
  rejectedMovieFragments: this.#rejectedMovieFragments
5704
5745
  };
5705
5746
  }
5747
+ /** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
5748
+ get recoveredHeadsAwaitingTiming() {
5749
+ let pending = 0;
5750
+ for (const { awaitingTiming } of this.#timingStreams.values()) {
5751
+ for (const head of awaitingTiming) if (head.options?.recovered) pending++;
5752
+ }
5753
+ return { ...this.#recoveredHeadsAwaitingTiming, pending };
5754
+ }
5706
5755
  get reorderStats() {
5707
5756
  let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
5708
5757
  for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
@@ -5876,7 +5925,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
5876
5925
  const now = Date.now();
5877
5926
  for (const { awaitingTiming } of this.#timingStreams.values()) {
5878
5927
  while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
5879
- awaitingTiming.shift();
5928
+ this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
5880
5929
  }
5881
5930
  }
5882
5931
  }
@@ -5994,11 +6043,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
5994
6043
  if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
5995
6044
  return this.#drop("fragment-unexpected-fragmentation");
5996
6045
  const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
5997
- if (!resolver2) return this.#drop("fragment-no-resolver");
6046
+ if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
5998
6047
  const pendingBefore = resolver2.pendingSampleCount;
5999
6048
  const evictedBefore = resolver2.evictedSampleCount;
6000
6049
  const remaining = this.#maxPendingTimingSamples - this.#pendingTimingSamples;
6001
- const admitted = resolver2.addMovieFragment(mpu.mpuSequenceNumber, data, remaining);
6050
+ const admitted = resolver2.addMovieFragment(
6051
+ mpu.mpuSequenceNumber,
6052
+ data,
6053
+ remaining,
6054
+ options?.recovered === true
6055
+ );
6002
6056
  this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
6003
6057
  this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
6004
6058
  if (!admitted) {
@@ -6018,7 +6072,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6018
6072
  };
6019
6073
  }
6020
6074
  if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
6021
- const resolver = this.#timingResolver(streamKey, info.packetId, false);
6075
+ const resolver = this.#timingResolver(streamKey, info.packetId, true);
6022
6076
  if (!resolver) return this.#drop("mfu-no-resolver");
6023
6077
  if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
6024
6078
  return this.#drop("mfu-empty-payload");
@@ -6039,6 +6093,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6039
6093
  mpuSequenceNumber: mpu.mpuSequenceNumber,
6040
6094
  movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
6041
6095
  sampleNumber: mfuDuHeader.sampleNumber,
6096
+ sequenceNumber: info.sequenceNumber,
6042
6097
  arrivedAt: Date.now()
6043
6098
  });
6044
6099
  return this.#drop("mfu-sample-timing-missing");
@@ -6427,6 +6482,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
6427
6482
  for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
6428
6483
  this.#sequencers.clear();
6429
6484
  this.#fecSequencer?.discard();
6485
+ for (const { awaitingTiming } of this.#timingStreams.values()) {
6486
+ for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
6487
+ }
6430
6488
  this.#timingStreams.clear();
6431
6489
  this.#pendingTimingSamples = 0;
6432
6490
  this.#reliableMoqStartupGate.clear();
@@ -6445,10 +6503,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
6445
6503
  return resolver;
6446
6504
  }
6447
6505
  #awaitTiming(streamKey, head) {
6448
- const awaitingTiming = this.#timingStreams.get(streamKey)?.awaitingTiming;
6449
- if (!awaitingTiming) return;
6506
+ const state = this.#timingStreams.get(streamKey);
6507
+ if (!state) return;
6508
+ if (!head.options?.recovered) {
6509
+ const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
6510
+ this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
6511
+ state.awaitingTiming = kept;
6512
+ }
6513
+ const awaitingTiming = state.awaitingTiming;
6450
6514
  awaitingTiming.push(head);
6451
- if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) awaitingTiming.shift();
6515
+ if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
6516
+ if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
6517
+ this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
6518
+ }
6519
+ }
6520
+ /** Count a recovered head leaving the queue other than by replay or its source copy. */
6521
+ #leaveAwaitingTiming(head, outcome) {
6522
+ if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
6452
6523
  }
6453
6524
  /**
6454
6525
  * Feed again the heads whose timing the FT=1 just admitted declares. That
@@ -6476,6 +6547,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6476
6547
  const state = this.#timingStreams.get(streamKey);
6477
6548
  if (!state) return;
6478
6549
  this.#timingStreams.delete(streamKey);
6550
+ for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
6479
6551
  this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
6480
6552
  if (this.#pendingTimingSamples < 0) {
6481
6553
  throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");