@blockcast/mmt-container 2.2.0-main.a88c3a0aa4f3 → 2.2.0-main.efb4fa4521af

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 (58) hide show
  1. package/dist/browser-runtime.js +293 -35
  2. package/dist/browser-runtime.js.map +4 -4
  3. package/dist/carp-catalog.js.map +1 -1
  4. package/dist/catalog-fec-params.js.map +1 -1
  5. package/dist/catalog-network-source.js.map +1 -1
  6. package/dist/delivery-catalog.js.map +1 -1
  7. package/dist/delta-fallback-config.js.map +1 -1
  8. package/dist/fec-manager.js.map +2 -2
  9. package/dist/fec-state.js.map +1 -1
  10. package/dist/fec-worker-setup.js.map +1 -1
  11. package/dist/held-layer-admission.js.map +1 -1
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +4 -0
  15. package/dist/index.js.map +2 -2
  16. package/dist/iso-block-locator.js.map +1 -1
  17. package/dist/mfu-reassembler.js +12 -19
  18. package/dist/mfu-reassembler.js.map +2 -2
  19. package/dist/mmtp-container-parser.js +12 -19
  20. package/dist/mmtp-container-parser.js.map +2 -2
  21. package/dist/mmtp-container-parser.no-modules.js +133 -34
  22. package/dist/mmtp-container-parser.no-modules.js.map +3 -3
  23. package/dist/mmtp-decode-pipeline.js +12 -19
  24. package/dist/mmtp-decode-pipeline.js.map +2 -2
  25. package/dist/mmtp-fec-session.d.ts +41 -3
  26. package/dist/mmtp-fec-session.d.ts.map +1 -1
  27. package/dist/mmtp-fec-session.js +118 -13
  28. package/dist/mmtp-fec-session.js.map +3 -3
  29. package/dist/mmtp-router.js +12 -19
  30. package/dist/mmtp-router.js.map +2 -2
  31. package/dist/multicast-auth-catalog.js.map +1 -1
  32. package/dist/multicast-input.js.map +1 -1
  33. package/dist/multicast-manager.d.ts +2 -0
  34. package/dist/multicast-manager.d.ts.map +1 -1
  35. package/dist/multicast-manager.js +13 -19
  36. package/dist/multicast-manager.js.map +2 -2
  37. package/dist/multicast-transport.js.map +1 -1
  38. package/dist/multicast-unwrap.js +12 -19
  39. package/dist/multicast-unwrap.js.map +2 -2
  40. package/dist/no-modules.js +130 -33
  41. package/dist/no-modules.js.map +3 -3
  42. package/dist/repair-fetch.js.map +1 -1
  43. package/dist/repair-layer-subscriptions.d.ts +78 -0
  44. package/dist/repair-layer-subscriptions.d.ts.map +1 -0
  45. package/dist/repair-layer-subscriptions.js +189 -0
  46. package/dist/repair-layer-subscriptions.js.map +7 -0
  47. package/dist/signaling.js.map +2 -2
  48. package/dist/timestamp-codec.js +12 -19
  49. package/dist/timestamp-codec.js.map +2 -2
  50. package/dist/wasm-mfu-reassembler.js.map +1 -1
  51. package/dist/wasm-mmtp-container-parser.js +12 -19
  52. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  53. package/package.json +5 -5
  54. package/schemas/catalog.schema.json +4 -0
  55. package/src/index.ts +11 -0
  56. package/src/mmtp-fec-session.ts +115 -15
  57. package/src/multicast-manager.ts +3 -0
  58. package/src/repair-layer-subscriptions.ts +253 -0
@@ -489,6 +489,8 @@ var PACKET_TYPE_SIGNALING = 2;
489
489
  var PACKET_TYPE_REPAIR = 3;
490
490
  var MMTP_SIGNALING_PACKET_ID = 0;
491
491
  var MMTP_PACKET_ID_MAX = 65535;
