@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
@@ -1,3 +1,550 @@
1
+ // ../mmt-base/dist/mmtp.js
2
+ var MPU_HEADER_SIZE = 8;
3
+ var MPU_LENGTH_FIELD_SIZE = 2;
4
+ var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
5
+
6
+ // ../mmt-base/dist/ntp-short.js
7
+ var NTP_SHORT_BITS = 32;
8
+ var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
9
+ var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
10
+
11
+ // ../mmt-base/dist/fec-block-profile.js
12
+ var FEC_BLOCK_PROFILES = Object.freeze([
13
+ "iso-ssbg1-v1"
14
+ ]);
15
+
16
+ // ../mmt-base/dist/fec-catalog.js
17
+ var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
18
+ var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
19
+ var RS_MAX_BLOCK = 255;
20
+ var RAPTORQ_MAX_K = 56403;
21
+ var MAX_24 = 16777215;
22
+ var ISO_MIN_T = 14;
23
+ var GEOMETRY_KEYS = [
24
+ "scheme",
25
+ "blockProfile",
26
+ "mmtpMode",
27
+ "timescale",
28
+ "symbolSize",
29
+ "interleaveDepthMs",
30
+ "reorderToleranceMs",
31
+ "sourceSymbols",
32
+ "fec"
33
+ ];
34
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
35
+ var has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
36
+ var isInt = (v) => typeof v === "number" && Number.isInteger(v);
37
+ var intIn = (v, lo, hi = Number.MAX_SAFE_INTEGER) => isInt(v) && v >= lo && v <= hi;
38
+ var profileAdmits = (profile, packaging) => profile !== "iso-ssbg1-v1" || packaging === "mmtp";
39
+ function validateFecCatalog(catalog, receiver = {}) {
40
+ const schemes = receiver.schemes ?? DEFAULT_SCHEMES;
41
+ const profiles = receiver.blockProfiles ?? DEFAULT_PROFILES;
42
+ const overlays = receiver.overlays ?? true;
43
+ const all = [];
44
+ const report = (code, path, inst) => {
45
+ const issue = { code, path };
46
+ all.push(issue);
47
+ inst?.issues.push(issue);
48
+ };
49
+ const tracks = isObj(catalog) && Array.isArray(catalog.tracks) ? catalog.tracks : [];
50
+ if (!isObj(catalog) || !Array.isArray(catalog.tracks))
51
+ report("fec_member", "/tracks");
52
+ let rejectAll = false;
53
+ tracks.forEach((t, ti) => {
54
+ if (!isObj(t))
55
+ return;
56
+ if (has(t, "enhancementRepair")) {
57
+ rejectAll = true;
58
+ report("fec_enhancement_repair", `/tracks/${ti}/enhancementRepair`);
59
+ }
60
+ if (Array.isArray(t.fec))
61
+ t.fec.forEach((o, ii) => {
62
+ if (isObj(o) && has(o, "enhancementRepair")) {
63
+ rejectAll = true;
64
+ report("fec_enhancement_repair", `/tracks/${ti}/fec/${ii}/enhancementRepair`);
65
+ }
66
+ });
67
+ });
68
+ const key = (ns, name) => JSON.stringify([ns ?? null, name]);
69
+ const byName = /* @__PURE__ */ new Map();
70
+ tracks.forEach((t, ti) => {
71
+ if (isObj(t) && typeof t.name === "string") {
72
+ const k = key(t.namespace, t.name);
73
+ if (!byName.has(k))
74
+ byName.set(k, ti);
75
+ }
76
+ });
77
+ const trackAt = (ti) => tracks[ti];
78
+ const insts = [];
79
+ tracks.forEach((t, ti) => {
80
+ if (!isObj(t) || !has(t, "fec") || t.packaging === "fec-repair" || t.packaging === "fec-source")
81
+ return;
82
+ if (!Array.isArray(t.fec)) {
83
+ report("fec_member", `/tracks/${ti}/fec`);
84
+ return;
85
+ }
86
+ t.fec.forEach((o, ii) => {
87
+ const path = `/tracks/${ti}/fec/${ii}`;
88
+ const inst = {
89
+ path,
90
+ ti,
91
+ track: t,
92
+ obj: isObj(o) ? o : null,
93
+ id: null,
94
+ unavailable: false,
95
+ overlay: false,
96
+ issues: [],
97
+ layer0: null,
98
+ symbolNamed: null,
99
+ attached: []
100
+ };
101
+ insts.push(inst);
102
+ if (!inst.obj)
103
+ return report("fec_member", path, inst);
104
+ const o2 = inst.obj;
105
+ if (!has(o2, "instanceId"))
106
+ inst.id = 0;
107
+ else if (intIn(o2.instanceId, 0, 255))
108
+ inst.id = o2.instanceId;
109
+ else
110
+ report("fec_member", `${path}/instanceId`, inst);
111
+ inst.overlay = has(o2, "scope");
112
+ });
113
+ });
114
+ const declaresFec = insts.length > 0;
115
+ for (const inst of insts) {
116
+ const o = inst.obj;
117
+ if (!o)
118
+ continue;
119
+ const avail = (field, impl) => {
120
+ const p = `${inst.path}/${field}`;
121
+ const absent = field === "scheme" ? "fec_scheme_absent" : "fec_block_profile_absent";
122
+ const unimpl = field === "scheme" ? "fec_scheme_unimplemented" : "fec_block_profile_unimplemented";
123
+ if (!has(o, field))
124
+ return report(absent, p, inst), false;
125
+ if (typeof o[field] !== "string")
126
+ return report("fec_member", p, inst), true;
127
+ if (!impl.includes(o[field]))
128
+ return report(unimpl, p, inst), false;
129
+ return true;
130
+ };
131
+ if (!avail("scheme", schemes) || !avail("blockProfile", profiles))
132
+ inst.unavailable = true;
133
+ else if (typeof o.blockProfile === "string" && !profileAdmits(o.blockProfile, inst.track.packaging)) {
134
+ report("fec_block_profile_packaging", `${inst.path}/blockProfile`, inst);
135
+ inst.unavailable = true;
136
+ }
137
+ }
138
+ const live = (i) => !i.unavailable;
139
+ for (const inst of insts.filter(live)) {
140
+ const o = inst.obj;
141
+ if (!o)
142
+ continue;
143
+ const p = inst.path;
144
+ for (const m of ["sourceSymbols", "repairSymbols", "symbolSize"])
145
+ if (!intIn(o[m], 1))
146
+ report("fec_member", `${p}/${m}`, inst);
147
+ if (typeof o.repairTrack !== "string")
148
+ report("fec_member", `${p}/repairTrack`, inst);
149
+ if (has(o, "symbolTrack") && typeof o.symbolTrack !== "string")
150
+ report("fec_member", `${p}/symbolTrack`, inst);
151
+ if (has(o, "interleaveDepthMs")) {
152
+ const d = o.interleaveDepthMs;
153
+ if (!intIn(d, 0) || d === 0 && o.blockProfile !== "iso-ssbg1-v1")
154
+ report("fec_member", `${p}/interleaveDepthMs`, inst);
155
+ }
156
+ if (has(o, "reorderToleranceMs")) {
157
+ const r = o.reorderToleranceMs;
158
+ if (typeof r !== "number" || !Number.isFinite(r) || r < 0)
159
+ report("fec_member", `${p}/reorderToleranceMs`, inst);
160
+ }
161
+ if (has(o, "label") && typeof o.label !== "string")
162
+ report("fec_member", `${p}/label`, inst);
163
+ if (inst.overlay && o.scope !== "keyframe")
164
+ report("fec_member", `${p}/scope`, inst);
165
+ }
166
+ const idGroups = /* @__PURE__ */ new Map();
167
+ for (const inst of insts)
168
+ if (inst.id !== null) {
169
+ const k = `${inst.ti}:${inst.id}`;
170
+ idGroups.set(k, [...idGroups.get(k) ?? [], inst]);
171
+ }
172
+ for (const group of idGroups.values())
173
+ if (group.length > 1)
174
+ for (const inst of group.filter(live))
175
+ report("fec_instance_id", inst.obj && has(inst.obj, "instanceId") ? `${inst.path}/instanceId` : inst.path, inst);
176
+ const nameUses = /* @__PURE__ */ new Map();
177
+ for (const inst of insts)
178
+ for (const field of ["repairTrack", "symbolTrack"])
179
+ if (inst.obj && typeof inst.obj[field] === "string") {
180
+ const k = key(inst.track.namespace, inst.obj[field]);
181
+ nameUses.set(k, [...nameUses.get(k) ?? [], { inst, field }]);
182
+ }
183
+ const namedBy = /* @__PURE__ */ new Map();
184
+ for (const [k, uses] of nameUses) {
185
+ const tj = byName.get(k);
186
+ for (const { inst, field } of uses) {
187
+ const path = `${inst.path}/${field}`;
188
+ if (tj === void 0 || trackAt(tj).packaging !== (field === "repairTrack" ? "fec-repair" : "fec-source")) {
189
+ if (live(inst))
190
+ report("fec_naming", path, inst);
191
+ } else if (uses.length > 1) {
192
+ if (live(inst))
193
+ report("fec_naming", path, inst);
194
+ if (!namedBy.has(tj))
195
+ namedBy.set(tj, inst);
196
+ } else {
197
+ namedBy.set(tj, inst);
198
+ if (field === "repairTrack")
199
+ inst.layer0 = tj;
200
+ else
201
+ inst.symbolNamed = tj;
202
+ }
203
+ }
204
+ }
205
+ const owner = /* @__PURE__ */ new Map();
206
+ if (declaresFec)
207
+ tracks.forEach((t, tj) => {
208
+ if (!isObj(t) || t.packaging !== "fec-repair" && t.packaging !== "fec-source")
209
+ return;
210
+ const tp = `/tracks/${tj}`;
211
+ const named = namedBy.get(tj);
212
+ if (named) {
213
+ owner.set(tj, named);
214
+ if (!live(named))
215
+ return;
216
+ if (has(t, "source") && t.source !== named.track.name)
217
+ report("fec_resolution", `${tp}/source`, named);
218
+ if (has(t, "instanceId") && t.instanceId !== named.id)
219
+ report("fec_resolution", `${tp}/instanceId`, named);
220
+ return;
221
+ }
222
+ if (!has(t, "source"))
223
+ return report("fec_orphan", tp);
224
+ const id = has(t, "instanceId") ? t.instanceId : 0;
225
+ const src = typeof t.source === "string" ? byName.get(key(t.namespace, t.source)) : void 0;
226
+ const target = src === void 0 ? void 0 : insts.find((i) => i.ti === src && i.id === id);
227
+ if (!target)
228
+ return report("fec_naming", has(t, "instanceId") && src !== void 0 ? `${tp}/instanceId` : `${tp}/source`);
229
+ owner.set(tj, target);
230
+ target.attached.push(tj);
231
+ });
232
+ for (const [tj, inst] of owner) {
233
+ if (!live(inst))
234
+ continue;
235
+ const t = trackAt(tj);
236
+ const tp = `/tracks/${tj}`;
237
+ const symbol = t.packaging === "fec-source";
238
+ if (has(t, "source") && typeof t.source !== "string")
239
+ report("fec_member", `${tp}/source`, inst);
240
+ if (has(t, "instanceId") && !intIn(t.instanceId, 0, 255))
241
+ report("fec_member", `${tp}/instanceId`, inst);
242
+ for (const m of ["repairLayer", "repairSymbols"]) {
243
+ if (!has(t, m))
244
+ continue;
245
+ if (symbol || !intIn(t[m], m === "repairLayer" ? 0 : 1))
246
+ report("fec_member", `${tp}/${m}`, inst);
247
+ }
248
+ if (has(t, "priority") && !intIn(t.priority, symbol ? 0 : 192, 255))
249
+ report("fec_member", `${tp}/priority`, inst);
250
+ for (const k of Object.keys(t))
251
+ if (GEOMETRY_KEYS.includes(k) || k.startsWith("groupDuration"))
252
+ report("fec_track_geometry", `${tp}/${k.replace(/~/g, "~0").replace(/\//g, "~1")}`, inst);
253
+ }
254
+ for (const inst of insts.filter(live)) {
255
+ const o = inst.obj;
256
+ if (!o)
257
+ continue;
258
+ const repairs = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-repair");
259
+ const symbols = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-source");
260
+ if (symbols.length + (inst.symbolNamed !== null ? 1 : 0) > 1)
261
+ for (const tj of symbols)
262
+ report("fec_symbol_tracks", `/tracks/${tj}`, inst);
263
+ const l0 = inst.layer0 === null ? null : trackAt(inst.layer0);
264
+ const l0p = `/tracks/${inst.layer0}`;
265
+ if (inst.overlay) {
266
+ if (l0 && has(l0, "repairLayer"))
267
+ report("fec_overlay", `${l0p}/repairLayer`, inst);
268
+ for (const tj of repairs)
269
+ report("fec_overlay", has(trackAt(tj), "repairLayer") ? `/tracks/${tj}/repairLayer` : `/tracks/${tj}`, inst);
270
+ } else {
271
+ if (l0 && has(l0, "repairLayer") && l0.repairLayer !== 0)
272
+ report("fec_layers", `${l0p}/repairLayer`, inst);
273
+ if (l0 && has(l0, "repairSymbols") && intIn(o.repairSymbols, 1) && l0.repairSymbols !== o.repairSymbols)
274
+ report("fec_layer0", `${l0p}/repairSymbols`, inst);
275
+ const layers = [];
276
+ for (const tj of repairs) {
277
+ const t = trackAt(tj);
278
+ if (!has(t, "repairLayer"))
279
+ report("fec_member", `/tracks/${tj}/repairLayer`, inst);
280
+ else if (t.repairLayer === 0)
281
+ report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
282
+ else if (intIn(t.repairLayer, 1))
283
+ layers.push({ tj, layer: t.repairLayer });
284
+ if (!has(t, "repairSymbols"))
285
+ report("fec_member", `/tracks/${tj}/repairSymbols`, inst);
286
+ }
287
+ const counts = /* @__PURE__ */ new Map();
288
+ for (const { layer } of layers)
289
+ counts.set(layer, (counts.get(layer) ?? 0) + 1);
290
+ let missing = 1;
291
+ while (counts.has(missing))
292
+ missing++;
293
+ for (const { tj, layer } of layers)
294
+ if (counts.get(layer) > 1 || layer > missing)
295
+ report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
296
+ const chain = [
297
+ { layer: 0, tj: inst.layer0, pri: l0 && intIn(l0.priority, 192, 255) ? l0.priority : l0 && has(l0, "priority") ? null : 240 },
298
+ ...layers.map(({ tj, layer }) => {
299
+ const pr = trackAt(tj).priority;
300
+ return { layer, tj, pri: intIn(pr, 192, 255) ? pr : null };
301
+ })
302
+ ].sort((a, b) => a.layer - b.layer);
303
+ let prev = null;
304
+ for (const c of chain) {
305
+ if (c.pri === null)
306
+ continue;
307
+ if (prev !== null && c.pri < prev)
308
+ report("fec_priority", `/tracks/${c.tj}/priority`, inst);
309
+ prev = Math.max(prev ?? c.pri, c.pri);
310
+ }
311
+ }
312
+ const K = o.sourceSymbols;
313
+ const T = o.symbolSize;
314
+ const P0 = o.repairSymbols;
315
+ if (intIn(K, 1) && intIn(P0, 1)) {
316
+ let P = P0;
317
+ if (!inst.overlay)
318
+ for (const tj of repairs) {
319
+ const t = trackAt(tj);
320
+ if (intIn(t.repairLayer, 1) && intIn(t.repairSymbols, 1))
321
+ P += t.repairSymbols;
322
+ }
323
+ if (P > MAX_24 || K + P - 1 > MAX_24)
324
+ report("fec_bounds", inst.path, inst);
325
+ else if (o.scheme === "reed-solomon" && K + P > RS_MAX_BLOCK)
326
+ report("fec_bounds", inst.path, inst);
327
+ }
328
+ if (o.scheme === "raptorq" && intIn(K, 1) && K > RAPTORQ_MAX_K)
329
+ report("fec_bounds", `${inst.path}/sourceSymbols`, inst);
330
+ if (intIn(T, 1)) {
331
+ if (o.scheme === "raptorq" && T % 8 !== 0)
332
+ report("fec_bounds", `${inst.path}/symbolSize`, inst);
333
+ else if (o.blockProfile === "iso-ssbg1-v1" && T < ISO_MIN_T)
334
+ report("fec_bounds", `${inst.path}/symbolSize`, inst);
335
+ }
336
+ }
337
+ const overlayTracks = new Set(insts.filter((i) => live(i) && i.overlay).map((i) => i.ti));
338
+ for (const ti of overlayTracks) {
339
+ const ofTrack = insts.filter((i) => i.ti === ti && live(i));
340
+ const ovs = ofTrack.filter((i) => i.overlay);
341
+ if (!overlays) {
342
+ for (const ov of ovs)
343
+ report("fec_overlay_unsupported", `${ov.path}/scope`, ov);
344
+ for (const i of ofTrack)
345
+ i.issues.length === 0 && i.issues.push({ code: "fec_overlay_unsupported", path: `${ovs[0].path}/scope` });
346
+ continue;
347
+ }
348
+ if (ovs.length > 1)
349
+ for (const ov of ovs)
350
+ report("fec_overlay", `${ov.path}/scope`, ov);
351
+ for (const ov of ovs)
352
+ if (ov.obj?.blockProfile !== "iso-ssbg1-v1")
353
+ report("fec_overlay", `${ov.path}/blockProfile`, ov);
354
+ const cause = ovs.flatMap((ov) => ov.issues).find((x) => x.code === "fec_overlay");
355
+ if (cause) {
356
+ for (const ov of ovs)
357
+ if (ov.issues.length === 0)
358
+ ov.issues.push(cause);
359
+ }
360
+ }
361
+ const rule4 = all.find((x) => x.code === "fec_enhancement_repair");
362
+ if (rejectAll && rule4) {
363
+ for (const i of insts)
364
+ if (i.issues.length === 0)
365
+ i.issues.push(rule4);
366
+ }
367
+ return {
368
+ instances: insts.map((i) => ({
369
+ path: i.path,
370
+ track: typeof i.track.name === "string" ? i.track.name : null,
371
+ instanceId: i.id,
372
+ outcome: rejectAll || !i.unavailable && i.issues.length > 0 ? "violation" : i.unavailable ? "unavailable" : "ok",
373
+ issues: i.issues,
374
+ trackIndex: i.ti,
375
+ repairTrack: i.layer0,
376
+ boundRepairTracks: i.attached.filter((tj) => trackAt(tj).packaging === "fec-repair"),
377
+ symbolTrack: i.symbolNamed ?? i.attached.find((tj) => trackAt(tj).packaging === "fec-source") ?? null
378
+ })),
379
+ issues: all
380
+ };
381
+ }
382
+
383
+ // ../mmt-base/dist/fec-bindings.js
384
+ var RUNTIME_FEC_RECEIVER = Object.freeze({
385
+ schemes: Object.freeze(["raptorq", "reed-solomon"]),
386
+ blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
387
+ overlays: true
388
+ });
389
+ var DEFAULT_FEC_REPAIR_PRIORITY = 240;
390
+ var recordOf = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
391
+ var escape = (s) => s.replace(/~/g, "~0").replace(/\//g, "~1");
392
+ function fecCatalogTracks(catalog) {
393
+ const root = recordOf(catalog) ?? {};
394
+ const tracks = [];
395
+ const pointers = [];
396
+ const hangNames = /* @__PURE__ */ new Set();
397
+ for (const role of ["video", "audio"]) {
398
+ for (const [name, r] of Object.entries(recordOf(recordOf(root[role])?.["renditions"]) ?? {})) {
399
+ const entry = recordOf(r);
400
+ if (!entry)
401
+ continue;
402
+ hangNames.add(name);
403
+ const kind = recordOf(entry["container"])?.["kind"];
404
+ const view = { name, packaging: kind === void 0 || kind === "mmtp-fec" ? "mmtp" : kind };
405
+ if (entry["fec"] !== void 0)
406
+ view["fec"] = entry["fec"];
407
+ tracks.push(view);
408
+ pointers.push(`/${role}/renditions/${escape(name)}`);
409
+ }
410
+ }
411
+ const hangRenditions = tracks.length;
412
+ for (const [name, r] of Object.entries(recordOf(recordOf(root["repair"])?.["renditions"]) ?? {})) {
413
+ hangNames.add(name);
414
+ tracks.push({ packaging: "fec-repair", ...recordOf(r), name });
415
+ pointers.push(`/repair/renditions/${escape(name)}`);
416
+ }
417
+ const raw = Array.isArray(root["tracks"]) ? root["tracks"] : [];
418
+ const named = new Set(hangNames);
419
+ raw.forEach((t, i) => {
420
+ const name = recordOf(t)?.["name"];
421
+ if (typeof name === "string")
422
+ named.add(name);
423
+ if (typeof name === "string" && hangNames.has(name))
424
+ return;
425
+ tracks.push(t);
426
+ pointers.push(`/tracks/${i}`);
427
+ });
428
+ tracks.slice(0, hangRenditions).forEach((t, ti) => {
429
+ const view = t;
430
+ if (!Array.isArray(view["fec"]))
431
+ return;
432
+ view["fec"].forEach((o, ii) => {
433
+ const repairTrack = recordOf(o)?.["repairTrack"];
434
+ if (typeof repairTrack !== "string" || named.has(repairTrack))
435
+ return;
436
+ named.add(repairTrack);
437
+ const instanceId = recordOf(o)?.["instanceId"];
438
+ tracks.push({ name: repairTrack, packaging: "fec-repair", source: view["name"], ...instanceId === void 0 ? {} : { instanceId } });
439
+ pointers.push(`${pointers[ti]}/fec/${ii}/repairTrack`);
440
+ });
441
+ });
442
+ return { tracks, pointers };
443
+ }
444
+ function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
445
+ const { tracks, pointers } = fecCatalogTracks(catalog);
446
+ const report = validateFecCatalog({ tracks }, receiver);
447
+ const at2 = (path) => {
448
+ const m = /^\/tracks\/(\d+)(.*)$/.exec(path);
449
+ return m ? pointers[Number(m[1])] + m[2] : path;
450
+ };
451
+ const trackAt = (i) => recordOf(tracks[i]) ?? {};
452
+ const nameAt = (i) => i === null ? void 0 : trackAt(i)["name"];
453
+ const why = (i) => Object.freeze({
454
+ code: i.issues[0]?.code ?? "fec_member",
455
+ path: at2(i.issues[0]?.path ?? i.path),
456
+ outcome: i.outcome === "unavailable" ? "unavailable" : "violation"
457
+ });
458
+ const out = /* @__PURE__ */ new Map();
459
+ tracks.forEach((t, ti) => {
460
+ const track = recordOf(t);
461
+ const name = track?.["name"];
462
+ if (!track || typeof name !== "string" || out.has(name))
463
+ return;
464
+ if (track["packaging"] === "fec-repair" || track["packaging"] === "fec-source")
465
+ return;
466
+ const fec = track["fec"];
467
+ if (fec === void 0)
468
+ return;
469
+ if (!Array.isArray(fec)) {
470
+ const reason = { code: "fec_member", path: at2(`/tracks/${ti}/fec`), outcome: "violation" };
471
+ out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason: Object.freeze(reason) }));
472
+ return;
473
+ }
474
+ if (fec.length === 0)
475
+ return;
476
+ const insts = report.instances.filter((i) => i.trackIndex === ti);
477
+ const isOverlay = (i) => Object.hasOwn(recordOf(fec[Number(i.path.split("/").pop())]) ?? {}, "scope");
478
+ const bases = insts.filter((i) => !isOverlay(i));
479
+ const okBases = bases.filter((i) => i.outcome === "ok");
480
+ if (okBases.length !== 1) {
481
+ const reason = (
482
+ // No base instance at all (overlay-only `fec`) is a count of 0, not
483
+ // the overlay's own issue.
484
+ okBases.length > 1 || bases.length === 0 ? Object.freeze({ code: "fec_base_instances", path: at2(`/tracks/${ti}/fec`), outcome: "violation" }) : why(bases[0] ?? insts[0])
485
+ );
486
+ out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason }));
487
+ return;
488
+ }
489
+ const b = okBases[0];
490
+ const base = geometry(recordOf(fec[Number(b.path.split("/").pop())]), b.instanceId, nameAt(b.symbolTrack));
491
+ const priorityOf = (i) => {
492
+ const p = i === null ? void 0 : trackAt(i)["priority"];
493
+ return typeof p === "number" ? p : DEFAULT_FEC_REPAIR_PRIORITY;
494
+ };
495
+ const layers = [
496
+ { layer: 0, repairTrackName: base.repairTrack, repairSymbols: base.repairSymbols, rsIdOffset: 0, priority: priorityOf(b.repairTrack) }
497
+ ];
498
+ const more = b.boundRepairTracks.map((tj) => ({ tj, layer: trackAt(tj)["repairLayer"] })).sort((x, y) => x.layer - y.layer);
499
+ let offset = base.repairSymbols;
500
+ for (const { tj, layer } of more) {
501
+ const repairSymbols = trackAt(tj)["repairSymbols"];
502
+ layers.push({ layer, repairTrackName: nameAt(tj), repairSymbols, rsIdOffset: offset, priority: priorityOf(tj) });
503
+ offset += repairSymbols;
504
+ }
505
+ let overlay;
506
+ let overlayUnavailable;
507
+ const ov = insts.find(isOverlay);
508
+ if (ov?.outcome === "ok") {
509
+ const g = geometry(recordOf(fec[Number(ov.path.split("/").pop())]), ov.instanceId, nameAt(ov.symbolTrack));
510
+ overlay = Object.freeze({
511
+ repairTrackName: g.repairTrack,
512
+ scope: "keyframe",
513
+ sourceSymbols: g.sourceSymbols,
514
+ repairSymbols: g.repairSymbols,
515
+ priority: priorityOf(ov.repairTrack),
516
+ geometry: g
517
+ });
518
+ } else if (ov)
519
+ overlayUnavailable = why(ov);
520
+ out.set(name, Object.freeze({
521
+ status: "ok",
522
+ track: name,
523
+ pointer: pointers[ti],
524
+ base,
525
+ layers: Object.freeze(layers.map((l) => Object.freeze(l))),
526
+ totalBaseRepairSymbols: offset,
527
+ ...overlay ? { overlay } : {},
528
+ ...overlayUnavailable ? { overlayUnavailable } : {}
529
+ }));
530
+ });
531
+ return out;
532
+ }
533
+ function geometry(o, instanceId, symbolTrack) {
534
+ return Object.freeze({
535
+ instanceId,
536
+ scheme: o["scheme"],
537
+ blockProfile: o["blockProfile"],
538
+ sourceSymbols: o["sourceSymbols"],
539
+ repairSymbols: o["repairSymbols"],
540
+ symbolSize: o["symbolSize"],
541
+ ...typeof o["interleaveDepthMs"] === "number" ? { interleaveDepthMs: o["interleaveDepthMs"] } : {},
542
+ ...typeof o["reorderToleranceMs"] === "number" ? { reorderToleranceMs: o["reorderToleranceMs"] } : {},
543
+ repairTrack: o["repairTrack"],
544
+ ...symbolTrack !== void 0 ? { symbolTrack } : {}
545
+ });
546
+ }
547
+
1
548
  // src/track-session.ts
