@blockcast/mmt-container 2.2.0-main.75b39a2cdddf → 2.2.0-main.7e8a9d13c01c

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 (52) hide show
  1. package/dist/browser-runtime.js +634 -129
  2. package/dist/browser-runtime.js.map +3 -3
  3. package/dist/delta-fallback-config.js.map +1 -1
  4. package/dist/fec-manager.d.ts +37 -11
  5. package/dist/fec-manager.d.ts.map +1 -1
  6. package/dist/fec-manager.js +181 -52
  7. package/dist/fec-manager.js.map +2 -2
  8. package/dist/fec-worker-setup.js.map +1 -1
  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 +39 -13
  13. package/dist/iso-block-locator.d.ts.map +1 -1
  14. package/dist/iso-block-locator.js +80 -25
  15. package/dist/iso-block-locator.js.map +2 -2
  16. package/dist/media-router.js.map +1 -1
  17. package/dist/mmtp-container-parser.d.ts +21 -0
  18. package/dist/mmtp-container-parser.d.ts.map +1 -1
  19. package/dist/mmtp-container-parser.js +114 -11
  20. package/dist/mmtp-container-parser.js.map +2 -2
  21. package/dist/mmtp-container-parser.no-modules.js +113 -12
  22. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  23. package/dist/mmtp-decode-pipeline.js +114 -11
  24. package/dist/mmtp-decode-pipeline.js.map +2 -2
  25. package/dist/mmtp-router.d.ts +18 -9
  26. package/dist/mmtp-router.d.ts.map +1 -1
  27. package/dist/mmtp-router.js +254 -44
  28. package/dist/mmtp-router.js.map +2 -2
  29. package/dist/multicast-manager.d.ts +13 -3
  30. package/dist/multicast-manager.d.ts.map +1 -1
  31. package/dist/multicast-manager.js +455 -103
  32. package/dist/multicast-manager.js.map +2 -2
  33. package/dist/no-modules.js +114 -11
  34. package/dist/no-modules.js.map +2 -2
  35. package/dist/repair-fetch.d.ts +36 -8
  36. package/dist/repair-fetch.d.ts.map +1 -1
  37. package/dist/repair-fetch.js +285 -26
  38. package/dist/repair-fetch.js.map +3 -3
  39. package/dist/track-session.d.ts.map +1 -1
  40. package/dist/track-session.js +1 -0
  41. package/dist/track-session.js.map +2 -2
  42. package/dist/wasm-mmtp-container-parser.js +114 -11
  43. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  44. package/package.json +5 -5
  45. package/src/fec-manager.ts +156 -26
  46. package/src/index.ts +2 -0
  47. package/src/iso-block-locator.ts +114 -31
  48. package/src/mmtp-container-parser.ts +181 -10
  49. package/src/mmtp-router.ts +231 -58
  50. package/src/multicast-manager.ts +44 -6
  51. package/src/repair-fetch.ts +363 -49
  52. package/src/track-session.ts +3 -0
@@ -5914,6 +5914,9 @@ function authoredGap(before, after) {
5914
5914
  function ssIdAfter(a, b) {
5915
5915
  return (a - b | 0) > 0;
5916
5916
  }
5917
+ function holdsRebuilt(stream) {
5918
+ return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
5919
+ }
5917
5920
  var FecReleaseSequencer = class {
5918
5921
  #streams = /* @__PURE__ */ new Map();
5919
5922
  #gate;
@@ -5976,6 +5979,7 @@ var FecReleaseSequencer = class {
5976
5979
  stream.lastSbn = void 0;
5977
5980
  stream.discontinuity = false;
5978
5981
  stream.rapSsId = void 0;
5982
+ stream.sourced = false;
5979
5983
  }
5980
5984
  if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
5981
5985
  this.#recordStat("droppedStale");
@@ -6002,6 +6006,7 @@ var FecReleaseSequencer = class {
6002
6006
  if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
6003
6007
  else hi = mid;
6004
6008
  }
6009
+ if (stream.buf[lo]?.passed === true) frame.passed = true;
6005
6010
  stream.buf.splice(lo, 0, frame);
6006
6011
  this.#drainUnlessDeferred(stream);
6007
6012
  }
@@ -6037,6 +6042,41 @@ var FecReleaseSequencer = class {
6037
6042
  }
6038
6043
  return false;
6039
6044
  }