492
+ var FEC_CODE_REED_SOLOMON = 1;
493
+ var FEC_CODE_RAPTORQ = 3;
492
494
  function parseMMTPHeader(buffer) {
493
495
  if (buffer.length < MMTP_HEADER_SIZE) {
494
496
  throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
@@ -655,30 +657,26 @@ function isEmptyOrExactLegacyTrailer(payload, start, end) {
655
657
  const authLength = view2.getUint16(2, false);
656
658
  return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
657
659
  }
658
- function resolveDeclaredTrailers(payload, declaredEnd, fecType, options, rapFlag = false) {
660
+ function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
659
661
  if (declaredEnd > payload.length)
660
662
  return null;
661
663
  if (options?.skipFecId) {
662
- return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false, hasOverlayFecId: false } : null;
664
+ return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
663
665
  }
664
- const overlay = options?.overlayDeclared === true && rapFlag;
665
666
  if (fecType === 1) {
666
- const fecStart = payload.length - sourceFecTrailerSize(overlay);
667
+ const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
667
668
  if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
668
669
  return null;
669
670
  }
670
- return { hasFecId: true, hasOverlayFecId: overlay };
671
+ return { hasFecId: true };
671
672
  }
672
- if (fecType !== 0 || overlay)
673
+ if (fecType !== 0)
673
674
  return null;
674
675
  if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
675
- return { hasFecId: false, hasOverlayFecId: false };
676
+ return { hasFecId: false };
676
677
  }
677
678
  return null;
678
679
  }
