@blockcast/mmt-container 0.5.0 → 1.0.0-main.3cb7359ea316

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 (136) hide show
  1. package/README.md +21 -16
  2. package/dist/browser-runtime.js +1024 -1078
  3. package/dist/browser-runtime.js.map +4 -4
  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 +12 -434
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +48 -161
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +567 -592
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +6 -2
  13. package/dist/catalog-network-source.js.map +3 -3
  14. package/dist/delivery-catalog.js +12 -4
  15. package/dist/delivery-catalog.js.map +4 -4
  16. package/dist/delta-fallback-config.d.ts +21 -26
  17. package/dist/delta-fallback-config.d.ts.map +1 -1
  18. package/dist/delta-fallback-config.js +582 -24
  19. package/dist/delta-fallback-config.js.map +4 -4
  20. package/dist/fec-manager.js +6 -2
  21. package/dist/fec-manager.js.map +3 -3
  22. package/dist/fec-state.d.ts +5 -43
  23. package/dist/fec-state.d.ts.map +1 -1
  24. package/dist/fec-state.js +529 -34
  25. package/dist/fec-state.js.map +4 -4
  26. package/dist/fec-worker-setup.js +6 -2
  27. package/dist/fec-worker-setup.js.map +3 -3
  28. package/dist/held-layer-admission.js +6 -2
  29. package/dist/held-layer-admission.js.map +3 -3
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5 -39
  33. package/dist/index.js.map +2 -2
  34. package/dist/iso-block-locator.js +6 -2
  35. package/dist/iso-block-locator.js.map +3 -3
  36. package/dist/mfu-reassembler.js +6 -2
  37. package/dist/mfu-reassembler.js.map +3 -3
  38. package/dist/mmtp-container-parser.js +6 -2
  39. package/dist/mmtp-container-parser.js.map +3 -3
  40. package/dist/mmtp-container-parser.no-modules.js +551 -452
  41. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  42. package/dist/mmtp-decode-pipeline.js +6 -2
  43. package/dist/mmtp-decode-pipeline.js.map +3 -3
  44. package/dist/mmtp-fec-session.d.ts +7 -6
  45. package/dist/mmtp-fec-session.d.ts.map +1 -1
  46. package/dist/mmtp-fec-session.js +13 -9
  47. package/dist/mmtp-fec-session.js.map +3 -3
  48. package/dist/mmtp-router.d.ts +7 -16
  49. package/dist/mmtp-router.d.ts.map +1 -1
  50. package/dist/mmtp-router.js +19 -29
  51. package/dist/mmtp-router.js.map +3 -3
  52. package/dist/multicast-auth-catalog.js +7 -3
  53. package/dist/multicast-auth-catalog.js.map +3 -3
  54. package/dist/multicast-input.js +7 -3
  55. package/dist/multicast-input.js.map +3 -3
  56. package/dist/multicast-manager.d.ts +29 -32
  57. package/dist/multicast-manager.d.ts.map +1 -1
  58. package/dist/multicast-manager.js +682 -253
  59. package/dist/multicast-manager.js.map +4 -4
  60. package/dist/multicast-transport.d.ts +23 -2
  61. package/dist/multicast-transport.d.ts.map +1 -1
  62. package/dist/multicast-transport.js +57 -16
  63. package/dist/multicast-transport.js.map +4 -4
  64. package/dist/multicast-unwrap.js +6 -2
  65. package/dist/multicast-unwrap.js.map +3 -3
  66. package/dist/no-modules.js +551 -452
  67. package/dist/no-modules.js.map +4 -4
  68. package/dist/phase14-catalog-override.d.ts +10 -5
  69. package/dist/phase14-catalog-override.d.ts.map +1 -1
  70. package/dist/phase14-catalog-override.js +35 -11
  71. package/dist/phase14-catalog-override.js.map +2 -2
  72. package/dist/repair-fetch.d.ts +3 -1
  73. package/dist/repair-fetch.d.ts.map +1 -1
  74. package/dist/repair-fetch.js +16 -3
  75. package/dist/repair-fetch.js.map +3 -3
  76. package/dist/repair-layers.d.ts +0 -123
  77. package/dist/repair-layers.d.ts.map +1 -1
  78. package/dist/repair-layers.js +1 -349
  79. package/dist/repair-layers.js.map +3 -3
  80. package/dist/signaling.js +6 -2
  81. package/dist/signaling.js.map +3 -3
  82. package/dist/timestamp-codec.js +6 -2
  83. package/dist/timestamp-codec.js.map +3 -3
  84. package/dist/wasm-mfu-reassembler.js +6 -2
  85. package/dist/wasm-mfu-reassembler.js.map +3 -3
  86. package/dist/wasm-mmtp-container-parser.js +6 -2
  87. package/dist/wasm-mmtp-container-parser.js.map +3 -3
  88. package/package.json +9 -5
  89. package/schemas/catalog.schema.json +53 -58
  90. package/src/carp-catalog.ts +39 -229
  91. package/src/catalog-fec-params.ts +130 -493
  92. package/src/delta-fallback-config.ts +63 -50
  93. package/src/fec-state.ts +7 -62
  94. package/src/index.ts +10 -30
  95. package/src/mmtp-fec-session.ts +11 -10
  96. package/src/mmtp-router.ts +14 -29
  97. package/src/multicast-manager.ts +89 -121
  98. package/src/multicast-transport.ts +65 -12
  99. package/src/phase14-catalog-override.ts +67 -16
  100. package/src/repair-fetch.ts +27 -3
  101. package/src/repair-layers.ts +7 -520
  102. package/test-vectors/catalog/README.md +39 -67
  103. package/test-vectors/catalog/fec-01-runtime/README.md +15 -0
  104. package/test-vectors/catalog/{negative/fec-block-profile-retired.json → fec-01-runtime/fec-iso-ssbg1.json} +1 -1
  105. package/test-vectors/catalog/fec-01-runtime/fec-layered-repair.json +106 -0
  106. package/test-vectors/catalog/{negative/fec-block-profile-unknown.json → fec-01-runtime/fec-multicast.json} +2 -1
  107. package/test-vectors/catalog/fec-01-runtime/multicast-native-ssm-no-network-source.json +44 -0
  108. package/test-vectors/catalog/negative/fec-block-profile-missing.json +24 -11
  109. package/test-vectors/catalog/negative/fec-iso-symbol-size-below-floor.json +13 -8
  110. package/test-vectors/catalog/negative/fec-reed-solomon-iso-symbol-size-13.json +13 -8
  111. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +20 -10
  112. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +20 -10
  113. package/test-vectors/catalog/negative/fec-repair-overlay-source-symbols-zero.json +25 -17
  114. package/test-vectors/catalog/negative/fec-repair-symbols-zero.json +13 -8
  115. package/test-vectors/catalog/negative/fec-source-symbols-zero.json +13 -8
  116. package/test-vectors/catalog/negative/fec-symbol-size-zero.json +13 -8
  117. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +18 -9
  118. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +13 -8
  119. package/test-vectors/catalog/positive/fec-algorithm-unimplemented.json +14 -9
  120. package/test-vectors/catalog/positive/fec-iso-ssbg1.json +25 -12
  121. package/test-vectors/catalog/positive/fec-layered-repair.json +79 -35
  122. package/test-vectors/catalog/positive/fec-multicast.json +26 -13
  123. package/test-vectors/catalog/positive/fec-reed-solomon-layered.json +29 -18
  124. package/test-vectors/catalog/positive/multicast-draft01-4-1-example.json +28 -20
  125. package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +14 -10
  126. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +22 -12
  127. package/test-vectors/catalog/section-codes.json +13 -22
  128. package/test-vectors/catalog-timing.json +2 -2
  129. package/test-vectors/catalog-validation.json +30 -243
  130. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +0 -28
  131. package/test-vectors/catalog/negative/fec-reed-solomon-k-plus-p-over-255.json +0 -39
  132. package/test-vectors/catalog/negative/fec-reed-solomon-overlay-over-255.json +0 -40
  133. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +0 -32
  134. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +0 -34
  135. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +0 -24
  136. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +0 -33