2
549
  import { fecBlockBudgetMs } from "@blockcast/mmt-fec";
3
550
 
@@ -26,33 +573,44 @@ function isPositiveFinite(value) {
26
573
  }
27
574
 
28
575
  // src/delta-fallback-config.ts
29
- function resolveDeltaFallback(base, delta) {
30
- const repair = base.fec?.repairTrack;
31
- if (!repair) throw new Error(`[delta-fallback] base track "${base.name}" missing fec.repairTrack`);
32
- const deltaFec = delta.fec;
33
- if (!deltaFec?.repairTrack) throw new Error(`[delta-fallback] delta track "${delta.name}" missing fec.repairTrack`);
34
- if (deltaFec.repairTrack !== repair)
35
- throw new Error(
36
- `[delta-fallback] inconsistent repair tracks: ${base.name} references "${repair}" but ${delta.name} references "${deltaFec.repairTrack}"`
37
- );
38
- if (!(typeof base.framerate === "number" && Number.isFinite(base.framerate) && base.framerate > 0))
39
- throw new Error(`[delta-fallback] base track "${base.name}" missing positive framerate`);
40
- if (!(isPositive(deltaFec.sourceSymbols) && isPositive(deltaFec.repairSymbols)))
41
- throw new Error(`[delta-fallback] delta track "${delta.name}" missing positive fec.sourceSymbols/repairSymbols`);
576
+ var recordOf2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
577
+ function at(root, pointer) {
578
+ let cur = root;
579
+ for (const raw of pointer.split("/").slice(1)) {
580
+ const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
581
+ cur = Array.isArray(cur) ? cur[Number(key)] : recordOf2(cur)?.[key];
582
+ }
583
+ return recordOf2(cur);
584
+ }
585
+ function resolveDeltaFallback(catalog, baseName, deltaName) {
586
+ const bindings = resolveFecBindings(catalog);
587
+ const bound = (name, role) => {
588
+ const b = bindings.get(name);
589
+ if (!b) throw new Error(`[delta-fallback] ${role} track "${name}" declares no fec`);
590
+ if (b.status !== "ok")
591
+ throw new Error(`[delta-fallback] ${role} track "${name}" has no FEC: ${b.reason.code} at ${b.reason.path}`);
592
+ const track = at(catalog, b.pointer) ?? {};
593
+ const instance = (Array.isArray(track["fec"]) ? track["fec"] : []).map(recordOf2).find((o) => o && (o["instanceId"] ?? 0) === b.base.instanceId) ?? {};
594
+ return { b, track, instance };
595
+ };
596
+ const base = bound(baseName, "base");
597
+ const delta = bound(deltaName, "delta");
598
+ const framerate = base.track["framerate"];
599
+ if (!(typeof framerate === "number" && Number.isFinite(framerate) && framerate > 0))
600
+ throw new Error(`[delta-fallback] base track "${baseName}" missing positive framerate`);
601
+ const key = (k) => base.instance[k] ?? delta.instance[k];
42
602
  const config = {
43
- framerate: base.framerate,
44
- sourceSymbols: deltaFec.sourceSymbols,
45
- repairSymbols: deltaFec.repairSymbols,
46
- deltaFailWindowBlocks: base.fec?.deltaFailWindowBlocks ?? deltaFec.deltaFailWindowBlocks,
47
- deltaProbeIntervalMs: base.fec?.deltaProbeIntervalMs ?? deltaFec.deltaProbeIntervalMs,
48
- interleaveDepthMs: base.fec?.interleaveDepthMs ?? deltaFec.interleaveDepthMs
603
+ framerate,
604
+ sourceSymbols: delta.b.base.sourceSymbols,
605
+ repairSymbols: delta.b.base.repairSymbols,
606
+ deltaFailWindowBlocks: key("deltaFailWindowBlocks"),
607
+ deltaProbeIntervalMs: key("deltaProbeIntervalMs"),
608
+ interleaveDepthMs: base.b.base.interleaveDepthMs ?? delta.b.base.interleaveDepthMs
49
609
  };
50
- assertDeltaFallbackConfig(config, `[delta-fallback] group ${base.name}/${delta.name}`);
51
- return { repairTrackName: repair, config };
52
- }
53
- function isPositive(value) {
54
- return typeof value === "number" && Number.isFinite(value) && value > 0;
610
+ assertDeltaFallbackConfig(config, `[delta-fallback] group ${baseName}/${deltaName}`);
611
+ return { baseRepairTrackName: repairOf(base.b), deltaRepairTrackName: repairOf(delta.b), config };
55
612
  }
613
+ var repairOf = (b) => b.layers[0].repairTrackName;
56
614
  export {
57
615
  resolveDeltaFallback
58
616
  };