6045
+ /**
6046
+ * These streams' sink is being replaced by one that has seen no source
6047
+ * frame yet. First, from a stream that holds a frame the overlay rebuilt,
6048
+ * release to the sink being replaced the frames the unanchored path had
6049
+ * already released to it (#cursorPassed): there they waited in its queue,
6050
+ * which its replacement flushes. A stream that holds none is not drained:
6051
+ * it holds nothing the anchor kept back, and a drain would still force out
6052
+ * a frame held past the hold limit, which before the anchor waited for the
6053
+ * next push. Then treat the streams as unsourced until a source frame is
6054
+ * released.
6055
+ */
6056
+ unsource(packetId, catalogTrackName) {
6057
+ for (const stream of this.#streams.values()) {
6058
+ if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
6059
+ if (stream.draining === 0 && holdsRebuilt(stream)) {
6060
+ if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
6061
+ else this.#drain(stream, true);
6062
+ }
6063
+ stream.sourced = false;
6064
+ }
6065
+ }
6066
+ }
6067
+ /**
6068
+ * Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
6069
+ * (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
6070
+ * packetId.
6071
+ */
6072
+ holdsRebuilt(packetId, catalogTrackName) {
6073
+ for (const stream of this.#streams.values()) {
6074
+ if (stream.packetId !== packetId) continue;
6075
+ if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
6076
+ if (holdsRebuilt(stream)) return true;
6077
+ }
6078
+ return false;
6079
+ }
6040
6080
  /** Re-evaluate held gaps after FEC state changed. */
6041
6081
  poke(streamKey) {
6042
6082
  if (streamKey !== void 0) {
@@ -6128,16 +6168,32 @@ var FecReleaseSequencer = class {
6128
6168
  last: null,
6129
6169
  lastSbn: void 0,
6130
6170
  discontinuity: false,
6131
- rapSsId: void 0
6171
+ rapSsId: void 0,
6172
+ sourced: false,
6173
+ draining: 0
6132
6174
  };
6133
6175
  this.#streams.set(streamKey, stream);
6134
6176
  }
6135
6177
  return stream;
6136
6178
  }
6137
- #drain(stream) {
6179
+ /** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
6180
+ #drain(stream, asUnanchored = false) {
6181
+ stream.draining++;
6182
+ try {
6183
+ this.#drainLoop(stream, asUnanchored);
6184
+ } finally {
6185
+ stream.draining--;
6186
+ }
6187
+ }
6188
+ #drainLoop(stream, asUnanchored) {
6138
6189
  while (stream.buf.length > 0 && stream.last !== null) {
6139
6190
  const head = stream.buf[0];
6140
6191
  const last = stream.last;
6192
+ if (compareFrameOrder(head, last) <= 0) {
6193
+ stream.buf.shift();
6194
+ this.#release(stream, head, void 0);
6195
+ continue;
6196
+ }
6141
6197
  if (stream.discontinuity) {
6142
6198
  stream.discontinuity = false;
6143
6199
  stream.buf.shift();
@@ -6151,9 +6207,10 @@ var FecReleaseSequencer = class {
6151
6207
  }
6152
6208
  const headSbn = head.meta.fecSbn;
6153
6209
  const from = stream.lastSbn ?? headSbn;
6154
- const to = headSbn ?? stream.lastSbn;
6210
+ const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
6155
6211
  let lost = [];
6156
6212
  const unanchored = from === void 0 && to === void 0;
6213
+ let unresolved = false;
6157
6214
  if (from !== void 0 && to !== void 0) {
6158
6215
  const resolved = this.#gate.resolvedLoss(
6159
6216
  stream.packetId,
@@ -6162,7 +6219,8 @@ var FecReleaseSequencer = class {
6162
6219
  Math.max(from, to),
6163
6220
  from
6164
6221
  );
6165
- if (resolved === null) {
6222
+ unresolved = resolved === null;
6223
+ if (unresolved && !this.#cursorPassed(stream, asUnanchored)) {
6166
6224
  const heldMs = performance.now() - head.arrivedAtMs;
6167
6225
  if (heldMs <= this.#gate.holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
6168
6226
  this.#recordStat("forcedWindowFlushes");
@@ -6170,22 +6228,56 @@ var FecReleaseSequencer = class {
6170
6228
  this.#release(stream, head, this.#gate.decide(last, head, [], true));
6171
6229
  continue;
6172
6230
  }
6173
- lost = resolved;
6231
+ lost = resolved ?? [];
6174
6232
  }
6175
6233
  stream.buf.shift();
6176
6234
  this.#release(
6177
6235
  stream,
6178
6236
  head,
6179
- lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
6237
+ lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
6180
6238
  );
6181
6239
  }
6182
6240
  }
6241
+ /**
6242
+ * An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
6243
+ * source cursor holds it unanchored, and neither does a later frame behind
6244
+ * it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
6245
+ * fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
6246
+ * like each frame that arrived in front of it since (stale there). The head
6247
+ * then goes, decided as it would be unanchored, once a frame the base path
6248
+ * delivered (a source frame, or one base FEC recovered) is buffered after a
6249
+ * marked one: the base path has passed that one, so a frame still missing
6250
+ * before it can only come from a repair. It also goes while no source frame
6251
+ * has been released, when the direct path shows a recovered frame at once.
6252
+ * And it goes at once while a staged switch decides on when frames arrive
6253
+ * (FecReleaseGate.releaseUnanchored), and when unsource releases into a
6254
+ * sink being replaced: unanchored, it had already reached the sink.
6255
+ * The scan also writes: the `passed` marks it sets stay on the frames,
6256
+ * and push marks a frame it inserts in front of a marked one from them.
6257
+ */
6258
+ #cursorPassed(stream, asUnanchored) {
6259
+ let at = -1;
6260
+ for (let i = 0; i < stream.buf.length; i++) {
6261
+ const frame = stream.buf[i];
6262
+ if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
6263
+ frame.passed = true;
6264
+ if (at < 0) at = i;
6265
+ } else if (frame.passed !== true) break;
6266
+ }
6267
+ if (at < 0) return false;
6268
+ return asUnanchored || !stream.sourced || 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));
6269
+ }
6183
6270
  #release(stream, frame, decision) {
