@blockcast/mmt-container 1.0.0-main.ac0d9896a088 → 1.0.0

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 (54) hide show
  1. package/README.md +20 -15
  2. package/dist/browser-runtime.js +119 -98
  3. package/dist/browser-runtime.js.map +3 -3
  4. package/dist/carp-catalog.d.ts +33 -87
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +0 -17
  7. package/dist/carp-catalog.js.map +2 -2
  8. package/dist/catalog-fec-params.js +1 -1
  9. package/dist/catalog-fec-params.js.map +2 -2
  10. package/dist/catalog-network-source.js.map +1 -1
  11. package/dist/delivery-catalog.js.map +1 -1
  12. package/dist/delta-fallback-config.d.ts +21 -26
  13. package/dist/delta-fallback-config.d.ts.map +1 -1
  14. package/dist/delta-fallback-config.js +582 -24
  15. package/dist/delta-fallback-config.js.map +4 -4
  16. package/dist/fec-manager.js.map +1 -1
  17. package/dist/fec-state.js +1 -1
  18. package/dist/fec-state.js.map +2 -2
  19. package/dist/fec-worker-setup.js.map +1 -1
  20. package/dist/held-layer-admission.js.map +1 -1
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +0 -4
  24. package/dist/index.js.map +2 -2
  25. package/dist/iso-block-locator.js.map +1 -1
  26. package/dist/mfu-reassembler.js.map +1 -1
  27. package/dist/mmtp-container-parser.js.map +1 -1
  28. package/dist/mmtp-container-parser.no-modules.js +1 -1
  29. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  30. package/dist/mmtp-decode-pipeline.js.map +1 -1
  31. package/dist/mmtp-fec-session.js.map +1 -1
  32. package/dist/mmtp-router.js.map +1 -1
  33. package/dist/multicast-auth-catalog.js.map +1 -1
  34. package/dist/multicast-input.js.map +1 -1
  35. package/dist/multicast-manager.js +1 -1
  36. package/dist/multicast-manager.js.map +2 -2
  37. package/dist/multicast-transport.js.map +1 -1
  38. package/dist/multicast-unwrap.js.map +1 -1
  39. package/dist/no-modules.js +1 -1
  40. package/dist/no-modules.js.map +2 -2
  41. package/dist/phase14-catalog-override.d.ts +10 -5
  42. package/dist/phase14-catalog-override.d.ts.map +1 -1
  43. package/dist/phase14-catalog-override.js +35 -11
  44. package/dist/phase14-catalog-override.js.map +2 -2
  45. package/dist/repair-fetch.js.map +1 -1
  46. package/dist/signaling.js.map +1 -1
  47. package/dist/timestamp-codec.js.map +1 -1
  48. package/dist/wasm-mfu-reassembler.js.map +1 -1
  49. package/dist/wasm-mmtp-container-parser.js.map +1 -1
  50. package/package.json +5 -5
  51. package/src/carp-catalog.ts +34 -109
  52. package/src/delta-fallback-config.ts +63 -50
  53. package/src/index.ts +1 -4
  54. package/src/phase14-catalog-override.ts +67 -16
