@blockcast/mmt-container 2.1.0 → 2.2.0-main.20c61ec37932

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/dist/browser-runtime.js +636 -77
  2. package/dist/browser-runtime.js.map +4 -4
  3. package/dist/carp-catalog.js.map +1 -1
  4. package/dist/catalog-fec-params.d.ts +16 -0
  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/catalog-network-source.js.map +1 -1
  9. package/dist/cmcd-producer.d.ts +5 -0
  10. package/dist/cmcd-producer.d.ts.map +1 -1
  11. package/dist/cmcd-producer.js.map +2 -2
  12. package/dist/delivery-catalog.js.map +1 -1
  13. package/dist/delta-fallback-config.js.map +1 -1
  14. package/dist/fec-manager.js.map +2 -2
  15. package/dist/fec-state.js.map +1 -1
  16. package/dist/fec-worker-setup.js.map +1 -1
  17. package/dist/held-layer-admission.js.map +1 -1
  18. package/dist/index.d.ts +3 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +6 -0
  21. package/dist/index.js.map +2 -2
  22. package/dist/iso-block-locator.js.map +1 -1
  23. package/dist/mfu-reassembler.js +12 -19
  24. package/dist/mfu-reassembler.js.map +2 -2
  25. package/dist/mmtp-container-parser.js +12 -19
  26. package/dist/mmtp-container-parser.js.map +2 -2
  27. package/dist/mmtp-container-parser.no-modules.js +133 -34
  28. package/dist/mmtp-container-parser.no-modules.js.map +3 -3
  29. package/dist/mmtp-decode-pipeline.js +12 -19
  30. package/dist/mmtp-decode-pipeline.js.map +2 -2
  31. package/dist/mmtp-fec-session.d.ts +41 -3
  32. package/dist/mmtp-fec-session.d.ts.map +1 -1
  33. package/dist/mmtp-fec-session.js +118 -13
  34. package/dist/mmtp-fec-session.js.map +3 -3
  35. package/dist/mmtp-router.d.ts +9 -1
  36. package/dist/mmtp-router.d.ts.map +1 -1
  37. package/dist/mmtp-router.js +1468 -33
  38. package/dist/mmtp-router.js.map +4 -4
  39. package/dist/multicast-auth-catalog.js.map +1 -1
  40. package/dist/multicast-input.js.map +1 -1
  41. package/dist/multicast-manager.d.ts +15 -2
  42. package/dist/multicast-manager.d.ts.map +1 -1
  43. package/dist/multicast-manager.js +15246 -14969
  44. package/dist/multicast-manager.js.map +4 -4
  45. package/dist/multicast-transport.d.ts +11 -3
  46. package/dist/multicast-transport.d.ts.map +1 -1
  47. package/dist/multicast-transport.js +255 -19
  48. package/dist/multicast-transport.js.map +4 -4
  49. package/dist/multicast-unwrap.js +12 -19
  50. package/dist/multicast-unwrap.js.map +2 -2
  51. package/dist/no-modules.js +130 -33
  52. package/dist/no-modules.js.map +3 -3
  53. package/dist/repair-fetch.js.map +1 -1
  54. package/dist/repair-layer-subscriptions.d.ts +82 -0
  55. package/dist/repair-layer-subscriptions.d.ts.map +1 -0
  56. package/dist/repair-layer-subscriptions.js +192 -0
  57. package/dist/repair-layer-subscriptions.js.map +7 -0
  58. package/dist/signaling.js.map +2 -2
  59. package/dist/timestamp-codec.js +12 -19
  60. package/dist/timestamp-codec.js.map +2 -2
  61. package/dist/wasm-mfu-reassembler.js.map +1 -1
  62. package/dist/wasm-mmtp-container-parser.js +12 -19
  63. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  64. package/package.json +5 -5
  65. package/schemas/catalog.schema.json +4 -0
  66. package/src/catalog-fec-params.ts +35 -0
  67. package/src/cmcd-producer.ts +5 -0
  68. package/src/index.ts +12 -0
  69. package/src/mmtp-fec-session.ts +115 -15
  70. package/src/mmtp-router.ts +81 -16
  71. package/src/multicast-manager.ts +26 -5
  72. package/src/multicast-transport.ts +44 -20
  73. package/src/repair-layer-subscriptions.ts +261 -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;
@@ -7457,11 +7496,33 @@ var AdaptiveBitrateController = class {
7457
7496
  };
7458
7497
 
7459
7498
  // node_modules/@blockcast/mmt-base/dist/mmtp.js
7499
+ var MMTP_HEADER_SIZE2 = 12;
7460
7500
  var MPU_HEADER_SIZE2 = 8;
7461
7501
  var MPU_LENGTH_FIELD_SIZE2 = 2;
7462
7502
  var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
7463
7503
  var MMTP_SIGNALING_PACKET_ID2 = 0;
7464
7504
  var MMTP_PACKET_ID_MAX2 = 65535;
7505
+ function readMMTPPacketId2(buffer) {
7506
+ if (buffer.length < MMTP_HEADER_SIZE2)
7507
+ return null;
7508
+ const flags = buffer[0];
7509
+ if ((flags >> 6 & 3) !== 0)
7510
+ return null;
7511
+ let offset = MMTP_HEADER_SIZE2;
7512
+ if ((flags & 32) !== 0) {
7513
+ if (offset + 4 > buffer.length)
7514
+ return null;
7515
+ offset += 4;
7516
+ }
7517
+ if ((flags & 2) !== 0) {
7518
+ if (offset + 4 > buffer.length)
7519
+ return null;
7520
+ const length = buffer[offset + 2] << 8 | buffer[offset + 3];
7521
+ if (offset + 4 + length > buffer.length)
7522
+ return null;
7523
+ }
7524
+ return buffer[2] << 8 | buffer[3];
7525
+ }
7465
7526
 
7466
7527
  // node_modules/@blockcast/mmt-base/dist/net-constants.js
7467
7528
  var DEFAULT_AMT_RELAY_PORT2 = 2268;
@@ -8447,6 +8508,205 @@ var TransportManager = class {
8447
8508
  };
8448
8509
  var transportManager = new TransportManager();
8449
8510
 