679
- function sourceFecTrailerSize(overlay) {
680
- return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
681
- }
682
680
  function extractNALUnit(mmtpPacket) {
683
681
  const extraction = extractMFUPayload(mmtpPacket);
684
682
  if (!extraction)
@@ -737,11 +735,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
737
735
  if ("rejection" in layoutResult)
738
736
  return layoutResult;
739
737
  const { layout } = layoutResult;
740
- const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options, header.rapFlag);
738
+ const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
741
739
  if (!trailerLayout) {
742
740
  if (!options?.skipFecId && header.fecType === 1) {
743
741
  const observed = payload.length - layout.dataEnd;
744
- const expected = sourceFecTrailerSize(options?.overlayDeclared === true && header.rapFlag);
742
+ const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
745
743
  if (observed !== expected) {
746
744
  return {
747
745
  rejection: {
@@ -776,13 +774,10 @@ function extractPlannedMfu(mmtpPacket, result) {
776
774
  const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
777
775
  const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
778
776
  const data = payload.subarray(mfuOffset, dataEnd);
779
- const trailerStart = mmtpPacket.length - sourceFecTrailerSize(trailerLayout.hasOverlayFecId);
780
- const ssIdAt = (offset) => ({
781
- ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + offset, 4).getUint32(0, false)
782
- });
783
- const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
784
- const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
785
- return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
777
+ const fecPayloadId = trailerLayout.hasFecId ? {
778
+ ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
779
+ } : null;
780
+ return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
786
781
  }
787
782
  function extractTimestamp(mmtpPacket) {
788
783
  const { header } = unwrapMMTP(mmtpPacket);
@@ -903,6 +898,50 @@ var NTP_SHORT_BITS = 32;
903
898
  var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
904
899
  var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
905
900
 
901
+ // ../mmt-base/dist/signalling.js
902
+ var FEC_CODING_STRUCTURE_ONE_STAGE = 1;
903
+ var SSBG_MODE_1 = 1;
904
+ var RAPTORQ_MAX_SOURCE_SYMBOLS = 56403;
905
+ var REPAIR_SYMBOL_ID_MAX = 16777215;
906
+ var MIN_REPAIR_SYMBOL_SIZE = 14;
907
+ var REED_SOLOMON_MAX_BLOCK = 255;
908
+ function alFecRaptorQFlowConfig(flow) {
909
+ if (flow.fecCodingStructure !== FEC_CODING_STRUCTURE_ONE_STAGE || flow.repairFlows.length !== 1)
910
+ return null;
911
+ if (flow.ssbgMode !== SSBG_MODE_1 || flow.ffsrptsFlag !== 0 || flow.fecPayloadIdMode !== 0)
912
+ return null;
913
+ if (flow.packetIds.length !== 1)
914
+ return null;
915
+ const repair = flow.repairFlows[0];
916
+ const { fecCodeId, maximumK: k, maximumP: p } = repair;
917
+ const t = flow.repairSymbolSize;
918
+ if (t < MIN_REPAIR_SYMBOL_SIZE || k < 1 || p < 1 || k + p - 1 > REPAIR_SYMBOL_ID_MAX)
919
+ return null;
920
+ if (fecCodeId === FEC_CODE_RAPTORQ) {
921
+ if (t % 8 !== 0 || k > RAPTORQ_MAX_SOURCE_SYMBOLS)
922
+ return null;
923
+ } else if (fecCodeId === FEC_CODE_REED_SOLOMON) {
924
+ if (k + p > REED_SOLOMON_MAX_BLOCK)
925
+ return null;
926
+ } else {
927
+ return null;
928
+ }
929
+ if (repair.repairFlowId === MMTP_SIGNALING_PACKET_ID || repair.repairFlowId === flow.packetIds[0])
930
+ return null;
931
+ return {
932
+ sourcePacketIds: [...flow.packetIds],
933
+ repairFlowId: repair.repairFlowId,
934
+ fecCodeId,
935
+ ssbgMode: flow.ssbgMode,
936
+ fecPayloadIdMode: flow.fecPayloadIdMode,
937
+ repairSymbolSize: t,
938
+ maximumK: k,
939
+ maximumP: p,
940
+ protectionWindowTime: repair.protectionWindowTime,
941
+ protectionWindowSize: repair.protectionWindowSize
942
+ };
943
+ }
944
+
906
945
  // ../mmt-base/dist/serial-number.js
907
946
  var SERIAL32_SPAN = 4294967296;
908
947
  var SERIAL32_HALF = 2147483648;
@@ -28678,6 +28717,7 @@ var MulticastManager = class {
28678
28717
  mode: endpoint.mode,
28679
28718
  amtRelay: endpoint.amtRelay,
28680
28719
  amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
28720
+ amtDiscovery: endpoint.amtDiscovery,
28681
28721
  packetIds: endpoint.packetIds,
28682
28722
  tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
28683
28723
  // A protected repair group's records ride in band on its port (7.2.3).
@@ -31453,12 +31493,12 @@ function moqtPriority2(value) {
31453
31493
  function catalogRepairTarget(catalog, source) {
31454
31494
  const binding = bindingOf(catalog, source);
31455
31495
  if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
31456
- const layerZero = binding.layers[0];
31496
+ const layerZero2 = binding.layers[0];
31457
31497
  const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
31458
31498
  const tracks = catalog.tracks;
31459
31499
  const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
31460
31500
  const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
31461
- return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
31501
+ return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
31462
31502
  }
31463
31503
  function numericField(value) {
31464
31504
  const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
@@ -31540,7 +31580,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
31540
31580
  var PATH_BITS = { moq: 1, multicast: 2 };
31541
31581
  var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
31542
31582
  var MmtpFecSession = class {
31583
+ /** P_0 = B_0, the held total every repair packet is checked against. */
31584
+ get repairSymbols() {
31585
+ return this.#repairSymbols;
31586
+ }
31543
31587
  #decoder;
31588
+ #repairSymbols;
31589
+ /** The message entry a catalog-less session was built from; undefined under a catalog. */
31590
+ #message;
31544
31591
  /** Blocks a repair has defined and that are not settled yet, by SS_Start. */
31545
31592
  #blocks = /* @__PURE__ */ new Map();
31546
31593
  /** SS_Starts of settled blocks, so a later repair for one is refused. */
@@ -31578,6 +31625,24 @@ var MmtpFecSession = class {
31578
31625
  * `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
31579
31626
  */
31580
31627
  constructor(config) {
31628
+ if (config && "alFecFlow" in config) {
31629
+ const message = alFecRaptorQFlowConfig(config.alFecFlow);
31630
+ if (!message) {
31631
+ throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
31632
+ }
31633
+ this.#message = message;
31634
+ this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
31635
+ this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
31636
+ this.sourceSymbols = message.maximumK;
31637
+ this.#repairSymbols = message.maximumP;
31638
+ this.symbolSize = message.repairSymbolSize;
31639
+ this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
31640
+ this.#repairPacketId = message.repairFlowId;
31641
+ this.#heldLayers = layerZero(message.maximumP);
31642
+ this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
31643
+ this.#decoder = buildDecoder(config.Decoder, this);
31644
+ return;
31645
+ }
31581
31646
  const fec = config?.fec;
31582
31647
  if (!fec || typeof fec !== "object") {
31583
31648
  throw new Error("MmtpFecSession: the catalog fec instance is required");
@@ -31588,7 +31653,7 @@ var MmtpFecSession = class {
31588
31653
  }
31589
31654
  this.algorithm = fec.scheme;
31590
31655
  this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
31591
- this.repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
31656
+ this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
31592
31657
  this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
31593
31658
  const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
31594
31659
  const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
@@ -31608,17 +31673,30 @@ var MmtpFecSession = class {
31608
31673
  config.heldLayers
31609
31674
  );
31610
31675
  this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
31611
- if (typeof config.Decoder !== "function") {
31612
- throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
31613
- }
31614
- const decoder = new config.Decoder(this.retentionBlocks, this.blockProfile, this.algorithm);
31615
- try {
31616
- decoder.configure_params(this.sourceSymbols, this.symbolSize);
31617
- } catch (error) {
31618
- decoder.free();
31619
- throw error;
31620
- }
31621
- this.#decoder = decoder;
31676
+ this.#decoder = buildDecoder(config.Decoder, this);
31677
+ }
31678
+ /**
31679
+ * A later version of the AL-FEC message, catalog-less sessions only. A
31680
+ * maximum_p_for_repair_flow above the held total raises it (`raised`): the
31681
+ * anchored check restarts at the new total, so a block generated under the
31682
+ * previous one stays valid before the first block carrying the new total
31683
+ * (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
31684
+ * (`unchanged`): no publisher lowers it, and the check catches a wire that
31685
+ * does. An entry with another asset, repair flow, code point, K or T, or
31686
+ * one outside 8.3, is `refused`: that is a new source encoding, which needs
31687
+ * a new session. A latched fail-closed stays latched either way.
31688
+ */
31689
+ updateAlFecFlow(flow) {
31690
+ const held = this.#message;
31691
+ if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
31692
+ const next = alFecRaptorQFlowConfig(flow);
31693
+ if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
31694
+ return "refused";
31695
+ }
31696
+ if (next.maximumP <= this.#repairSymbols) return "unchanged";
31697
+ this.#repairSymbols = next.maximumP;
31698
+ this.#heldLayers = layerZero(next.maximumP);
31699
+ return "raised";
31622
31700
  }
31623
31701
  /** True once release() ran. */
31624
31702
  get released() {
@@ -31713,7 +31791,7 @@ var MmtpFecSession = class {
31713
31791
  }
31714
31792
  if (admission.reject) {
31715
31793
  const verdict2 = this.#heldLayers.failsClosed(
31716
- repairTrackName,
31794
+ this.#trackFor(repairTrackName),
31717
31795
  frame.ssStart,
31718
31796
  frame.rsbLength,
31719
31797
  this.#anchorHorizon,
@@ -31728,7 +31806,7 @@ var MmtpFecSession = class {
31728
31806
  this.#repairPacketId ??= frame.packetId;
31729
31807
  if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
31730
31808
  const verdict = this.#heldLayers.admit(
31731
- repairTrackName,
31809
+ this.#trackFor(repairTrackName),
31732
31810
  frame.ssStart,
31733
31811
  frame.rsbLength,
31734
31812
  this.#anchorHorizon,
@@ -31787,6 +31865,10 @@ var MmtpFecSession = class {
31787
31865
  decoder.free();
31788
31866
  return true;
31789
31867
  }
31868
+ /** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
31869
+ #trackFor(repairTrackName) {
31870
+ return this.#message ? void 0 : repairTrackName;
31871
+ }
31790
31872
  #drop(reason) {
31791
31873
  this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
31792
31874
  return [];
@@ -31908,6 +31990,22 @@ var MmtpFecSession = class {
31908
31990
  }
31909
31991
  }
31910
31992
  };
31993
+ function layerZero(total) {
31994
+ return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
31995
+ }
31996
+ function buildDecoder(Decoder, session) {
31997
+ if (typeof Decoder !== "function") {
31998
+ throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
31999
+ }
32000
+ const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
32001
+ try {
32002
+ decoder.configure_params(session.sourceSymbols, session.symbolSize);
32003
+ } catch (error) {
32004
+ decoder.free();
32005
+ throw error;
32006
+ }
32007
+ return decoder;
32008
+ }
31911
32009
  function requirePositiveInteger(value, path) {
31912
32010
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
31913
32011
  throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
@@ -31921,6 +32019,165 @@ function requireNumber(value, path) {
31921
32019
  return value;
31922
32020
  }
31923
32021
 
32022
+ // src/repair-layer-subscriptions.ts
32023
+ var SERIAL32_HALF2 = 2147483648;
32024
+ var RepairLayerSubscriptions = class {
32025
+ #opts;
32026
+ #windowPackets;
32027
+ #margin;
32028
+ #dropAfterWindows;
32029
+ #source;
32030
+ #layerCount = 0;
32031
+ #subs = /* @__PURE__ */ new Map();
32032
+ #disposed = false;
32033
+ #lastSeq;
32034
+ #expected = 0;
32035
+ #lost = 0;
32036
+ #wantFewer = 0;
32037
+ #lossRate = null;
32038
+ #repairPackets = 0;
32039
+ #repairBytes = 0;
32040
+ #staleDropped = 0;
32041
+ constructor(opts) {
32042
+ this.#opts = opts;
32043
+ this.#source = opts.source;
32044
+ this.#windowPackets = opts.windowPackets ?? 500;
32045
+ this.#margin = opts.margin ?? 2;
32046
+ this.#dropAfterWindows = opts.dropAfterWindows ?? 3;
32047
+ }
32048
+ get layerCount() {
32049
+ return this.#layerCount;
32050
+ }
32051
+ /**
32052
+ * One source packet's MMTP `packet_sequence_number`, in arrival order.
32053
+ * The sequence is per `packet_id`: pass the packets of ONE packet_id only,
32054
+ * or interleaved sequences read as loss.
32055
+ */
32056
+ noteSourceSequence(seq) {
32057
+ if (this.#disposed || this.#opts.delivery === "stream") return;
32058
+ seq >>>= 0;
32059
+ if (this.#lastSeq === void 0) {
32060
+ this.#lastSeq = seq;
32061
+ return;
32062
+ }
32063
+ const d = serial32Distance(this.#lastSeq, seq);
32064
+ if (d === 0) return;
32065
+ const back = d >= SERIAL32_HALF2 ? 2 ** 32 - d : 0;
32066
+ if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
32067
+ this.#lastSeq = seq;
32068
+ this.#expected = 0;
32069
+ this.#lost = 0;
32070
+ return;
32071
+ }
32072
+ if (back === 0) {
32073
+ this.#expected += d;
32074
+ this.#lost += d - 1;
32075
+ this.#lastSeq = seq;
32076
+ } else if (this.#lost > 0) {
32077
+ this.#lost -= 1;
32078
+ }
32079
+ if (this.#expected >= this.#windowPackets) this.#closeWindow();
32080
+ }
32081
+ /** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
32082
+ switchSource(next) {
32083
+ if (this.#disposed) return;
32084
+ this.#closeAll();
32085
+ this.#source = next;
32086
+ this.#lastSeq = void 0;
32087
+ this.#expected = 0;
32088
+ this.#lost = 0;
32089
+ this.#wantFewer = 0;
32090
+ this.#layerCount = Math.min(this.#layerCount, this.#layers().length);
32091
+ this.#reconcile();
32092
+ }
32093
+ snapshot() {
32094
+ return {
32095
+ delivery: this.#opts.delivery,
32096
+ layerCount: this.#layerCount,
32097
+ subscribedTracks: [...this.#subs.keys()],
32098
+ repairPackets: this.#repairPackets,
32099
+ repairBytes: this.#repairBytes,
32100
+ staleDropped: this.#staleDropped,
32101
+ lossRate: this.#lossRate
32102
+ };
32103
+ }
32104
+ dispose() {
32105
+ if (this.#disposed) return;
32106
+ this.#closeAll();
32107
+ this.#disposed = true;
32108
+ }
32109
+ #layers() {
32110
+ return this.#source.repairLayers ?? [];
32111
+ }
32112
+ #closeWindow() {
32113
+ const rate = this.#lost / this.#expected;
32114
+ this.#lossRate = rate;
32115
+ this.#expected = 0;
32116
+ this.#lost = 0;
32117
+ const target = this.#targetLayers(rate);
32118
+ if (target > this.#layerCount) {
32119
+ this.#wantFewer = 0;
32120
+ this.#layerCount = target;
32121
+ } else if (target < this.#layerCount) {
32122
+ this.#wantFewer += 1;
32123
+ if (this.#wantFewer >= this.#dropAfterWindows) {
32124
+ this.#wantFewer = 0;
32125
+ this.#layerCount -= 1;
32126
+ }
32127
+ } else {
32128
+ this.#wantFewer = 0;
32129
+ }
32130
+ this.#reconcile();
32131
+ }
32132
+ /** Fewest base layers whose summed P covers margin x the measured loss of K. */
32133
+ #targetLayers(rate) {
32134
+ if (rate <= 0) return 0;
32135
+ const needed = Math.ceil(rate * this.#source.fecK * this.#margin);
32136
+ const layers = this.#layers();
32137
+ let held = 0;
32138
+ for (let n = 0; n < layers.length; n++) {
32139
+ held += layers[n].repairSymbols;
32140
+ if (held >= needed) return n + 1;
32141
+ }
32142
+ return layers.length;
32143
+ }
32144
+ /** Stream delivery never leaves layer count 0, so it never subscribes. */
32145
+ #reconcile() {
32146
+ const want = /* @__PURE__ */ new Map();
32147
+ for (const layer of this.#layers().slice(0, this.#layerCount)) {
32148
+ want.set(layer.repairTrackName, layer.priority);
32149
+ }
32150
+ const overlay = this.#source.repairOverlays?.[0];
32151
+ if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority);
32152
+ for (const [name, entry] of this.#subs) {
32153
+ if (!want.has(name)) {
32154
+ entry.sub.close();
32155
+ this.#subs.delete(name);
32156
+ }
32157
+ }
32158
+ for (const [name, priority] of want) {
32159
+ if (this.#subs.has(name)) continue;
32160
+ const entry = {};
32161
+ entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet));
32162
+ this.#subs.set(name, entry);
32163
+ }
32164
+ }
32165
+ /** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
32166
+ #onPacket(name, entry, packet) {
32167
+ if (this.#disposed || this.#subs.get(name) !== entry) {
32168
+ this.#staleDropped += 1;
32169
+ return;
32170
+ }
32171
+ this.#repairPackets += 1;
32172
+ this.#repairBytes += packet.byteLength;
32173
+ this.#opts.feed(packet, name);
32174
+ }
32175
+ #closeAll() {
32176
+ for (const entry of this.#subs.values()) entry.sub.close();
32177
+ this.#subs.clear();
32178
+ }
32179
+ };
32180
+
31924
32181
  // src/repair-fetch.ts
31925
32182
  var MOQT_PRIORITY_MAX = 255;
31926
32183
  var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
@@ -32566,6 +32823,7 @@ export {
32566
32823
  REPAIR_LIFECYCLE_LOG_PREFIX,
32567
32824
  REPAIR_PRIORITY_MAX,
32568
32825
  REPAIR_PRIORITY_MIN,
32826
+ RepairLayerSubscriptions,
32569
32827
  SOURCE_MEDIA_PRIORITY_MAX,
32570
32828
  SampleCombineError,
32571
32829
  SampleCombiner,