@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.
- package/README.md +21 -16
- package/dist/browser-runtime.js +1024 -1078
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +33 -87
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +12 -434
- package/dist/carp-catalog.js.map +4 -4
- package/dist/catalog-fec-params.d.ts +48 -161
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +567 -592
- package/dist/catalog-fec-params.js.map +4 -4
- package/dist/catalog-network-source.js +6 -2
- package/dist/catalog-network-source.js.map +3 -3
- package/dist/delivery-catalog.js +12 -4
- package/dist/delivery-catalog.js.map +4 -4
- package/dist/delta-fallback-config.d.ts +21 -26
- package/dist/delta-fallback-config.d.ts.map +1 -1
- package/dist/delta-fallback-config.js +582 -24
- package/dist/delta-fallback-config.js.map +4 -4
- package/dist/fec-manager.js +6 -2
- package/dist/fec-manager.js.map +3 -3
- package/dist/fec-state.d.ts +5 -43
- package/dist/fec-state.d.ts.map +1 -1
- package/dist/fec-state.js +529 -34
- package/dist/fec-state.js.map +4 -4
- package/dist/fec-worker-setup.js +6 -2
- package/dist/fec-worker-setup.js.map +3 -3
- package/dist/held-layer-admission.js +6 -2
- package/dist/held-layer-admission.js.map +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -39
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.js +6 -2
- package/dist/iso-block-locator.js.map +3 -3
- package/dist/mfu-reassembler.js +6 -2
- package/dist/mfu-reassembler.js.map +3 -3
- package/dist/mmtp-container-parser.js +6 -2
- package/dist/mmtp-container-parser.js.map +3 -3
- package/dist/mmtp-container-parser.no-modules.js +551 -452
- package/dist/mmtp-container-parser.no-modules.js.map +4 -4
- package/dist/mmtp-decode-pipeline.js +6 -2
- package/dist/mmtp-decode-pipeline.js.map +3 -3
- package/dist/mmtp-fec-session.d.ts +7 -6
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +13 -9
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +7 -16
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +19 -29
- package/dist/mmtp-router.js.map +3 -3
- package/dist/multicast-auth-catalog.js +7 -3
- package/dist/multicast-auth-catalog.js.map +3 -3
- package/dist/multicast-input.js +7 -3
- package/dist/multicast-input.js.map +3 -3
- package/dist/multicast-manager.d.ts +29 -32
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +682 -253
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +23 -2
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +57 -16
- package/dist/multicast-transport.js.map +4 -4
- package/dist/multicast-unwrap.js +6 -2
- package/dist/multicast-unwrap.js.map +3 -3
- package/dist/no-modules.js +551 -452
- package/dist/no-modules.js.map +4 -4
- package/dist/phase14-catalog-override.d.ts +10 -5
- package/dist/phase14-catalog-override.d.ts.map +1 -1
- package/dist/phase14-catalog-override.js +35 -11
- package/dist/phase14-catalog-override.js.map +2 -2
- package/dist/repair-fetch.d.ts +3 -1
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +16 -3
- package/dist/repair-fetch.js.map +3 -3
- package/dist/repair-layers.d.ts +0 -123
- package/dist/repair-layers.d.ts.map +1 -1
- package/dist/repair-layers.js +1 -349
- package/dist/repair-layers.js.map +3 -3
- package/dist/signaling.js +6 -2
- package/dist/signaling.js.map +3 -3
- package/dist/timestamp-codec.js +6 -2
- package/dist/timestamp-codec.js.map +3 -3
- package/dist/wasm-mfu-reassembler.js +6 -2
- package/dist/wasm-mfu-reassembler.js.map +3 -3
- package/dist/wasm-mmtp-container-parser.js +6 -2
- package/dist/wasm-mmtp-container-parser.js.map +3 -3
- package/package.json +9 -5
- package/schemas/catalog.schema.json +53 -58
- package/src/carp-catalog.ts +39 -229
- package/src/catalog-fec-params.ts +130 -493
- package/src/delta-fallback-config.ts +63 -50
- package/src/fec-state.ts +7 -62
- package/src/index.ts +10 -30
- package/src/mmtp-fec-session.ts +11 -10
- package/src/mmtp-router.ts +14 -29
- package/src/multicast-manager.ts +89 -121
- package/src/multicast-transport.ts +65 -12
- package/src/phase14-catalog-override.ts +67 -16
- package/src/repair-fetch.ts +27 -3
- package/src/repair-layers.ts +7 -520
- package/test-vectors/catalog/README.md +39 -67
- package/test-vectors/catalog/fec-01-runtime/README.md +15 -0
- package/test-vectors/catalog/{negative/fec-block-profile-retired.json → fec-01-runtime/fec-iso-ssbg1.json} +1 -1
- package/test-vectors/catalog/fec-01-runtime/fec-layered-repair.json +106 -0
- package/test-vectors/catalog/{negative/fec-block-profile-unknown.json → fec-01-runtime/fec-multicast.json} +2 -1
- package/test-vectors/catalog/fec-01-runtime/multicast-native-ssm-no-network-source.json +44 -0
- package/test-vectors/catalog/negative/fec-block-profile-missing.json +24 -11
- package/test-vectors/catalog/negative/fec-iso-symbol-size-below-floor.json +13 -8
- package/test-vectors/catalog/negative/fec-reed-solomon-iso-symbol-size-13.json +13 -8
- package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +20 -10
- package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +20 -10
- package/test-vectors/catalog/negative/fec-repair-overlay-source-symbols-zero.json +25 -17
- package/test-vectors/catalog/negative/fec-repair-symbols-zero.json +13 -8
- package/test-vectors/catalog/negative/fec-source-symbols-zero.json +13 -8
- package/test-vectors/catalog/negative/fec-symbol-size-zero.json +13 -8
- package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +18 -9
- package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +13 -8
- package/test-vectors/catalog/positive/fec-algorithm-unimplemented.json +14 -9
- package/test-vectors/catalog/positive/fec-iso-ssbg1.json +25 -12
- package/test-vectors/catalog/positive/fec-layered-repair.json +79 -35
- package/test-vectors/catalog/positive/fec-multicast.json +26 -13
- package/test-vectors/catalog/positive/fec-reed-solomon-layered.json +29 -18
- package/test-vectors/catalog/positive/multicast-draft01-4-1-example.json +28 -20
- package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +14 -10
- package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +22 -12
- package/test-vectors/catalog/section-codes.json +13 -22
- package/test-vectors/catalog-timing.json +2 -2
- package/test-vectors/catalog-validation.json +30 -243
- package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +0 -28
- package/test-vectors/catalog/negative/fec-reed-solomon-k-plus-p-over-255.json +0 -39
- package/test-vectors/catalog/negative/fec-reed-solomon-overlay-over-255.json +0 -40
- package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +0 -32
- package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +0 -34
- package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +0 -24
- 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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
44
|
-
sourceSymbols:
|
|
45
|
-
repairSymbols:
|
|
46
|
-
deltaFailWindowBlocks:
|
|
47
|
-
deltaProbeIntervalMs:
|
|
48
|
-
interleaveDepthMs: base.
|
|
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 ${
|
|
51
|
-
return {
|
|
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
|
};
|