8511
+ // node_modules/@blockcast/mmt-transport/dist/multicast-receiver-gateway.js
8512
+ var REGISTRY = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/native-provider-registry/v1");
8513
+ var USER_AGENT_VERIFIED = /* @__PURE__ */ Symbol.for("@blockcast/mmt-transport/user-agent-verified/v1");
8514
+ var INTEGRITY_SCHEME = "manifest-ed25519-2";
8515
+ var MulticastError = class extends Error {
8516
+ code;
8517
+ constructor(code, message, cause) {
8518
+ super(message, cause === void 0 ? void 0 : { cause });
8519
+ this.name = "MulticastError";
8520
+ this.code = code;
8521
+ }
8522
+ };
8523
+ function datagramsOf(chunk) {
8524
+ const list = Array.isArray(chunk) ? chunk : chunk?.datagrams ?? [];
8525
+ const out = [];
8526
+ for (const d of list) {
8527
+ const data = d instanceof Uint8Array || d instanceof ArrayBuffer ? d : d?.data;
8528
+ if (data instanceof Uint8Array)
8529
+ out.push(data);
8530
+ else if (data instanceof ArrayBuffer)
8531
+ out.push(new Uint8Array(data));
8532
+ }
8533
+ return out;
8534
+ }
8535
+ function reasonOf(value) {
8536
+ return typeof value === "object" && value !== null && "reason" in value ? value.reason : value;
8537
+ }
8538
+ function registry() {
8539
+ const g = globalThis;
8540
+ if (g[REGISTRY] === void 0) {
8541
+ const providers = /* @__PURE__ */ new WeakSet();
8542
+ const byScope = /* @__PURE__ */ new WeakMap();
8543
+ Object.defineProperty(g, REGISTRY, {
8544
+ value: Object.freeze({
8545
+ isProvider: (x) => typeof x === "object" && x !== null && providers.has(x),
8546
+ providerFor: (scope) => {
8547
+ let provider = byScope.get(scope);
8548
+ if (provider === void 0) {
8549
+ provider = createProvider(scope);
8550
+ providers.add(provider);
8551
+ byScope.set(scope, provider);
8552
+ }
8553
+ return provider;
8554
+ }
8555
+ }),
8556
+ writable: false,
8557
+ configurable: false,
8558
+ enumerable: false
8559
+ });
8560
+ }
8561
+ return g[REGISTRY];
8562
+ }
8563
+ function resolveMulticastGateway(scope = globalThis) {
8564
+ const s = scope;
8565
+ if (typeof s.MulticastReceiver === "function")
8566
+ return registry().providerFor(scope);
8567
+ if (typeof s.multicast?.subscribe === "function")
8568
+ return s.multicast;
8569
+ return void 0;
8570
+ }
8571
+ function isUserAgentVerifiedGateway(gateway) {
8572
+ return registry().isProvider(gateway);
8573
+ }
8574
+ function userAgentVerifiedIngress(ingress, gateway) {
8575
+ if (isUserAgentVerifiedGateway(gateway))
8576
+ return { ...ingress, admittedBy: USER_AGENT_VERIFIED };
8577
+ if (!("admittedBy" in ingress))
8578
+ return ingress;
8579
+ const { admittedBy: _, ...rest } = ingress;
8580
+ return rest;
8581
+ }
8582
+ function unsupported(message, cause) {
8583
+ return new MulticastError("capability-unsupported", message, cause);
8584
+ }
8585
+ function createProvider(scope) {
8586
+ const Receiver = scope.MulticastReceiver;
8587
+ let gate = null;
8588
+ return {
8589
+ capabilities: () => Promise.resolve({ consumeModes: ["bytes"] }),
8590
+ subscribe: async (config) => {
8591
+ const c = config;
8592
+ if (c.consume !== "bytes")
8593
+ throw unsupported(`consume mode ${String(c.consume)} is not supported`);
8594
+ const origin = c.origin ?? "ip-multicast";
8595
+ if (origin !== "ip-multicast" && !(origin === "amt" && c.discovery === "driad")) {
8596
+ throw unsupported(`origin ${String(origin)} is not supported by MulticastReceiver`);
8597
+ }
8598
+ if (c.relay !== void 0)
8599
+ throw unsupported("MulticastReceiver picks its own AMT relay");
8600
+ if (!c.source)
8601
+ throw unsupported("MulticastReceiver needs an SSM source");
8602
+ if (c.senderAddress !== void 0 && c.senderAddress !== c.source) {
8603
+ throw unsupported("MulticastReceiver filters by source only");
8604
+ }
8605
+ const options = { sourceAddress: c.source, groupAddress: c.group, localPort: c.port };
8606
+ if (gate !== null)
8607
+ await gate;
8608
+ let rx;
8609
+ try {
8610
+ rx = new Receiver(options);
8611
+ } catch (error) {
8612
+ throw unsupported("MulticastReceiver refused the request", error);
8613
+ }
8614
+ const opened = rx.opened;
8615
+ if (gate === null)
8616
+ gate = opened.then(() => void 0, () => void 0);
8617
+ return openSubscription(rx, opened, options, c.packetIds);
8618
+ }
8619
+ };
8620
+ }
8621
+ async function openSubscription(rx, openedPromise, options, packetIds) {
8622
+ let opened;
8623
+ try {
8624
+ opened = await openedPromise;
8625
+ } catch (error) {
8626
+ throw new MulticastError("subscription-failed", "MulticastReceiver did not open", error);
8627
+ }
8628
+ if (rx.integrityScheme !== INTEGRITY_SCHEME) {
8629
+ await rx.close();
8630
+ throw unsupported(`integrity scheme ${String(rx.integrityScheme)} is not ${INTEGRITY_SCHEME}`);
8631
+ }
8632
+ const events = new EventTarget();
8633
+ let state = "connected";
8634
+ let filter = packetIds?.length ? new Set(packetIds) : null;
8635
+ let closing = false;
8636
+ const setState = (next) => {
8637
+ if (state === next)
8638
+ return;
8639
+ state = next;
8640
+ events.dispatchEvent(new CustomEvent("state", { detail: next }));
8641
+ };
8642
+ const sourceAddress = options.sourceAddress;
8643
+ const subscription = {
8644
+ id: `${options.sourceAddress}|${options.groupAddress}|${options.localPort}`,
8645
+ get state() {
8646
+ return state;
8647
+ },
8648
+ delivery: "datagram",
8649
+ eventTypes: Object.freeze(["bytes", "packets", "state", "error"]),
8650
+ setPacketIds(ids) {
8651
+ filter = ids.length ? new Set(ids) : null;
8652
+ },
8653
+ addEventListener: events.addEventListener.bind(events),
8654
+ removeEventListener: events.removeEventListener.bind(events),
8655
+ async unsubscribe() {
8656
+ closing = true;
8657
+ await rx.close();
8658
+ setState("closed");
8659
+ }
8660
+ };
8661
+ void (async () => {
8662
+ const reader = opened.readable.getReader();
8663
+ try {
8664
+ for (; ; ) {
8665
+ const { value, done } = await reader.read();
8666
+ if (done)
8667
+ break;
8668
+ const packets = [];
8669
+ for (const data of datagramsOf(value)) {
8670
+ if (filter !== null) {
8671
+ const id = readMMTPPacketId2(data);
8672
+ if (id === null || !filter.has(id))
8673
+ continue;
8674
+ }
8675
+ const copy = data.slice().buffer;
8676
+ packets.push(copy);
8677
+ events.dispatchEvent(Object.assign(new CustomEvent("bytes", { detail: copy }), { sourceAddress }));
8678
+ }
8679
+ if (packets.length > 0)
8680
+ events.dispatchEvent(new CustomEvent("packets", { detail: packets }));
8681
+ }
8682
+ } catch {
8683
+ }
8684
+ })();
8685
+ void rx.closed.then((value) => {
8686
+ const reason = reasonOf(value);
8687
+ if (!closing) {
8688
+ if (reason === "consent-withdrawn") {
8689
+ events.dispatchEvent(new CustomEvent("error", {
8690
+ detail: new MulticastError("origin-not-trusted", "multicast consent withdrawn")
8691
+ }));
8692
+ } else if (reason === "oversubscribed" || reason === "network") {
8693
+ events.dispatchEvent(new CustomEvent("error", {
8694
+ detail: new MulticastError("subscription-failed", `MulticastReceiver closed: ${reason}`)
8695
+ }));
8696
+ }
8697
+ }
8698
+ setState("closed");
8699
+ }, (error) => {
8700
+ if (!closing) {
8701
+ events.dispatchEvent(new CustomEvent("error", {
8702
+ detail: new MulticastError("subscription-failed", "MulticastReceiver failed", error)
8703
+ }));
8704
+ }
8705
+ setState("closed");
8706
+ });
8707
+ return subscription;
8708
+ }
8709
+
8450
8710
  // node_modules/@blockcast/mmt-transport/dist/moqt-priority.js