6271
+ const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
6272
+ if (stale) this.#recordStat("droppedStale");
6184
6273
  if (decision) frame.meta.gapBefore = decision;
6185
6274
  stream.last = frame;
6186
- if (frame.meta.fecSbn !== void 0) stream.lastSbn = frame.meta.fecSbn;
6275
+ if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
6276
+ stream.lastSbn = frame.meta.fecSbn;
6277
+ }
6278
+ if (frame.meta.recovered !== true) stream.sourced = true;
6187
6279
  this.#gate.onReleased?.(frame.data, frame.meta);
6188
- this.#emit(frame.data, frame.meta);
6280
+ if (!stale) this.#emit(frame.data, frame.meta);
6189
6281
  }
6190
6282
  };
6191
6283
  var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
@@ -6511,6 +6603,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
6511
6603
  fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
6512
6604
  return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
6513
6605
  }
6606
+ /** See FecReleaseSequencer.unsource. */
6607
+ unsourceFecRelease(packetId, catalogTrackName) {
6608
+ this.#fecSequencer?.unsource(packetId, catalogTrackName);
6609
+ }
6610
+ /** See FecReleaseSequencer.holdsRebuilt. */
6611
+ fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
6612
+ return this.#fecSequencer?.holdsRebuilt(packetId, catalogTrackName) ?? false;
6613
+ }
6514
6614
  /** Re-evaluate held gaps after FEC block state changed. */
6515
6615
  pokeFecRelease(packetId, catalogTrackName) {
6516
6616
  if (!this.#fecSequencer) return;
@@ -6837,7 +6937,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
6837
6937
  info,
6838
6938
  resolver,
6839
6939
  options?.recovered === true,
6840
- options?.fecSbn
6940
+ options?.fecSbn,
6941
+ options?.fecSbnFirst
6841
6942
  );
6842
6943
  if (deferred.record === void 0) this.#countDeferredOutcome(deferred.reason);
6843
6944
  }
@@ -6930,7 +7031,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
6930
7031
  info,
6931
7032
  resolver,
6932
7033
  options?.recovered === true,
6933
- options?.fecSbn
7034
+ options?.fecSbn,
7035
+ options?.fecSbnFirst
6934
7036
  );
6935
7037
  if (outcome.record !== void 0) return outcome.record;
6936
7038
  return this.#drop(outcome.reason);
@@ -6950,7 +7052,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6950
7052
  * attribution exists to keep readable. Leaving the choice at the call site
6951
7053
  * is what keeps both true.
6952
7054
  */
6953
- #emitCombinedSample(combined, info, resolver, recovered, fecSbn) {
7055
+ #emitCombinedSample(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
6954
7056
  const timing = resolver.sampleTiming(
6955
7057
  combined.mpuSequenceNumber,
6956
7058
  combined.movieFragmentSequenceNumber,
@@ -6967,6 +7069,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
6967
7069
  );
6968
7070
  if (!meta) return { reason: "combined-meta-build-failed" };
6969
7071
  if (fecSbn !== void 0) meta.fecSbn = fecSbn;
7072
+ if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
6970
7073
  if (!resolver.consumeSample(
6971
7074
  combined.mpuSequenceNumber,
6972
7075
  combined.movieFragmentSequenceNumber,