@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
|
@@ -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/
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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 (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
`${
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
"
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 (
|
|
311
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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 (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
`${
|
|
334
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
`${
|
|
354
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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 (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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/
|
|
456
|
-
var
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
var
|
|
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
|
|
532
|
-
|
|
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
|
|
535
|
-
const
|
|
536
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
const
|
|
643
|
-
const entry =
|
|
644
|
-
const
|
|
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
|
|
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
|
|
709
|
-
if (
|
|
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:
|
|
727
|
-
fecAlgorithm:
|
|
728
|
-
fecBlockProfile,
|
|
729
|
-
fecRepairCount:
|
|
730
|
-
// The wire RSB_length: every base layer's packets carry the base total
|
|
731
|
-
fecTotalRepairCount:
|
|
732
|
-
fecSymbolSize:
|
|
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(
|
|
735
|
-
// Additive, never a multiplier
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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,
|
|
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" },
|
|
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" },
|
|
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
|
-
|
|
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
|
-
|
|
799
|
+
validateFecCatalog
|
|
825
800
|
};
|
|
826
801
|
//# sourceMappingURL=catalog-fec-params.js.map
|