8451
8711
  var MAX_MOQT_PRIORITY = 255;
8452
8712
  function moqtPriority(value, field) {
@@ -8733,9 +8993,9 @@ function validateTemplatePair(mediaUriTemplate, mediaObjectTemplate, path) {
8733
8993
  }
8734
8994
  function validateMmtpDashTemplate(template, path) {
8735
8995
  const variables = [...template.matchAll(TEMPLATE_VARIABLE)].map((match) => match[1]);
8736
- const unsupported = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
8737
- if (unsupported !== void 0) {
8738
- throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported}$`);
8996
+ const unsupported2 = variables.find((variable) => variable !== "Number" && variable !== "RepresentationID");
8997
+ if (unsupported2 !== void 0) {
8998
+ throw new Error(`${path} contains unsupported MMTP-backed DASH template variable $${unsupported2}$`);
8739
8999
  }
8740
9000
  if (variables.filter((variable) => variable === "Number").length !== 1) {
8741
9001
  throw new Error(`${path} must contain exactly one $Number$ for MMTP-backed DASH`);
@@ -17098,17 +17358,17 @@ var FECManager = class _FECManager {
17098
17358
 
17099
17359
  // src/mfu-reassembly-policy.ts
17100
17360
  function validateReliableMoqOptionShape(value) {
17101
- const unsupported = Object.keys(value).filter(
17361
+ const unsupported2 = Object.keys(value).filter(
17102
17362
  (key) => key !== "transport" && key !== "startsAtRandomAccessGroup"
17103
17363
  );
17104
- if (unsupported.length > 0) {
17105
- if (unsupported.includes("maxRacingGroups")) {
17364
+ if (unsupported2.length > 0) {
17365
+ if (unsupported2.includes("maxRacingGroups")) {
17106
17366
  throw new Error(
17107
17367
  "[MFU reassembly] maxRacingGroups was removed; reliable MoQ completion is transport-owned"
17108
17368
  );
17109
17369
  }
17110
17370
  throw new Error(
17111
- `[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported.join(", ")}`
17371
+ `[MFU reassembly] unsupported reliable MoQ option(s): ${unsupported2.join(", ")}`
17112
17372
  );
17113
17373
  }
17114
17374
  const startsAtRandomAccessGroup = value["startsAtRandomAccessGroup"];
@@ -17152,12 +17412,12 @@ function assertValidMfuReassemblyPolicy(policy) {
17152
17412
  return;
17153
17413
  }
17154
17414
  if (value["transport"] === "datagram") {
17155
- const unsupported = Object.keys(value).filter(
17415
+ const unsupported2 = Object.keys(value).filter(
17156
17416
  (key) => key !== "transport" && key !== "deadlineMs"
17157
17417
  );
17158
- if (unsupported.length > 0) {
17418
+ if (unsupported2.length > 0) {
17159
17419
  throw new Error(
17160
- `[MFU reassembly] unsupported datagram option(s): ${unsupported.join(", ")}`
17420
+ `[MFU reassembly] unsupported datagram option(s): ${unsupported2.join(", ")}`
17161
17421
  );
17162
17422
  }
17163
17423
  assertValidDatagramDeadline(value["deadlineMs"]);
@@ -23579,10 +23839,11 @@ var MmtpRouter = class {
23579
23839
  if (!entry) return;
23580
23840
  entry.receiver.release();
23581
23841
  entry.protected = false;
23842
+ entry.userAgent = false;
23582
23843
  this.#assistBlocks.clear();
23583
23844
  this.#assistFailures.clear();
23584
23845
  }
23585
- /** MoQ packets of a track a protected flow lists, dropped on its unicast path: duplicate, overflow, or listed by more than one flow. */
23846
+ /** Packets of a track a protected flow lists, dropped unread: a MoQ copy (duplicate, overflow, or listed by more than one flow) or a multicast source datagram that names no flow, of a track a user-agent-admitted flow lists. */
23586
23847
  get unadmittedListedDropCount() {
23587
23848
  return this.#unadmittedListedDrops;
23588
23849
  }
@@ -23623,6 +23884,13 @@ var MmtpRouter = class {
23623
23884
  }
23624
23885
  return false;
23625
23886
  }
23887
+ /** Whether a protected flow the user agent admitted lists `packetId`. */
23888
+ #userAgentGoverns(packetId) {
23889
+ for (const entry of this.#provenance.values()) {
23890
+ if (entry.protected && entry.userAgent && ingressHasPacketId(entry.ingress, packetId)) return true;
23891
+ }
23892
+ return false;
23893
+ }
23626
23894
  /**
23627
23895
  * A MoQ repair symbol no protected flow lists, owned by a source track one
23628
23896
  * does: its FEC instance is governed (7.2.7 step 2) and takes only admitted
@@ -23713,7 +23981,7 @@ var MmtpRouter = class {
23713
23981
  fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
23714
23982
  fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
23715
23983
  fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
23716
- })) {
23984
+ }, true)) {
23717
23985
  this.#unlistedAssistedRecoveries++;
23718
23986
  this.#assistBlocks.delete(key);
23719
23987
  return;
@@ -24869,6 +25137,12 @@ var MmtpRouter = class {
24869
25137
  debugRouter(`[MmtpRouter] Dropping multicast packet pid=${info.packetId}: flow ${ingress.flow} has no receiver`);
24870
25138
  return;
24871
25139
  }
25140
+ if (ingress.admittedBy === USER_AGENT_VERIFIED) {
25141
+ this.#noteFlowAdmitted(ingress.flow);
25142
+ protectedFlow.userAgent = true;
25143
+ this.#processMMTP(packet, source, true, ingress, overlapCopy);
25144
+ return;
25145
+ }
24872
25146
  const { verdict } = protectedFlow.receiver.onDatagram(packet, "multicast");
24873
25147
  if (verdict === "admitted") {
24874
25148
  this.#noteFlowAdmitted(ingress.flow);
@@ -24886,6 +25160,11 @@ var MmtpRouter = class {
24886
25160
  return;
24887
25161
  }
24888
25162
  }
25163
+ const flowless = source === "multicast" && !manifestAdmitted && ingress?.flow === void 0;
25164
+ if (flowless && !info.isRepair && this.#userAgentGoverns(info.packetId)) {
25165
+ this.#unadmittedListedDrops++;
25166
+ return;
25167
+ }
24889
25168
  if (info.isRepair) {
24890
25169
  const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
24891
25170
  if (overlay !== void 0) {
@@ -24903,7 +25182,7 @@ var MmtpRouter = class {
24903
25182
  );
24904
25183
  if (!repair) return;
24905
25184
  if (ownerPacketId != null && this.#fecManager) {
24906
- if (source === "moq" && !manifestAdmitted && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
25185
+ if ((flowless && this.#userAgentGoverns(ownerPacketId) || source === "moq" && !manifestAdmitted) && this.#setAsideUnlistedRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair)) return;
24907
25186
  this.#repairPidCounts[info.packetId] = (this.#repairPidCounts[info.packetId] || 0) + 1;
24908
25187
  const count = this.#repairPidCounts[info.packetId];
24909
25188
  if (count <= 2 || count % 500 === 0) {
@@ -25482,7 +25761,7 @@ var MmtpRouter = class {
25482
25761
  }
25483
25762
  // ── Private: FEC recovery routing ───────────────────────────────
25484
25763
  /** Route WASM-recovered block through appropriate path based on container kind. */
25485
- #routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
25764
+ #routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete, assisted = false) {
25486
25765
  const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
25487
25766
  if (ownerPacketId === void 0) {
25488
25767
  throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
@@ -25499,7 +25778,7 @@ var MmtpRouter = class {
25499
25778
  ownerPacketId,
25500
25779
  blockProfile
25501
25780
  });
25502
- return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
25781
+ return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted);
25503
25782
  }
25504
25783
  /**
25505
25784
  * A recovered source packet of a protected flow is admitted on its own
@@ -25511,9 +25790,24 @@ var MmtpRouter = class {
25511
25790
  * recovered packets is refused is unrecovered (7.2.7): its caller leaves it
25512
25791
  * open, so it reaches its FEC deadline and fails by class. Always true
25513
25792
  * outside a protected flow.
25514
- */
25515
- #routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete) {
25516
- const receiver = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
25793
+ *
25794
+ * On a flow the user agent admitted, the live decoder's recoveries are kept
25795
+ * without a record: while the flow is protected that decoder takes only
25796
+ * symbols the user agent or a record verified (a flow-less multicast
25797
+ * datagram of a listed track is dropped or set aside in #processMMTP), so
25798
+ * what it recovers is as verified as they are. An `assisted` (scratch-decoder) recovery mixes in
25799
+ * unlisted MoQ repair and still needs a record.
25800
+ * ponytail: per-flow flag; per-block provenance only if a protected flow
25801
+ * ever mixes verified and unverified live inputs.
25802
+ * ponytail: a worker frame computed before the protection reset can still
25803
+ * arrive after it (frames carry no epoch); its inputs are MoQ copies read
25804
+ * and played before protection, so it admits nothing new. Drop pre-reset
25805
+ * frames by epoch only if pre-protection input ever matters.
25806
+ */
25807
+ #routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete, assisted = false) {
25808
+ const entry = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
25809
+ const receiver = entry === void 0 || entry === "ambiguous" ? entry : entry.receiver;
25810
+ const userAgent = !assisted && entry !== void 0 && entry !== "ambiguous" && entry.protected === true && entry.userAgent === true;
25517
25811
  let recovered = true;
25518
25812
  if (receiver) {
25519
25813
  const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
@@ -25525,6 +25819,8 @@ var MmtpRouter = class {
25525
25819
  let admitted = false;
25526
25820
  if (receiver === "ambiguous") {
25527
25821
  this.#ambiguousRecoveredDrops++;
25822
+ } else if (userAgent) {
25823
+ admitted = true;
25528
25824
  } else {
25529
25825
  const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
25530
25826
  wire.set(sym.rawSymbol);
@@ -25552,12 +25848,12 @@ var MmtpRouter = class {
25552
25848
  catalogTrackName === void 0 || !byName ? byName?.values() : byName.has(catalogTrackName) ? [byName.get(catalogTrackName)] : []
25553
25849
  );
25554
25850
  if (flows.size > 1) return "ambiguous";
25555
- for (const bound of flows) return this.#provenance.get(bound)?.receiver ?? "ambiguous";
25851
+ for (const bound of flows) return this.#provenance.get(bound) ?? "ambiguous";
25556
25852
  let match;
25557
- for (const { receiver, ingress } of this.#provenance.values()) {
25558
- if (ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
25853
+ for (const entry of this.#provenance.values()) {
25854
+ if (entry.ingress.tracks.some((t) => t.packetId === packetId && (catalogTrackName === void 0 || t.name === catalogTrackName))) {
25559
25855
  if (match) return "ambiguous";
25560
- match = receiver;
25856
+ match = entry;
25561
25857
  }
25562
25858
  }
25563
25859
  return match;
@@ -26339,11 +26635,8 @@ function providerErrorOf(error) {
26339
26635
  return Object.freeze(typeof code === "string" ? { name, message, code } : { name, message });
26340
26636
  }
26341
26637
  var MAX_TRACKED_DROPPED_PIDS = 16;
26342
- function getMulticastGlobals() {
26343
- return globalThis;
26344
- }
26345
26638
  function hasMulticastGateway() {
26346
- return typeof getMulticastGlobals().multicast?.subscribe === "function";
26639
+ return typeof resolveMulticastGateway()?.subscribe === "function";
26347
26640
  }
26348
26641
  function senderOf(event) {
26349
26642
  const sourceAddress = event.sourceAddress;
@@ -26435,6 +26728,9 @@ var MulticastTransport = class {
26435
26728
  // listener closure, so an in-place admission change (setContext) applies
26436
26729
  // from the next packet on without rejoining.
26437
26730
  #context = void 0;
26731
+ #deliveredContext = void 0;
26732
+ /** The live join's gateway, when it is the user agent's MulticastReceiver provider. */
26733
+ #userAgentGateway = void 0;
26438
26734
  // The live join's provider-side filter. An in-place widening must reach it,
26439
26735
  // or the provider never delivers the new ID; null without a live join.
26440
26736
  #providerFilter = null;
@@ -26471,7 +26767,7 @@ var MulticastTransport = class {
26471
26767
  this.#dataPlaneLive = false;
26472
26768
  this.#sender = void 0;
26473
26769
  if (this.#disposed) return this.#declined(generation, "disposed");
26474
- const gateway = this.#gateway ?? getMulticastGlobals().multicast;
26770
+ const gateway = this.#gateway ?? resolveMulticastGateway();
26475
26771
  if (typeof gateway?.subscribe !== "function") {
26476
26772
  console.warn("[MulticastTransport] Multicast API not available");
26477
26773
  return this.#declined(generation, "unavailable");
@@ -26487,15 +26783,16 @@ var MulticastTransport = class {
26487
26783
  console.log(
26488
26784
  `[MulticastTransport] window.multicast.subscribe(${config.sourceAddress ?? config.senderAddress ?? "*"}, ${config.groupAddress}, ${config.port})`
26489
26785
  );
26786
+ const userAgent = isUserAgentVerifiedGateway(gateway);
26490
26787
  const subscribeConfig = toWindowMulticastSubscribeConfig({
26491
26788
  source: config.sourceAddress,
26492
26789
  senderAddress: config.senderAddress,
26493
26790
  group: config.groupAddress,
26494
26791
  port: config.port,
26495
- mode: config.mode,
26496
- amtRelay: config.amtRelay,
26497
- amtRelayPort: config.amtRelayPort,
26498
- amtDiscovery: config.amtDiscovery,
26792
+ mode: userAgent ? "native" : config.mode,
26793
+ amtRelay: userAgent ? void 0 : config.amtRelay,
26794
+ amtRelayPort: userAgent ? void 0 : config.amtRelayPort,
26795
+ amtDiscovery: userAgent ? void 0 : config.amtDiscovery,
26499
26796
  packetIds: config.packetIds,
26500
26797
  tracks: config.tracks,
26501
26798
  atsc3: config.atsc3
@@ -26541,7 +26838,8 @@ var MulticastTransport = class {
26541
26838
  acknowledgedUpdate: 0,
26542
26839
  inFlight: 0
26543
26840
  };
26544
- this.#context = context;
26841
+ this.#userAgentGateway = userAgent ? gateway : void 0;
26842
+ this.#setContext(context);
26545
26843
  if (packetIds) {
26546
26844
  for (const packetId of packetIds) {
26547
26845
  if (!this.#acceptedBytesByPacketId.has(packetId)) {
@@ -26549,7 +26847,7 @@ var MulticastTransport = class {
26549
26847
  }
26550
26848
  }
26551
26849
  }
26552
- this.#resolvedTransport = subscribeConfig.origin === "amt" ? "amt" : "native";
26850
+ this.#resolvedTransport = userAgent ? "user-agent" : subscribeConfig.origin === "amt" ? "amt" : "native";
26553
26851
  const delivery = subscription.delivery ?? "datagram";
26554
26852
  if (delivery !== "datagram" && delivery !== "ordered-relay") {
26555
26853
  throw new Error(
@@ -26630,7 +26928,7 @@ var MulticastTransport = class {
26630
26928
  }
26631
26929
  if (!this.#acceptPacket(packet, sourceAddress)) return false;
26632
26930
  const liveness = !this.#isRecordCandidate(packet);
26633
- this.#onPacket(packet, this.#context);
26931
+ this.#onPacket(packet, this.#deliveredContext);
26634
26932
  return liveness;
26635
26933
  }
26636
26934
  #isRecordCandidate(packet) {
@@ -26841,8 +27139,10 @@ var MulticastTransport = class {
26841
27139
  return this.#subscription !== null;
26842
27140
  }
26843
27141
  /**
26844
- * What the live join delivers with every accepted packet (the join's
26845
- * `context`, or the last setContext), or undefined without a live join.
27142
+ * The live join's context as the caller gave it (the join's `context`, or
27143
+ * the last setContext), or undefined without a live join. On a native
27144
+ * provider each accepted packet is delivered with a marked copy of it
27145
+ * (`userAgentVerifiedIngress`); this getter never carries the mark.
26846
27146
  */
26847
27147
  get context() {
26848
27148
  return this.#context;
@@ -26866,7 +27166,16 @@ var MulticastTransport = class {
26866
27166
  "[MulticastTransport] setContext needs a live join; join() carries the first context"
26867
27167
  );
26868
27168
  }
27169
+ this.#setContext(context);
27170
+ }
27171
+ /**
27172
+ * The join's context, and what the subscription delivers with each packet:
27173
+ * marked admitted by the user agent when the gateway is its native
27174
+ * MulticastReceiver provider. feedPacket never marks.
27175
+ */
27176
+ #setContext(context) {
26869
27177
  this.#context = context;
27178
+ this.#deliveredContext = this.#userAgentGateway !== void 0 && typeof context === "object" && context !== null ? userAgentVerifiedIngress(context, this.#userAgentGateway) : context;
26870
27179
  }
26871
27180
  /**
26872
27181
  * Replace the live join's packet-ID allow-list in place, without rejoining.
@@ -26998,6 +27307,8 @@ var MulticastTransport = class {
26998
27307
  this.#matchedSource = void 0;
26999
27308
  this.#providerFilter = null;
27000
27309
  this.#context = void 0;
27310
+ this.#deliveredContext = void 0;
27311
+ this.#userAgentGateway = void 0;
27001
27312
  this.#withdrawnPacketIds.clear();
27002
27313
  }
27003
27314
  /** The ingress filter; an accepted packet is counted here, once. */
@@ -28350,8 +28661,17 @@ var MulticastManager = class {
28350
28661
  * repair roles and owners are registered here; in-band owners stay the
28351
28662
  * caller's. Joining is best effort: a group that fails to join is logged
28352
28663
  * and leaves the media join as it was.
28353
- */
28354
- joinRepairEndpoints(endpoints, provider) {
28664
+ *
28665
+ * `options.gateway` names the gateway a subscribe callback reads through.
28666
+ * When it is the browser's own MulticastReceiver provider, the callback's
28667
+ * packets are fed as admitted by the user agent
28668
+ * (`userAgentVerifiedIngress`); nothing the callback passes can mark them.
28669
+ * The mark rests on this statement, not on the data path: every packet the
28670
+ * callback delivers must come from a subscription opened on
28671
+ * `options.gateway`. A callback that also merges another source (a MoQ or
28672
+ * WebSocket repair feed) must not name a native provider here.
28673
+ */
28674
+ joinRepairEndpoints(endpoints, provider, options) {
28355
28675
  const releases = [];
28356
28676
  let released = false;
28357
28677
  const safely = (each) => {
@@ -28373,17 +28693,19 @@ var MulticastManager = class {
28373
28693
  ...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
28374
28694
  };
28375
28695
  let sender;
28376
- const onPacket = (packet, sourceAddress) => {
28696
+ const feed = (packet, sourceAddress, ingressToFeed) => {
28377
28697
  if (released) return;
28378
28698
  if (sourceAddress !== void 0 && !sameDatagramSource(sourceAddress, this.#transport.sender ?? (sender ??= sourceAddress))) return;
28379
- this.#router.feedPacket(packet, "multicast", ingress);
28699
+ this.#router.feedPacket(packet, "multicast", ingressToFeed);
28380
28700
  };
28381
28701
  const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
28382
28702
  if (typeof provider === "function") {
28703
+ const marked = userAgentVerifiedIngress(ingress, options?.gateway);
28704
+ const onPacket = (packet, sourceAddress) => feed(packet, sourceAddress, marked);
28383
28705
  Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
28384
28706
  continue;
28385
28707
  }
28386
- const transport = new MulticastTransport((packet) => onPacket(packet), {
28708
+ const transport = new MulticastTransport((packet, delivered) => feed(packet, void 0, delivered ?? ingress), {
28387
28709
  gateway: provider,
28388
28710
  lockedTo: () => this.#transport.sender
28389
28711
  });
@@ -28395,11 +28717,12 @@ var MulticastManager = class {
28395
28717
  mode: endpoint.mode,
28396
28718
  amtRelay: endpoint.amtRelay,
28397
28719
  amtRelayPort: endpoint.amtRelayPort ?? (endpoint.amtRelay?.trim() ? DEFAULT_AMT_RELAY_PORT : void 0),
28720
+ amtDiscovery: endpoint.amtDiscovery,
28398
28721
  packetIds: endpoint.packetIds,
28399
28722
  tracks: endpoint.tracks.map(({ name, packetId }) => ({ name, packetId })),
28400
28723
  // A protected repair group's records ride in band on its port (7.2.3).
28401
28724
  ...endpoint.flow === void 0 ? {} : { protected: true }
28402
- }).then((joined) => {
28725
+ }, ingress).then((joined) => {
28403
28726
  if (!joined && !released) fail(transport.lastJoinOutcome);
28404
28727
  });
28405
28728
  }
@@ -31164,18 +31487,31 @@ function resolveRepairLayers(catalog, source) {
31164
31487
  totalBaseRepairSymbols: binding.totalBaseRepairSymbols
31165
31488
  };
31166
31489
  }
31490
+ function valueAt(catalog, pointer) {
31491
+ let node = catalog;
31492
+ for (const segment of pointer.split("/").slice(1)) {
31493
+ if (typeof node !== "object" || node === null) return void 0;
31494
+ node = node[segment.replace(/~1/g, "/").replace(/~0/g, "~")];
31495
+ }
31496
+ return node;
31497
+ }
31498
+ function catalogTrackDeliveryMode(catalog, source) {
31499
+ const binding = bindingOf(catalog, source);
31500
+ const entry = binding ? valueAt(catalog, binding.pointer) : void 0;
31501
+ return entry?.deliveryMode === "stream" ? "stream" : "datagram";
31502
+ }
31167
31503
  function moqtPriority2(value) {
31168
31504
  return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
31169
31505
  }
31170
31506
  function catalogRepairTarget(catalog, source) {
31171
31507
  const binding = bindingOf(catalog, source);
31172
31508
  if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
31173
- const layerZero = binding.layers[0];
31509
+ const layerZero2 = binding.layers[0];
31174
31510
  const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
31175
31511
  const tracks = catalog.tracks;
31176
31512
  const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
31177
31513
  const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
31178
- return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
31514
+ return { repairTrack: layerZero2.repairTrackName, sourcePriority, repairPriority: layerZero2.priority };
31179
31515
  }
31180
31516
  function numericField(value) {
31181
31517
  const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
@@ -31207,6 +31543,7 @@ function fecParams(catalog, track, fps, sampleRate) {
31207
31543
  repairTrackName: base.repairTrack,
31208
31544
  repairLayers: repair.baseLayers,
31209
31545
  repairOverlays: repair.overlays,
31546
+ deliveryMode: catalogTrackDeliveryMode(catalog, track),
31210
31547
  ...mediaRate
31211
31548
  };
31212
31549
  }
@@ -31257,7 +31594,14 @@ function resolveCatalogTrackFec(catalog, tracks) {
31257
31594
  var PATH_BITS = { moq: 1, multicast: 2 };
31258
31595
  var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
31259
31596
  var MmtpFecSession = class {
31597
+ /** P_0 = B_0, the held total every repair packet is checked against. */
31598
+ get repairSymbols() {
31599
+ return this.#repairSymbols;
31600
+ }
31260
31601
  #decoder;
31602
+ #repairSymbols;
31603
+ /** The message entry a catalog-less session was built from; undefined under a catalog. */
31604
+ #message;
31261
31605
  /** Blocks a repair has defined and that are not settled yet, by SS_Start. */
31262
31606
  #blocks = /* @__PURE__ */ new Map();
31263
31607
  /** SS_Starts of settled blocks, so a later repair for one is refused. */
@@ -31295,6 +31639,24 @@ var MmtpFecSession = class {
31295
31639
  * `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
31296
31640
  */
31297
31641
  constructor(config) {
31642
+ if (config && "alFecFlow" in config) {
31643
+ const message = alFecRaptorQFlowConfig(config.alFecFlow);
31644
+ if (!message) {
31645
+ throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
31646
+ }
31647
+ this.#message = message;
31648
+ this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
31649
+ this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
31650
+ this.sourceSymbols = message.maximumK;
31651
+ this.#repairSymbols = message.maximumP;
31652
+ this.symbolSize = message.repairSymbolSize;
31653
+ this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
31654
+ this.#repairPacketId = message.repairFlowId;
31655
+ this.#heldLayers = layerZero(message.maximumP);
31656
+ this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
31657
+ this.#decoder = buildDecoder(config.Decoder, this);
31658
+ return;
31659
+ }
31298
31660
  const fec = config?.fec;
31299
31661
  if (!fec || typeof fec !== "object") {
31300
31662
  throw new Error("MmtpFecSession: the catalog fec instance is required");
@@ -31305,7 +31667,7 @@ var MmtpFecSession = class {
31305
31667
  }
31306
31668
  this.algorithm = fec.scheme;
31307
31669
  this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
31308
- this.repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
31670
+ this.#repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
31309
31671
  this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
31310
31672
  const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
31311
31673
  const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
@@ -31325,17 +31687,30 @@ var MmtpFecSession = class {
31325
31687
  config.heldLayers
31326
31688
  );
31327
31689
  this.#anchorHorizon = heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols);
31328
- if (typeof config.Decoder !== "function") {
31329
- throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
31330
- }
31331
- const decoder = new config.Decoder(this.retentionBlocks, this.blockProfile, this.algorithm);
31332
- try {
31333
- decoder.configure_params(this.sourceSymbols, this.symbolSize);
31334
- } catch (error) {
31335
- decoder.free();
31336
- throw error;
31337
- }
31338
- this.#decoder = decoder;
31690
+ this.#decoder = buildDecoder(config.Decoder, this);
31691
+ }
31692
+ /**
31693
+ * A later version of the AL-FEC message, catalog-less sessions only. A
31694
+ * maximum_p_for_repair_flow above the held total raises it (`raised`): the
31695
+ * anchored check restarts at the new total, so a block generated under the
31696
+ * previous one stays valid before the first block carrying the new total
31697
+ * (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
31698
+ * (`unchanged`): no publisher lowers it, and the check catches a wire that
31699
+ * does. An entry with another asset, repair flow, code point, K or T, or
31700
+ * one outside 8.3, is `refused`: that is a new source encoding, which needs
31701
+ * a new session. A latched fail-closed stays latched either way.
31702
+ */
31703
+ updateAlFecFlow(flow) {
31704
+ const held = this.#message;
31705
+ if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
31706
+ const next = alFecRaptorQFlowConfig(flow);
31707
+ if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
31708
+ return "refused";
31709
+ }
31710
+ if (next.maximumP <= this.#repairSymbols) return "unchanged";
31711
+ this.#repairSymbols = next.maximumP;
31712
+ this.#heldLayers = layerZero(next.maximumP);
31713
+ return "raised";
31339
31714
  }
31340
31715
  /** True once release() ran. */
31341
31716
  get released() {
@@ -31430,7 +31805,7 @@ var MmtpFecSession = class {
31430
31805
  }
31431
31806
  if (admission.reject) {
31432
31807
  const verdict2 = this.#heldLayers.failsClosed(
31433
- repairTrackName,
31808
+ this.#trackFor(repairTrackName),
31434
31809
  frame.ssStart,
31435
31810
  frame.rsbLength,
31436
31811
  this.#anchorHorizon,
@@ -31445,7 +31820,7 @@ var MmtpFecSession = class {
31445
31820
  this.#repairPacketId ??= frame.packetId;
31446
31821
  if (frame.ssbLength > this.sourceSymbols) return this.#drop("repair:ssb-length");
31447
31822
  const verdict = this.#heldLayers.admit(
31448
- repairTrackName,
31823
+ this.#trackFor(repairTrackName),
31449
31824
  frame.ssStart,
31450
31825
  frame.rsbLength,
31451
31826
  this.#anchorHorizon,
@@ -31504,6 +31879,10 @@ var MmtpFecSession = class {
31504
31879
  decoder.free();
31505
31880
  return true;
31506
31881
  }
31882
+ /** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
31883
+ #trackFor(repairTrackName) {
31884
+ return this.#message ? void 0 : repairTrackName;
31885
+ }
31507
31886
  #drop(reason) {
31508
31887
  this.#stats.dropped[reason] = (this.#stats.dropped[reason] ?? 0) + 1;
31509
31888
  return [];
@@ -31625,6 +32004,22 @@ var MmtpFecSession = class {
31625
32004
  }
31626
32005
  }
31627
32006
  };
32007
+ function layerZero(total) {
32008
+ return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
32009
+ }
32010
+ function buildDecoder(Decoder, session) {
32011
+ if (typeof Decoder !== "function") {
32012
+ throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
32013
+ }
32014
+ const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
32015
+ try {
32016
+ decoder.configure_params(session.sourceSymbols, session.symbolSize);
32017
+ } catch (error) {
32018
+ decoder.free();
32019
+ throw error;
32020
+ }
32021
+ return decoder;
32022
+ }
31628
32023
  function requirePositiveInteger(value, path) {
31629
32024
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
31630
32025
  throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
@@ -31638,6 +32033,168 @@ function requireNumber(value, path) {
31638
32033
  return value;
31639
32034
  }
31640
32035
 
32036
+ // src/repair-layer-subscriptions.ts
32037
+ var SERIAL32_HALF2 = 2147483648;
32038
+ var RepairLayerSubscriptions = class {
32039
+ #opts;
32040
+ #windowPackets;
32041
+ #margin;
32042
+ #dropAfterWindows;
32043
+ #source;
32044
+ #layerCount = 0;
32045
+ #subs = /* @__PURE__ */ new Map();
32046
+ #disposed = false;
32047
+ #lastSeq;
32048
+ #expected = 0;
32049
+ #lost = 0;
32050
+ #wantFewer = 0;
32051
+ #lossRate = null;
32052
+ #repairPackets = 0;
32053
+ #repairBytes = 0;
32054
+ #staleDropped = 0;
32055
+ constructor(opts) {
32056
+ this.#opts = opts;
32057
+ this.#source = opts.source;
32058
+ this.#windowPackets = opts.windowPackets ?? 500;
32059
+ this.#margin = opts.margin ?? 2;
32060
+ this.#dropAfterWindows = opts.dropAfterWindows ?? 3;
32061
+ }
32062
+ get layerCount() {
32063
+ return this.#layerCount;
32064
+ }
32065
+ /**
32066
+ * One source packet's MMTP `packet_sequence_number`, in arrival order.
32067
+ * The sequence is per `packet_id`: pass the packets of ONE packet_id only,
32068
+ * or interleaved sequences read as loss.
32069
+ */
32070
+ noteSourceSequence(seq) {
32071
+ if (this.#disposed || this.#delivery() === "stream") return;
32072
+ seq >>>= 0;
32073
+ if (this.#lastSeq === void 0) {
32074
+ this.#lastSeq = seq;
32075
+ return;
32076
+ }
32077
+ const d = serial32Distance(this.#lastSeq, seq);
32078
+ if (d === 0) return;
32079
+ const back = d >= SERIAL32_HALF2 ? 2 ** 32 - d : 0;
32080
+ if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
32081
+ this.#lastSeq = seq;
32082
+ this.#expected = 0;
32083
+ this.#lost = 0;
32084
+ return;
32085
+ }
32086
+ if (back === 0) {
32087
+ this.#expected += d;
32088
+ this.#lost += d - 1;
32089
+ this.#lastSeq = seq;
32090
+ } else if (this.#lost > 0) {
32091
+ this.#lost -= 1;
32092
+ }
32093
+ if (this.#expected >= this.#windowPackets) this.#closeWindow();
32094
+ }
32095
+ /** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
32096
+ switchSource(next) {
32097
+ if (this.#disposed) return;
32098
+ this.#closeAll();
32099
+ this.#source = next;
32100
+ this.#lastSeq = void 0;
32101
+ this.#expected = 0;
32102
+ this.#lost = 0;
32103
+ this.#wantFewer = 0;
32104
+ this.#layerCount = this.#delivery() === "stream" ? 0 : Math.min(this.#layerCount, this.#layers().length);
32105
+ this.#reconcile();
32106
+ }
32107
+ snapshot() {
32108
+ return {
32109
+ delivery: this.#delivery(),
32110
+ layerCount: this.#layerCount,
32111
+ subscribedTracks: [...this.#subs.keys()],
32112
+ repairPackets: this.#repairPackets,
32113
+ repairBytes: this.#repairBytes,
32114
+ staleDropped: this.#staleDropped,
32115
+ lossRate: this.#lossRate
32116
+ };
32117
+ }
32118
+ dispose() {
32119
+ if (this.#disposed) return;
32120
+ this.#closeAll();
32121
+ this.#disposed = true;
32122
+ }
32123
+ #delivery() {
32124
+ return this.#source.deliveryMode === "stream" ? "stream" : "datagram";
32125
+ }
32126
+ #layers() {
32127
+ return this.#source.repairLayers ?? [];
32128
+ }
32129
+ #closeWindow() {
32130
+ const rate = this.#lost / this.#expected;
32131
+ this.#lossRate = rate;
32132
+ this.#expected = 0;
32133
+ this.#lost = 0;
32134
+ const target = this.#targetLayers(rate);
32135
+ if (target > this.#layerCount) {
32136
+ this.#wantFewer = 0;
32137
+ this.#layerCount = target;
32138
+ } else if (target < this.#layerCount) {
32139
+ this.#wantFewer += 1;
32140
+ if (this.#wantFewer >= this.#dropAfterWindows) {
32141
+ this.#wantFewer = 0;
32142
+ this.#layerCount -= 1;
32143
+ }
32144
+ } else {
32145
+ this.#wantFewer = 0;
32146
+ }
32147
+ this.#reconcile();
32148
+ }
32149
+ /** Fewest base layers whose summed P covers margin x the measured loss of K. */
32150
+ #targetLayers(rate) {
32151
+ if (rate <= 0) return 0;
32152
+ const needed = Math.ceil(rate * this.#source.fecK * this.#margin);
32153
+ const layers = this.#layers();
32154
+ let held = 0;
32155
+ for (let n = 0; n < layers.length; n++) {
32156
+ held += layers[n].repairSymbols;
32157
+ if (held >= needed) return n + 1;
32158
+ }
32159
+ return layers.length;
32160
+ }
32161
+ /** Stream delivery never leaves layer count 0, so it never subscribes. */
32162
+ #reconcile() {
32163
+ const want = /* @__PURE__ */ new Map();
32164
+ for (const layer of this.#layers().slice(0, this.#layerCount)) {
32165
+ want.set(layer.repairTrackName, layer.priority);
32166
+ }
32167
+ const overlay = this.#source.repairOverlays?.[0];
32168
+ if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority);
32169
+ for (const [name, entry] of this.#subs) {
32170
+ if (!want.has(name)) {
32171
+ entry.sub.close();
32172
+ this.#subs.delete(name);
32173
+ }
32174
+ }
32175
+ for (const [name, priority] of want) {
32176
+ if (this.#subs.has(name)) continue;
32177
+ const entry = {};
32178
+ entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet));
32179
+ this.#subs.set(name, entry);
32180
+ }
32181
+ }
32182
+ /** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
32183
+ #onPacket(name, entry, packet) {
32184
+ if (this.#disposed || this.#subs.get(name) !== entry) {
32185
+ this.#staleDropped += 1;
32186
+ return;
32187
+ }
32188
+ this.#repairPackets += 1;
32189
+ this.#repairBytes += packet.byteLength;
32190
+ this.#opts.feed(packet, name);
32191
+ }
32192
+ #closeAll() {
32193
+ for (const entry of this.#subs.values()) entry.sub.close();
32194
+ this.#subs.clear();
32195
+ }
32196
+ };
32197
+
31641
32198
  // src/repair-fetch.ts
31642
32199
  var MOQT_PRIORITY_MAX = 255;
31643
32200
  var SOURCE_MEDIA_PRIORITY_MAX = REPAIR_PRIORITY_MIN - 1;
@@ -32283,6 +32840,7 @@ export {
32283
32840
  REPAIR_LIFECYCLE_LOG_PREFIX,
32284
32841
  REPAIR_PRIORITY_MAX,
32285
32842
  REPAIR_PRIORITY_MIN,
32843
+ RepairLayerSubscriptions,
32286
32844
  SOURCE_MEDIA_PRIORITY_MAX,
32287
32845
  SampleCombineError,
32288
32846
  SampleCombiner,
@@ -32307,6 +32865,7 @@ export {
32307
32865
  catalogGroupDurationMs,
32308
32866
  catalogKeyframeCadenceMs,
32309
32867
  catalogRepairTarget,
32868
+ catalogTrackDeliveryMode,
32310
32869
  catalogTrackFecUnavailable,
32311
32870
  classifyH264Keyframe,
32312
32871
  classifyHevcKeyframe,