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