@@ -24,397 +24,541 @@ function parseFecBlockProfile(value, where) {
24
24
  throw new Error(`${where} must be "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
25
25
  }
26
26
 
27
- // ../mmt-base/dist/catalog-fec-source.js
28
- var ALL_ROLES = Object.freeze(["video", "audio"]);
29
- function recordOf(value) {
30
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
31
- }
32
- function locateCatalogFecSource(catalog, source, roles = ALL_ROLES) {
33
- const root = recordOf(catalog);
34
- if (root === void 0)
35
- return void 0;
36
- for (const role of roles) {
37
- const renditions = recordOf(recordOf(root[role])?.["renditions"]);
38
- if (renditions === void 0 || !Object.hasOwn(renditions, source))
39
- continue;
40
- const entry2 = recordOf(renditions[source]);
41
- if (entry2 === void 0)
42
- continue;
43
- return Object.freeze({
44
- dialect: "hang",
45
- role,
46
- entry: entry2,
47
- fec: entry2["fec"],
48
- fecPath: `$.${role}.renditions[${JSON.stringify(source)}].fec`
49
- });
50
- }
51
- const tracks = root["tracks"];
52
- if (!Array.isArray(tracks))
53
- return void 0;
54
- const index = tracks.findIndex((track) => recordOf(track)?.["name"] === source);
55
- if (index < 0)
56
- return void 0;
57
- const entry = recordOf(tracks[index]) ?? {};
58
- return Object.freeze({
59
- dialect: "msf",
60
- index,
61
- entry,
62
- fec: entry["fec"],
63
- fecPath: `$.tracks[${index}].fec`
64
- });
65
- }
66
- function isoBlockSpanUsable(fec) {
67
- const block = recordOf(fec);
68
- if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1")
69
- return true;
70
- const ms = block["interleaveDepthMs"];
71
- return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
72
- }
73
- function catalogFecAvailable(fec) {
74
- const block = recordOf(fec);
75
- if (block === void 0)
76
- return true;
77
- if (fecAlgorithmUnimplemented(block["algorithm"]))
78
- return false;
79
- const declaresFecInstance = block["algorithm"] !== "none";
80
- const profile = block["blockProfile"];
81
- const profileOk = isFecBlockProfile(profile) || profile === void 0 && !declaresFecInstance;
82
- return profileOk && (!declaresFecInstance || isoBlockSpanUsable(block));
83
- }
84
- function fecAlgorithmUnimplemented(algorithm) {
85
- return typeof algorithm === "string" && algorithm !== "none" && algorithm !== "raptorq" && algorithm !== "reed-solomon";
86
- }
87
- function isPositiveSafeInteger(value) {
88
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
89
- }
90
- var ISO_SSBG1_MIN_SYMBOL_SIZE = 14;
91
- function catalogFecDeclaresUnconfigurableInstance(fec) {
92
- const block = recordOf(fec);
93
- if (block === void 0 || block["algorithm"] === "none" || fecAlgorithmUnimplemented(block["algorithm"]))
94
- return false;
95
- if (!(isPositiveSafeInteger(block["sourceSymbols"]) && isPositiveSafeInteger(block["symbolSize"])))
96
- return true;
97
- return block["blockProfile"] === "iso-ssbg1-v1" && block["symbolSize"] < ISO_SSBG1_MIN_SYMBOL_SIZE;
98
- }
99
-
100
- // src/catalog-fec-params.ts
101
- import { catalogFecInterleaveMs } from "@blockcast/mmt-fec";
102
-
103
- // src/repair-layers.ts
104
- var REPAIR_PRIORITY_MIN = 192;
105
- var REPAIR_PRIORITY_MAX = 255;
106
- var DEFAULT_REPAIR_PRIORITY = 240;
107
- var MAX_RAPTORQ_SOURCE_SYMBOLS = 56403;
108
- var MAX_REPAIR_SYMBOLS = 16777215;
109
- var MAX_ENCODING_SYMBOL_ID = 16777215;
110
- var MAX_REED_SOLOMON_BLOCK_SYMBOLS = 255;
111
- var RepairContractError = class extends Error {
112
- constructor(code, path, message) {
113
- super(`[repair-layers:${code}] ${path}: ${message}`);
114
- this.code = code;
115
- this.path = path;
116
- this.name = "RepairContractError";
117
- }
118
- };
119
- var throwRepairContractError = (code, path, message) => {
120
- throw new RepairContractError(code, path, message);
121
- };
122
- function readFecBlockProfile(value, path, declaresFecInstance, fail) {
123
- if (value === void 0 && !declaresFecInstance) return void 0;
124
- if (isFecBlockProfile(value)) return value;
125
- if (typeof value === "string" && value.length > 0) {
126
- return fail(
127
- "track_fec_block_profile_unimplemented",
128
- path,
129
- `${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
130
- );
131
- }
132
- try {
133
- return parseFecBlockProfile(value, "blockProfile");
134
- } catch (error) {
135
- return fail("track_fec_block_profile", path, error.message);
136
- }
137
- }
138
- var REPAIR_FIELDS = ["depends", "repairLayer", "repairSymbols", "priority", "scope", "sourceSymbols"];
139
- var REPAIR_TRACK_GEOMETRY_KEYS = [
27
+ // ../mmt-base/dist/fec-catalog.js
28
+ var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
29
+ var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
30
+ var RS_MAX_BLOCK = 255;
31
+ var RAPTORQ_MAX_K = 56403;
32
+ var MAX_24 = 16777215;
33
+ var ISO_MIN_T = 14;
34
+ var GEOMETRY_KEYS = [
35
+ "scheme",
36
+ "blockProfile",
140
37
  "mmtpMode",
141
38
  "timescale",
142
- "groupDurationMs",
143
- "groupDurationTicks",
144
- "keyframeIntervalMs",
145
- "keyframeIntervalTicks",
146
- "fec",
147
39
  "symbolSize",
148
- "interleaveDepthMs"
40
+ "interleaveDepthMs",
41
+ "reorderToleranceMs",
42
+ "sourceSymbols",
43
+ "fec"
149
44
  ];
150
- var REPAIR_TRACK_ONLY_KEYS = ["repairLayer", "repairSymbols", "sourceSymbols", "scope"];
151
- function rejectRepairTrackGeometry(path, fields, fail) {
152
- for (const key of REPAIR_TRACK_GEOMETRY_KEYS) {
153
- if (fields[key] !== void 0) {
154
- fail(
155
- "track_repair_geometry",
156
- `${path}.${key}`,
157
- "a repair track carries no block geometry; it is shared with the source track's fec object"
158
- );
159
- }
160
- }
161
- }
162
- function repairReachability(fecObjects) {
163
- let declaresFecSource = false;
164
- const layerZeroNames = /* @__PURE__ */ new Set();
165
- for (const candidate of fecObjects) {
166
- if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue;
167
- const fec = candidate;
168
- if (fec["algorithm"] === "none") continue;
169
- declaresFecSource = true;
170
- const repairTrack = fec["repairTrack"];
171
- if (typeof repairTrack === "string" && repairTrack.length > 0) layerZeroNames.add(repairTrack);
172
- }
173
- return { declaresFecSource, layerZeroNames };
174
- }
175
- function assertRepairTrackReachable(descriptor, reachability, fail) {
176
- if (dependsTarget(descriptor) !== void 0) return;
177
- if (reachability.declaresFecSource && !reachability.layerZeroNames.has(descriptor.name)) {
178
- fail(
179
- "track_repair_depends",
180
- `${descriptor.path}.depends`,
181
- "no source names this repair track in fec.repairTrack and it declares no depends"
182
- );
183
- }
184
- }
185
- function rejectRepairFieldsOnMediaTrack(path, fields, fail) {
186
- for (const key of REPAIR_TRACK_ONLY_KEYS) {
187
- if (fields[key] !== void 0) {
188
- fail("track_repair_layer", `${path}.${key}`, `${key} is a repair track field`);
45
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
46
+ var has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
47
+ var isInt = (v) => typeof v === "number" && Number.isInteger(v);
48
+ var intIn = (v, lo, hi = Number.MAX_SAFE_INTEGER) => isInt(v) && v >= lo && v <= hi;
49
+ var profileAdmits = (profile, packaging) => profile !== "iso-ssbg1-v1" || packaging === "mmtp";
50
+ function validateFecCatalog(catalog, receiver = {}) {
51
+ const schemes = receiver.schemes ?? DEFAULT_SCHEMES;
52
+ const profiles = receiver.blockProfiles ?? DEFAULT_PROFILES;
53
+ const overlays = receiver.overlays ?? true;
54
+ const all = [];
55
+ const report = (code, path, inst) => {
56
+ const issue = { code, path };
57
+ all.push(issue);
58
+ inst?.issues.push(issue);
59
+ };
60
+ const tracks = isObj(catalog) && Array.isArray(catalog.tracks) ? catalog.tracks : [];
61
+ if (!isObj(catalog) || !Array.isArray(catalog.tracks))
62
+ report("fec_member", "/tracks");
63
+ let rejectAll = false;
64
+ tracks.forEach((t, ti) => {
65
+ if (!isObj(t))
66
+ return;
67
+ if (has(t, "enhancementRepair")) {
68
+ rejectAll = true;
69
+ report("fec_enhancement_repair", `/tracks/${ti}/enhancementRepair`);
189
70
  }
190
- }
191
- }
192
- function describeRepairTrack(name, path, fields) {
193
- const descriptor = { name, path };
194
- for (const field of REPAIR_FIELDS) {
195
- if (field in fields) descriptor[field] = fields[field];
196
- }
197
- return descriptor;
198
- }
199
- function isSafeInteger(value) {
200
- return typeof value === "number" && Number.isSafeInteger(value);
201
- }
202
- function positiveInteger(value) {
203
- return isSafeInteger(value) && value > 0;
204
- }
205
- function dependsTarget(descriptor) {
206
- const depends = descriptor.depends;
207
- return Array.isArray(depends) && depends.length === 1 && typeof depends[0] === "string" && depends[0].length > 0 ? depends[0] : void 0;
208
- }
209
- function validateRepairTrackFields(descriptor, fail) {
210
- const { path } = descriptor;
211
- if (descriptor.depends !== void 0 && dependsTarget(descriptor) === void 0) {
212
- fail("track_repair_depends", `${path}.depends`, "must name exactly one source track");
213
- }
214
- if (descriptor.repairLayer !== void 0 && (!isSafeInteger(descriptor.repairLayer) || descriptor.repairLayer < 0)) {
215
- fail("track_repair_layer", `${path}.repairLayer`, "must be a non-negative integer");
216
- }
217
- if (descriptor.repairSymbols !== void 0 && (!positiveInteger(descriptor.repairSymbols) || descriptor.repairSymbols > MAX_REPAIR_SYMBOLS)) {
218
- fail("track_repair_layer", `${path}.repairSymbols`, `must be a positive integer at most ${MAX_REPAIR_SYMBOLS}`);
219
- }
220
- if (descriptor.priority !== void 0 && (!isSafeInteger(descriptor.priority) || descriptor.priority < REPAIR_PRIORITY_MIN || descriptor.priority > REPAIR_PRIORITY_MAX)) {
221
- fail(
222
- "track_repair_priority",
223
- `${path}.priority`,
224
- `must be an integer from ${REPAIR_PRIORITY_MIN} to ${REPAIR_PRIORITY_MAX} (MoQT scale, lower first)`
225
- );
226
- }
227
- if (descriptor.scope !== void 0) {
228
- if (descriptor.scope !== "keyframe") {
229
- fail("track_repair_overlay", `${path}.scope`, 'must be "keyframe"');
71
+ if (Array.isArray(t.fec))
72
+ t.fec.forEach((o, ii) => {
73
+ if (isObj(o) && has(o, "enhancementRepair")) {
74
+ rejectAll = true;
75
+ report("fec_enhancement_repair", `/tracks/${ti}/fec/${ii}/enhancementRepair`);
76
+ }
77
+ });
78
+ });
79
+ const key = (ns, name) => JSON.stringify([ns ?? null, name]);
80
+ const byName = /* @__PURE__ */ new Map();
81
+ tracks.forEach((t, ti) => {
82
+ if (isObj(t) && typeof t.name === "string") {
83
+ const k = key(t.namespace, t.name);
84
+ if (!byName.has(k))
85
+ byName.set(k, ti);
230
86
  }
231
- if (descriptor.repairLayer !== void 0) {
232
- fail("track_repair_overlay", `${path}.repairLayer`, "an overlay is a separate FEC instance and carries no repairLayer");
87
+ });
88
+ const trackAt = (ti) => tracks[ti];
89
+ const insts = [];
90
+ tracks.forEach((t, ti) => {
91
+ if (!isObj(t) || !has(t, "fec") || t.packaging === "fec-repair" || t.packaging === "fec-source")
92
+ return;
93
+ if (!Array.isArray(t.fec)) {
94
+ report("fec_member", `/tracks/${ti}/fec`);
95
+ return;
233
96
  }
234
- for (const field of ["depends", "sourceSymbols", "repairSymbols", "priority"]) {
235
- if (descriptor[field] === void 0) {
236
- fail("track_repair_overlay", `${path}.${field}`, `an overlay must declare ${field}`);
237
- }
97
+ t.fec.forEach((o, ii) => {
98
+ const path = `/tracks/${ti}/fec/${ii}`;
99
+ const inst = {
100
+ path,
101
+ ti,
102
+ track: t,
103
+ obj: isObj(o) ? o : null,
104
+ id: null,
105
+ unavailable: false,
106
+ overlay: false,
107
+ issues: [],
108
+ layer0: null,
109
+ symbolNamed: null,
110
+ attached: []
111
+ };
112
+ insts.push(inst);
113
+ if (!inst.obj)
114
+ return report("fec_member", path, inst);
115
+ const o2 = inst.obj;
116
+ if (!has(o2, "instanceId"))
117
+ inst.id = 0;
118
+ else if (intIn(o2.instanceId, 0, 255))
119
+ inst.id = o2.instanceId;
120
+ else
121
+ report("fec_member", `${path}/instanceId`, inst);
122
+ inst.overlay = has(o2, "scope");
123
+ });
124
+ });
125
+ const declaresFec = insts.length > 0;
126
+ for (const inst of insts) {
127
+ const o = inst.obj;
128
+ if (!o)
129
+ continue;
130
+ const avail = (field, impl) => {
131
+ const p = `${inst.path}/${field}`;
132
+ const absent = field === "scheme" ? "fec_scheme_absent" : "fec_block_profile_absent";
133
+ const unimpl = field === "scheme" ? "fec_scheme_unimplemented" : "fec_block_profile_unimplemented";
134
+ if (!has(o, field))
135
+ return report(absent, p, inst), false;
136
+ if (typeof o[field] !== "string")
137
+ return report("fec_member", p, inst), true;
138
+ if (!impl.includes(o[field]))
139
+ return report(unimpl, p, inst), false;
140
+ return true;
141
+ };
142
+ if (!avail("scheme", schemes) || !avail("blockProfile", profiles))
143
+ inst.unavailable = true;
144
+ else if (typeof o.blockProfile === "string" && !profileAdmits(o.blockProfile, inst.track.packaging)) {
145
+ report("fec_block_profile_packaging", `${inst.path}/blockProfile`, inst);
146
+ inst.unavailable = true;
238
147
  }
239
148
  }
240
- if (descriptor.sourceSymbols !== void 0) {
241
- if (descriptor.scope === void 0) {
242
- fail("track_repair_overlay", `${path}.sourceSymbols`, "only an overlay (scope: keyframe) declares its own sourceSymbols");
149
+ const live = (i) => !i.unavailable;
150
+ for (const inst of insts.filter(live)) {
151
+ const o = inst.obj;
152
+ if (!o)
153
+ continue;
154
+ const p = inst.path;
155
+ for (const m of ["sourceSymbols", "repairSymbols", "symbolSize"])
156
+ if (!intIn(o[m], 1))
157
+ report("fec_member", `${p}/${m}`, inst);
158
+ if (typeof o.repairTrack !== "string")
159
+ report("fec_member", `${p}/repairTrack`, inst);
160
+ if (has(o, "symbolTrack") && typeof o.symbolTrack !== "string")
161
+ report("fec_member", `${p}/symbolTrack`, inst);
162
+ if (has(o, "interleaveDepthMs")) {
163
+ const d = o.interleaveDepthMs;
164
+ if (!intIn(d, 0) || d === 0 && o.blockProfile !== "iso-ssbg1-v1")
165
+ report("fec_member", `${p}/interleaveDepthMs`, inst);
243
166
  }
244
- if (!positiveInteger(descriptor.sourceSymbols) || descriptor.sourceSymbols > MAX_RAPTORQ_SOURCE_SYMBOLS) {
245
- fail(
246
- "track_fec_bounds",
247
- `${path}.sourceSymbols`,
248
- `must be a positive integer at most ${MAX_RAPTORQ_SOURCE_SYMBOLS} (RFC 6330 source block limit)`
249
- );
167
+ if (has(o, "reorderToleranceMs")) {
168
+ const r = o.reorderToleranceMs;
169
+ if (typeof r !== "number" || !Number.isFinite(r) || r < 0)
170
+ report("fec_member", `${p}/reorderToleranceMs`, inst);
250
171
  }
251
- }
252
- if (isSafeInteger(descriptor.repairLayer) && descriptor.repairLayer >= 1) {
253
- for (const field of ["depends", "repairSymbols", "priority"]) {
254
- if (descriptor[field] === void 0) {
255
- fail("track_repair_layer", `${path}.${field}`, `repair layer ${descriptor.repairLayer} must declare ${field}`);
172
+ if (has(o, "label") && typeof o.label !== "string")
173
+ report("fec_member", `${p}/label`, inst);
174
+ if (inst.overlay && o.scope !== "keyframe")
175
+ report("fec_member", `${p}/scope`, inst);
176
+ }
177
+ const idGroups = /* @__PURE__ */ new Map();
178
+ for (const inst of insts)
179
+ if (inst.id !== null) {
180
+ const k = `${inst.ti}:${inst.id}`;
181
+ idGroups.set(k, [...idGroups.get(k) ?? [], inst]);
182
+ }
183
+ for (const group of idGroups.values())
184
+ if (group.length > 1)
185
+ for (const inst of group.filter(live))
186
+ report("fec_instance_id", inst.obj && has(inst.obj, "instanceId") ? `${inst.path}/instanceId` : inst.path, inst);
187
+ const nameUses = /* @__PURE__ */ new Map();
188
+ for (const inst of insts)
189
+ for (const field of ["repairTrack", "symbolTrack"])
190
+ if (inst.obj && typeof inst.obj[field] === "string") {
191
+ const k = key(inst.track.namespace, inst.obj[field]);
192
+ nameUses.set(k, [...nameUses.get(k) ?? [], { inst, field }]);
193
+ }
194
+ const namedBy = /* @__PURE__ */ new Map();
195
+ for (const [k, uses] of nameUses) {
196
+ const tj = byName.get(k);
197
+ for (const { inst, field } of uses) {
198
+ const path = `${inst.path}/${field}`;
199
+ if (tj === void 0 || trackAt(tj).packaging !== (field === "repairTrack" ? "fec-repair" : "fec-source")) {
200
+ if (live(inst))
201
+ report("fec_naming", path, inst);
202
+ } else if (uses.length > 1) {
203
+ if (live(inst))
204
+ report("fec_naming", path, inst);
205
+ if (!namedBy.has(tj))
206
+ namedBy.set(tj, inst);
207
+ } else {
208
+ namedBy.set(tj, inst);
209
+ if (field === "repairTrack")
210
+ inst.layer0 = tj;
211
+ else
212
+ inst.symbolNamed = tj;
256
213
  }
257
214
  }
258
215
  }
259
- }
260
- function assembleRepairLayers(source, descriptors, fail = throwRepairContractError, options = {}) {
261
- const layerZeroName = typeof source.repairTrack === "string" && source.repairTrack.length > 0 ? source.repairTrack : void 0;
262
- const layerZero = layerZeroName === void 0 ? void 0 : descriptors.find((d) => d.name === layerZeroName);
263
- if (layerZeroName !== void 0 && layerZero === void 0 && options.layerZeroMustBeDeclared) {
264
- fail("track_repair_track", `${source.path}.repairTrack`, `unknown FEC repair track ${JSON.stringify(layerZeroName)}`);
265
- }
266
- const dependents = descriptors.filter((d) => d.name !== layerZeroName && dependsTarget(d) === source.name);
267
- const k = positiveInteger(source.sourceSymbols) ? source.sourceSymbols : 0;
268
- if (source.algorithm === "raptorq" && k > MAX_RAPTORQ_SOURCE_SYMBOLS) {
269
- fail(
270
- "track_fec_bounds",
271
- `${source.path}.sourceSymbols`,
272
- `must be at most ${MAX_RAPTORQ_SOURCE_SYMBOLS} (RFC 6330 source block limit)`
273
- );
274
- }
275
- if (layerZeroName === void 0) {
276
- if (dependents.length === 0) return void 0;
277
- fail(
278
- "track_repair_layer",
279
- `${dependents[0].path}.depends`,
280
- `repair layers of ${JSON.stringify(source.name)} require a layer 0 (fec.repairTrack) on the source`
281
- );
282
- }
283
- const layered = dependents.length > 0;
284
- let layerZeroSymbols = 0;
285
- if (positiveInteger(source.repairSymbols)) {
286
- layerZeroSymbols = source.repairSymbols;
287
- } else if (layered) {
288
- fail("track_fec", `${source.path}.repairSymbols`, "layered or overlay repair needs a positive integer repairSymbols on layer 0");
289
- }
290
- if (layered && !positiveInteger(source.sourceSymbols)) {
291
- fail("track_fec", `${source.path}.sourceSymbols`, "layered or overlay repair needs a positive integer sourceSymbols on the source");
292
- }
293
- const reedSolomon = source.algorithm === "reed-solomon";
294
- if (layered && source.algorithm !== "raptorq" && !reedSolomon) {
295
- fail(
296
- "track_fec",
297
- `${source.path}.algorithm`,
298
- "repair layers and overlays are defined only for raptorq and reed-solomon (draft-ramadan-moq-fec-01 6.3.4 rule 7)"
299
- );
300
- }
301
- let layerZeroPriority = DEFAULT_REPAIR_PRIORITY;
302
- if (layerZero !== void 0) {
303
- if (layerZero.depends !== void 0 && dependsTarget(layerZero) !== source.name) {
304
- fail(
305
- "track_repair_depends",
306
- `${layerZero.path}.depends`,
307
- `layer 0 of ${JSON.stringify(source.name)} must depend on that source`
308
- );
216
+ const owner = /* @__PURE__ */ new Map();
217
+ if (declaresFec)
218
+ tracks.forEach((t, tj) => {
219
+ if (!isObj(t) || t.packaging !== "fec-repair" && t.packaging !== "fec-source")
220
+ return;
221
+ const tp = `/tracks/${tj}`;
222
+ const named = namedBy.get(tj);
223
+ if (named) {
224
+ owner.set(tj, named);
225
+ if (!live(named))
226
+ return;
227
+ if (has(t, "source") && t.source !== named.track.name)
228
+ report("fec_resolution", `${tp}/source`, named);
229
+ if (has(t, "instanceId") && t.instanceId !== named.id)
230
+ report("fec_resolution", `${tp}/instanceId`, named);
231
+ return;
232
+ }
233
+ if (!has(t, "source"))
234
+ return report("fec_orphan", tp);
235
+ const id = has(t, "instanceId") ? t.instanceId : 0;
236
+ const src = typeof t.source === "string" ? byName.get(key(t.namespace, t.source)) : void 0;
237
+ const target = src === void 0 ? void 0 : insts.find((i) => i.ti === src && i.id === id);
238
+ if (!target)
239
+ return report("fec_naming", has(t, "instanceId") && src !== void 0 ? `${tp}/instanceId` : `${tp}/source`);
240
+ owner.set(tj, target);
241
+ target.attached.push(tj);
242
+ });
243
+ for (const [tj, inst] of owner) {
244
+ if (!live(inst))
245
+ continue;
246
+ const t = trackAt(tj);
247
+ const tp = `/tracks/${tj}`;
248
+ const symbol = t.packaging === "fec-source";
249
+ if (has(t, "source") && typeof t.source !== "string")
250
+ report("fec_member", `${tp}/source`, inst);
251
+ if (has(t, "instanceId") && !intIn(t.instanceId, 0, 255))
252
+ report("fec_member", `${tp}/instanceId`, inst);
253
+ for (const m of ["repairLayer", "repairSymbols"]) {
254
+ if (!has(t, m))
255
+ continue;
256
+ if (symbol || !intIn(t[m], m === "repairLayer" ? 0 : 1))
257
+ report("fec_member", `${tp}/${m}`, inst);
309
258
  }
310
- if (layerZero.repairLayer !== void 0 && layerZero.repairLayer !== 0) {
311
- fail("track_repair_layer", `${layerZero.path}.repairLayer`, "the track named by fec.repairTrack is layer 0");
259
+ if (has(t, "priority") && !intIn(t.priority, symbol ? 0 : 192, 255))
260
+ report("fec_member", `${tp}/priority`, inst);
261
+ for (const k of Object.keys(t))
262
+ if (GEOMETRY_KEYS.includes(k) || k.startsWith("groupDuration"))
263
+ report("fec_track_geometry", `${tp}/${k.replace(/~/g, "~0").replace(/\//g, "~1")}`, inst);
264
+ }
265
+ for (const inst of insts.filter(live)) {
266
+ const o = inst.obj;
267
+ if (!o)
268
+ continue;
269
+ const repairs = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-repair");
270
+ const symbols = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-source");
271
+ if (symbols.length + (inst.symbolNamed !== null ? 1 : 0) > 1)
272
+ for (const tj of symbols)
273
+ report("fec_symbol_tracks", `/tracks/${tj}`, inst);
274
+ const l0 = inst.layer0 === null ? null : trackAt(inst.layer0);
275
+ const l0p = `/tracks/${inst.layer0}`;
276
+ if (inst.overlay) {
277
+ if (l0 && has(l0, "repairLayer"))
278
+ report("fec_overlay", `${l0p}/repairLayer`, inst);
279
+ for (const tj of repairs)
280
+ report("fec_overlay", has(trackAt(tj), "repairLayer") ? `/tracks/${tj}/repairLayer` : `/tracks/${tj}`, inst);
281
+ } else {
282
+ if (l0 && has(l0, "repairLayer") && l0.repairLayer !== 0)
283
+ report("fec_layers", `${l0p}/repairLayer`, inst);
284
+ if (l0 && has(l0, "repairSymbols") && intIn(o.repairSymbols, 1) && l0.repairSymbols !== o.repairSymbols)
285
+ report("fec_layer0", `${l0p}/repairSymbols`, inst);
286
+ const layers = [];
287
+ for (const tj of repairs) {
288
+ const t = trackAt(tj);
289
+ if (!has(t, "repairLayer"))
290
+ report("fec_member", `/tracks/${tj}/repairLayer`, inst);
291
+ else if (t.repairLayer === 0)
292
+ report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
293
+ else if (intIn(t.repairLayer, 1))
294
+ layers.push({ tj, layer: t.repairLayer });
295
+ if (!has(t, "repairSymbols"))
296
+ report("fec_member", `/tracks/${tj}/repairSymbols`, inst);
297
+ }
298
+ const counts = /* @__PURE__ */ new Map();
299
+ for (const { layer } of layers)
300
+ counts.set(layer, (counts.get(layer) ?? 0) + 1);
301
+ let missing = 1;
302
+ while (counts.has(missing))
303
+ missing++;
304
+ for (const { tj, layer } of layers)
305
+ if (counts.get(layer) > 1 || layer > missing)
306
+ report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
307
+ const chain = [
308
+ { layer: 0, tj: inst.layer0, pri: l0 && intIn(l0.priority, 192, 255) ? l0.priority : l0 && has(l0, "priority") ? null : 240 },
309
+ ...layers.map(({ tj, layer }) => {
310
+ const pr = trackAt(tj).priority;
311
+ return { layer, tj, pri: intIn(pr, 192, 255) ? pr : null };
312
+ })
313
+ ].sort((a, b) => a.layer - b.layer);
314
+ let prev = null;
315
+ for (const c of chain) {
316
+ if (c.pri === null)
317
+ continue;
318
+ if (prev !== null && c.pri < prev)
319
+ report("fec_priority", `/tracks/${c.tj}/priority`, inst);
320
+ prev = Math.max(prev ?? c.pri, c.pri);
321
+ }
312
322
  }
313
- if (layerZero.scope !== void 0) {
314
- fail("track_repair_overlay", `${layerZero.path}.scope`, "the track named by fec.repairTrack is base layer 0, not an overlay");
323
+ const K = o.sourceSymbols;
324
+ const T = o.symbolSize;
325
+ const P0 = o.repairSymbols;
326
+ if (intIn(K, 1) && intIn(P0, 1)) {
327
+ let P = P0;
328
+ if (!inst.overlay)
329
+ for (const tj of repairs) {
330
+ const t = trackAt(tj);
331
+ if (intIn(t.repairLayer, 1) && intIn(t.repairSymbols, 1))
332
+ P += t.repairSymbols;
333
+ }
334
+ if (P > MAX_24 || K + P - 1 > MAX_24)
335
+ report("fec_bounds", inst.path, inst);
336
+ else if (o.scheme === "reed-solomon" && K + P > RS_MAX_BLOCK)
337
+ report("fec_bounds", inst.path, inst);
315
338
  }
316
- if (layerZero.repairSymbols !== void 0 && layerZero.repairSymbols !== layerZeroSymbols) {
317
- fail(
318
- "track_repair_layer",
319
- `${layerZero.path}.repairSymbols`,
320
- `must agree with the source's fec.repairSymbols (${layerZeroSymbols})`
321
- );
339
+ if (o.scheme === "raptorq" && intIn(K, 1) && K > RAPTORQ_MAX_K)
340
+ report("fec_bounds", `${inst.path}/sourceSymbols`, inst);
341
+ if (intIn(T, 1)) {
342
+ if (o.scheme === "raptorq" && T % 8 !== 0)
343
+ report("fec_bounds", `${inst.path}/symbolSize`, inst);
344
+ else if (o.blockProfile === "iso-ssbg1-v1" && T < ISO_MIN_T)
345
+ report("fec_bounds", `${inst.path}/symbolSize`, inst);
322
346
  }
323
- if (typeof layerZero.priority === "number") layerZeroPriority = layerZero.priority;
324
347
  }
325
- const baseLayers = [
326
- { layer: 0, repairTrackName: layerZeroName, repairSymbols: layerZeroSymbols, rsIdOffset: 0, priority: layerZeroPriority }
327
- ];
328
- const overlays = [];
329
- for (const d of dependents) {
330
- if (d.scope === void 0 && d.repairLayer === void 0) {
331
- fail(
332
- "track_repair_layer",
333
- `${d.path}.repairLayer`,
334
- `a repair track that depends on ${JSON.stringify(source.name)} must declare repairLayer (1..n) or scope: "keyframe"`
335
- );
336
- }
337
- }
338
- const layerTracks = dependents.filter((d) => d.scope === void 0).sort((a, b) => a.repairLayer - b.repairLayer);
339
- for (const d of dependents) {
340
- if (d.scope !== "keyframe") continue;
341
- if (overlays.length === 1) {
342
- fail(
343
- "track_repair_overlay",
344
- `${d.path}.scope`,
345
- `${JSON.stringify(source.name)} already has keyframe overlay ${JSON.stringify(overlays[0].repairTrackName)}: a RAP packet carries exactly two Source FEC Payload IDs (base, then overlay), so one overlay per source`
346
- );
348
+ const overlayTracks = new Set(insts.filter((i) => live(i) && i.overlay).map((i) => i.ti));
349
+ for (const ti of overlayTracks) {
350
+ const ofTrack = insts.filter((i) => i.ti === ti && live(i));
351
+ const ovs = ofTrack.filter((i) => i.overlay);
352
+ if (!overlays) {
353
+ for (const ov of ovs)
354
+ report("fec_overlay_unsupported", `${ov.path}/scope`, ov);
355
+ for (const i of ofTrack)
356
+ i.issues.length === 0 && i.issues.push({ code: "fec_overlay_unsupported", path: `${ovs[0].path}/scope` });
357
+ continue;
347
358
  }
348
- const sourceSymbols = d.sourceSymbols;
349
- const repairSymbols = d.repairSymbols;
350
- if (sourceSymbols + repairSymbols - 1 > MAX_ENCODING_SYMBOL_ID) {
351
- fail(
352
- "track_fec_bounds",
353
- `${d.path}.repairSymbols`,
354
- `overlay sourceSymbols + repairSymbols - 1 exceeds the 24-bit ESI space (${MAX_ENCODING_SYMBOL_ID})`
355
- );
359
+ if (ovs.length > 1)
360
+ for (const ov of ovs)
361
+ report("fec_overlay", `${ov.path}/scope`, ov);
362
+ for (const ov of ovs)
363
+ if (ov.obj?.blockProfile !== "iso-ssbg1-v1")
364
+ report("fec_overlay", `${ov.path}/blockProfile`, ov);
365
+ const cause = ovs.flatMap((ov) => ov.issues).find((x) => x.code === "fec_overlay");
366
+ if (cause) {
367
+ for (const ov of ovs)
368
+ if (ov.issues.length === 0)
369
+ ov.issues.push(cause);
356
370
  }
357
- if (reedSolomon && sourceSymbols + repairSymbols > MAX_REED_SOLOMON_BLOCK_SYMBOLS) {
358
- fail(
359
- "track_fec_bounds",
360
- `${d.path}.repairSymbols`,
361
- `reed-solomon overlay sourceSymbols + repairSymbols exceeds ${MAX_REED_SOLOMON_BLOCK_SYMBOLS} (draft-ramadan-moq-fec-01 6.3.4 rule 12)`
362
- );
371
+ }
372
+ const rule4 = all.find((x) => x.code === "fec_enhancement_repair");
373
+ if (rejectAll && rule4) {
374
+ for (const i of insts)
375
+ if (i.issues.length === 0)
376
+ i.issues.push(rule4);
377
+ }
378
+ return {
379
+ instances: insts.map((i) => ({
380
+ path: i.path,
381
+ track: typeof i.track.name === "string" ? i.track.name : null,
382
+ instanceId: i.id,
383
+ outcome: rejectAll || !i.unavailable && i.issues.length > 0 ? "violation" : i.unavailable ? "unavailable" : "ok",
384
+ issues: i.issues,
385
+ trackIndex: i.ti,
386
+ repairTrack: i.layer0,
387
+ boundRepairTracks: i.attached.filter((tj) => trackAt(tj).packaging === "fec-repair"),
388
+ symbolTrack: i.symbolNamed ?? i.attached.find((tj) => trackAt(tj).packaging === "fec-source") ?? null
389
+ })),
390
+ issues: all
391
+ };
392
+ }
393
+
394
+ // ../mmt-base/dist/fec-bindings.js
395
+ var RUNTIME_FEC_RECEIVER = Object.freeze({
396
+ schemes: Object.freeze(["raptorq", "reed-solomon"]),
397
+ blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
398
+ overlays: true
399
+ });
400
+ var DEFAULT_FEC_REPAIR_PRIORITY = 240;
401
+ var recordOf = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
402
+ var escape = (s) => s.replace(/~/g, "~0").replace(/\//g, "~1");
403
+ function fecCatalogTracks(catalog) {
404
+ const root = recordOf(catalog) ?? {};
405
+ const tracks = [];
406
+ const pointers = [];
407
+ const hangNames = /* @__PURE__ */ new Set();
408
+ for (const role of ["video", "audio"]) {
409
+ for (const [name, r] of Object.entries(recordOf(recordOf(root[role])?.["renditions"]) ?? {})) {
410
+ const entry = recordOf(r);
411
+ if (!entry)
412
+ continue;
413
+ hangNames.add(name);
414
+ const kind = recordOf(entry["container"])?.["kind"];
415
+ const view = { name, packaging: kind === void 0 || kind === "mmtp-fec" ? "mmtp" : kind };
416
+ if (entry["fec"] !== void 0)
417
+ view["fec"] = entry["fec"];
418
+ tracks.push(view);
419
+ pointers.push(`/${role}/renditions/${escape(name)}`);
363
420
  }
364
- overlays.push({ repairTrackName: d.name, scope: "keyframe", sourceSymbols, repairSymbols, priority: d.priority });
365
421
  }
366
- let offset = layerZeroSymbols;
367
- let previousPriority = layerZeroPriority;
368
- layerTracks.forEach((d, i) => {
369
- const expected = i + 1;
370
- if (d.repairLayer === 0) {
371
- fail("track_repair_layer", `${d.path}.repairLayer`, `layer 0 of ${JSON.stringify(source.name)} is ${JSON.stringify(layerZeroName)}, named by fec.repairTrack`);
422
+ const hangRenditions = tracks.length;
423
+ for (const [name, r] of Object.entries(recordOf(recordOf(root["repair"])?.["renditions"]) ?? {})) {
424
+ hangNames.add(name);
425
+ tracks.push({ packaging: "fec-repair", ...recordOf(r), name });
426
+ pointers.push(`/repair/renditions/${escape(name)}`);
427
+ }
428
+ const raw = Array.isArray(root["tracks"]) ? root["tracks"] : [];
429
+ const named = new Set(hangNames);
430
+ raw.forEach((t, i) => {
431
+ const name = recordOf(t)?.["name"];
432
+ if (typeof name === "string")
433
+ named.add(name);
434
+ if (typeof name === "string" && hangNames.has(name))
435
+ return;
436
+ tracks.push(t);
437
+ pointers.push(`/tracks/${i}`);
438
+ });
439
+ tracks.slice(0, hangRenditions).forEach((t, ti) => {
440
+ const view = t;
441
+ if (!Array.isArray(view["fec"]))
442
+ return;
443
+ view["fec"].forEach((o, ii) => {
444
+ const repairTrack = recordOf(o)?.["repairTrack"];
445
+ if (typeof repairTrack !== "string" || named.has(repairTrack))
446
+ return;
447
+ named.add(repairTrack);
448
+ const instanceId = recordOf(o)?.["instanceId"];
449
+ tracks.push({ name: repairTrack, packaging: "fec-repair", source: view["name"], ...instanceId === void 0 ? {} : { instanceId } });
450
+ pointers.push(`${pointers[ti]}/fec/${ii}/repairTrack`);
451
+ });
452
+ });
453
+ return { tracks, pointers };
454
+ }
455
+ function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
456
+ const { tracks, pointers } = fecCatalogTracks(catalog);
457
+ const report = validateFecCatalog({ tracks }, receiver);
458
+ const at = (path) => {
459
+ const m = /^\/tracks\/(\d+)(.*)$/.exec(path);
460
+ return m ? pointers[Number(m[1])] + m[2] : path;
461
+ };
462
+ const trackAt = (i) => recordOf(tracks[i]) ?? {};
463
+ const nameAt = (i) => i === null ? void 0 : trackAt(i)["name"];
464
+ const why = (i) => Object.freeze({
465
+ code: i.issues[0]?.code ?? "fec_member",
466
+ path: at(i.issues[0]?.path ?? i.path),
467
+ outcome: i.outcome === "unavailable" ? "unavailable" : "violation"
468
+ });
469
+ const out = /* @__PURE__ */ new Map();
470
+ tracks.forEach((t, ti) => {
471
+ const track = recordOf(t);
472
+ const name = track?.["name"];
473
+ if (!track || typeof name !== "string" || out.has(name))
474
+ return;
475
+ if (track["packaging"] === "fec-repair" || track["packaging"] === "fec-source")
476
+ return;
477
+ const fec = track["fec"];
478
+ if (fec === void 0)
479
+ return;
480
+ if (!Array.isArray(fec)) {
481
+ const reason = { code: "fec_member", path: at(`/tracks/${ti}/fec`), outcome: "violation" };
482
+ out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason: Object.freeze(reason) }));
483
+ return;
372
484
  }
373
- if (d.repairLayer !== expected) {
374
- fail(
375
- "track_repair_layer",
376
- `${d.path}.repairLayer`,
377
- i > 0 && d.repairLayer === layerTracks[i - 1].repairLayer ? `repair layer ${d.repairLayer} is declared twice for ${JSON.stringify(source.name)}` : `repair layers must be contiguous from 1: expected ${expected}, found ${d.repairLayer}`
485
+ if (fec.length === 0)
486
+ return;
487
+ const insts = report.instances.filter((i) => i.trackIndex === ti);
488
+ const isOverlay = (i) => Object.hasOwn(recordOf(fec[Number(i.path.split("/").pop())]) ?? {}, "scope");
489
+ const bases = insts.filter((i) => !isOverlay(i));
490
+ const okBases = bases.filter((i) => i.outcome === "ok");
491
+ if (okBases.length !== 1) {
492
+ const reason = (
493
+ // No base instance at all (overlay-only `fec`) is a count of 0, not
494
+ // the overlay's own issue.
495
+ okBases.length > 1 || bases.length === 0 ? Object.freeze({ code: "fec_base_instances", path: at(`/tracks/${ti}/fec`), outcome: "violation" }) : why(bases[0] ?? insts[0])
378
496
  );
497
+ out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason }));
498
+ return;
379
499
  }
380
- const priority = d.priority;
381
- if (priority < previousPriority) {
382
- fail(
383
- "track_repair_priority",
384
- `${d.path}.priority`,
385
- `must be at least ${previousPriority}, the priority of layer ${expected - 1}: priority is monotonic non-decreasing with layer index`
386
- );
500
+ const b = okBases[0];
501
+ const base = geometry(recordOf(fec[Number(b.path.split("/").pop())]), b.instanceId, nameAt(b.symbolTrack));
502
+ const priorityOf = (i) => {
503
+ const p = i === null ? void 0 : trackAt(i)["priority"];
504
+ return typeof p === "number" ? p : DEFAULT_FEC_REPAIR_PRIORITY;
505
+ };
506
+ const layers = [
507
+ { layer: 0, repairTrackName: base.repairTrack, repairSymbols: base.repairSymbols, rsIdOffset: 0, priority: priorityOf(b.repairTrack) }
508
+ ];
509
+ const more = b.boundRepairTracks.map((tj) => ({ tj, layer: trackAt(tj)["repairLayer"] })).sort((x, y) => x.layer - y.layer);
510
+ let offset = base.repairSymbols;
511
+ for (const { tj, layer } of more) {
512
+ const repairSymbols = trackAt(tj)["repairSymbols"];
513
+ layers.push({ layer, repairTrackName: nameAt(tj), repairSymbols, rsIdOffset: offset, priority: priorityOf(tj) });
514
+ offset += repairSymbols;
387
515
  }
388
- previousPriority = priority;
389
- const repairSymbols = d.repairSymbols;
390
- baseLayers.push({ layer: expected, repairTrackName: d.name, repairSymbols, rsIdOffset: offset, priority });
391
- offset += repairSymbols;
516
+ let overlay;
517
+ let overlayUnavailable;
518
+ const ov = insts.find(isOverlay);
519
+ if (ov?.outcome === "ok") {
520
+ const g = geometry(recordOf(fec[Number(ov.path.split("/").pop())]), ov.instanceId, nameAt(ov.symbolTrack));
521
+ overlay = Object.freeze({
522
+ repairTrackName: g.repairTrack,
523
+ scope: "keyframe",
524
+ sourceSymbols: g.sourceSymbols,
525
+ repairSymbols: g.repairSymbols,
526
+ priority: priorityOf(ov.repairTrack),
527
+ geometry: g
528
+ });
529
+ } else if (ov)
530
+ overlayUnavailable = why(ov);
531
+ out.set(name, Object.freeze({
532
+ status: "ok",
533
+ track: name,
534
+ pointer: pointers[ti],
535
+ base,
536
+ layers: Object.freeze(layers.map((l) => Object.freeze(l))),
537
+ totalBaseRepairSymbols: offset,
538
+ ...overlay ? { overlay } : {},
539
+ ...overlayUnavailable ? { overlayUnavailable } : {}
540
+ }));
541
+ });
542
+ return out;
543
+ }
544
+ function geometry(o, instanceId, symbolTrack) {
545
+ return Object.freeze({
546
+ instanceId,
547
+ scheme: o["scheme"],
548
+ blockProfile: o["blockProfile"],
549
+ sourceSymbols: o["sourceSymbols"],
550
+ repairSymbols: o["repairSymbols"],
551
+ symbolSize: o["symbolSize"],
552
+ ...typeof o["interleaveDepthMs"] === "number" ? { interleaveDepthMs: o["interleaveDepthMs"] } : {},
553
+ ...typeof o["reorderToleranceMs"] === "number" ? { reorderToleranceMs: o["reorderToleranceMs"] } : {},
554
+ repairTrack: o["repairTrack"],
555
+ ...symbolTrack !== void 0 ? { symbolTrack } : {}
392
556
  });
393
- const totalBaseRepairSymbols = offset;
394
- if (totalBaseRepairSymbols > MAX_REPAIR_SYMBOLS) {
395
- fail(
396
- "track_fec_bounds",
397
- `${layerTracks.length === 0 ? source.path : layerTracks[layerTracks.length - 1].path}.repairSymbols`,
398
- `total repair symbols across base layers exceeds the 24-bit RS_ID space (${MAX_REPAIR_SYMBOLS})`
399
- );
400
- }
401
- if (k + totalBaseRepairSymbols - 1 > MAX_ENCODING_SYMBOL_ID) {
402
- fail(
403
- "track_fec_bounds",
404
- `${source.path}.sourceSymbols`,
405
- `sourceSymbols + total base repair symbols - 1 exceeds the 24-bit ESI space (${MAX_ENCODING_SYMBOL_ID})`
406
- );
407
- }
408
- if (reedSolomon && k + totalBaseRepairSymbols > MAX_REED_SOLOMON_BLOCK_SYMBOLS) {
409
- fail(
410
- "track_fec_bounds",
411
- `${source.path}.sourceSymbols`,
412
- `reed-solomon sourceSymbols + total base repair symbols exceeds ${MAX_REED_SOLOMON_BLOCK_SYMBOLS} (draft-ramadan-moq-fec-01 6.3.4 rule 12)`
413
- );
414
- }
415
- return { sourceTrackName: source.name, baseLayers, overlays, totalBaseRepairSymbols };
416
557
  }
417
558
 
559
+ // src/catalog-fec-params.ts
560
+ import { catalogFecInterleaveMs } from "@blockcast/mmt-fec";
561
+
418
562
  // src/carp-catalog.ts
419
563
  function isTrackTimingConfig(obj) {
420
564
  if (typeof obj !== "object" || obj === null) return false;
@@ -452,28 +596,13 @@ function resolveGroupDurationMs(timing, timescale) {
452
596
  return durationMs;
453
597
  }
454
598
 
455
- // src/fec-state.ts
456
- var FEC_INACTIVE_REASONS = Object.freeze([
457
- "no-catalog-fec",
458
- "reliable-moq",
459
- "no-multicast-bridge"
460
- ]);
461
- var INACTIVE = Object.freeze({
462
- "no-catalog-fec": Object.freeze({ state: "inactive", reason: "no-catalog-fec" }),
463
- "reliable-moq": Object.freeze({ state: "inactive", reason: "reliable-moq" }),
464
- "no-multicast-bridge": Object.freeze({ state: "inactive", reason: "no-multicast-bridge" })
465
- });
466
- function recordOf2(value) {
467
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
468
- }
469
- function isPositiveSafeInteger2(value) {
470
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
471
- }
472
- function catalogDeclaresFec(fec) {
473
- const block = recordOf2(fec);
474
- if (block === void 0 || block["algorithm"] === "none" || fecAlgorithmUnimplemented(block["algorithm"])) return false;
475
- return isPositiveSafeInteger2(block["sourceSymbols"]) && isPositiveSafeInteger2(block["symbolSize"]) && isFecBlockProfile(block["blockProfile"]) && isoBlockSpanUsable(block) && !catalogFecDeclaresUnconfigurableInstance(block);
476
- }
599
+ // src/repair-layers.ts
600
+ var REPAIR_PRIORITY_MIN = 192;
601
+ var REPAIR_PRIORITY_MAX = 255;
602
+ var DEFAULT_REPAIR_PRIORITY = 240;
603
+ var MAX_RAPTORQ_SOURCE_SYMBOLS = 56403;
604
+ var MAX_REPAIR_SYMBOLS = 16777215;
605
+ var MAX_ENCODING_SYMBOL_ID = 16777215;
477
606
 
478
607
  // src/catalog-fec-params.ts
479
608
  var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
@@ -528,137 +657,50 @@ function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
528
657
  }
529
658
  return total;
530
659
  }
531
- function recordOf3(value) {
532
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
660
+ function bindingOf(catalog, source) {
661
+ if (!catalog) return void 0;
662
+ const name = typeof source === "string" ? source : source.name;
663
+ const binding = resolveFecBindings(catalog).get(name);
664
+ if (binding === void 0 || typeof source === "string") return binding;
665
+ const role = binding.pointer.split("/")[1];
666
+ return role === "tracks" || role === source.type ? binding : void 0;
533
667
  }
534
- function msfTracks(catalog) {
535
- const raw = catalog.tracks;
536
- if (!Array.isArray(raw)) return void 0;
537
- return { tracks: raw.map((t) => recordOf3(t) ?? {}) };
668
+ function catalogTrackFecUnavailable(catalog, source) {
669
+ const binding = bindingOf(catalog, source);
670
+ return binding?.status === "unavailable" ? binding.reason : void 0;
538
671
  }
539
672
  function resolveRepairLayers(catalog, source) {
540
- if (!catalog) return void 0;
541
- const sourceName = typeof source === "string" ? source : source.name;
542
- const hang = catalog;
543
- const roles = typeof source === "string" ? ["video", "audio"] : source.type === "audio" ? ["audio"] : source.type === "video" ? ["video"] : ["video", "audio"];
544
- const located = locateCatalogFecSource(catalog, sourceName, roles);
545
- if (located?.dialect === "hang") {
546
- const renditionPath = `$.${located.role}.renditions[${JSON.stringify(sourceName)}]`;
547
- const rendition = located.entry;
548
- rejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);
549
- const repairRenditions = recordOf3(hang.repair?.renditions) ?? {};
550
- const descriptors2 = Object.entries(repairRenditions).map(([name, entry]) => {
551
- const path = `$.repair.renditions[${JSON.stringify(name)}]`;
552
- const fields = recordOf3(entry);
553
- if (fields === void 0) {
554
- throwRepairContractError("track_repair_layer", path, "a repair rendition must be an object");
555
- }
556
- rejectRepairTrackGeometry(path, fields, throwRepairContractError);
557
- return describeRepairTrack(name, path, fields);
558
- });
559
- const fecOfRendition = (entry) => {
560
- const fec2 = recordOf3(recordOf3(entry)?.["fec"]);
561
- return fec2 !== void 0 && fec2["algorithm"] !== "none" ? fec2 : void 0;
562
- };
563
- const mediaRenditions = [
564
- ...Object.values(recordOf3(hang.video?.renditions) ?? {}),
565
- ...Object.values(recordOf3(hang.audio?.renditions) ?? {})
566
- ];
567
- const reachability2 = repairReachability(mediaRenditions.map((entry) => recordOf3(entry)?.["fec"]));
568
- for (const d of descriptors2) {
569
- validateRepairTrackFields(d, throwRepairContractError);
570
- assertRepairTrackReachable(d, reachability2, throwRepairContractError);
571
- const target = dependsTarget(d);
572
- if (target === void 0) continue;
573
- const targetFec = fecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);
574
- if (targetFec === void 0) {
575
- throwRepairContractError(
576
- "track_repair_depends",
577
- `${d.path}.depends`,
578
- `${JSON.stringify(target)} is not a video or audio rendition with FEC`
579
- );
580
- }
673
+ const binding = bindingOf(catalog, source);
674
+ if (binding?.status !== "ok") return void 0;
675
+ const overlays = binding.overlay ? [
676
+ {
677
+ repairTrackName: binding.overlay.repairTrackName,
678
+ scope: "keyframe",
679
+ sourceSymbols: binding.overlay.sourceSymbols,
680
+ repairSymbols: binding.overlay.repairSymbols,
681
+ priority: binding.overlay.priority
581
682
  }
582
- const layers2 = assembleRepairLayers(
583
- geometryOf(sourceName, `${renditionPath}.fec`, recordOf3(rendition["fec"])),
584
- descriptors2,
585
- throwRepairContractError
586
- );
587
- return catalogFecUnavailable(rendition["fec"], `${renditionPath}.fec`) === void 0 ? layers2 : void 0;
588
- }
589
- const msf = msfTracks(catalog);
590
- if (!msf || located?.dialect !== "msf") return void 0;
591
- const msfIndex = located.index;
592
- const track = msf.tracks[msfIndex];
593
- const fec = recordOf3(track["fec"]);
594
- const packagingOf = (t) => t["packaging"];
595
- const fecOfTrack = (t) => {
596
- const trackFec = recordOf3(t?.["fec"]);
597
- return trackFec !== void 0 && trackFec["algorithm"] !== "none" ? trackFec : void 0;
683
+ ] : [];
684
+ return {
685
+ sourceTrackName: binding.track,
686
+ baseLayers: binding.layers,
687
+ overlays,
688
+ totalBaseRepairSymbols: binding.totalBaseRepairSymbols
598
689
  };
599
- const descriptors = [];
600
- const mediaTracks = [];
601
- msf.tracks.forEach((t, i) => {
602
- const path = `$.tracks[${i}]`;
603
- if (packagingOf(t) === "fec-repair") {
604
- rejectRepairTrackGeometry(path, t, throwRepairContractError);
605
- if (typeof t["name"] === "string") descriptors.push(describeRepairTrack(t["name"], path, t));
606
- return;
607
- }
608
- rejectRepairFieldsOnMediaTrack(path, t, throwRepairContractError);
609
- mediaTracks.push(t);
610
- });
611
- const reachability = repairReachability(mediaTracks.map((t) => t["fec"]));
612
- for (const d of descriptors) {
613
- validateRepairTrackFields(d, throwRepairContractError);
614
- assertRepairTrackReachable(d, reachability, throwRepairContractError);
615
- const target = dependsTarget(d);
616
- if (target === void 0) continue;
617
- const targetTrack = msf.tracks.find((t) => t["name"] === target);
618
- if (targetTrack === void 0 || packagingOf(targetTrack) !== "mmtp" || fecOfTrack(targetTrack) === void 0) {
619
- throwRepairContractError(
620
- "track_repair_depends",
621
- `${d.path}.depends`,
622
- `${JSON.stringify(target)} is not an mmtp track with FEC`
623
- );
624
- }
625
- }
626
- const layers = assembleRepairLayers(
627
- geometryOf(sourceName, `$.tracks[${msfIndex}].fec`, fec),
628
- descriptors,
629
- throwRepairContractError,
630
- { layerZeroMustBeDeclared: true }
631
- );
632
- return catalogFecUnavailable(fec, `$.tracks[${msfIndex}].fec`) === void 0 ? layers : void 0;
633
690
  }
634
691
  function moqtPriority(value) {
635
692
  return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
636
693
  }
637
694
  function catalogRepairTarget(catalog, source) {
638
- const repair = resolveRepairLayers(catalog, source);
639
- const layerZero = repair?.baseLayers[0];
640
- if (!catalog || !layerZero) return null;
641
- const sourceName = typeof source === "string" ? source : source.name;
642
- const msf = msfTracks(catalog);
643
- const entry = msf?.tracks.find((t) => t["name"] === sourceName);
644
- const hang = catalog;
645
- const roles = typeof source === "string" ? ["video", "audio"] : source.type === "video" ? ["video"] : source.type === "audio" ? ["audio"] : ["video", "audio"];
646
- const rendition = roles.map((role) => hang[role]?.renditions?.[sourceName]).find((r) => recordOf3(r) !== void 0);
647
- const fec = rendition === void 0 ? recordOf3(entry?.["fec"]) : recordOf3(rendition.fec);
648
- if (!fec || !(catalogFecInterleaveMs(fec) ?? 0)) return null;
649
- const sourcePriority = entry === void 0 ? null : moqtPriority(entry["priority"]);
695
+ const binding = bindingOf(catalog, source);
696
+ if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
697
+ const layerZero = binding.layers[0];
698
+ const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
699
+ const tracks = catalog.tracks;
700
+ const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
701
+ const sourcePriority = entry ? moqtPriority(entry["priority"]) : null;
650
702
  return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
651
703
  }
652
- function geometryOf(name, path, fec) {
653
- return {
654
- name,
655
- path,
656
- algorithm: fec?.["algorithm"],
657
- sourceSymbols: fec?.["sourceSymbols"],
658
- repairSymbols: fec?.["repairSymbols"],
659
- repairTrack: fec?.["repairTrack"]
660
- };
661
- }
662
704
  function numericField(value) {
663
705
  const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
664
706
  return Number.isFinite(n) && n > 0 ? n : void 0;
@@ -667,79 +709,28 @@ function nonNegativeField(value) {
667
709
  const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
668
710
  return Number.isFinite(n) && n >= 0 ? n : void 0;
669
711
  }
670
- function blockProfileUnavailable(value, path, declaresFecInstance) {
671
- try {
672
- readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
673
- return void 0;
674
- } catch (error) {
675
- if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
676
- throw error;
677
- }
678
- return Object.freeze({ code: error.code, path: error.path, message: error.message });
679
- }
680
- }
681
- function isoSpanUnavailable(fec, path, declaresFecInstance) {
682
- if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
683
- return Object.freeze({
684
- code: "track_fec",
685
- path,
686
- message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
687
- });
688
- }
689
- function catalogFecUnavailable(fec, fecPath) {
690
- if (catalogFecAvailable(fec)) return void 0;
691
- const block = recordOf3(fec);
692
- if (fecAlgorithmUnimplemented(block["algorithm"])) {
693
- return Object.freeze({
694
- code: "track_fec_algorithm_unimplemented",
695
- path: `${fecPath}.algorithm`,
696
- message: `algorithm ${JSON.stringify(block["algorithm"])} is not implemented: the track plays without FEC`
697
- });
698
- }
699
- const declaresFecInstance = block["algorithm"] !== "none";
700
- const reason = blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
701
- if (reason === void 0) {
702
- throw new Error(`catalogFecUnavailable: ${fecPath} is unavailable but no reason applies (rule drift)`);
703
- }
704
- return reason;
705
- }
706
- function fecParams(catalog, track, rendition, fps, sampleRate) {
712
+ function fecParams(catalog, track, fps, sampleRate) {
707
713
  const mediaRate = sampleRate === void 0 ? { fps } : { fps, sampleRate };
708
- const fec = rendition?.fec;
709
- if (!fec || !track) return { ...NO_FEC, ...mediaRate };
714
+ const binding = track ? bindingOf(catalog, track) : void 0;
715
+ if (binding === void 0) return { ...NO_FEC, ...mediaRate };
716
+ if (binding.status === "unavailable") return { ...NO_FEC, ...mediaRate, fecUnavailable: binding.reason };
717
+ const { base } = binding;
710
718
  const repair = resolveRepairLayers(catalog, track);
711
- const fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;
712
- if (!catalogDeclaresFec(fec)) {
713
- if (catalogFecDeclaresUnconfigurableInstance(fec)) {
714
- const kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;
715
- throwRepairContractError(
716
- "track_fec",
717
- `${fecPath}.${kValid ? "symbolSize" : "sourceSymbols"}`,
718
- kValid && Number.isSafeInteger(fec.symbolSize) && (fec.symbolSize ?? 0) > 0 ? `iso-ssbg1-v1 symbolSize must be at least ${ISO_SSBG1_MIN_SYMBOL_SIZE}` : "must be a positive integer"
719
- );
720
- }
721
- const fecUnavailable = catalogFecUnavailable(fec, fecPath);
722
- return fecUnavailable === void 0 ? { ...NO_FEC, ...mediaRate } : { ...NO_FEC, ...mediaRate, fecUnavailable };
723
- }
724
- const fecBlockProfile = readFecBlockProfile(fec.blockProfile, `${fecPath}.blockProfile`, true, throwRepairContractError);
725
719
  return {
726
- fecK: fec.sourceSymbols ?? 0,
727
- fecAlgorithm: fec.algorithm === "reed-solomon" ? "reed-solomon" : "raptorq",
728
- fecBlockProfile,
729
- fecRepairCount: fec.repairSymbols ?? 0,
730
- // The wire RSB_length: every base layer's packets carry the base total (§7.1).
731
- fecTotalRepairCount: repair?.totalBaseRepairSymbols ?? fec.repairSymbols ?? 0,
732
- fecSymbolSize: fec.symbolSize ?? 0,
720
+ fecK: base.sourceSymbols,
721
+ fecAlgorithm: base.scheme,
722
+ fecBlockProfile: parseFecBlockProfile(base.blockProfile, `${binding.pointer}/fec/blockProfile`),
723
+ fecRepairCount: base.repairSymbols,
724
+ // The wire RSB_length: every base layer's packets carry the base total.
725
+ fecTotalRepairCount: binding.totalBaseRepairSymbols,
726
+ fecSymbolSize: base.symbolSize,
733
727
  // Zero/absence leaves the elapsed deadline unknown; geometry stays in-band.
734
- fecInterleaveMs: catalogFecInterleaveMs(fec) ?? 0,
735
- // Additive, never a multiplier on the span: jitter belongs to the network
736
- // path and the span belongs to the encoder, so scaling one by the other
737
- // hands one network different slack per track. `numericField` rejects 0,
738
- // so it cannot be reused here — a declared 0 is a legitimate value.
739
- fecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),
740
- repairTrackName: fec.repairTrack,
741
- repairLayers: repair?.baseLayers,
742
- repairOverlays: repair?.overlays,
728
+ fecInterleaveMs: catalogFecInterleaveMs(base) ?? 0,
729
+ // Additive to the span, never a multiplier; a declared 0 stays 0.
730
+ fecReorderToleranceMs: nonNegativeField(base.reorderToleranceMs),
731
+ repairTrackName: base.repairTrack,
732
+ repairLayers: repair.baseLayers,
733
+ repairOverlays: repair.overlays,
743
734
  ...mediaRate
744
735
  };
745
736
  }
@@ -753,11 +744,10 @@ function resolveCatalogTrackFec(catalog, tracks) {
753
744
  const catalogFps = numericField(video?.framerate);
754
745
  const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
755
746
  const audioFps = sampleRate ? sampleRate / 1024 : void 0;
756
- const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, video, catalogFps);
747
+ const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, catalogFps);
757
748
  const audioParams = fecParams(
758
749
  catalog,
759
750
  audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
760
- audio,
761
751
  audioFps,
762
752
  sampleRate
763
753
  );
@@ -767,12 +757,12 @@ function resolveCatalogTrackFec(catalog, tracks) {
767
757
  const rendition2 = audioRenditions?.[track.name];
768
758
  if (!rendition2) return { ...NO_FEC };
769
759
  const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
770
- return fecParams(catalog, { name: track.name, type: "audio" }, rendition2, rate ? rate / 1024 : void 0, rate);
760
+ return fecParams(catalog, { name: track.name, type: "audio" }, rate ? rate / 1024 : void 0, rate);
771
761
  }
772
762
  if (track.name === videoTrackName) return videoParams;
773
763
  const rendition = videoRenditions?.[track.name];
774
764
  if (!rendition) return { ...NO_FEC };
775
- return fecParams(catalog, { name: track.name, type: "video" }, rendition, numericField(rendition.framerate));
765
+ return fecParams(catalog, { name: track.name, type: "video" }, numericField(rendition.framerate));
776
766
  };
777
767
  return {
778
768
  videoTrackName,
@@ -794,33 +784,18 @@ export {
794
784
  MAX_REPAIR_SYMBOLS,
795
785
  REPAIR_PRIORITY_MAX,
796
786
  REPAIR_PRIORITY_MIN,
797
- REPAIR_TRACK_GEOMETRY_KEYS,
798
- REPAIR_TRACK_ONLY_KEYS,
799
- RepairContractError,
800
- assembleRepairLayers,
801
- assertRepairTrackReachable,
802
- catalogFecAvailable,
803
- catalogFecDeclaresUnconfigurableInstance,
804
787
  catalogFecInterleaveMs,
805
788
  catalogFecMediaUnitsPerSecond,
806
- catalogFecUnavailable,
807
789
  catalogGroupDurationMs,
808
790
  catalogKeyframeCadenceMs,
809
791
  catalogRepairTarget,
810
- dependsTarget,
811
- describeRepairTrack,
812
- fecAlgorithmUnimplemented,
792
+ catalogTrackFecUnavailable,
813
793
  isFecBlockProfile,
814
794
  joinedRepairSymbolCount,
815
- locateCatalogFecSource,
816
795
  parseFecBlockProfile,
817
- readFecBlockProfile,
818
- rejectRepairFieldsOnMediaTrack,
819
- rejectRepairTrackGeometry,
820
- repairReachability,
821
796
  resolveCatalogTrackFec,
822
797
  resolveCatalogTrackGroupDurationMs,
823
798
  resolveRepairLayers,
824
- validateRepairTrackFields
799
+ validateFecCatalog
825
800
  };
826
801
  //# sourceMappingURL=catalog-fec-params.js.map