package/README.md CHANGED
@@ -177,20 +177,26 @@ if (!validateCatalog(catalog)) {
177
177
  Implements `draft-law-ramadan-moq-carp-reliability` §3 (FEC), §4 (multicast endpoints), and `draft-ramadan-moq-mmt-01` §4.4.2 (track timing). Plain TypeScript interfaces with type guards — no Zod, no runtime schema dependency. Upstream Zod schemas should derive from these via `z.infer<>`.
178
178
 
179
179
  ```ts
180
- type FecAlgorithm = "none" | "raptorq" | "reed-solomon"
181
-
182
- interface FecCatalogConfig {
183
- algorithm: FecAlgorithm // §3.1
184
- blockProfile?: "iso-ssbg1-v1" // REQUIRED unless algorithm is "none"; never defaulted (BLO-35597)
185
- sourceSymbols: number // K (REQUIRED): K_max, the largest SSB_length of any block
186
- repairSymbols: number // P (REQUIRED)
187
- symbolSize: number // T, 8-byte aligned (REQUIRED)
188
- interleaveDepthMs?: number // FEC block span in MILLISECONDS, default 0 (single-group blocks)
189
- repairTrack: string // §6.1 — required
190
- mode?: "interleaved" | "object" // extension; "object" (per-frame object blocks) is retired: @blockcast/mmt-fec refuses it
191
- uep?: { keyRatio: number; deltaRatio: number } // Unequal Error Protection — Blockcast extension, not in draft-ramadan-moq-fec-01
192
- deltaFailWindowBlocks?: number // D-11, base track only
193
- deltaProbeIntervalMs?: number // D-13, base track only
180
+ // draft-ramadan-moq-fec-catalog-00 3: a track's `fec` is an array of instance
181
+ // objects; an empty array or no `fec` declares no FEC. Usability is
182
+ // validateFecCatalog's per-instance answer; runtime readers take it from
183
+ // resolveFecBindings (both in @blockcast/mmt-base, re-exported here).
184
+ type FecCatalogConfig = readonly FecInstanceConfig[]
185
+
186
+ interface FecInstanceConfig {
187
+ instanceId?: number // 0..255, default 0, unique per track
188
+ scheme: string // "raptorq" | "reed-solomon" | other token (= no FEC here)
189
+ blockProfile: string // REQUIRED, never defaulted; this library implements "iso-ssbg1-v1"
190
+ sourceSymbols: number // K
191
+ repairSymbols: number // P of layer 0, >= 1
192
+ symbolSize: number // T
193
+ interleaveDepthMs?: number
194
+ reorderToleranceMs?: number
195
+ repairTrack: string // layer 0; other layers/overlay are fec-repair tracks (source + instanceId)
196
+ symbolTrack?: string
197
+ label?: string // display only
198
+ deltaFailWindowBlocks?: number // Phase-14 extension key (D-11), ignored by the validator
199
+ deltaProbeIntervalMs?: number // Phase-14 extension key (D-13), ignored by the validator
194
200
  }
195
201
 
196
202
  interface MulticastEndpoint {
@@ -214,7 +220,6 @@ interface TrackTimingConfig {
214
220
  groupDurationTicks?: number // override when ms × timescale isn't integer
215
221
  }
216
222
 
217
- function isFecCatalogConfig(x: unknown): x is FecCatalogConfig
218
223
  function isMulticastCatalogConfig(x: unknown): x is MulticastCatalogConfig
219
224
  function resolveGroupDurationTicks(timing: TrackTimingConfig, timescale: number): number
220
225
  function validateSwitchingSetTiming(...): { ok: true } | { ok: false; ... }
@@ -1022,22 +1022,22 @@ function authIssues(auth) {
1022
1022
  const firstEpochs = /* @__PURE__ */ new Set();
1023
1023
  for (let index = 0; index < keys.length; index++) {
1024
1024
  const key = keys[index];
1025
- const at = `.keys[${index}]`;
1025
+ const at2 = `.keys[${index}]`;
1026
1026
  if (!isObject(key) || Object.keys(key).some((member) => !KEY_MEMBERS.has(member))) {
1027
- return issue(at, "a key is exactly {kid, publicKey, firstEpoch}");
1027
+ return issue(at2, "a key is exactly {kid, publicKey, firstEpoch}");
1028
1028
  }
1029
1029
  if (!isIntegerIn(key["kid"], 0, UINT32_MAX))
1030
- return issue(`${at}.kid`, "kid must be an unsigned 32-bit integer");
1030
+ return issue(`${at2}.kid`, "kid must be an unsigned 32-bit integer");
1031
1031
  if (typeof key["publicKey"] !== "string" || !ED25519_PUBLIC_KEY_BASE64URL.test(key["publicKey"])) {
1032
- return issue(`${at}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
1032
+ return issue(`${at2}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
1033
1033
  }
1034
1034
  if (!isIntegerIn(key["firstEpoch"], 0, UINT32_MAX)) {
1035
- return issue(`${at}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
1035
+ return issue(`${at2}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
1036
1036
  }
1037
1037
  if (kids.has(key["kid"]))
1038
- return issue(`${at}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
1038
+ return issue(`${at2}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
1039
1039
  if (firstEpochs.has(key["firstEpoch"])) {
1040
- return issue(`${at}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
1040
+ return issue(`${at2}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
1041
1041
  }
1042
1042
  kids.add(key["kid"]);
1043
1043
  firstEpochs.add(key["firstEpoch"]);
@@ -1047,36 +1047,36 @@ function authIssues(auth) {
1047
1047
  }
1048
1048
  return [];
1049
1049
  }
1050
- function endpointAuthIssue(endpoint, mode, at) {
1050
+ function endpointAuthIssue(endpoint, mode, at2) {
1051
1051
  const auth = endpoint["auth"];
1052
1052
  if (!isObject(auth))
1053
- return { path: `${at}.auth`, message: "auth must be an object" };
1053
+ return { path: `${at2}.auth`, message: "auth must be an object" };
1054
1054
  const unknown = Object.keys(auth).filter((key) => !ENDPOINT_AUTH_MEMBERS.has(key));
1055
1055
  if (unknown.length > 0)
1056
- return { path: `${at}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
1056
+ return { path: `${at2}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
1057
1057
  if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
1058
- return { path: `${at}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
1058
+ return { path: `${at2}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
1059
1059
  }
1060
1060
  if (!isIntegerIn(auth["streamId"], 0, UINT32_MAX)) {
1061
- return { path: `${at}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
1061
+ return { path: `${at2}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
1062
1062
  }
1063
1063
  if (mode === "ssm" && auth["senderAddress"] !== void 0) {
1064
- return { path: `${at}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
1064
+ return { path: `${at2}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
1065
1065
  }
1066
1066
  if (mode === "asm" && !isNonEmptyString(auth["senderAddress"])) {
1067
- return { path: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
1067
+ return { path: `${at2}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
1068
1068
  }
1069
1069
  if (auth["maxAuthDelayMs"] !== void 0 && !isIntegerIn(auth["maxAuthDelayMs"], 50, 1e3)) {
1070
- return { path: `${at}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
1070
+ return { path: `${at2}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
1071
1071
  }
1072
1072
  if (auth["manifestTimerMs"] !== void 0 && !isIntegerIn(auth["manifestTimerMs"], 1, Number.MAX_SAFE_INTEGER)) {
1073
- return { path: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
1073
+ return { path: `${at2}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
1074
1074
  }
1075
1075
  if (auth["manifestTrack"] !== void 0 && !isNonEmptyString(auth["manifestTrack"])) {
1076
- return { path: `${at}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
1076
+ return { path: `${at2}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
1077
1077
  }
1078
1078
  if (!isIntegerIn(endpoint["bandwidth"], 1, Number.MAX_SAFE_INTEGER)) {
1079
- return { path: `${at}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
1079
+ return { path: `${at2}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
1080
1080
  }
1081
1081
  return void 0;
1082
1082
  }
@@ -1112,41 +1112,41 @@ function multicastCatalogIssues(catalog, options = {}) {
1112
1112
  const sources = [];
1113
1113
  for (let index = 0; index < endpoints.length; index++) {
1114
1114
  const endpoint = endpoints[index];
1115
- const at = `$.multicast.endpoints[${index}]`;
1115
+ const at2 = `$.multicast.endpoints[${index}]`;
1116
1116
  const fail = (code, path, message) => {
1117
1117
  issues.push({ code, path, message, endpointIndex: index });
1118
1118
  };
1119
1119
  sources.push(void 0);
1120
1120
  if (!isObject(endpoint)) {
1121
- fail("multicast_endpoint", at, "endpoint must be an object");
1121
+ fail("multicast_endpoint", at2, "endpoint must be an object");
1122
1122
  continue;
1123
1123
  }
1124
1124
  const protocol = endpoint["protocol"];
1125
1125
  if (protocol !== void 0 && protocol !== "ssm" && protocol !== "asm") {
1126
- fail("multicast_endpoint", `${at}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -01 4.2)`);
1126
+ fail("multicast_endpoint", `${at2}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -01 4.2)`);
1127
1127
  continue;
1128
1128
  }
1129
1129
  const hasAuth = endpoint["auth"] !== void 0;
1130
1130
  if (protectedCatalog !== hasAuth) {
1131
- fail("multicast_endpoint_auth", `${at}.auth`, protectedCatalog ? "every endpoint requires auth when multicast carries auth; a receiver ignores this endpoint" : "auth names no key without multicast.auth; a receiver ignores this endpoint");
1131
+ fail("multicast_endpoint_auth", `${at2}.auth`, protectedCatalog ? "every endpoint requires auth when multicast carries auth; a receiver ignores this endpoint" : "auth names no key without multicast.auth; a receiver ignores this endpoint");
1132
1132
  continue;
1133
1133
  }
1134
1134
  const sourceAddress = endpoint["sourceAddress"];
1135
1135
  if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress)) {
1136
- fail("multicast_endpoint", `${at}.sourceAddress`, "sourceAddress must be a non-empty string");
1136
+ fail("multicast_endpoint", `${at2}.sourceAddress`, "sourceAddress must be a non-empty string");
1137
1137
  continue;
1138
1138
  }
1139
1139
  const mode = sourceAddress === void 0 ? "asm" : "ssm";
1140
1140
  if (protocol !== void 0 && protocol !== mode) {
1141
- fail("multicast_endpoint", `${at}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
1141
+ fail("multicast_endpoint", `${at2}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
1142
1142
  continue;
1143
1143
  }
1144
1144
  if (!isIntegerIn(endpoint["port"], 1, 65535)) {
1145
- fail("multicast_endpoint", `${at}.port`, "port must be an integer from 1 to 65535");
1145
+ fail("multicast_endpoint", `${at2}.port`, "port must be an integer from 1 to 65535");
1146
1146
  continue;
1147
1147
  }
1148
1148
  if (hasAuth) {
1149
- const authIssue = endpointAuthIssue(endpoint, mode, at);
1149
+ const authIssue = endpointAuthIssue(endpoint, mode, at2);
1150
1150
  if (authIssue) {
1151
1151
  fail("multicast_endpoint_auth", authIssue.path, authIssue.message);
1152
1152
  continue;
@@ -1164,7 +1164,7 @@ function multicastCatalogIssues(catalog, options = {}) {
1164
1164
  continue;
1165
1165
  referenceNamespace ??= namespace;
1166
1166
  if (namespace !== referenceNamespace) {
1167
- fail("multicast_track", `${at}.tracks[${refIndex}].name`, `track ${JSON.stringify(name)} declares namespace ${JSON.stringify(namespace)}, not the catalog track's ${JSON.stringify(referenceNamespace)}`);
1167
+ fail("multicast_track", `${at2}.tracks[${refIndex}].name`, `track ${JSON.stringify(name)} declares namespace ${JSON.stringify(namespace)}, not the catalog track's ${JSON.stringify(referenceNamespace)}`);
1168
1168
  }
1169
1169
  }
1170
1170
  }
@@ -1181,9 +1181,9 @@ function multicastCatalogIssues(catalog, options = {}) {
1181
1181
  if (!sources[index])
1182
1182
  continue;
1183
1183
  const own = tupleOf(index);
1184
- const at = `$.multicast.endpoints[${index}]`;
1184
+ const at2 = `$.multicast.endpoints[${index}]`;
1185
1185
  if (own.manifestPort !== void 0 && own.manifestPort === own.port) {
1186
- issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
1186
+ issues.push({ code: "multicast_endpoint_auth", path: `${at2}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
1187
1187
  continue;
1188
1188
  }
1189
1189
  for (let other = 0; other < endpoints.length; other++) {
@@ -1193,11 +1193,11 @@ function multicastCatalogIssues(catalog, options = {}) {
1193
1193
  if (peer.sg !== own.sg)
1194
1194
  continue;
1195
1195
  if (peer.port === own.port) {
1196
- issues.push({ code: "multicast_endpoint", path: `${at}.port`, message: `(S, G, port) is shared with endpoint ${other}; one endpoint object per multicast tuple`, endpointIndex: index });
1196
+ issues.push({ code: "multicast_endpoint", path: `${at2}.port`, message: `(S, G, port) is shared with endpoint ${other}; one endpoint object per multicast tuple`, endpointIndex: index });
1197
1197
  break;
1198
1198
  }
1199
1199
  if (own.manifestPort !== void 0 && (own.manifestPort === peer.port || own.manifestPort === peer.manifestPort)) {
1200
- issues.push({ code: "multicast_endpoint_auth", path: `${at}.auth.manifestPort`, message: `manifestPort is a port or manifestPort of endpoint ${other} on the same (S, G)`, endpointIndex: index });
1200
+ issues.push({ code: "multicast_endpoint_auth", path: `${at2}.auth.manifestPort`, message: `manifestPort is a port or manifestPort of endpoint ${other} on the same (S, G)`, endpointIndex: index });
1201
1201
  break;
1202
1202
  }
1203
1203
  }
@@ -1593,7 +1593,7 @@ function fecCatalogTracks(catalog) {
1593
1593
  continue;
1594
1594
  hangNames.add(name);
1595
1595
  const kind = recordOf(entry["container"])?.["kind"];
1596
- const view2 = { name, packaging: kind ?? "mmtp" };
1596
+ const view2 = { name, packaging: kind === void 0 || kind === "mmtp-fec" ? "mmtp" : kind };
1597
1597
  if (entry["fec"] !== void 0)
1598
1598
  view2["fec"] = entry["fec"];
1599
1599
  tracks.push(view2);
@@ -1636,7 +1636,7 @@ function fecCatalogTracks(catalog) {
1636
1636
  function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
1637
1637
  const { tracks, pointers } = fecCatalogTracks(catalog);
1638
1638
  const report = validateFecCatalog({ tracks }, receiver);
1639
- const at = (path) => {
1639
+ const at2 = (path) => {
1640
1640
  const m = /^\/tracks\/(\d+)(.*)$/.exec(path);
1641
1641
  return m ? pointers[Number(m[1])] + m[2] : path;
1642
1642
  };
@@ -1644,7 +1644,7 @@ function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
1644
1644
  const nameAt = (i) => i === null ? void 0 : trackAt(i)["name"];
1645
1645
  const why = (i) => Object.freeze({
1646
1646
  code: i.issues[0]?.code ?? "fec_member",
1647
- path: at(i.issues[0]?.path ?? i.path),
1647
+ path: at2(i.issues[0]?.path ?? i.path),
1648
1648
  outcome: i.outcome === "unavailable" ? "unavailable" : "violation"
1649
1649
  });
1650
1650
  const out = /* @__PURE__ */ new Map();
@@ -1659,7 +1659,7 @@ function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
1659
1659
  if (fec === void 0)
1660
1660
  return;
1661
1661
  if (!Array.isArray(fec)) {
1662
- const reason = { code: "fec_member", path: at(`/tracks/${ti}/fec`), outcome: "violation" };
1662
+ const reason = { code: "fec_member", path: at2(`/tracks/${ti}/fec`), outcome: "violation" };
1663
1663
  out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason: Object.freeze(reason) }));
1664
1664
  return;
1665
1665
  }
@@ -1673,7 +1673,7 @@ function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
1673
1673
  const reason = (
1674
1674
  // No base instance at all (overlay-only `fec`) is a count of 0, not
1675
1675
  // the overlay's own issue.
1676
- okBases.length > 1 || bases.length === 0 ? Object.freeze({ code: "fec_base_instances", path: at(`/tracks/${ti}/fec`), outcome: "violation" }) : why(bases[0] ?? insts[0])
1676
+ okBases.length > 1 || bases.length === 0 ? Object.freeze({ code: "fec_base_instances", path: at2(`/tracks/${ti}/fec`), outcome: "violation" }) : why(bases[0] ?? insts[0])
1677
1677
  );
1678
1678
  out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason }));
1679
1679
  return;
@@ -6777,18 +6777,6 @@ function hasSwitchingSetRole(names) {
6777
6777
  return suffix === "base" || suffix === "delta";
6778
6778
  });
6779
6779
  }
6780
- function isFecCatalogGeometry(obj) {
6781
- if (typeof obj !== "object" || obj === null) return false;
6782
- const o = obj;
6783
- const algorithm = o["algorithm"];
6784
- const symbolSize = o["symbolSize"];
6785
- return typeof algorithm === "string" && algorithm.length > 0 && typeof o["sourceSymbols"] === "number" && o["sourceSymbols"] >= 1 && typeof o["repairSymbols"] === "number" && o["repairSymbols"] >= 1 && typeof symbolSize === "number" && symbolSize >= 1 && (algorithm !== "raptorq" || symbolSize % 8 === 0) && typeof o["repairTrack"] === "string" && o["repairTrack"].length > 0;
6786
- }
6787
- function isFecCatalogConfig(obj) {
6788
- if (!isFecCatalogGeometry(obj)) return false;
6789
- const o = obj;
6790
- return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
6791
- }
6792
6780
  function isMulticastTrackObject(obj) {
6793
6781
  if (typeof obj !== "object" || obj === null) return false;
6794
6782
  const o = obj;
@@ -9280,6 +9268,7 @@ function getPhase14CatalogOverride() {
9280
9268
  function mergeOverrideIntoHangRenditions(catalog, override) {
9281
9269
  if (!override) return catalog;
9282
9270
  for (const track of override.tracks) {
9271
+ if (track.packaging === "fec-repair") continue;
9283
9272
  const role = track.role === "audio" || track.name.startsWith("audio") ? "audio" : "video";
9284
9273
  const bagKey = role === "audio" ? "audio" : "video";
9285
9274
  const bag = catalog[bagKey] ?? { renditions: {}, priority: 1 };
@@ -9298,8 +9287,24 @@ function mergeOverrideIntoHangRenditions(catalog, override) {
9298
9287
  bag.renditions[track.name] = merged;
9299
9288
  catalog[bagKey] = bag;
9300
9289
  }
9290
+ for (const track of override.tracks) {
9291
+ if (track.packaging !== "fec-repair") continue;
9292
+ const source = catalog["video"]?.renditions?.[track.source] ?? catalog["audio"]?.renditions?.[track.source];
9293
+ if (!namesRepairTrack(source?.fec, track.name, track.instanceId)) continue;
9294
+ const repair = catalog["repair"] ?? {};
9295
+ repair.renditions ??= {};
9296
+ const { name, packaging: _p, mmtpMode: _m, timescale: _t, groupDurationMs: _g, altGroup: _a, ...entry } = track;
9297
+ repair.renditions[name] ??= entry;
9298
+ catalog["repair"] = repair;
9299
+ }
9301
9300
  return catalog;
9302
9301
  }
9302
+ function namesRepairTrack(fec, name, instanceId) {
9303
+ const id = instanceId ?? 0;
9304
+ return Array.isArray(fec) && fec.some(
9305
+ (i) => typeof i === "object" && i !== null && (i.instanceId ?? 0) === id && i.repairTrack === name
9306
+ );
9307
+ }
9303
9308
  function isPositiveInteger(value) {
9304
9309
  return typeof value === "number" && Number.isInteger(value) && value > 0;
9305
9310
  }
@@ -9316,7 +9321,11 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
9316
9321
  const existingAudioTrack = tracks.find(
9317
9322
  (t) => t.role === "audio" && typeof t.codec === "string" && t.codec.length > 0
9318
9323
  );
9319
- for (const ot of override.tracks) {
9324
+ const ordered = [
9325
+ ...override.tracks.filter((t) => t.packaging !== "fec-repair"),
9326
+ ...override.tracks.filter((t) => t.packaging === "fec-repair")
9327
+ ];
9328
+ for (const ot of ordered) {
9320
9329
  const existing = byName.get(ot.name);
9321
9330
  if (existing !== void 0) {
9322
9331
  if (existing.fec === void 0 && ot.fec !== void 0) existing.fec = ot.fec;
@@ -9354,10 +9363,14 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
9354
9363
  `[phase14-catalog-override] track "${ot.name}" has no packaging \u2014 supply per-track or set top-level streamingFormat`
9355
9364
  );
9356
9365
  }
9357
- const role = ot.role ?? (ot.name.endsWith("/repair") ? void 0 : "video");
9366
+ const isRepair = packaging === "fec-repair";
9367
+ if (isRepair && !namesRepairTrack(byName.get(ot.source)?.fec, ot.name, ot.instanceId)) {
9368
+ continue;
9369
+ }
9370
+ const role = ot.role ?? (isRepair ? void 0 : "video");
9358
9371
  const inheritedCodec = role === "audio" ? existingAudioTrack?.codec : existingVideoTrack?.codec;
9359
9372
  const codec = ot.codec ?? inheritedCodec;
9360
- if (packaging !== "timeline" && (typeof codec !== "string" || codec.length === 0)) {
9373
+ if (packaging !== "timeline" && !isRepair && (typeof codec !== "string" || codec.length === 0)) {
9361
9374
  throw new Error(
9362
9375
  `[phase14-catalog-override] track "${ot.name}" has no codec (packaging='${packaging}') \u2014 supply per-track or ensure the publisher catalog has a ${role ?? "video"} track to inherit from`
9363
9376
  );
@@ -9378,14 +9391,13 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
9378
9391
  `[phase14-catalog-override] track "${ot.name}" has packaging='mmtp' but no valid groupDurationMs`
9379
9392
  );
9380
9393
  }
9381
- const entry = {
9382
- name: ot.name,
9383
- packaging,
9384
- codec
9385
- };
9386
- if (mmtpMode !== void 0) entry.mmtpMode = mmtpMode;
9387
- if (ot.timescale !== void 0) entry.timescale = ot.timescale;
9388
- if (ot.groupDurationMs !== void 0) entry.groupDurationMs = ot.groupDurationMs;
9394
+ const entry = { name: ot.name, packaging };
9395
+ if (codec !== void 0 && !isRepair) entry.codec = codec;
9396
+ if (ot.source !== void 0) entry["source"] = ot.source;
9397
+ if (ot.instanceId !== void 0) entry["instanceId"] = ot.instanceId;
9398
+ if (mmtpMode !== void 0 && !isRepair) entry.mmtpMode = mmtpMode;
9399
+ if (ot.timescale !== void 0 && !isRepair) entry.timescale = ot.timescale;
9400
+ if (ot.groupDurationMs !== void 0 && !isRepair) entry.groupDurationMs = ot.groupDurationMs;
9389
9401
  if (role !== void 0) entry.role = role;
9390
9402
  if (ot.framerate !== void 0) entry.framerate = ot.framerate;
9391
9403
  if (ot.fec !== void 0) entry.fec = ot.fec;
@@ -13193,13 +13205,13 @@ var BlockClassifier = class {
13193
13205
  if (compareDataUnitStarts(starts[mid], start) < 0) lo = mid + 1;
13194
13206
  else hi = mid;
13195
13207
  }
13196
- const at = starts[lo];
13197
- if (at !== void 0 && compareDataUnitStarts(at, start) === 0) {
13198
- if (at.sbn !== sbn) return true;
13208
+ const at2 = starts[lo];
13209
+ if (at2 !== void 0 && compareDataUnitStarts(at2, start) === 0) {
13210
+ if (at2.sbn !== sbn) return true;
13199
13211
  } else {
13200
13212
  const before = starts[lo - 1];
13201
13213
  if (before !== void 0 && before.sbn > sbn) return true;
13202
- if (at !== void 0 && at.sbn < sbn) return true;
13214
+ if (at2 !== void 0 && at2.sbn < sbn) return true;
13203
13215
  insertAt = lo;
13204
13216
  }
13205
13217
  }
@@ -19497,11 +19509,11 @@ function isStream(meta, stream) {
19497
19509
  return meta.packetId === stream.packetId && meta.catalogTrackName === stream.catalogTrackName;
19498
19510
  }
19499
19511
  function comparePresentation(a, b) {
19500
- const at = a.authoredTiming;
19512
+ const at2 = a.authoredTiming;
19501
19513
  const bt = b.authoredTiming;
19502
- if (at !== void 0 && bt !== void 0) {
19503
- const left = at.presentationTime * BigInt(bt.mediaTimescale);
19504
- const right = bt.presentationTime * BigInt(at.mediaTimescale);
19514
+ if (at2 !== void 0 && bt !== void 0) {
19515
+ const left = at2.presentationTime * BigInt(bt.mediaTimescale);
19516
+ const right = bt.presentationTime * BigInt(at2.mediaTimescale);
19505
19517
  return left < right ? -1 : left > right ? 1 : 0;
19506
19518
  }
19507
19519
  return a.timestamp - b.timestamp;
@@ -29253,33 +29265,44 @@ var MediaRouter = class {
29253
29265
  };
29254
29266
 
29255
29267
  // src/delta-fallback-config.ts
29256
- function resolveDeltaFallback(base, delta) {
29257
- const repair = base.fec?.repairTrack;
29258
- if (!repair) throw new Error(`[delta-fallback] base track "${base.name}" missing fec.repairTrack`);
29259
- const deltaFec = delta.fec;
29260
- if (!deltaFec?.repairTrack) throw new Error(`[delta-fallback] delta track "${delta.name}" missing fec.repairTrack`);
29261
- if (deltaFec.repairTrack !== repair)
29262
- throw new Error(
29263
- `[delta-fallback] inconsistent repair tracks: ${base.name} references "${repair}" but ${delta.name} references "${deltaFec.repairTrack}"`
29264
- );
29265
- if (!(typeof base.framerate === "number" && Number.isFinite(base.framerate) && base.framerate > 0))
29266
- throw new Error(`[delta-fallback] base track "${base.name}" missing positive framerate`);
29267
- if (!(isPositive(deltaFec.sourceSymbols) && isPositive(deltaFec.repairSymbols)))
29268
- throw new Error(`[delta-fallback] delta track "${delta.name}" missing positive fec.sourceSymbols/repairSymbols`);
29268
+ var recordOf3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
29269
+ function at(root, pointer) {
29270
+ let cur = root;
29271
+ for (const raw of pointer.split("/").slice(1)) {
29272
+ const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
29273
+ cur = Array.isArray(cur) ? cur[Number(key)] : recordOf3(cur)?.[key];
29274
+ }
29275
+ return recordOf3(cur);
29276
+ }
29277
+ function resolveDeltaFallback(catalog, baseName, deltaName) {
29278
+ const bindings = resolveFecBindings(catalog);
29279
+ const bound = (name, role) => {
29280
+ const b = bindings.get(name);
29281
+ if (!b) throw new Error(`[delta-fallback] ${role} track "${name}" declares no fec`);
29282
+ if (b.status !== "ok")
29283
+ throw new Error(`[delta-fallback] ${role} track "${name}" has no FEC: ${b.reason.code} at ${b.reason.path}`);
29284
+ const track = at(catalog, b.pointer) ?? {};
29285
+ const instance = (Array.isArray(track["fec"]) ? track["fec"] : []).map(recordOf3).find((o) => o && (o["instanceId"] ?? 0) === b.base.instanceId) ?? {};
29286
+ return { b, track, instance };
29287
+ };
29288
+ const base = bound(baseName, "base");
29289
+ const delta = bound(deltaName, "delta");
29290
+ const framerate = base.track["framerate"];
29291
+ if (!(typeof framerate === "number" && Number.isFinite(framerate) && framerate > 0))
29292
+ throw new Error(`[delta-fallback] base track "${baseName}" missing positive framerate`);
29293
+ const key = (k) => base.instance[k] ?? delta.instance[k];
29269
29294
  const config = {
29270
- framerate: base.framerate,
29271
- sourceSymbols: deltaFec.sourceSymbols,
29272
- repairSymbols: deltaFec.repairSymbols,
29273
- deltaFailWindowBlocks: base.fec?.deltaFailWindowBlocks ?? deltaFec.deltaFailWindowBlocks,
29274
- deltaProbeIntervalMs: base.fec?.deltaProbeIntervalMs ?? deltaFec.deltaProbeIntervalMs,
29275
- interleaveDepthMs: base.fec?.interleaveDepthMs ?? deltaFec.interleaveDepthMs
29295
+ framerate,
29296
+ sourceSymbols: delta.b.base.sourceSymbols,
29297
+ repairSymbols: delta.b.base.repairSymbols,
29298
+ deltaFailWindowBlocks: key("deltaFailWindowBlocks"),
29299
+ deltaProbeIntervalMs: key("deltaProbeIntervalMs"),
29300
+ interleaveDepthMs: base.b.base.interleaveDepthMs ?? delta.b.base.interleaveDepthMs
29276
29301
  };
29277
- assertDeltaFallbackConfig(config, `[delta-fallback] group ${base.name}/${delta.name}`);
29278
- return { repairTrackName: repair, config };
29279
- }
29280
- function isPositive(value) {
29281
- return typeof value === "number" && Number.isFinite(value) && value > 0;
29302
+ assertDeltaFallbackConfig(config, `[delta-fallback] group ${baseName}/${deltaName}`);
29303
+ return { baseRepairTrackName: repairOf(base.b), deltaRepairTrackName: repairOf(delta.b), config };
29282
29304
  }
29305
+ var repairOf = (b) => b.layers[0].repairTrackName;
29283
29306
 
29284
29307
  // src/align.ts
29285
29308
  function secondsFromTicks(ticks, timescale) {
@@ -31252,15 +31275,15 @@ function wireRepairFetch(mc, opts) {
31252
31275
  const retentionMs = mc.fecManager.terminalRetentionMsForPacket(packetId);
31253
31276
  if (retentionMs === void 0) return null;
31254
31277
  const ceilingAt = Math.min(anchor, earliestGatedFrameAt(packetId, catalogTrackName) ?? anchor) + retentionMs;
31255
- const at = (delay, delayBound) => {
31278
+ const at2 = (delay, delayBound) => {
31256
31279
  const presentAt = Math.min(anchor + delay, ceilingAt);
31257
31280
  if (waitDeadlineAt >= presentAt) return { deadlineAt: waitDeadlineAt, bound: "wait" };
31258
31281
  return { deadlineAt: presentAt, bound: anchor + delay <= ceilingAt ? delayBound : "retention" };
31259
31282
  };
31260
- const base = at(delayMs, "presentation");
31283
+ const base = at2(delayMs, "presentation");
31261
31284
  const budgetMs = keyframeBudgetEligible(blockClass, classifierMode(packetId, catalogTrackName)) && groupCanComplete(sbn, packetId, catalogTrackName) ? readKeyframeBudget(packetId, catalogTrackName) : null;
31262
31285
  if (budgetMs === null || budgetMs <= delayMs) return { ...base, extensionMs: 0, anchorAt: anchor, delayMs };
31263
- const extended = at(budgetMs, "keyframe");
31286
+ const extended = at2(budgetMs, "keyframe");
31264
31287
  return {
31265
31288
  ...extended,
31266
31289
  extensionMs: extended.deadlineAt - base.deadlineAt,
@@ -31270,8 +31293,8 @@ function wireRepairFetch(mc, opts) {
31270
31293
  };
31271
31294
  const earliestGatedFrameAt = (packetId, catalogTrackName) => {
31272
31295
  try {
31273
- const at = mc.earliestGatedFrameAtMs?.(packetId, catalogTrackName);
31274
- return at !== void 0 && Number.isFinite(at) ? at : void 0;
31296
+ const at2 = mc.earliestGatedFrameAtMs?.(packetId, catalogTrackName);
31297
+ return at2 !== void 0 && Number.isFinite(at2) ? at2 : void 0;
31275
31298
  } catch {
31276
31299
  return void 0;
31277
31300
  }
@@ -31578,8 +31601,6 @@ export {
31578
31601
  isCompleteAvccSample,
31579
31602
  isCompleteFmp4DataUnit,
31580
31603
  isFecBlockProfile,
31581
- isFecCatalogConfig,
31582
- isFecCatalogGeometry,
31583
31604
  isFmp4InitSegment,
31584
31605
  isHevcCodec,
31585
31606
  isHevcIrapNalUnitType,