@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
package/dist/browser-runtime.js
CHANGED
|
@@ -957,79 +957,6 @@ function isoBlocksBehindLowerBound(a, b, kMax) {
|
|
|
957
957
|
return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0;
|
|
958
958
|
}
|
|
959
959
|
|
|
960
|
-
// ../mmt-base/dist/catalog-fec-source.js
|
|
961
|
-
var ALL_ROLES = Object.freeze(["video", "audio"]);
|
|
962
|
-
function recordOf(value) {
|
|
963
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
964
|
-
}
|
|
965
|
-
function locateCatalogFecSource(catalog, source, roles = ALL_ROLES) {
|
|
966
|
-
const root = recordOf(catalog);
|
|
967
|
-
if (root === void 0)
|
|
968
|
-
return void 0;
|
|
969
|
-
for (const role of roles) {
|
|
970
|
-
const renditions = recordOf(recordOf(root[role])?.["renditions"]);
|
|
971
|
-
if (renditions === void 0 || !Object.hasOwn(renditions, source))
|
|
972
|
-
continue;
|
|
973
|
-
const entry2 = recordOf(renditions[source]);
|
|
974
|
-
if (entry2 === void 0)
|
|
975
|
-
continue;
|
|
976
|
-
return Object.freeze({
|
|
977
|
-
dialect: "hang",
|
|
978
|
-
role,
|
|
979
|
-
entry: entry2,
|
|
980
|
-
fec: entry2["fec"],
|
|
981
|
-
fecPath: `$.${role}.renditions[${JSON.stringify(source)}].fec`
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
const tracks = root["tracks"];
|
|
985
|
-
if (!Array.isArray(tracks))
|
|
986
|
-
return void 0;
|
|
987
|
-
const index = tracks.findIndex((track) => recordOf(track)?.["name"] === source);
|
|
988
|
-
if (index < 0)
|
|
989
|
-
return void 0;
|
|
990
|
-
const entry = recordOf(tracks[index]) ?? {};
|
|
991
|
-
return Object.freeze({
|
|
992
|
-
dialect: "msf",
|
|
993
|
-
index,
|
|
994
|
-
entry,
|
|
995
|
-
fec: entry["fec"],
|
|
996
|
-
fecPath: `$.tracks[${index}].fec`
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
function isoBlockSpanUsable(fec) {
|
|
1000
|
-
const block = recordOf(fec);
|
|
1001
|
-
if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1")
|
|
1002
|
-
return true;
|
|
1003
|
-
const ms = block["interleaveDepthMs"];
|
|
1004
|
-
return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
|
|
1005
|
-
}
|
|
1006
|
-
function catalogFecAvailable(fec) {
|
|
1007
|
-
const block = recordOf(fec);
|
|
1008
|
-
if (block === void 0)
|
|
1009
|
-
return true;
|
|
1010
|
-
if (fecAlgorithmUnimplemented(block["algorithm"]))
|
|
1011
|
-
return false;
|
|
1012
|
-
const declaresFecInstance = block["algorithm"] !== "none";
|
|
1013
|
-
const profile = block["blockProfile"];
|
|
1014
|
-
const profileOk = isFecBlockProfile(profile) || profile === void 0 && !declaresFecInstance;
|
|
1015
|
-
return profileOk && (!declaresFecInstance || isoBlockSpanUsable(block));
|
|
1016
|
-
}
|
|
1017
|
-
function fecAlgorithmUnimplemented(algorithm) {
|
|
1018
|
-
return typeof algorithm === "string" && algorithm !== "none" && algorithm !== "raptorq" && algorithm !== "reed-solomon";
|
|
1019
|
-
}
|
|
1020
|
-
function isPositiveSafeInteger(value) {
|
|
1021
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
1022
|
-
}
|
|
1023
|
-
var ISO_SSBG1_MIN_SYMBOL_SIZE = 14;
|
|
1024
|
-
function catalogFecDeclaresUnconfigurableInstance(fec) {
|
|
1025
|
-
const block = recordOf(fec);
|
|
1026
|
-
if (block === void 0 || block["algorithm"] === "none" || fecAlgorithmUnimplemented(block["algorithm"]))
|
|
1027
|
-
return false;
|
|
1028
|
-
if (!(isPositiveSafeInteger(block["sourceSymbols"]) && isPositiveSafeInteger(block["symbolSize"])))
|
|
1029
|
-
return true;
|
|
1030
|
-
return block["blockProfile"] === "iso-ssbg1-v1" && block["symbolSize"] < ISO_SSBG1_MIN_SYMBOL_SIZE;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
960
|
// ../mmt-base/dist/multicast-catalog.js
|
|
1034
961
|
var ED25519_PUBLIC_KEY_BASE64URL = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
1035
962
|
var UINT32_MAX = 4294967295;
|
|
@@ -1095,22 +1022,22 @@ function authIssues(auth) {
|
|
|
1095
1022
|
const firstEpochs = /* @__PURE__ */ new Set();
|
|
1096
1023
|
for (let index = 0; index < keys.length; index++) {
|
|
1097
1024
|
const key = keys[index];
|
|
1098
|
-
const
|
|
1025
|
+
const at2 = `.keys[${index}]`;
|
|
1099
1026
|
if (!isObject(key) || Object.keys(key).some((member) => !KEY_MEMBERS.has(member))) {
|
|
1100
|
-
return issue(
|
|
1027
|
+
return issue(at2, "a key is exactly {kid, publicKey, firstEpoch}");
|
|
1101
1028
|
}
|
|
1102
1029
|
if (!isIntegerIn(key["kid"], 0, UINT32_MAX))
|
|
1103
|
-
return issue(`${
|
|
1030
|
+
return issue(`${at2}.kid`, "kid must be an unsigned 32-bit integer");
|
|
1104
1031
|
if (typeof key["publicKey"] !== "string" || !ED25519_PUBLIC_KEY_BASE64URL.test(key["publicKey"])) {
|
|
1105
|
-
return issue(`${
|
|
1032
|
+
return issue(`${at2}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
|
|
1106
1033
|
}
|
|
1107
1034
|
if (!isIntegerIn(key["firstEpoch"], 0, UINT32_MAX)) {
|
|
1108
|
-
return issue(`${
|
|
1035
|
+
return issue(`${at2}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
|
|
1109
1036
|
}
|
|
1110
1037
|
if (kids.has(key["kid"]))
|
|
1111
|
-
return issue(`${
|
|
1038
|
+
return issue(`${at2}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
|
|
1112
1039
|
if (firstEpochs.has(key["firstEpoch"])) {
|
|
1113
|
-
return issue(`${
|
|
1040
|
+
return issue(`${at2}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
|
|
1114
1041
|
}
|
|
1115
1042
|
kids.add(key["kid"]);
|
|
1116
1043
|
firstEpochs.add(key["firstEpoch"]);
|
|
@@ -1120,36 +1047,36 @@ function authIssues(auth) {
|
|
|
1120
1047
|
}
|
|
1121
1048
|
return [];
|
|
1122
1049
|
}
|
|
1123
|
-
function endpointAuthIssue(endpoint, mode,
|
|
1050
|
+
function endpointAuthIssue(endpoint, mode, at2) {
|
|
1124
1051
|
const auth = endpoint["auth"];
|
|
1125
1052
|
if (!isObject(auth))
|
|
1126
|
-
return { path: `${
|
|
1053
|
+
return { path: `${at2}.auth`, message: "auth must be an object" };
|
|
1127
1054
|
const unknown = Object.keys(auth).filter((key) => !ENDPOINT_AUTH_MEMBERS.has(key));
|
|
1128
1055
|
if (unknown.length > 0)
|
|
1129
|
-
return { path: `${
|
|
1056
|
+
return { path: `${at2}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
|
|
1130
1057
|
if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
|
|
1131
|
-
return { path: `${
|
|
1058
|
+
return { path: `${at2}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
|
|
1132
1059
|
}
|
|
1133
1060
|
if (!isIntegerIn(auth["streamId"], 0, UINT32_MAX)) {
|
|
1134
|
-
return { path: `${
|
|
1061
|
+
return { path: `${at2}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
|
|
1135
1062
|
}
|
|
1136
1063
|
if (mode === "ssm" && auth["senderAddress"] !== void 0) {
|
|
1137
|
-
return { path: `${
|
|
1064
|
+
return { path: `${at2}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
|
|
1138
1065
|
}
|
|
1139
1066
|
if (mode === "asm" && !isNonEmptyString(auth["senderAddress"])) {
|
|
1140
|
-
return { path: `${
|
|
1067
|
+
return { path: `${at2}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
|
|
1141
1068
|
}
|
|
1142
1069
|
if (auth["maxAuthDelayMs"] !== void 0 && !isIntegerIn(auth["maxAuthDelayMs"], 50, 1e3)) {
|
|
1143
|
-
return { path: `${
|
|
1070
|
+
return { path: `${at2}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
|
|
1144
1071
|
}
|
|
1145
1072
|
if (auth["manifestTimerMs"] !== void 0 && !isIntegerIn(auth["manifestTimerMs"], 1, Number.MAX_SAFE_INTEGER)) {
|
|
1146
|
-
return { path: `${
|
|
1073
|
+
return { path: `${at2}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
|
|
1147
1074
|
}
|
|
1148
1075
|
if (auth["manifestTrack"] !== void 0 && !isNonEmptyString(auth["manifestTrack"])) {
|
|
1149
|
-
return { path: `${
|
|
1076
|
+
return { path: `${at2}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
|
|
1150
1077
|
}
|
|
1151
1078
|
if (!isIntegerIn(endpoint["bandwidth"], 1, Number.MAX_SAFE_INTEGER)) {
|
|
1152
|
-
return { path: `${
|
|
1079
|
+
return { path: `${at2}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
|
|
1153
1080
|
}
|
|
1154
1081
|
return void 0;
|
|
1155
1082
|
}
|
|
@@ -1185,41 +1112,41 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
1185
1112
|
const sources = [];
|
|
1186
1113
|
for (let index = 0; index < endpoints.length; index++) {
|
|
1187
1114
|
const endpoint = endpoints[index];
|
|
1188
|
-
const
|
|
1115
|
+
const at2 = `$.multicast.endpoints[${index}]`;
|
|
1189
1116
|
const fail = (code, path, message) => {
|
|
1190
1117
|
issues.push({ code, path, message, endpointIndex: index });
|
|
1191
1118
|
};
|
|
1192
1119
|
sources.push(void 0);
|
|
1193
1120
|
if (!isObject(endpoint)) {
|
|
1194
|
-
fail("multicast_endpoint",
|
|
1121
|
+
fail("multicast_endpoint", at2, "endpoint must be an object");
|
|
1195
1122
|
continue;
|
|
1196
1123
|
}
|
|
1197
1124
|
const protocol = endpoint["protocol"];
|
|
1198
1125
|
if (protocol !== void 0 && protocol !== "ssm" && protocol !== "asm") {
|
|
1199
|
-
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)`);
|
|
1200
1127
|
continue;
|
|
1201
1128
|
}
|
|
1202
1129
|
const hasAuth = endpoint["auth"] !== void 0;
|
|
1203
1130
|
if (protectedCatalog !== hasAuth) {
|
|
1204
|
-
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");
|
|
1205
1132
|
continue;
|
|
1206
1133
|
}
|
|
1207
1134
|
const sourceAddress = endpoint["sourceAddress"];
|
|
1208
1135
|
if (sourceAddress !== void 0 && !isNonEmptyString(sourceAddress)) {
|
|
1209
|
-
fail("multicast_endpoint", `${
|
|
1136
|
+
fail("multicast_endpoint", `${at2}.sourceAddress`, "sourceAddress must be a non-empty string");
|
|
1210
1137
|
continue;
|
|
1211
1138
|
}
|
|
1212
1139
|
const mode = sourceAddress === void 0 ? "asm" : "ssm";
|
|
1213
1140
|
if (protocol !== void 0 && protocol !== mode) {
|
|
1214
|
-
fail("multicast_endpoint", `${
|
|
1141
|
+
fail("multicast_endpoint", `${at2}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
|
|
1215
1142
|
continue;
|
|
1216
1143
|
}
|
|
1217
1144
|
if (!isIntegerIn(endpoint["port"], 1, 65535)) {
|
|
1218
|
-
fail("multicast_endpoint", `${
|
|
1145
|
+
fail("multicast_endpoint", `${at2}.port`, "port must be an integer from 1 to 65535");
|
|
1219
1146
|
continue;
|
|
1220
1147
|
}
|
|
1221
1148
|
if (hasAuth) {
|
|
1222
|
-
const authIssue = endpointAuthIssue(endpoint, mode,
|
|
1149
|
+
const authIssue = endpointAuthIssue(endpoint, mode, at2);
|
|
1223
1150
|
if (authIssue) {
|
|
1224
1151
|
fail("multicast_endpoint_auth", authIssue.path, authIssue.message);
|
|
1225
1152
|
continue;
|
|
@@ -1237,7 +1164,7 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
1237
1164
|
continue;
|
|
1238
1165
|
referenceNamespace ??= namespace;
|
|
1239
1166
|
if (namespace !== referenceNamespace) {
|
|
1240
|
-
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)}`);
|
|
1241
1168
|
}
|
|
1242
1169
|
}
|
|
1243
1170
|
}
|
|
@@ -1254,9 +1181,9 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
1254
1181
|
if (!sources[index])
|
|
1255
1182
|
continue;
|
|
1256
1183
|
const own = tupleOf(index);
|
|
1257
|
-
const
|
|
1184
|
+
const at2 = `$.multicast.endpoints[${index}]`;
|
|
1258
1185
|
if (own.manifestPort !== void 0 && own.manifestPort === own.port) {
|
|
1259
|
-
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 });
|
|
1260
1187
|
continue;
|
|
1261
1188
|
}
|
|
1262
1189
|
for (let other = 0; other < endpoints.length; other++) {
|
|
@@ -1266,11 +1193,11 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
1266
1193
|
if (peer.sg !== own.sg)
|
|
1267
1194
|
continue;
|
|
1268
1195
|
if (peer.port === own.port) {
|
|
1269
|
-
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 });
|
|
1270
1197
|
break;
|
|
1271
1198
|
}
|
|
1272
1199
|
if (own.manifestPort !== void 0 && (own.manifestPort === peer.port || own.manifestPort === peer.manifestPort)) {
|
|
1273
|
-
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 });
|
|
1274
1201
|
break;
|
|
1275
1202
|
}
|
|
1276
1203
|
}
|
|
@@ -1278,6 +1205,538 @@ function multicastCatalogIssues(catalog, options = {}) {
|
|
|
1278
1205
|
return issues;
|
|
1279
1206
|
}
|
|
1280
1207
|
|
|
1208
|
+
// ../mmt-base/dist/fec-catalog.js
|
|
1209
|
+
var DEFAULT_SCHEMES = ["reed-solomon", "raptorq"];
|
|
1210
|
+
var DEFAULT_PROFILES = ["moq-object-v1", "iso-ssbg1-v1"];
|
|
1211
|
+
var RS_MAX_BLOCK = 255;
|
|
1212
|
+
var RAPTORQ_MAX_K = 56403;
|
|
1213
|
+
var MAX_24 = 16777215;
|
|
1214
|
+
var ISO_MIN_T = 14;
|
|
1215
|
+
var GEOMETRY_KEYS = [
|
|
1216
|
+
"scheme",
|
|
1217
|
+
"blockProfile",
|
|
1218
|
+
"mmtpMode",
|
|
1219
|
+
"timescale",
|
|
1220
|
+
"symbolSize",
|
|
1221
|
+
"interleaveDepthMs",
|
|
1222
|
+
"reorderToleranceMs",
|
|
1223
|
+
"sourceSymbols",
|
|
1224
|
+
"fec"
|
|
1225
|
+
];
|
|
1226
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1227
|
+
var has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
1228
|
+
var isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
1229
|
+
var intIn = (v, lo, hi = Number.MAX_SAFE_INTEGER) => isInt(v) && v >= lo && v <= hi;
|
|
1230
|
+
var profileAdmits = (profile, packaging) => profile !== "iso-ssbg1-v1" || packaging === "mmtp";
|
|
1231
|
+
function validateFecCatalog(catalog, receiver = {}) {
|
|
1232
|
+
const schemes = receiver.schemes ?? DEFAULT_SCHEMES;
|
|
1233
|
+
const profiles = receiver.blockProfiles ?? DEFAULT_PROFILES;
|
|
1234
|
+
const overlays = receiver.overlays ?? true;
|
|
1235
|
+
const all = [];
|
|
1236
|
+
const report = (code, path, inst) => {
|
|
1237
|
+
const issue = { code, path };
|
|
1238
|
+
all.push(issue);
|
|
1239
|
+
inst?.issues.push(issue);
|
|
1240
|
+
};
|
|
1241
|
+
const tracks = isObj(catalog) && Array.isArray(catalog.tracks) ? catalog.tracks : [];
|
|
1242
|
+
if (!isObj(catalog) || !Array.isArray(catalog.tracks))
|
|
1243
|
+
report("fec_member", "/tracks");
|
|
1244
|
+
let rejectAll = false;
|
|
1245
|
+
tracks.forEach((t, ti) => {
|
|
1246
|
+
if (!isObj(t))
|
|
1247
|
+
return;
|
|
1248
|
+
if (has(t, "enhancementRepair")) {
|
|
1249
|
+
rejectAll = true;
|
|
1250
|
+
report("fec_enhancement_repair", `/tracks/${ti}/enhancementRepair`);
|
|
1251
|
+
}
|
|
1252
|
+
if (Array.isArray(t.fec))
|
|
1253
|
+
t.fec.forEach((o, ii) => {
|
|
1254
|
+
if (isObj(o) && has(o, "enhancementRepair")) {
|
|
1255
|
+
rejectAll = true;
|
|
1256
|
+
report("fec_enhancement_repair", `/tracks/${ti}/fec/${ii}/enhancementRepair`);
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
});
|
|
1260
|
+
const key = (ns, name) => JSON.stringify([ns ?? null, name]);
|
|
1261
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1262
|
+
tracks.forEach((t, ti) => {
|
|
1263
|
+
if (isObj(t) && typeof t.name === "string") {
|
|
1264
|
+
const k = key(t.namespace, t.name);
|
|
1265
|
+
if (!byName.has(k))
|
|
1266
|
+
byName.set(k, ti);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
const trackAt = (ti) => tracks[ti];
|
|
1270
|
+
const insts = [];
|
|
1271
|
+
tracks.forEach((t, ti) => {
|
|
1272
|
+
if (!isObj(t) || !has(t, "fec") || t.packaging === "fec-repair" || t.packaging === "fec-source")
|
|
1273
|
+
return;
|
|
1274
|
+
if (!Array.isArray(t.fec)) {
|
|
1275
|
+
report("fec_member", `/tracks/${ti}/fec`);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
t.fec.forEach((o, ii) => {
|
|
1279
|
+
const path = `/tracks/${ti}/fec/${ii}`;
|
|
1280
|
+
const inst = {
|
|
1281
|
+
path,
|
|
1282
|
+
ti,
|
|
1283
|
+
track: t,
|
|
1284
|
+
obj: isObj(o) ? o : null,
|
|
1285
|
+
id: null,
|
|
1286
|
+
unavailable: false,
|
|
1287
|
+
overlay: false,
|
|
1288
|
+
issues: [],
|
|
1289
|
+
layer0: null,
|
|
1290
|
+
symbolNamed: null,
|
|
1291
|
+
attached: []
|
|
1292
|
+
};
|
|
1293
|
+
insts.push(inst);
|
|
1294
|
+
if (!inst.obj)
|
|
1295
|
+
return report("fec_member", path, inst);
|
|
1296
|
+
const o2 = inst.obj;
|
|
1297
|
+
if (!has(o2, "instanceId"))
|
|
1298
|
+
inst.id = 0;
|
|
1299
|
+
else if (intIn(o2.instanceId, 0, 255))
|
|
1300
|
+
inst.id = o2.instanceId;
|
|
1301
|
+
else
|
|
1302
|
+
report("fec_member", `${path}/instanceId`, inst);
|
|
1303
|
+
inst.overlay = has(o2, "scope");
|
|
1304
|
+
});
|
|
1305
|
+
});
|
|
1306
|
+
const declaresFec = insts.length > 0;
|
|
1307
|
+
for (const inst of insts) {
|
|
1308
|
+
const o = inst.obj;
|
|
1309
|
+
if (!o)
|
|
1310
|
+
continue;
|
|
1311
|
+
const avail = (field, impl) => {
|
|
1312
|
+
const p = `${inst.path}/${field}`;
|
|
1313
|
+
const absent = field === "scheme" ? "fec_scheme_absent" : "fec_block_profile_absent";
|
|
1314
|
+
const unimpl = field === "scheme" ? "fec_scheme_unimplemented" : "fec_block_profile_unimplemented";
|
|
1315
|
+
if (!has(o, field))
|
|
1316
|
+
return report(absent, p, inst), false;
|
|
1317
|
+
if (typeof o[field] !== "string")
|
|
1318
|
+
return report("fec_member", p, inst), true;
|
|
1319
|
+
if (!impl.includes(o[field]))
|
|
1320
|
+
return report(unimpl, p, inst), false;
|
|
1321
|
+
return true;
|
|
1322
|
+
};
|
|
1323
|
+
if (!avail("scheme", schemes) || !avail("blockProfile", profiles))
|
|
1324
|
+
inst.unavailable = true;
|
|
1325
|
+
else if (typeof o.blockProfile === "string" && !profileAdmits(o.blockProfile, inst.track.packaging)) {
|
|
1326
|
+
report("fec_block_profile_packaging", `${inst.path}/blockProfile`, inst);
|
|
1327
|
+
inst.unavailable = true;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const live = (i) => !i.unavailable;
|
|
1331
|
+
for (const inst of insts.filter(live)) {
|
|
1332
|
+
const o = inst.obj;
|
|
1333
|
+
if (!o)
|
|
1334
|
+
continue;
|
|
1335
|
+
const p = inst.path;
|
|
1336
|
+
for (const m of ["sourceSymbols", "repairSymbols", "symbolSize"])
|
|
1337
|
+
if (!intIn(o[m], 1))
|
|
1338
|
+
report("fec_member", `${p}/${m}`, inst);
|
|
1339
|
+
if (typeof o.repairTrack !== "string")
|
|
1340
|
+
report("fec_member", `${p}/repairTrack`, inst);
|
|
1341
|
+
if (has(o, "symbolTrack") && typeof o.symbolTrack !== "string")
|
|
1342
|
+
report("fec_member", `${p}/symbolTrack`, inst);
|
|
1343
|
+
if (has(o, "interleaveDepthMs")) {
|
|
1344
|
+
const d = o.interleaveDepthMs;
|
|
1345
|
+
if (!intIn(d, 0) || d === 0 && o.blockProfile !== "iso-ssbg1-v1")
|
|
1346
|
+
report("fec_member", `${p}/interleaveDepthMs`, inst);
|
|
1347
|
+
}
|
|
1348
|
+
if (has(o, "reorderToleranceMs")) {
|
|
1349
|
+
const r = o.reorderToleranceMs;
|
|
1350
|
+
if (typeof r !== "number" || !Number.isFinite(r) || r < 0)
|
|
1351
|
+
report("fec_member", `${p}/reorderToleranceMs`, inst);
|
|
1352
|
+
}
|
|
1353
|
+
if (has(o, "label") && typeof o.label !== "string")
|
|
1354
|
+
report("fec_member", `${p}/label`, inst);
|
|
1355
|
+
if (inst.overlay && o.scope !== "keyframe")
|
|
1356
|
+
report("fec_member", `${p}/scope`, inst);
|
|
1357
|
+
}
|
|
1358
|
+
const idGroups = /* @__PURE__ */ new Map();
|
|
1359
|
+
for (const inst of insts)
|
|
1360
|
+
if (inst.id !== null) {
|
|
1361
|
+
const k = `${inst.ti}:${inst.id}`;
|
|
1362
|
+
idGroups.set(k, [...idGroups.get(k) ?? [], inst]);
|
|
1363
|
+
}
|
|
1364
|
+
for (const group of idGroups.values())
|
|
1365
|
+
if (group.length > 1)
|
|
1366
|
+
for (const inst of group.filter(live))
|
|
1367
|
+
report("fec_instance_id", inst.obj && has(inst.obj, "instanceId") ? `${inst.path}/instanceId` : inst.path, inst);
|
|
1368
|
+
const nameUses = /* @__PURE__ */ new Map();
|
|
1369
|
+
for (const inst of insts)
|
|
1370
|
+
for (const field of ["repairTrack", "symbolTrack"])
|
|
1371
|
+
if (inst.obj && typeof inst.obj[field] === "string") {
|
|
1372
|
+
const k = key(inst.track.namespace, inst.obj[field]);
|
|
1373
|
+
nameUses.set(k, [...nameUses.get(k) ?? [], { inst, field }]);
|
|
1374
|
+
}
|
|
1375
|
+
const namedBy = /* @__PURE__ */ new Map();
|
|
1376
|
+
for (const [k, uses] of nameUses) {
|
|
1377
|
+
const tj = byName.get(k);
|
|
1378
|
+
for (const { inst, field } of uses) {
|
|
1379
|
+
const path = `${inst.path}/${field}`;
|
|
1380
|
+
if (tj === void 0 || trackAt(tj).packaging !== (field === "repairTrack" ? "fec-repair" : "fec-source")) {
|
|
1381
|
+
if (live(inst))
|
|
1382
|
+
report("fec_naming", path, inst);
|
|
1383
|
+
} else if (uses.length > 1) {
|
|
1384
|
+
if (live(inst))
|
|
1385
|
+
report("fec_naming", path, inst);
|
|
1386
|
+
if (!namedBy.has(tj))
|
|
1387
|
+
namedBy.set(tj, inst);
|
|
1388
|
+
} else {
|
|
1389
|
+
namedBy.set(tj, inst);
|
|
1390
|
+
if (field === "repairTrack")
|
|
1391
|
+
inst.layer0 = tj;
|
|
1392
|
+
else
|
|
1393
|
+
inst.symbolNamed = tj;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
const owner = /* @__PURE__ */ new Map();
|
|
1398
|
+
if (declaresFec)
|
|
1399
|
+
tracks.forEach((t, tj) => {
|
|
1400
|
+
if (!isObj(t) || t.packaging !== "fec-repair" && t.packaging !== "fec-source")
|
|
1401
|
+
return;
|
|
1402
|
+
const tp = `/tracks/${tj}`;
|
|
1403
|
+
const named = namedBy.get(tj);
|
|
1404
|
+
if (named) {
|
|
1405
|
+
owner.set(tj, named);
|
|
1406
|
+
if (!live(named))
|
|
1407
|
+
return;
|
|
1408
|
+
if (has(t, "source") && t.source !== named.track.name)
|
|
1409
|
+
report("fec_resolution", `${tp}/source`, named);
|
|
1410
|
+
if (has(t, "instanceId") && t.instanceId !== named.id)
|
|
1411
|
+
report("fec_resolution", `${tp}/instanceId`, named);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (!has(t, "source"))
|
|
1415
|
+
return report("fec_orphan", tp);
|
|
1416
|
+
const id = has(t, "instanceId") ? t.instanceId : 0;
|
|
1417
|
+
const src = typeof t.source === "string" ? byName.get(key(t.namespace, t.source)) : void 0;
|
|
1418
|
+
const target = src === void 0 ? void 0 : insts.find((i) => i.ti === src && i.id === id);
|
|
1419
|
+
if (!target)
|
|
1420
|
+
return report("fec_naming", has(t, "instanceId") && src !== void 0 ? `${tp}/instanceId` : `${tp}/source`);
|
|
1421
|
+
owner.set(tj, target);
|
|
1422
|
+
target.attached.push(tj);
|
|
1423
|
+
});
|
|
1424
|
+
for (const [tj, inst] of owner) {
|
|
1425
|
+
if (!live(inst))
|
|
1426
|
+
continue;
|
|
1427
|
+
const t = trackAt(tj);
|
|
1428
|
+
const tp = `/tracks/${tj}`;
|
|
1429
|
+
const symbol = t.packaging === "fec-source";
|
|
1430
|
+
if (has(t, "source") && typeof t.source !== "string")
|
|
1431
|
+
report("fec_member", `${tp}/source`, inst);
|
|
1432
|
+
if (has(t, "instanceId") && !intIn(t.instanceId, 0, 255))
|
|
1433
|
+
report("fec_member", `${tp}/instanceId`, inst);
|
|
1434
|
+
for (const m of ["repairLayer", "repairSymbols"]) {
|
|
1435
|
+
if (!has(t, m))
|
|
1436
|
+
continue;
|
|
1437
|
+
if (symbol || !intIn(t[m], m === "repairLayer" ? 0 : 1))
|
|
1438
|
+
report("fec_member", `${tp}/${m}`, inst);
|
|
1439
|
+
}
|
|
1440
|
+
if (has(t, "priority") && !intIn(t.priority, symbol ? 0 : 192, 255))
|
|
1441
|
+
report("fec_member", `${tp}/priority`, inst);
|
|
1442
|
+
for (const k of Object.keys(t))
|
|
1443
|
+
if (GEOMETRY_KEYS.includes(k) || k.startsWith("groupDuration"))
|
|
1444
|
+
report("fec_track_geometry", `${tp}/${k.replace(/~/g, "~0").replace(/\//g, "~1")}`, inst);
|
|
1445
|
+
}
|
|
1446
|
+
for (const inst of insts.filter(live)) {
|
|
1447
|
+
const o = inst.obj;
|
|
1448
|
+
if (!o)
|
|
1449
|
+
continue;
|
|
1450
|
+
const repairs = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-repair");
|
|
1451
|
+
const symbols = inst.attached.filter((tj) => trackAt(tj).packaging === "fec-source");
|
|
1452
|
+
if (symbols.length + (inst.symbolNamed !== null ? 1 : 0) > 1)
|
|
1453
|
+
for (const tj of symbols)
|
|
1454
|
+
report("fec_symbol_tracks", `/tracks/${tj}`, inst);
|
|
1455
|
+
const l0 = inst.layer0 === null ? null : trackAt(inst.layer0);
|
|
1456
|
+
const l0p = `/tracks/${inst.layer0}`;
|
|
1457
|
+
if (inst.overlay) {
|
|
1458
|
+
if (l0 && has(l0, "repairLayer"))
|
|
1459
|
+
report("fec_overlay", `${l0p}/repairLayer`, inst);
|
|
1460
|
+
for (const tj of repairs)
|
|
1461
|
+
report("fec_overlay", has(trackAt(tj), "repairLayer") ? `/tracks/${tj}/repairLayer` : `/tracks/${tj}`, inst);
|
|
1462
|
+
} else {
|
|
1463
|
+
if (l0 && has(l0, "repairLayer") && l0.repairLayer !== 0)
|
|
1464
|
+
report("fec_layers", `${l0p}/repairLayer`, inst);
|
|
1465
|
+
if (l0 && has(l0, "repairSymbols") && intIn(o.repairSymbols, 1) && l0.repairSymbols !== o.repairSymbols)
|
|
1466
|
+
report("fec_layer0", `${l0p}/repairSymbols`, inst);
|
|
1467
|
+
const layers = [];
|
|
1468
|
+
for (const tj of repairs) {
|
|
1469
|
+
const t = trackAt(tj);
|
|
1470
|
+
if (!has(t, "repairLayer"))
|
|
1471
|
+
report("fec_member", `/tracks/${tj}/repairLayer`, inst);
|
|
1472
|
+
else if (t.repairLayer === 0)
|
|
1473
|
+
report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
|
|
1474
|
+
else if (intIn(t.repairLayer, 1))
|
|
1475
|
+
layers.push({ tj, layer: t.repairLayer });
|
|
1476
|
+
if (!has(t, "repairSymbols"))
|
|
1477
|
+
report("fec_member", `/tracks/${tj}/repairSymbols`, inst);
|
|
1478
|
+
}
|
|
1479
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1480
|
+
for (const { layer } of layers)
|
|
1481
|
+
counts.set(layer, (counts.get(layer) ?? 0) + 1);
|
|
1482
|
+
let missing = 1;
|
|
1483
|
+
while (counts.has(missing))
|
|
1484
|
+
missing++;
|
|
1485
|
+
for (const { tj, layer } of layers)
|
|
1486
|
+
if (counts.get(layer) > 1 || layer > missing)
|
|
1487
|
+
report("fec_layers", `/tracks/${tj}/repairLayer`, inst);
|
|
1488
|
+
const chain = [
|
|
1489
|
+
{ layer: 0, tj: inst.layer0, pri: l0 && intIn(l0.priority, 192, 255) ? l0.priority : l0 && has(l0, "priority") ? null : 240 },
|
|
1490
|
+
...layers.map(({ tj, layer }) => {
|
|
1491
|
+
const pr = trackAt(tj).priority;
|
|
1492
|
+
return { layer, tj, pri: intIn(pr, 192, 255) ? pr : null };
|
|
1493
|
+
})
|
|
1494
|
+
].sort((a, b) => a.layer - b.layer);
|
|
1495
|
+
let prev = null;
|
|
1496
|
+
for (const c of chain) {
|
|
1497
|
+
if (c.pri === null)
|
|
1498
|
+
continue;
|
|
1499
|
+
if (prev !== null && c.pri < prev)
|
|
1500
|
+
report("fec_priority", `/tracks/${c.tj}/priority`, inst);
|
|
1501
|
+
prev = Math.max(prev ?? c.pri, c.pri);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
const K = o.sourceSymbols;
|
|
1505
|
+
const T = o.symbolSize;
|
|
1506
|
+
const P0 = o.repairSymbols;
|
|
1507
|
+
if (intIn(K, 1) && intIn(P0, 1)) {
|
|
1508
|
+
let P = P0;
|
|
1509
|
+
if (!inst.overlay)
|
|
1510
|
+
for (const tj of repairs) {
|
|
1511
|
+
const t = trackAt(tj);
|
|
1512
|
+
if (intIn(t.repairLayer, 1) && intIn(t.repairSymbols, 1))
|
|
1513
|
+
P += t.repairSymbols;
|
|
1514
|
+
}
|
|
1515
|
+
if (P > MAX_24 || K + P - 1 > MAX_24)
|
|
1516
|
+
report("fec_bounds", inst.path, inst);
|
|
1517
|
+
else if (o.scheme === "reed-solomon" && K + P > RS_MAX_BLOCK)
|
|
1518
|
+
report("fec_bounds", inst.path, inst);
|
|
1519
|
+
}
|
|
1520
|
+
if (o.scheme === "raptorq" && intIn(K, 1) && K > RAPTORQ_MAX_K)
|
|
1521
|
+
report("fec_bounds", `${inst.path}/sourceSymbols`, inst);
|
|
1522
|
+
if (intIn(T, 1)) {
|
|
1523
|
+
if (o.scheme === "raptorq" && T % 8 !== 0)
|
|
1524
|
+
report("fec_bounds", `${inst.path}/symbolSize`, inst);
|
|
1525
|
+
else if (o.blockProfile === "iso-ssbg1-v1" && T < ISO_MIN_T)
|
|
1526
|
+
report("fec_bounds", `${inst.path}/symbolSize`, inst);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
const overlayTracks = new Set(insts.filter((i) => live(i) && i.overlay).map((i) => i.ti));
|
|
1530
|
+
for (const ti of overlayTracks) {
|
|
1531
|
+
const ofTrack = insts.filter((i) => i.ti === ti && live(i));
|
|
1532
|
+
const ovs = ofTrack.filter((i) => i.overlay);
|
|
1533
|
+
if (!overlays) {
|
|
1534
|
+
for (const ov of ovs)
|
|
1535
|
+
report("fec_overlay_unsupported", `${ov.path}/scope`, ov);
|
|
1536
|
+
for (const i of ofTrack)
|
|
1537
|
+
i.issues.length === 0 && i.issues.push({ code: "fec_overlay_unsupported", path: `${ovs[0].path}/scope` });
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (ovs.length > 1)
|
|
1541
|
+
for (const ov of ovs)
|
|
1542
|
+
report("fec_overlay", `${ov.path}/scope`, ov);
|
|
1543
|
+
for (const ov of ovs)
|
|
1544
|
+
if (ov.obj?.blockProfile !== "iso-ssbg1-v1")
|
|
1545
|
+
report("fec_overlay", `${ov.path}/blockProfile`, ov);
|
|
1546
|
+
const cause = ovs.flatMap((ov) => ov.issues).find((x) => x.code === "fec_overlay");
|
|
1547
|
+
if (cause) {
|
|
1548
|
+
for (const ov of ovs)
|
|
1549
|
+
if (ov.issues.length === 0)
|
|
1550
|
+
ov.issues.push(cause);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const rule4 = all.find((x) => x.code === "fec_enhancement_repair");
|
|
1554
|
+
if (rejectAll && rule4) {
|
|
1555
|
+
for (const i of insts)
|
|
1556
|
+
if (i.issues.length === 0)
|
|
1557
|
+
i.issues.push(rule4);
|
|
1558
|
+
}
|
|
1559
|
+
return {
|
|
1560
|
+
instances: insts.map((i) => ({
|
|
1561
|
+
path: i.path,
|
|
1562
|
+
track: typeof i.track.name === "string" ? i.track.name : null,
|
|
1563
|
+
instanceId: i.id,
|
|
1564
|
+
outcome: rejectAll || !i.unavailable && i.issues.length > 0 ? "violation" : i.unavailable ? "unavailable" : "ok",
|
|
1565
|
+
issues: i.issues,
|
|
1566
|
+
trackIndex: i.ti,
|
|
1567
|
+
repairTrack: i.layer0,
|
|
1568
|
+
boundRepairTracks: i.attached.filter((tj) => trackAt(tj).packaging === "fec-repair"),
|
|
1569
|
+
symbolTrack: i.symbolNamed ?? i.attached.find((tj) => trackAt(tj).packaging === "fec-source") ?? null
|
|
1570
|
+
})),
|
|
1571
|
+
issues: all
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// ../mmt-base/dist/fec-bindings.js
|
|
1576
|
+
var RUNTIME_FEC_RECEIVER = Object.freeze({
|
|
1577
|
+
schemes: Object.freeze(["raptorq", "reed-solomon"]),
|
|
1578
|
+
blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
|
|
1579
|
+
overlays: true
|
|
1580
|
+
});
|
|
1581
|
+
var DEFAULT_FEC_REPAIR_PRIORITY = 240;
|
|
1582
|
+
var recordOf = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
1583
|
+
var escape = (s) => s.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1584
|
+
function fecCatalogTracks(catalog) {
|
|
1585
|
+
const root = recordOf(catalog) ?? {};
|
|
1586
|
+
const tracks = [];
|
|
1587
|
+
const pointers = [];
|
|
1588
|
+
const hangNames = /* @__PURE__ */ new Set();
|
|
1589
|
+
for (const role of ["video", "audio"]) {
|
|
1590
|
+
for (const [name, r] of Object.entries(recordOf(recordOf(root[role])?.["renditions"]) ?? {})) {
|
|
1591
|
+
const entry = recordOf(r);
|
|
1592
|
+
if (!entry)
|
|
1593
|
+
continue;
|
|
1594
|
+
hangNames.add(name);
|
|
1595
|
+
const kind = recordOf(entry["container"])?.["kind"];
|
|
1596
|
+
const view2 = { name, packaging: kind === void 0 || kind === "mmtp-fec" ? "mmtp" : kind };
|
|
1597
|
+
if (entry["fec"] !== void 0)
|
|
1598
|
+
view2["fec"] = entry["fec"];
|
|
1599
|
+
tracks.push(view2);
|
|
1600
|
+
pointers.push(`/${role}/renditions/${escape(name)}`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const hangRenditions = tracks.length;
|
|
1604
|
+
for (const [name, r] of Object.entries(recordOf(recordOf(root["repair"])?.["renditions"]) ?? {})) {
|
|
1605
|
+
hangNames.add(name);
|
|
1606
|
+
tracks.push({ packaging: "fec-repair", ...recordOf(r), name });
|
|
1607
|
+
pointers.push(`/repair/renditions/${escape(name)}`);
|
|
1608
|
+
}
|
|
1609
|
+
const raw = Array.isArray(root["tracks"]) ? root["tracks"] : [];
|
|
1610
|
+
const named = new Set(hangNames);
|
|
1611
|
+
raw.forEach((t, i) => {
|
|
1612
|
+
const name = recordOf(t)?.["name"];
|
|
1613
|
+
if (typeof name === "string")
|
|
1614
|
+
named.add(name);
|
|
1615
|
+
if (typeof name === "string" && hangNames.has(name))
|
|
1616
|
+
return;
|
|
1617
|
+
tracks.push(t);
|
|
1618
|
+
pointers.push(`/tracks/${i}`);
|
|
1619
|
+
});
|
|
1620
|
+
tracks.slice(0, hangRenditions).forEach((t, ti) => {
|
|
1621
|
+
const view2 = t;
|
|
1622
|
+
if (!Array.isArray(view2["fec"]))
|
|
1623
|
+
return;
|
|
1624
|
+
view2["fec"].forEach((o, ii) => {
|
|
1625
|
+
const repairTrack = recordOf(o)?.["repairTrack"];
|
|
1626
|
+
if (typeof repairTrack !== "string" || named.has(repairTrack))
|
|
1627
|
+
return;
|
|
1628
|
+
named.add(repairTrack);
|
|
1629
|
+
const instanceId = recordOf(o)?.["instanceId"];
|
|
1630
|
+
tracks.push({ name: repairTrack, packaging: "fec-repair", source: view2["name"], ...instanceId === void 0 ? {} : { instanceId } });
|
|
1631
|
+
pointers.push(`${pointers[ti]}/fec/${ii}/repairTrack`);
|
|
1632
|
+
});
|
|
1633
|
+
});
|
|
1634
|
+
return { tracks, pointers };
|
|
1635
|
+
}
|
|
1636
|
+
function resolveFecBindings(catalog, receiver = RUNTIME_FEC_RECEIVER) {
|
|
1637
|
+
const { tracks, pointers } = fecCatalogTracks(catalog);
|
|
1638
|
+
const report = validateFecCatalog({ tracks }, receiver);
|
|
1639
|
+
const at2 = (path) => {
|
|
1640
|
+
const m = /^\/tracks\/(\d+)(.*)$/.exec(path);
|
|
1641
|
+
return m ? pointers[Number(m[1])] + m[2] : path;
|
|
1642
|
+
};
|
|
1643
|
+
const trackAt = (i) => recordOf(tracks[i]) ?? {};
|
|
1644
|
+
const nameAt = (i) => i === null ? void 0 : trackAt(i)["name"];
|
|
1645
|
+
const why = (i) => Object.freeze({
|
|
1646
|
+
code: i.issues[0]?.code ?? "fec_member",
|
|
1647
|
+
path: at2(i.issues[0]?.path ?? i.path),
|
|
1648
|
+
outcome: i.outcome === "unavailable" ? "unavailable" : "violation"
|
|
1649
|
+
});
|
|
1650
|
+
const out = /* @__PURE__ */ new Map();
|
|
1651
|
+
tracks.forEach((t, ti) => {
|
|
1652
|
+
const track = recordOf(t);
|
|
1653
|
+
const name = track?.["name"];
|
|
1654
|
+
if (!track || typeof name !== "string" || out.has(name))
|
|
1655
|
+
return;
|
|
1656
|
+
if (track["packaging"] === "fec-repair" || track["packaging"] === "fec-source")
|
|
1657
|
+
return;
|
|
1658
|
+
const fec = track["fec"];
|
|
1659
|
+
if (fec === void 0)
|
|
1660
|
+
return;
|
|
1661
|
+
if (!Array.isArray(fec)) {
|
|
1662
|
+
const reason = { code: "fec_member", path: at2(`/tracks/${ti}/fec`), outcome: "violation" };
|
|
1663
|
+
out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason: Object.freeze(reason) }));
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (fec.length === 0)
|
|
1667
|
+
return;
|
|
1668
|
+
const insts = report.instances.filter((i) => i.trackIndex === ti);
|
|
1669
|
+
const isOverlay = (i) => Object.hasOwn(recordOf(fec[Number(i.path.split("/").pop())]) ?? {}, "scope");
|
|
1670
|
+
const bases = insts.filter((i) => !isOverlay(i));
|
|
1671
|
+
const okBases = bases.filter((i) => i.outcome === "ok");
|
|
1672
|
+
if (okBases.length !== 1) {
|
|
1673
|
+
const reason = (
|
|
1674
|
+
// No base instance at all (overlay-only `fec`) is a count of 0, not
|
|
1675
|
+
// the overlay's own issue.
|
|
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
|
+
);
|
|
1678
|
+
out.set(name, Object.freeze({ status: "unavailable", track: name, pointer: pointers[ti], reason }));
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const b = okBases[0];
|
|
1682
|
+
const base = geometry(recordOf(fec[Number(b.path.split("/").pop())]), b.instanceId, nameAt(b.symbolTrack));
|
|
1683
|
+
const priorityOf = (i) => {
|
|
1684
|
+
const p = i === null ? void 0 : trackAt(i)["priority"];
|
|
1685
|
+
return typeof p === "number" ? p : DEFAULT_FEC_REPAIR_PRIORITY;
|
|
1686
|
+
};
|
|
1687
|
+
const layers = [
|
|
1688
|
+
{ layer: 0, repairTrackName: base.repairTrack, repairSymbols: base.repairSymbols, rsIdOffset: 0, priority: priorityOf(b.repairTrack) }
|
|
1689
|
+
];
|
|
1690
|
+
const more = b.boundRepairTracks.map((tj) => ({ tj, layer: trackAt(tj)["repairLayer"] })).sort((x, y) => x.layer - y.layer);
|
|
1691
|
+
let offset = base.repairSymbols;
|
|
1692
|
+
for (const { tj, layer } of more) {
|
|
1693
|
+
const repairSymbols = trackAt(tj)["repairSymbols"];
|
|
1694
|
+
layers.push({ layer, repairTrackName: nameAt(tj), repairSymbols, rsIdOffset: offset, priority: priorityOf(tj) });
|
|
1695
|
+
offset += repairSymbols;
|
|
1696
|
+
}
|
|
1697
|
+
let overlay;
|
|
1698
|
+
let overlayUnavailable;
|
|
1699
|
+
const ov = insts.find(isOverlay);
|
|
1700
|
+
if (ov?.outcome === "ok") {
|
|
1701
|
+
const g = geometry(recordOf(fec[Number(ov.path.split("/").pop())]), ov.instanceId, nameAt(ov.symbolTrack));
|
|
1702
|
+
overlay = Object.freeze({
|
|
1703
|
+
repairTrackName: g.repairTrack,
|
|
1704
|
+
scope: "keyframe",
|
|
1705
|
+
sourceSymbols: g.sourceSymbols,
|
|
1706
|
+
repairSymbols: g.repairSymbols,
|
|
1707
|
+
priority: priorityOf(ov.repairTrack),
|
|
1708
|
+
geometry: g
|
|
1709
|
+
});
|
|
1710
|
+
} else if (ov)
|
|
1711
|
+
overlayUnavailable = why(ov);
|
|
1712
|
+
out.set(name, Object.freeze({
|
|
1713
|
+
status: "ok",
|
|
1714
|
+
track: name,
|
|
1715
|
+
pointer: pointers[ti],
|
|
1716
|
+
base,
|
|
1717
|
+
layers: Object.freeze(layers.map((l) => Object.freeze(l))),
|
|
1718
|
+
totalBaseRepairSymbols: offset,
|
|
1719
|
+
...overlay ? { overlay } : {},
|
|
1720
|
+
...overlayUnavailable ? { overlayUnavailable } : {}
|
|
1721
|
+
}));
|
|
1722
|
+
});
|
|
1723
|
+
return out;
|
|
1724
|
+
}
|
|
1725
|
+
function geometry(o, instanceId, symbolTrack) {
|
|
1726
|
+
return Object.freeze({
|
|
1727
|
+
instanceId,
|
|
1728
|
+
scheme: o["scheme"],
|
|
1729
|
+
blockProfile: o["blockProfile"],
|
|
1730
|
+
sourceSymbols: o["sourceSymbols"],
|
|
1731
|
+
repairSymbols: o["repairSymbols"],
|
|
1732
|
+
symbolSize: o["symbolSize"],
|
|
1733
|
+
...typeof o["interleaveDepthMs"] === "number" ? { interleaveDepthMs: o["interleaveDepthMs"] } : {},
|
|
1734
|
+
...typeof o["reorderToleranceMs"] === "number" ? { reorderToleranceMs: o["reorderToleranceMs"] } : {},
|
|
1735
|
+
repairTrack: o["repairTrack"],
|
|
1736
|
+
...symbolTrack !== void 0 ? { symbolTrack } : {}
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1281
1740
|
// src/mfu-reassembler.ts
|
|
1282
1741
|
function fragmentProvenance(fragment) {
|
|
1283
1742
|
if (fragment.deliveryProvenance !== void 0) return fragment.deliveryProvenance;
|
|
@@ -6279,321 +6738,6 @@ if (void 0) {
|
|
|
6279
6738
|
});
|
|
6280
6739
|
}
|
|
6281
6740
|
|
|
6282
|
-
// src/repair-layers.ts
|
|
6283
|
-
var REPAIR_PRIORITY_MIN = 192;
|
|
6284
|
-
var REPAIR_PRIORITY_MAX = 255;
|
|
6285
|
-
var DEFAULT_REPAIR_PRIORITY = 240;
|
|
6286
|
-
var MAX_RAPTORQ_SOURCE_SYMBOLS = 56403;
|
|
6287
|
-
var MAX_REPAIR_SYMBOLS = 16777215;
|
|
6288
|
-
var MAX_ENCODING_SYMBOL_ID = 16777215;
|
|
6289
|
-
var MAX_REED_SOLOMON_BLOCK_SYMBOLS = 255;
|
|
6290
|
-
var RepairContractError = class extends Error {
|
|
6291
|
-
constructor(code, path, message) {
|
|
6292
|
-
super(`[repair-layers:${code}] ${path}: ${message}`);
|
|
6293
|
-
this.code = code;
|
|
6294
|
-
this.path = path;
|
|
6295
|
-
this.name = "RepairContractError";
|
|
6296
|
-
}
|
|
6297
|
-
};
|
|
6298
|
-
var throwRepairContractError = (code, path, message) => {
|
|
6299
|
-
throw new RepairContractError(code, path, message);
|
|
6300
|
-
};
|
|
6301
|
-
function readFecBlockProfile(value, path, declaresFecInstance, fail) {
|
|
6302
|
-
if (value === void 0 && !declaresFecInstance) return void 0;
|
|
6303
|
-
if (isFecBlockProfile(value)) return value;
|
|
6304
|
-
if (typeof value === "string" && value.length > 0) {
|
|
6305
|
-
return fail(
|
|
6306
|
-
"track_fec_block_profile_unimplemented",
|
|
6307
|
-
path,
|
|
6308
|
-
`${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
|
|
6309
|
-
);
|
|
6310
|
-
}
|
|
6311
|
-
try {
|
|
6312
|
-
return parseFecBlockProfile(value, "blockProfile");
|
|
6313
|
-
} catch (error) {
|
|
6314
|
-
return fail("track_fec_block_profile", path, error.message);
|
|
6315
|
-
}
|
|
6316
|
-
}
|
|
6317
|
-
var REPAIR_FIELDS = ["depends", "repairLayer", "repairSymbols", "priority", "scope", "sourceSymbols"];
|
|
6318
|
-
var REPAIR_TRACK_GEOMETRY_KEYS = [
|
|
6319
|
-
"mmtpMode",
|
|
6320
|
-
"timescale",
|
|
6321
|
-
"groupDurationMs",
|
|
6322
|
-
"groupDurationTicks",
|
|
6323
|
-
"keyframeIntervalMs",
|
|
6324
|
-
"keyframeIntervalTicks",
|
|
6325
|
-
"fec",
|
|
6326
|
-
"symbolSize",
|
|
6327
|
-
"interleaveDepthMs"
|
|
6328
|
-
];
|
|
6329
|
-
var REPAIR_TRACK_ONLY_KEYS = ["repairLayer", "repairSymbols", "sourceSymbols", "scope"];
|
|
6330
|
-
function rejectRepairTrackGeometry(path, fields, fail) {
|
|
6331
|
-
for (const key of REPAIR_TRACK_GEOMETRY_KEYS) {
|
|
6332
|
-
if (fields[key] !== void 0) {
|
|
6333
|
-
fail(
|
|
6334
|
-
"track_repair_geometry",
|
|
6335
|
-
`${path}.${key}`,
|
|
6336
|
-
"a repair track carries no block geometry; it is shared with the source track's fec object"
|
|
6337
|
-
);
|
|
6338
|
-
}
|
|
6339
|
-
}
|
|
6340
|
-
}
|
|
6341
|
-
function repairReachability(fecObjects) {
|
|
6342
|
-
let declaresFecSource = false;
|
|
6343
|
-
const layerZeroNames = /* @__PURE__ */ new Set();
|
|
6344
|
-
for (const candidate of fecObjects) {
|
|
6345
|
-
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue;
|
|
6346
|
-
const fec = candidate;
|
|
6347
|
-
if (fec["algorithm"] === "none") continue;
|
|
6348
|
-
declaresFecSource = true;
|
|
6349
|
-
const repairTrack = fec["repairTrack"];
|
|
6350
|
-
if (typeof repairTrack === "string" && repairTrack.length > 0) layerZeroNames.add(repairTrack);
|
|
6351
|
-
}
|
|
6352
|
-
return { declaresFecSource, layerZeroNames };
|
|
6353
|
-
}
|
|
6354
|
-
function assertRepairTrackReachable(descriptor, reachability, fail) {
|
|
6355
|
-
if (dependsTarget(descriptor) !== void 0) return;
|
|
6356
|
-
if (reachability.declaresFecSource && !reachability.layerZeroNames.has(descriptor.name)) {
|
|
6357
|
-
fail(
|
|
6358
|
-
"track_repair_depends",
|
|
6359
|
-
`${descriptor.path}.depends`,
|
|
6360
|
-
"no source names this repair track in fec.repairTrack and it declares no depends"
|
|
6361
|
-
);
|
|
6362
|
-
}
|
|
6363
|
-
}
|
|
6364
|
-
function rejectRepairFieldsOnMediaTrack(path, fields, fail) {
|
|
6365
|
-
for (const key of REPAIR_TRACK_ONLY_KEYS) {
|
|
6366
|
-
if (fields[key] !== void 0) {
|
|
6367
|
-
fail("track_repair_layer", `${path}.${key}`, `${key} is a repair track field`);
|
|
6368
|
-
}
|
|
6369
|
-
}
|
|
6370
|
-
}
|
|
6371
|
-
function describeRepairTrack(name, path, fields) {
|
|
6372
|
-
const descriptor = { name, path };
|
|
6373
|
-
for (const field of REPAIR_FIELDS) {
|
|
6374
|
-
if (field in fields) descriptor[field] = fields[field];
|
|
6375
|
-
}
|
|
6376
|
-
return descriptor;
|
|
6377
|
-
}
|
|
6378
|
-
function isSafeInteger(value) {
|
|
6379
|
-
return typeof value === "number" && Number.isSafeInteger(value);
|
|
6380
|
-
}
|
|
6381
|
-
function positiveInteger(value) {
|
|
6382
|
-
return isSafeInteger(value) && value > 0;
|
|
6383
|
-
}
|
|
6384
|
-
function dependsTarget(descriptor) {
|
|
6385
|
-
const depends = descriptor.depends;
|
|
6386
|
-
return Array.isArray(depends) && depends.length === 1 && typeof depends[0] === "string" && depends[0].length > 0 ? depends[0] : void 0;
|
|
6387
|
-
}
|
|
6388
|
-
function validateRepairTrackFields(descriptor, fail) {
|
|
6389
|
-
const { path } = descriptor;
|
|
6390
|
-
if (descriptor.depends !== void 0 && dependsTarget(descriptor) === void 0) {
|
|
6391
|
-
fail("track_repair_depends", `${path}.depends`, "must name exactly one source track");
|
|
6392
|
-
}
|
|
6393
|
-
if (descriptor.repairLayer !== void 0 && (!isSafeInteger(descriptor.repairLayer) || descriptor.repairLayer < 0)) {
|
|
6394
|
-
fail("track_repair_layer", `${path}.repairLayer`, "must be a non-negative integer");
|
|
6395
|
-
}
|
|
6396
|
-
if (descriptor.repairSymbols !== void 0 && (!positiveInteger(descriptor.repairSymbols) || descriptor.repairSymbols > MAX_REPAIR_SYMBOLS)) {
|
|
6397
|
-
fail("track_repair_layer", `${path}.repairSymbols`, `must be a positive integer at most ${MAX_REPAIR_SYMBOLS}`);
|
|
6398
|
-
}
|
|
6399
|
-
if (descriptor.priority !== void 0 && (!isSafeInteger(descriptor.priority) || descriptor.priority < REPAIR_PRIORITY_MIN || descriptor.priority > REPAIR_PRIORITY_MAX)) {
|
|
6400
|
-
fail(
|
|
6401
|
-
"track_repair_priority",
|
|
6402
|
-
`${path}.priority`,
|
|
6403
|
-
`must be an integer from ${REPAIR_PRIORITY_MIN} to ${REPAIR_PRIORITY_MAX} (MoQT scale, lower first)`
|
|
6404
|
-
);
|
|
6405
|
-
}
|
|
6406
|
-
if (descriptor.scope !== void 0) {
|
|
6407
|
-
if (descriptor.scope !== "keyframe") {
|
|
6408
|
-
fail("track_repair_overlay", `${path}.scope`, 'must be "keyframe"');
|
|
6409
|
-
}
|
|
6410
|
-
if (descriptor.repairLayer !== void 0) {
|
|
6411
|
-
fail("track_repair_overlay", `${path}.repairLayer`, "an overlay is a separate FEC instance and carries no repairLayer");
|
|
6412
|
-
}
|
|
6413
|
-
for (const field of ["depends", "sourceSymbols", "repairSymbols", "priority"]) {
|
|
6414
|
-
if (descriptor[field] === void 0) {
|
|
6415
|
-
fail("track_repair_overlay", `${path}.${field}`, `an overlay must declare ${field}`);
|
|
6416
|
-
}
|
|
6417
|
-
}
|
|
6418
|
-
}
|
|
6419
|
-
if (descriptor.sourceSymbols !== void 0) {
|
|
6420
|
-
if (descriptor.scope === void 0) {
|
|
6421
|
-
fail("track_repair_overlay", `${path}.sourceSymbols`, "only an overlay (scope: keyframe) declares its own sourceSymbols");
|
|
6422
|
-
}
|
|
6423
|
-
if (!positiveInteger(descriptor.sourceSymbols) || descriptor.sourceSymbols > MAX_RAPTORQ_SOURCE_SYMBOLS) {
|
|
6424
|
-
fail(
|
|
6425
|
-
"track_fec_bounds",
|
|
6426
|
-
`${path}.sourceSymbols`,
|
|
6427
|
-
`must be a positive integer at most ${MAX_RAPTORQ_SOURCE_SYMBOLS} (RFC 6330 source block limit)`
|
|
6428
|
-
);
|
|
6429
|
-
}
|
|
6430
|
-
}
|
|
6431
|
-
if (isSafeInteger(descriptor.repairLayer) && descriptor.repairLayer >= 1) {
|
|
6432
|
-
for (const field of ["depends", "repairSymbols", "priority"]) {
|
|
6433
|
-
if (descriptor[field] === void 0) {
|
|
6434
|
-
fail("track_repair_layer", `${path}.${field}`, `repair layer ${descriptor.repairLayer} must declare ${field}`);
|
|
6435
|
-
}
|
|
6436
|
-
}
|
|
6437
|
-
}
|
|
6438
|
-
}
|
|
6439
|
-
function assembleRepairLayers(source, descriptors, fail = throwRepairContractError, options = {}) {
|
|
6440
|
-
const layerZeroName = typeof source.repairTrack === "string" && source.repairTrack.length > 0 ? source.repairTrack : void 0;
|
|
6441
|
-
const layerZero = layerZeroName === void 0 ? void 0 : descriptors.find((d) => d.name === layerZeroName);
|
|
6442
|
-
if (layerZeroName !== void 0 && layerZero === void 0 && options.layerZeroMustBeDeclared) {
|
|
6443
|
-
fail("track_repair_track", `${source.path}.repairTrack`, `unknown FEC repair track ${JSON.stringify(layerZeroName)}`);
|
|
6444
|
-
}
|
|
6445
|
-
const dependents = descriptors.filter((d) => d.name !== layerZeroName && dependsTarget(d) === source.name);
|
|
6446
|
-
const k = positiveInteger(source.sourceSymbols) ? source.sourceSymbols : 0;
|
|
6447
|
-
if (source.algorithm === "raptorq" && k > MAX_RAPTORQ_SOURCE_SYMBOLS) {
|
|
6448
|
-
fail(
|
|
6449
|
-
"track_fec_bounds",
|
|
6450
|
-
`${source.path}.sourceSymbols`,
|
|
6451
|
-
`must be at most ${MAX_RAPTORQ_SOURCE_SYMBOLS} (RFC 6330 source block limit)`
|
|
6452
|
-
);
|
|
6453
|
-
}
|
|
6454
|
-
if (layerZeroName === void 0) {
|
|
6455
|
-
if (dependents.length === 0) return void 0;
|
|
6456
|
-
fail(
|
|
6457
|
-
"track_repair_layer",
|
|
6458
|
-
`${dependents[0].path}.depends`,
|
|
6459
|
-
`repair layers of ${JSON.stringify(source.name)} require a layer 0 (fec.repairTrack) on the source`
|
|
6460
|
-
);
|
|
6461
|
-
}
|
|
6462
|
-
const layered = dependents.length > 0;
|
|
6463
|
-
let layerZeroSymbols = 0;
|
|
6464
|
-
if (positiveInteger(source.repairSymbols)) {
|
|
6465
|
-
layerZeroSymbols = source.repairSymbols;
|
|
6466
|
-
} else if (layered) {
|
|
6467
|
-
fail("track_fec", `${source.path}.repairSymbols`, "layered or overlay repair needs a positive integer repairSymbols on layer 0");
|
|
6468
|
-
}
|
|
6469
|
-
if (layered && !positiveInteger(source.sourceSymbols)) {
|
|
6470
|
-
fail("track_fec", `${source.path}.sourceSymbols`, "layered or overlay repair needs a positive integer sourceSymbols on the source");
|
|
6471
|
-
}
|
|
6472
|
-
const reedSolomon = source.algorithm === "reed-solomon";
|
|
6473
|
-
if (layered && source.algorithm !== "raptorq" && !reedSolomon) {
|
|
6474
|
-
fail(
|
|
6475
|
-
"track_fec",
|
|
6476
|
-
`${source.path}.algorithm`,
|
|
6477
|
-
"repair layers and overlays are defined only for raptorq and reed-solomon (draft-ramadan-moq-fec-01 6.3.4 rule 7)"
|
|
6478
|
-
);
|
|
6479
|
-
}
|
|
6480
|
-
let layerZeroPriority = DEFAULT_REPAIR_PRIORITY;
|
|
6481
|
-
if (layerZero !== void 0) {
|
|
6482
|
-
if (layerZero.depends !== void 0 && dependsTarget(layerZero) !== source.name) {
|
|
6483
|
-
fail(
|
|
6484
|
-
"track_repair_depends",
|
|
6485
|
-
`${layerZero.path}.depends`,
|
|
6486
|
-
`layer 0 of ${JSON.stringify(source.name)} must depend on that source`
|
|
6487
|
-
);
|
|
6488
|
-
}
|
|
6489
|
-
if (layerZero.repairLayer !== void 0 && layerZero.repairLayer !== 0) {
|
|
6490
|
-
fail("track_repair_layer", `${layerZero.path}.repairLayer`, "the track named by fec.repairTrack is layer 0");
|
|
6491
|
-
}
|
|
6492
|
-
if (layerZero.scope !== void 0) {
|
|
6493
|
-
fail("track_repair_overlay", `${layerZero.path}.scope`, "the track named by fec.repairTrack is base layer 0, not an overlay");
|
|
6494
|
-
}
|
|
6495
|
-
if (layerZero.repairSymbols !== void 0 && layerZero.repairSymbols !== layerZeroSymbols) {
|
|
6496
|
-
fail(
|
|
6497
|
-
"track_repair_layer",
|
|
6498
|
-
`${layerZero.path}.repairSymbols`,
|
|
6499
|
-
`must agree with the source's fec.repairSymbols (${layerZeroSymbols})`
|
|
6500
|
-
);
|
|
6501
|
-
}
|
|
6502
|
-
if (typeof layerZero.priority === "number") layerZeroPriority = layerZero.priority;
|
|
6503
|
-
}
|
|
6504
|
-
const baseLayers = [
|
|
6505
|
-
{ layer: 0, repairTrackName: layerZeroName, repairSymbols: layerZeroSymbols, rsIdOffset: 0, priority: layerZeroPriority }
|
|
6506
|
-
];
|
|
6507
|
-
const overlays = [];
|
|
6508
|
-
for (const d of dependents) {
|
|
6509
|
-
if (d.scope === void 0 && d.repairLayer === void 0) {
|
|
6510
|
-
fail(
|
|
6511
|
-
"track_repair_layer",
|
|
6512
|
-
`${d.path}.repairLayer`,
|
|
6513
|
-
`a repair track that depends on ${JSON.stringify(source.name)} must declare repairLayer (1..n) or scope: "keyframe"`
|
|
6514
|
-
);
|
|
6515
|
-
}
|
|
6516
|
-
}
|
|
6517
|
-
const layerTracks = dependents.filter((d) => d.scope === void 0).sort((a, b) => a.repairLayer - b.repairLayer);
|
|
6518
|
-
for (const d of dependents) {
|
|
6519
|
-
if (d.scope !== "keyframe") continue;
|
|
6520
|
-
if (overlays.length === 1) {
|
|
6521
|
-
fail(
|
|
6522
|
-
"track_repair_overlay",
|
|
6523
|
-
`${d.path}.scope`,
|
|
6524
|
-
`${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`
|
|
6525
|
-
);
|
|
6526
|
-
}
|
|
6527
|
-
const sourceSymbols = d.sourceSymbols;
|
|
6528
|
-
const repairSymbols = d.repairSymbols;
|
|
6529
|
-
if (sourceSymbols + repairSymbols - 1 > MAX_ENCODING_SYMBOL_ID) {
|
|
6530
|
-
fail(
|
|
6531
|
-
"track_fec_bounds",
|
|
6532
|
-
`${d.path}.repairSymbols`,
|
|
6533
|
-
`overlay sourceSymbols + repairSymbols - 1 exceeds the 24-bit ESI space (${MAX_ENCODING_SYMBOL_ID})`
|
|
6534
|
-
);
|
|
6535
|
-
}
|
|
6536
|
-
if (reedSolomon && sourceSymbols + repairSymbols > MAX_REED_SOLOMON_BLOCK_SYMBOLS) {
|
|
6537
|
-
fail(
|
|
6538
|
-
"track_fec_bounds",
|
|
6539
|
-
`${d.path}.repairSymbols`,
|
|
6540
|
-
`reed-solomon overlay sourceSymbols + repairSymbols exceeds ${MAX_REED_SOLOMON_BLOCK_SYMBOLS} (draft-ramadan-moq-fec-01 6.3.4 rule 12)`
|
|
6541
|
-
);
|
|
6542
|
-
}
|
|
6543
|
-
overlays.push({ repairTrackName: d.name, scope: "keyframe", sourceSymbols, repairSymbols, priority: d.priority });
|
|
6544
|
-
}
|
|
6545
|
-
let offset = layerZeroSymbols;
|
|
6546
|
-
let previousPriority = layerZeroPriority;
|
|
6547
|
-
layerTracks.forEach((d, i) => {
|
|
6548
|
-
const expected = i + 1;
|
|
6549
|
-
if (d.repairLayer === 0) {
|
|
6550
|
-
fail("track_repair_layer", `${d.path}.repairLayer`, `layer 0 of ${JSON.stringify(source.name)} is ${JSON.stringify(layerZeroName)}, named by fec.repairTrack`);
|
|
6551
|
-
}
|
|
6552
|
-
if (d.repairLayer !== expected) {
|
|
6553
|
-
fail(
|
|
6554
|
-
"track_repair_layer",
|
|
6555
|
-
`${d.path}.repairLayer`,
|
|
6556
|
-
i > 0 && d.repairLayer === layerTracks[i - 1].repairLayer ? `repair layer ${d.repairLayer} is declared twice for ${JSON.stringify(source.name)}` : `repair layers must be contiguous from 1: expected ${expected}, found ${d.repairLayer}`
|
|
6557
|
-
);
|
|
6558
|
-
}
|
|
6559
|
-
const priority = d.priority;
|
|
6560
|
-
if (priority < previousPriority) {
|
|
6561
|
-
fail(
|
|
6562
|
-
"track_repair_priority",
|
|
6563
|
-
`${d.path}.priority`,
|
|
6564
|
-
`must be at least ${previousPriority}, the priority of layer ${expected - 1}: priority is monotonic non-decreasing with layer index`
|
|
6565
|
-
);
|
|
6566
|
-
}
|
|
6567
|
-
previousPriority = priority;
|
|
6568
|
-
const repairSymbols = d.repairSymbols;
|
|
6569
|
-
baseLayers.push({ layer: expected, repairTrackName: d.name, repairSymbols, rsIdOffset: offset, priority });
|
|
6570
|
-
offset += repairSymbols;
|
|
6571
|
-
});
|
|
6572
|
-
const totalBaseRepairSymbols = offset;
|
|
6573
|
-
if (totalBaseRepairSymbols > MAX_REPAIR_SYMBOLS) {
|
|
6574
|
-
fail(
|
|
6575
|
-
"track_fec_bounds",
|
|
6576
|
-
`${layerTracks.length === 0 ? source.path : layerTracks[layerTracks.length - 1].path}.repairSymbols`,
|
|
6577
|
-
`total repair symbols across base layers exceeds the 24-bit RS_ID space (${MAX_REPAIR_SYMBOLS})`
|
|
6578
|
-
);
|
|
6579
|
-
}
|
|
6580
|
-
if (k + totalBaseRepairSymbols - 1 > MAX_ENCODING_SYMBOL_ID) {
|
|
6581
|
-
fail(
|
|
6582
|
-
"track_fec_bounds",
|
|
6583
|
-
`${source.path}.sourceSymbols`,
|
|
6584
|
-
`sourceSymbols + total base repair symbols - 1 exceeds the 24-bit ESI space (${MAX_ENCODING_SYMBOL_ID})`
|
|
6585
|
-
);
|
|
6586
|
-
}
|
|
6587
|
-
if (reedSolomon && k + totalBaseRepairSymbols > MAX_REED_SOLOMON_BLOCK_SYMBOLS) {
|
|
6588
|
-
fail(
|
|
6589
|
-
"track_fec_bounds",
|
|
6590
|
-
`${source.path}.sourceSymbols`,
|
|
6591
|
-
`reed-solomon sourceSymbols + total base repair symbols exceeds ${MAX_REED_SOLOMON_BLOCK_SYMBOLS} (draft-ramadan-moq-fec-01 6.3.4 rule 12)`
|
|
6592
|
-
);
|
|
6593
|
-
}
|
|
6594
|
-
return { sourceTrackName: source.name, baseLayers, overlays, totalBaseRepairSymbols };
|
|
6595
|
-
}
|
|
6596
|
-
|
|
6597
6741
|
// src/carp-catalog.ts
|
|
6598
6742
|
var VALID_ROLE_SUFFIXES = /* @__PURE__ */ new Set(["base", "delta", "repair"]);
|
|
6599
6743
|
function parseTrackRole(trackName) {
|
|
@@ -6633,18 +6777,6 @@ function hasSwitchingSetRole(names) {
|
|
|
6633
6777
|
return suffix === "base" || suffix === "delta";
|
|
6634
6778
|
});
|
|
6635
6779
|
}
|
|
6636
|
-
function isFecCatalogGeometry(obj) {
|
|
6637
|
-
if (typeof obj !== "object" || obj === null) return false;
|
|
6638
|
-
const o = obj;
|
|
6639
|
-
const algorithm = o["algorithm"];
|
|
6640
|
-
const symbolSize = o["symbolSize"];
|
|
6641
|
-
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;
|
|
6642
|
-
}
|
|
6643
|
-
function isFecCatalogConfig(obj) {
|
|
6644
|
-
if (!isFecCatalogGeometry(obj)) return false;
|
|
6645
|
-
const o = obj;
|
|
6646
|
-
return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
|
|
6647
|
-
}
|
|
6648
6780
|
function isMulticastTrackObject(obj) {
|
|
6649
6781
|
if (typeof obj !== "object" || obj === null) return false;
|
|
6650
6782
|
const o = obj;
|
|
@@ -6805,7 +6937,7 @@ function validateCmsfMmtpCatalog(value, options = {}) {
|
|
|
6805
6937
|
}
|
|
6806
6938
|
const tracks = catalog["tracks"];
|
|
6807
6939
|
const trackRecords = [];
|
|
6808
|
-
const
|
|
6940
|
+
const positiveInteger2 = (input) => Number.isSafeInteger(input) && input > 0;
|
|
6809
6941
|
const positiveNumber2 = (input) => typeof input === "number" && Number.isFinite(input) && input > 0;
|
|
6810
6942
|
for (let index = 0; index < tracks.length; index++) {
|
|
6811
6943
|
const raw = tracks[index];
|
|
@@ -6837,7 +6969,7 @@ function validateCmsfMmtpCatalog(value, options = {}) {
|
|
|
6837
6969
|
);
|
|
6838
6970
|
}
|
|
6839
6971
|
const codec = track["codec"];
|
|
6840
|
-
if (packaging !== "timeline" && packaging !== "mediatimeline" && packaging !== "eventtimeline" && packaging !== "fec-repair" && (typeof codec !== "string" || codec.length === 0)) {
|
|
6972
|
+
if (packaging !== "timeline" && packaging !== "mediatimeline" && packaging !== "eventtimeline" && packaging !== "fec-repair" && packaging !== "fec-source" && (typeof codec !== "string" || codec.length === 0)) {
|
|
6841
6973
|
catalogError("track_codec", `${path}.codec`, `codec is required for packaging=${packaging}`);
|
|
6842
6974
|
}
|
|
6843
6975
|
const timescale = track["timescale"];
|
|
@@ -6845,13 +6977,13 @@ function validateCmsfMmtpCatalog(value, options = {}) {
|
|
|
6845
6977
|
const groupDurationTicks = track["groupDurationTicks"];
|
|
6846
6978
|
const keyframeIntervalMs = track["keyframeIntervalMs"];
|
|
6847
6979
|
const keyframeIntervalTicks = track["keyframeIntervalTicks"];
|
|
6848
|
-
if (groupDurationMs !== void 0 && !
|
|
6980
|
+
if (groupDurationMs !== void 0 && !positiveInteger2(groupDurationMs)) {
|
|
6849
6981
|
catalogError("track_group_duration_ms", `${path}.groupDurationMs`, "must be a positive integer");
|
|
6850
6982
|
}
|
|
6851
|
-
if (groupDurationTicks !== void 0 && !
|
|
6983
|
+
if (groupDurationTicks !== void 0 && !positiveInteger2(groupDurationTicks)) {
|
|
6852
6984
|
catalogError("track_group_duration_ticks", `${path}.groupDurationTicks`, "must be a positive integer");
|
|
6853
6985
|
}
|
|
6854
|
-
if (keyframeIntervalMs !== void 0 && !positiveNumber2(keyframeIntervalMs) || keyframeIntervalTicks !== void 0 && !
|
|
6986
|
+
if (keyframeIntervalMs !== void 0 && !positiveNumber2(keyframeIntervalMs) || keyframeIntervalTicks !== void 0 && !positiveInteger2(keyframeIntervalTicks)) {
|
|
6855
6987
|
catalogError("track_keyframe_interval", path, "keyframe interval fields must be positive numbers");
|
|
6856
6988
|
}
|
|
6857
6989
|
const anchor = track["presentationAnchorTicks"];
|
|
@@ -6862,17 +6994,11 @@ function validateCmsfMmtpCatalog(value, options = {}) {
|
|
|
6862
6994
|
if (initRef !== void 0 && (typeof initRef !== "string" || initRef.length === 0)) {
|
|
6863
6995
|
catalogError("track_init_ref", `${path}.initRef`, "must be a non-empty string");
|
|
6864
6996
|
}
|
|
6865
|
-
if (packaging === "fec-repair") {
|
|
6866
|
-
rejectRepairTrackGeometry(path, track, catalogError);
|
|
6867
|
-
validateRepairTrackFields(describeRepairTrack(name, path, track), catalogError);
|
|
6868
|
-
} else {
|
|
6869
|
-
rejectRepairFieldsOnMediaTrack(path, track, catalogError);
|
|
6870
|
-
}
|
|
6871
6997
|
if (packaging === "mmtp") {
|
|
6872
6998
|
if (track["mmtpMode"] !== "mfu") {
|
|
6873
6999
|
catalogError("track_mmtp_mode", `${path}.mmtpMode`, "must be mfu");
|
|
6874
7000
|
}
|
|
6875
|
-
if (!
|
|
7001
|
+
if (!positiveInteger2(timescale)) {
|
|
6876
7002
|
catalogError("track_timescale", `${path}.timescale`, "must be a positive integer");
|
|
6877
7003
|
}
|
|
6878
7004
|
if (groupDurationMs === void 0 && groupDurationTicks === void 0) {
|
|
@@ -6886,85 +7012,6 @@ function validateCmsfMmtpCatalog(value, options = {}) {
|
|
|
6886
7012
|
catalogError("track_group_duration_exact", `${path}.groupDurationMs`, "does not resolve to an exact integer tick count");
|
|
6887
7013
|
}
|
|
6888
7014
|
}
|
|
6889
|
-
if (track["fec"] !== void 0) {
|
|
6890
|
-
const fec = track["fec"];
|
|
6891
|
-
if (typeof fec !== "object" || fec === null || Array.isArray(fec)) {
|
|
6892
|
-
catalogError("track_fec", `${path}.fec`, "FEC must be an object");
|
|
6893
|
-
}
|
|
6894
|
-
const config = fec;
|
|
6895
|
-
const algorithm = config["algorithm"];
|
|
6896
|
-
if (typeof algorithm !== "string" || algorithm.length === 0) {
|
|
6897
|
-
catalogError("track_fec", `${path}.fec.algorithm`, "must be a non-empty string");
|
|
6898
|
-
}
|
|
6899
|
-
if ("interleaveDepth" in config || "interleaveMs" in config) {
|
|
6900
|
-
catalogError("track_fec", `${path}.fec`, "interleaveDepthMs is the only supported interleave field");
|
|
6901
|
-
}
|
|
6902
|
-
if ("enhancementRepair" in config) {
|
|
6903
|
-
catalogError(
|
|
6904
|
-
"track_fec",
|
|
6905
|
-
`${path}.fec.enhancementRepair`,
|
|
6906
|
-
"repair layers are self-describing fec-repair tracks (depends, repairLayer); fec.enhancementRepair is not supported"
|
|
6907
|
-
);
|
|
6908
|
-
}
|
|
6909
|
-
if (config["interleaveDepthMs"] !== void 0 && (!Number.isSafeInteger(config["interleaveDepthMs"]) || config["interleaveDepthMs"] < 0)) {
|
|
6910
|
-
catalogError("track_fec", `${path}.fec.interleaveDepthMs`, "must be a non-negative safe integer");
|
|
6911
|
-
}
|
|
6912
|
-
if (algorithm !== "none") {
|
|
6913
|
-
for (const field of ["sourceSymbols", "repairSymbols", "symbolSize"]) {
|
|
6914
|
-
if (!positiveInteger3(config[field])) {
|
|
6915
|
-
catalogError("track_fec", `${path}.fec.${field}`, "must be a positive integer");
|
|
6916
|
-
}
|
|
6917
|
-
}
|
|
6918
|
-
if (algorithm === "raptorq" && config["symbolSize"] % 8 !== 0) {
|
|
6919
|
-
catalogError("track_fec", `${path}.fec.symbolSize`, "RaptorQ symbolSize must be a multiple of 8");
|
|
6920
|
-
}
|
|
6921
|
-
if (typeof config["repairTrack"] !== "string" || config["repairTrack"].length === 0) {
|
|
6922
|
-
catalogError("track_fec", `${path}.fec.repairTrack`, "must be a non-empty string");
|
|
6923
|
-
}
|
|
6924
|
-
}
|
|
6925
|
-
readFecBlockProfile(config["blockProfile"], `${path}.fec.blockProfile`, algorithm !== "none", catalogError);
|
|
6926
|
-
if (algorithm !== "none" && config["symbolSize"] < ISO_SSBG1_MIN_SYMBOL_SIZE) {
|
|
6927
|
-
catalogError("track_fec", `${path}.fec.symbolSize`, "iso-ssbg1-v1 symbolSize must be at least 14");
|
|
6928
|
-
}
|
|
6929
|
-
}
|
|
6930
|
-
}
|
|
6931
|
-
const repairDescriptors = trackRecords.flatMap(
|
|
6932
|
-
(track, index) => track["packaging"] === "fec-repair" ? [describeRepairTrack(track["name"], `$.tracks[${index}]`, track)] : []
|
|
6933
|
-
);
|
|
6934
|
-
const reachability = repairReachability(
|
|
6935
|
-
trackRecords.filter((track) => track["packaging"] !== "fec-repair").map((track) => track["fec"])
|
|
6936
|
-
);
|
|
6937
|
-
for (const descriptor of repairDescriptors) {
|
|
6938
|
-
assertRepairTrackReachable(descriptor, reachability, catalogError);
|
|
6939
|
-
const target = dependsTarget(descriptor);
|
|
6940
|
-
if (target === void 0) continue;
|
|
6941
|
-
const source = trackRecords.find((candidate) => candidate["name"] === target);
|
|
6942
|
-
const sourceFec = source?.["fec"];
|
|
6943
|
-
if (!source || source["packaging"] !== "mmtp" || !sourceFec || sourceFec["algorithm"] === "none") {
|
|
6944
|
-
catalogError(
|
|
6945
|
-
"track_repair_depends",
|
|
6946
|
-
`${descriptor.path}.depends`,
|
|
6947
|
-
`${JSON.stringify(target)} is not an mmtp track with FEC`
|
|
6948
|
-
);
|
|
6949
|
-
}
|
|
6950
|
-
}
|
|
6951
|
-
for (let index = 0; index < trackRecords.length; index++) {
|
|
6952
|
-
const track = trackRecords[index];
|
|
6953
|
-
const fec = track["fec"];
|
|
6954
|
-
if (!fec || fec["algorithm"] === "none") continue;
|
|
6955
|
-
assembleRepairLayers(
|
|
6956
|
-
{
|
|
6957
|
-
name: track["name"],
|
|
6958
|
-
path: `$.tracks[${index}].fec`,
|
|
6959
|
-
algorithm: fec["algorithm"],
|
|
6960
|
-
sourceSymbols: fec["sourceSymbols"],
|
|
6961
|
-
repairSymbols: fec["repairSymbols"],
|
|
6962
|
-
repairTrack: fec["repairTrack"]
|
|
6963
|
-
},
|
|
6964
|
-
repairDescriptors,
|
|
6965
|
-
catalogError,
|
|
6966
|
-
{ layerZeroMustBeDeclared: true }
|
|
6967
|
-
);
|
|
6968
7015
|
}
|
|
6969
7016
|
const switchingSets = /* @__PURE__ */ new Map();
|
|
6970
7017
|
for (let index = 0; index < trackRecords.length; index++) {
|
|
@@ -7333,8 +7380,12 @@ var FEC_BLOCK_PROFILES2 = Object.freeze([
|
|
|
7333
7380
|
"iso-ssbg1-v1"
|
|
7334
7381
|
]);
|
|
7335
7382
|
|
|
7336
|
-
// node_modules/@blockcast/mmt-base/dist/
|
|
7337
|
-
var
|
|
7383
|
+
// node_modules/@blockcast/mmt-base/dist/fec-bindings.js
|
|
7384
|
+
var RUNTIME_FEC_RECEIVER2 = Object.freeze({
|
|
7385
|
+
schemes: Object.freeze(["raptorq", "reed-solomon"]),
|
|
7386
|
+
blockProfiles: Object.freeze(["iso-ssbg1-v1"]),
|
|
7387
|
+
overlays: true
|
|
7388
|
+
});
|
|
7338
7389
|
|
|
7339
7390
|
// node_modules/@blockcast/mmt-transport/dist/multicast-orchestrator.js
|
|
7340
7391
|
var DEFAULT_DWELL_MS = 3e3;
|
|
@@ -8501,7 +8552,7 @@ function string(value, path) {
|
|
|
8501
8552
|
}
|
|
8502
8553
|
return value;
|
|
8503
8554
|
}
|
|
8504
|
-
function
|
|
8555
|
+
function positiveInteger(value, path) {
|
|
8505
8556
|
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
8506
8557
|
throw new Error(`${path} must be a positive integer`);
|
|
8507
8558
|
}
|
|
@@ -8524,7 +8575,7 @@ function mpuSequenceNumber(value, path) {
|
|
|
8524
8575
|
return parsed;
|
|
8525
8576
|
}
|
|
8526
8577
|
function port(value, path) {
|
|
8527
|
-
const parsed =
|
|
8578
|
+
const parsed = positiveInteger(value, path);
|
|
8528
8579
|
if (parsed > 65535) throw new Error(`${path} must be between 1 and 65535`);
|
|
8529
8580
|
return parsed;
|
|
8530
8581
|
}
|
|
@@ -8605,11 +8656,11 @@ function validateDeliveryPolicy(value) {
|
|
|
8605
8656
|
throw new Error("multicast.deliveryPolicy.missPolicy must be 'fail'");
|
|
8606
8657
|
}
|
|
8607
8658
|
return {
|
|
8608
|
-
bufferWindowSegments:
|
|
8659
|
+
bufferWindowSegments: positiveInteger(
|
|
8609
8660
|
policy["bufferWindowSegments"],
|
|
8610
8661
|
"multicast.deliveryPolicy.bufferWindowSegments"
|
|
8611
8662
|
),
|
|
8612
|
-
requestTimeoutMs:
|
|
8663
|
+
requestTimeoutMs: positiveInteger(policy["requestTimeoutMs"], "multicast.deliveryPolicy.requestTimeoutMs"),
|
|
8613
8664
|
missPolicy
|
|
8614
8665
|
};
|
|
8615
8666
|
}
|
|
@@ -8637,10 +8688,10 @@ function validateRouteBase(value, path, endpoints) {
|
|
|
8637
8688
|
mediaObjectTemplate
|
|
8638
8689
|
},
|
|
8639
8690
|
fec: {
|
|
8640
|
-
K:
|
|
8691
|
+
K: positiveInteger(fec["K"], `${path}.fec.K`),
|
|
8641
8692
|
P: nonNegativeInteger(fec["P"], `${path}.fec.P`),
|
|
8642
|
-
T:
|
|
8643
|
-
interleaveDepthMs:
|
|
8693
|
+
T: positiveInteger(fec["T"], `${path}.fec.T`),
|
|
8694
|
+
interleaveDepthMs: positiveInteger(fec["interleaveDepthMs"], `${path}.fec.interleaveDepthMs`),
|
|
8644
8695
|
blockProfile: parseFecBlockProfile(fec["blockProfile"], `${path}.fec.blockProfile`)
|
|
8645
8696
|
}
|
|
8646
8697
|
};
|
|
@@ -8693,13 +8744,13 @@ function validateRoutes(value, kind, endpoints) {
|
|
|
8693
8744
|
if (repairTrack !== void 0 && (typeof repairTrack !== "string" || repairTrack.trim().length === 0)) {
|
|
8694
8745
|
throw new Error(`${path}.mmtp.repairTrack must be a non-empty string when provided`);
|
|
8695
8746
|
}
|
|
8696
|
-
const mediaUnitsPerObject =
|
|
8747
|
+
const mediaUnitsPerObject = positiveInteger(mmtp["mediaUnitsPerObject"], `${path}.mmtp.mediaUnitsPerObject`);
|
|
8697
8748
|
const firstMpuSequenceNumber = mpuSequenceNumber(
|
|
8698
8749
|
mmtp["firstMpuSequenceNumber"],
|
|
8699
8750
|
`${path}.mmtp.firstMpuSequenceNumber`
|
|
8700
8751
|
);
|
|
8701
8752
|
const firstObjectNumber = nonNegativeSafeInteger(mmtp["firstObjectNumber"], `${path}.mmtp.firstObjectNumber`);
|
|
8702
|
-
const mpuSequencesPerObject =
|
|
8753
|
+
const mpuSequencesPerObject = positiveInteger(mmtp["mpuSequencesPerObject"], `${path}.mmtp.mpuSequencesPerObject`);
|
|
8703
8754
|
if (mpuSequencesPerObject > mediaUnitsPerObject) {
|
|
8704
8755
|
throw new Error(`${path}.mmtp.mpuSequencesPerObject must not exceed mediaUnitsPerObject`);
|
|
8705
8756
|
}
|
|
@@ -8714,8 +8765,8 @@ function validateRoutes(value, kind, endpoints) {
|
|
|
8714
8765
|
packetId,
|
|
8715
8766
|
mode,
|
|
8716
8767
|
codec: string(mmtp["codec"], `${path}.mmtp.codec`),
|
|
8717
|
-
timescale:
|
|
8718
|
-
mediaUnitDurationTicks:
|
|
8768
|
+
timescale: positiveInteger(mmtp["timescale"], `${path}.mmtp.timescale`),
|
|
8769
|
+
mediaUnitDurationTicks: positiveInteger(
|
|
8719
8770
|
mmtp["mediaUnitDurationTicks"],
|
|
8720
8771
|
`${path}.mmtp.mediaUnitDurationTicks`
|
|
8721
8772
|
),
|
|
@@ -9217,6 +9268,7 @@ function getPhase14CatalogOverride() {
|
|
|
9217
9268
|
function mergeOverrideIntoHangRenditions(catalog, override) {
|
|
9218
9269
|
if (!override) return catalog;
|
|
9219
9270
|
for (const track of override.tracks) {
|
|
9271
|
+
if (track.packaging === "fec-repair") continue;
|
|
9220
9272
|
const role = track.role === "audio" || track.name.startsWith("audio") ? "audio" : "video";
|
|
9221
9273
|
const bagKey = role === "audio" ? "audio" : "video";
|
|
9222
9274
|
const bag = catalog[bagKey] ?? { renditions: {}, priority: 1 };
|
|
@@ -9235,8 +9287,24 @@ function mergeOverrideIntoHangRenditions(catalog, override) {
|
|
|
9235
9287
|
bag.renditions[track.name] = merged;
|
|
9236
9288
|
catalog[bagKey] = bag;
|
|
9237
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
|
+
}
|
|
9238
9300
|
return catalog;
|
|
9239
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
|
+
}
|
|
9240
9308
|
function isPositiveInteger(value) {
|
|
9241
9309
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
9242
9310
|
}
|
|
@@ -9253,7 +9321,11 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
|
|
|
9253
9321
|
const existingAudioTrack = tracks.find(
|
|
9254
9322
|
(t) => t.role === "audio" && typeof t.codec === "string" && t.codec.length > 0
|
|
9255
9323
|
);
|
|
9256
|
-
|
|
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) {
|
|
9257
9329
|
const existing = byName.get(ot.name);
|
|
9258
9330
|
if (existing !== void 0) {
|
|
9259
9331
|
if (existing.fec === void 0 && ot.fec !== void 0) existing.fec = ot.fec;
|
|
@@ -9291,10 +9363,14 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
|
|
|
9291
9363
|
`[phase14-catalog-override] track "${ot.name}" has no packaging \u2014 supply per-track or set top-level streamingFormat`
|
|
9292
9364
|
);
|
|
9293
9365
|
}
|
|
9294
|
-
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");
|
|
9295
9371
|
const inheritedCodec = role === "audio" ? existingAudioTrack?.codec : existingVideoTrack?.codec;
|
|
9296
9372
|
const codec = ot.codec ?? inheritedCodec;
|
|
9297
|
-
if (packaging !== "timeline" && (typeof codec !== "string" || codec.length === 0)) {
|
|
9373
|
+
if (packaging !== "timeline" && !isRepair && (typeof codec !== "string" || codec.length === 0)) {
|
|
9298
9374
|
throw new Error(
|
|
9299
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`
|
|
9300
9376
|
);
|
|
@@ -9315,14 +9391,13 @@ function mergeOverrideIntoCmsfTracks(tracks, override) {
|
|
|
9315
9391
|
`[phase14-catalog-override] track "${ot.name}" has packaging='mmtp' but no valid groupDurationMs`
|
|
9316
9392
|
);
|
|
9317
9393
|
}
|
|
9318
|
-
const entry = {
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
if (
|
|
9324
|
-
if (ot.
|
|
9325
|
-
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;
|
|
9326
9401
|
if (role !== void 0) entry.role = role;
|
|
9327
9402
|
if (ot.framerate !== void 0) entry.framerate = ot.framerate;
|
|
9328
9403
|
if (ot.fec !== void 0) entry.fec = ot.fec;
|
|
@@ -12781,13 +12856,8 @@ var INACTIVE = Object.freeze({
|
|
|
12781
12856
|
function recordOf2(value) {
|
|
12782
12857
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12783
12858
|
}
|
|
12784
|
-
function
|
|
12785
|
-
return
|
|
12786
|
-
}
|
|
12787
|
-
function catalogDeclaresFec(fec) {
|
|
12788
|
-
const block = recordOf2(fec);
|
|
12789
|
-
if (block === void 0 || block["algorithm"] === "none" || fecAlgorithmUnimplemented(block["algorithm"])) return false;
|
|
12790
|
-
return isPositiveSafeInteger2(block["sourceSymbols"]) && isPositiveSafeInteger2(block["symbolSize"]) && isFecBlockProfile(block["blockProfile"]) && isoBlockSpanUsable(block) && !catalogFecDeclaresUnconfigurableInstance(block);
|
|
12859
|
+
function catalogDeclaresFec(catalog, track) {
|
|
12860
|
+
return resolveFecBindings(catalog).get(track)?.status === "ok";
|
|
12791
12861
|
}
|
|
12792
12862
|
function requireBoolean(value, field) {
|
|
12793
12863
|
if (typeof value !== "boolean") {
|
|
@@ -12881,10 +12951,10 @@ function deriveBlockClassifierGeometry(input) {
|
|
|
12881
12951
|
keyframeInterval
|
|
12882
12952
|
};
|
|
12883
12953
|
}
|
|
12884
|
-
function keyframeIntervalMediaTicks(
|
|
12885
|
-
if (
|
|
12954
|
+
function keyframeIntervalMediaTicks(geometry2, mediaTimescale) {
|
|
12955
|
+
if (geometry2.trackKind !== "video" || geometry2.mode !== "classifying") return null;
|
|
12886
12956
|
if (!Number.isSafeInteger(mediaTimescale) || mediaTimescale <= 0) return null;
|
|
12887
|
-
const { ticks, timescale } =
|
|
12957
|
+
const { ticks, timescale } = geometry2.keyframeInterval;
|
|
12888
12958
|
if (!Number.isSafeInteger(ticks) || !Number.isSafeInteger(timescale)) return null;
|
|
12889
12959
|
const scaled = BigInt(ticks) * BigInt(mediaTimescale);
|
|
12890
12960
|
const divisor = BigInt(timescale);
|
|
@@ -12919,11 +12989,11 @@ var BlockClassifier = class {
|
|
|
12919
12989
|
/** MPU → movie_fragment_sequence_number observed in it. */
|
|
12920
12990
|
#mpuFragments = /* @__PURE__ */ new Map();
|
|
12921
12991
|
#lastRap = null;
|
|
12922
|
-
constructor(
|
|
12923
|
-
this.#geometry =
|
|
12992
|
+
constructor(geometry2) {
|
|
12993
|
+
this.#geometry = geometry2;
|
|
12924
12994
|
this.#mode = "classifying";
|
|
12925
12995
|
this.#reason = null;
|
|
12926
|
-
this.reset(
|
|
12996
|
+
this.reset(geometry2);
|
|
12927
12997
|
}
|
|
12928
12998
|
get geometry() {
|
|
12929
12999
|
return this.#geometry;
|
|
@@ -12943,16 +13013,16 @@ var BlockClassifier = class {
|
|
|
12943
13013
|
return this.#mpuBoundsRaps;
|
|
12944
13014
|
}
|
|
12945
13015
|
/** Begin a fresh epoch (catalog change or track reset). */
|
|
12946
|
-
reset(
|
|
12947
|
-
this.#geometry =
|
|
13016
|
+
reset(geometry2 = this.#geometry) {
|
|
13017
|
+
this.#geometry = geometry2;
|
|
12948
13018
|
this.#blocks.clear();
|
|
12949
13019
|
this.#clearContiguityLedger();
|
|
12950
13020
|
this.#mpuFragments.clear();
|
|
12951
13021
|
this.#lastRap = null;
|
|
12952
13022
|
this.#mpuBoundsRaps = true;
|
|
12953
|
-
if (
|
|
13023
|
+
if (geometry2.trackKind === "video" && geometry2.mode === "all-protected") {
|
|
12954
13024
|
this.#mode = "all-protected";
|
|
12955
|
-
this.#reason =
|
|
13025
|
+
this.#reason = geometry2.reason;
|
|
12956
13026
|
} else {
|
|
12957
13027
|
this.#mode = "classifying";
|
|
12958
13028
|
this.#reason = null;
|
|
@@ -13068,15 +13138,15 @@ var BlockClassifier = class {
|
|
|
13068
13138
|
}
|
|
13069
13139
|
#checkInvariants(sbn, block, evidence) {
|
|
13070
13140
|
if (this.#mode === "all-protected" || this.#geometry.trackKind !== "video") return null;
|
|
13071
|
-
const
|
|
13072
|
-
if (
|
|
13141
|
+
const geometry2 = this.#geometry;
|
|
13142
|
+
if (geometry2.mode !== "classifying") return null;
|
|
13073
13143
|
if (evidence.rap && evidence.idx !== void 0 && evidence.idx !== 0) {
|
|
13074
13144
|
return "rap-not-mpu-start";
|
|
13075
13145
|
}
|
|
13076
|
-
if (evidence.idx !== void 0 && evidence.idx >=
|
|
13146
|
+
if (evidence.idx !== void 0 && evidence.idx >= geometry2.gopFrames) {
|
|
13077
13147
|
return "mpu-longer-than-gop";
|
|
13078
13148
|
}
|
|
13079
|
-
if (block.mpus.size === 1 && block.idxMin !== void 0 && block.idxMax !== void 0 && block.idxMax - block.idxMin >=
|
|
13149
|
+
if (block.mpus.size === 1 && block.idxMin !== void 0 && block.idxMax !== void 0 && block.idxMax - block.idxMin >= geometry2.interleaveUnits) {
|
|
13080
13150
|
return "block-wider-than-interleave";
|
|
13081
13151
|
}
|
|
13082
13152
|
if (evidence.mpu !== void 0 && evidence.movieFragment !== void 0) {
|
|
@@ -13135,13 +13205,13 @@ var BlockClassifier = class {
|
|
|
13135
13205
|
if (compareDataUnitStarts(starts[mid], start) < 0) lo = mid + 1;
|
|
13136
13206
|
else hi = mid;
|
|
13137
13207
|
}
|
|
13138
|
-
const
|
|
13139
|
-
if (
|
|
13140
|
-
if (
|
|
13208
|
+
const at2 = starts[lo];
|
|
13209
|
+
if (at2 !== void 0 && compareDataUnitStarts(at2, start) === 0) {
|
|
13210
|
+
if (at2.sbn !== sbn) return true;
|
|
13141
13211
|
} else {
|
|
13142
13212
|
const before = starts[lo - 1];
|
|
13143
13213
|
if (before !== void 0 && before.sbn > sbn) return true;
|
|
13144
|
-
if (
|
|
13214
|
+
if (at2 !== void 0 && at2.sbn < sbn) return true;
|
|
13145
13215
|
insertAt = lo;
|
|
13146
13216
|
}
|
|
13147
13217
|
}
|
|
@@ -13192,11 +13262,11 @@ var BlockClassifier = class {
|
|
|
13192
13262
|
#classify(block) {
|
|
13193
13263
|
if (block.rapSeen || block.mpus.size >= 2) return "keyframe";
|
|
13194
13264
|
if (this.#mode === "all-protected") return "unknown";
|
|
13195
|
-
const
|
|
13196
|
-
if (
|
|
13265
|
+
const geometry2 = this.#geometry;
|
|
13266
|
+
if (geometry2.trackKind !== "video" || geometry2.mode !== "classifying") return "unknown";
|
|
13197
13267
|
if (block.mpus.size === 1 && block.idxMin !== void 0 && block.idxMax !== void 0) {
|
|
13198
|
-
const span =
|
|
13199
|
-
if (block.idxMax - span >= 1 && block.idxMin + span <=
|
|
13268
|
+
const span = geometry2.interleaveUnits - 1;
|
|
13269
|
+
if (block.idxMax - span >= 1 && block.idxMin + span <= geometry2.gopFrames - 1) {
|
|
13200
13270
|
return "delta";
|
|
13201
13271
|
}
|
|
13202
13272
|
}
|
|
@@ -14594,15 +14664,15 @@ var FECManager = class _FECManager {
|
|
|
14594
14664
|
*/
|
|
14595
14665
|
#refreshDeliveryGapWindow() {
|
|
14596
14666
|
let windowMs;
|
|
14597
|
-
const consider = (
|
|
14667
|
+
const consider = (geometry2, packetId) => {
|
|
14598
14668
|
if (!this.#hasCatalogTimingForPacket(packetId)) return;
|
|
14599
|
-
if (
|
|
14600
|
-
const { ticks, timescale } =
|
|
14669
|
+
if (geometry2?.trackKind !== "video" || geometry2.mode !== "classifying") return;
|
|
14670
|
+
const { ticks, timescale } = geometry2.keyframeInterval;
|
|
14601
14671
|
const ms = ticks / timescale * 1e3;
|
|
14602
14672
|
if (Number.isFinite(ms) && ms > 0 && (windowMs === void 0 || ms > windowMs)) windowMs = ms;
|
|
14603
14673
|
};
|
|
14604
|
-
for (const [streamKey,
|
|
14605
|
-
consider(
|
|
14674
|
+
for (const [streamKey, geometry2] of this.#streamGeometries) {
|
|
14675
|
+
consider(geometry2, this.#parseStreamKey(streamKey).packetId);
|
|
14606
14676
|
}
|
|
14607
14677
|
for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId);
|
|
14608
14678
|
this.#deliveryGaps.setWindowMs(windowMs);
|
|
@@ -14850,8 +14920,8 @@ var FECManager = class _FECManager {
|
|
|
14850
14920
|
for (const [key, classifier] of [...this.#classifiers]) {
|
|
14851
14921
|
if (!this.#belongsToPacket(key, packetId)) continue;
|
|
14852
14922
|
const stream = this.#parseStreamKey(key);
|
|
14853
|
-
const
|
|
14854
|
-
if (hasCatalogTiming && JSON.stringify(
|
|
14923
|
+
const geometry2 = this.#geometryFor(stream.packetId, stream.catalogTrackName);
|
|
14924
|
+
if (hasCatalogTiming && JSON.stringify(geometry2) === JSON.stringify(classifier.geometry)) continue;
|
|
14855
14925
|
this.#classifiers.delete(key);
|
|
14856
14926
|
if (!hasCatalogTiming) continue;
|
|
14857
14927
|
this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire());
|
|
@@ -19210,12 +19280,12 @@ function assertPositiveMs(name, value) {
|
|
|
19210
19280
|
);
|
|
19211
19281
|
}
|
|
19212
19282
|
}
|
|
19213
|
-
function duplicateRetentionMs(
|
|
19214
|
-
assertPositiveMs("blockSpanMs",
|
|
19215
|
-
assertPositiveMs("blockLifetimeMs",
|
|
19216
|
-
const untilTerminalMs =
|
|
19217
|
-
const terminalRecordMs =
|
|
19218
|
-
return
|
|
19283
|
+
function duplicateRetentionMs(geometry2) {
|
|
19284
|
+
assertPositiveMs("blockSpanMs", geometry2.blockSpanMs);
|
|
19285
|
+
assertPositiveMs("blockLifetimeMs", geometry2.blockLifetimeMs);
|
|
19286
|
+
const untilTerminalMs = geometry2.blockLifetimeMs;
|
|
19287
|
+
const terminalRecordMs = geometry2.blockLifetimeMs;
|
|
19288
|
+
return geometry2.blockSpanMs + untilTerminalMs + terminalRecordMs;
|
|
19219
19289
|
}
|
|
19220
19290
|
var GenerationalNumberSet = class {
|
|
19221
19291
|
#members = /* @__PURE__ */ new Set();
|
|
@@ -19439,11 +19509,11 @@ function isStream(meta, stream) {
|
|
|
19439
19509
|
return meta.packetId === stream.packetId && meta.catalogTrackName === stream.catalogTrackName;
|
|
19440
19510
|
}
|
|
19441
19511
|
function comparePresentation(a, b) {
|
|
19442
|
-
const
|
|
19512
|
+
const at2 = a.authoredTiming;
|
|
19443
19513
|
const bt = b.authoredTiming;
|
|
19444
|
-
if (
|
|
19445
|
-
const left =
|
|
19446
|
-
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);
|
|
19447
19517
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
19448
19518
|
}
|
|
19449
19519
|
return a.timestamp - b.timestamp;
|
|
@@ -19622,8 +19692,6 @@ var MmtpRouter = class {
|
|
|
19622
19692
|
// Callbacks (legacy — used when per-track callbacks are not registered)
|
|
19623
19693
|
#onVideoFrame = null;
|
|
19624
19694
|
#onAudioFrame = null;
|
|
19625
|
-
// Init segment (ftyp+moov) from MMTP MPU metadata — needed for MSE SourceBuffer
|
|
19626
|
-
#onInitSegment = null;
|
|
19627
19695
|
// Per-track init segment callbacks keyed by packetId
|
|
19628
19696
|
#trackInitCallbacks = /* @__PURE__ */ new Map();
|
|
19629
19697
|
// Track whether init has been delivered per physical stream — prevent duplicates
|
|
@@ -19886,11 +19954,10 @@ var MmtpRouter = class {
|
|
|
19886
19954
|
};
|
|
19887
19955
|
}
|
|
19888
19956
|
/**
|
|
19889
|
-
* Init segments
|
|
19890
|
-
*
|
|
19891
|
-
*
|
|
19892
|
-
* 0 here
|
|
19893
|
-
* aggregate callback (MOQT-01, BLO-21181).
|
|
19957
|
+
* Init segments rejected since construction because their packetId had no
|
|
19958
|
+
* `onTrackInit` handler. A rejected init is delivered nowhere; there is no
|
|
19959
|
+
* aggregate fallback (removed in 1.0.0, MOQT-01 / BLO-21181). A supported
|
|
19960
|
+
* deployment reads 0 here.
|
|
19894
19961
|
*/
|
|
19895
19962
|
get unmappedInitDeliveryCount() {
|
|
19896
19963
|
return this.#unmappedInitDeliveries;
|
|
@@ -20067,20 +20134,10 @@ var MmtpRouter = class {
|
|
|
20067
20134
|
onTrackPacket(packetId, cb) {
|
|
20068
20135
|
this.#trackPacketCallbacks.set(packetId, cb);
|
|
20069
20136
|
}
|
|
20070
|
-
/**
|
|
20071
|
-
* Register an init segment callback (ftyp+moov from MMTP MPU metadata).
|
|
20072
|
-
* Receives an init only for a packetId with no `onTrackInit` handler.
|
|
20073
|
-
*
|
|
20074
|
-
* @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
|
|
20075
|
-
* removed in the 1.0.0 major, no earlier than one minor release after this
|
|
20076
|
-
* deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
|
|
20077
|
-
* deliveries with `unmappedInitDeliveryCount`.
|
|
20078
|
-
*/
|
|
20079
|
-
onInitSegment(cb) {
|
|
20080
|
-
this.#onInitSegment = cb;
|
|
20081
|
-
}
|
|
20082
20137
|
/** Register an init segment callback for a specific MMTP packetId.
|
|
20083
|
-
* Called when FT=0 init arrives via direct source or FEC recovery.
|
|
20138
|
+
* Called when FT=0 init arrives via direct source or FEC recovery. An init
|
|
20139
|
+
* for a packetId with no handler is rejected and counted by
|
|
20140
|
+
* `unmappedInitDeliveryCount`. */
|
|
20084
20141
|
onTrackInit(packetId, cb) {
|
|
20085
20142
|
this.#trackInitCallbacks.set(packetId, cb);
|
|
20086
20143
|
}
|
|
@@ -21480,16 +21537,15 @@ var MmtpRouter = class {
|
|
|
21480
21537
|
if (this.#trackInitDelivered.has(streamKey)) return;
|
|
21481
21538
|
if (data.byteLength <= 8) return;
|
|
21482
21539
|
this.#trackInitDelivered.add(streamKey);
|
|
21540
|
+
const track = `packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`}`;
|
|
21483
21541
|
const cb = this.#trackInitCallbacks.get(packetId);
|
|
21484
|
-
if (cb) {
|
|
21485
|
-
cb(data);
|
|
21486
|
-
} else {
|
|
21542
|
+
if (!cb) {
|
|
21487
21543
|
this.#unmappedInitDeliveries++;
|
|
21488
|
-
|
|
21544
|
+
console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
|
|
21545
|
+
return;
|
|
21489
21546
|
}
|
|
21490
|
-
|
|
21491
|
-
|
|
21492
|
-
);
|
|
21547
|
+
cb(data);
|
|
21548
|
+
console.log(`[MmtpRouter] Init delivered for ${track} (${data.byteLength}B)`);
|
|
21493
21549
|
}
|
|
21494
21550
|
/**
|
|
21495
21551
|
* Core MMTP packet processing — source packets → play + FEC, repair → FEC only.
|
|
@@ -22989,6 +23045,10 @@ function getMulticastGlobals() {
|
|
|
22989
23045
|
function hasMulticastGateway() {
|
|
22990
23046
|
return typeof getMulticastGlobals().multicast?.subscribe === "function";
|
|
22991
23047
|
}
|
|
23048
|
+
function senderOf(event) {
|
|
23049
|
+
const sourceAddress = event.sourceAddress;
|
|
23050
|
+
return typeof sourceAddress === "string" ? sourceAddress : void 0;
|
|
23051
|
+
}
|
|
22992
23052
|
function normalizeBytesEventDetail(detail) {
|
|
22993
23053
|
const value = detail && typeof detail === "object" && "data" in detail ? detail.data : detail;
|
|
22994
23054
|
if (value instanceof Uint8Array) return value;
|
|
@@ -23035,7 +23095,8 @@ var MulticastTransport = class {
|
|
|
23035
23095
|
bytesReceived: 0,
|
|
23036
23096
|
packetsDroppedUnregisteredPid: 0,
|
|
23037
23097
|
packetsDroppedUnparseable: 0,
|
|
23038
|
-
packetsDroppedSignaling: 0
|
|
23098
|
+
packetsDroppedSignaling: 0,
|
|
23099
|
+
packetsDroppedForeignSender: 0
|
|
23039
23100
|
};
|
|
23040
23101
|
// join() calls by how they resolved; see joinStats.
|
|
23041
23102
|
#joinsSucceeded = 0;
|
|
@@ -23060,6 +23121,11 @@ var MulticastTransport = class {
|
|
|
23060
23121
|
// per packet once a count outgrows the small-integer range.
|
|
23061
23122
|
#acceptedBytesByPacketId = /* @__PURE__ */ new Map();
|
|
23062
23123
|
#packetIds = null;
|
|
23124
|
+
// The sender S this join's accepted packets came from, once known; see
|
|
23125
|
+
// packetsDroppedForeignSender. Cleared by every join and by a silence
|
|
23126
|
+
// failback, so a replacement publisher is not locked out.
|
|
23127
|
+
#sender = void 0;
|
|
23128
|
+
#lockedTo;
|
|
23063
23129
|
// What the live join delivers with each accepted packet. A field, not a
|
|
23064
23130
|
// listener closure, so an in-place admission change (setContext) applies
|
|
23065
23131
|
// from the next packet on without rejoining.
|
|
@@ -23081,9 +23147,15 @@ var MulticastTransport = class {
|
|
|
23081
23147
|
#joinGeneration = 0;
|
|
23082
23148
|
// The provider to subscribe through; window.multicast when unset.
|
|
23083
23149
|
#gateway;
|
|
23150
|
+
/**
|
|
23151
|
+
* `lockedTo` reads the sender another transport is locked to (a repair
|
|
23152
|
+
* group follows its media join, whose publisher sends both). While it reads
|
|
23153
|
+
* a sender, this join admits only that one; until then its own lock applies.
|
|
23154
|
+
*/
|
|
23084
23155
|
constructor(onPacket, options = {}) {
|
|
23085
23156
|
this.#onPacket = onPacket;
|
|
23086
23157
|
this.#gateway = options.gateway;
|
|
23158
|
+
this.#lockedTo = options.lockedTo;
|
|
23087
23159
|
}
|
|
23088
23160
|
static isAvailable() {
|
|
23089
23161
|
return hasMulticastGateway();
|
|
@@ -23092,6 +23164,7 @@ var MulticastTransport = class {
|
|
|
23092
23164
|
const generation = ++this.#joinGeneration;
|
|
23093
23165
|
this.#releaseSubscription();
|
|
23094
23166
|
this.#dataPlaneLive = false;
|
|
23167
|
+
this.#sender = void 0;
|
|
23095
23168
|
if (this.#disposed) return this.#declined(generation, "disposed");
|
|
23096
23169
|
const gateway = this.#gateway ?? getMulticastGlobals().multicast;
|
|
23097
23170
|
if (typeof gateway?.subscribe !== "function") {
|
|
@@ -23133,7 +23206,8 @@ var MulticastTransport = class {
|
|
|
23133
23206
|
}
|
|
23134
23207
|
this.#subscription = subscription;
|
|
23135
23208
|
const live = () => !this.#disposed && generation === this.#joinGeneration;
|
|
23136
|
-
const
|
|
23209
|
+
const anySource = !config.sourceAddress && !config.senderAddress && !config.atsc3;
|
|
23210
|
+
const packetListener = !anySource && subscriptionDeliversPacketBatches(subscription) ? {
|
|
23137
23211
|
type: "packets",
|
|
23138
23212
|
listener: (event) => {
|
|
23139
23213
|
if (!live()) return;
|
|
@@ -23143,7 +23217,7 @@ var MulticastTransport = class {
|
|
|
23143
23217
|
type: "bytes",
|
|
23144
23218
|
listener: (event) => {
|
|
23145
23219
|
if (!live()) return;
|
|
23146
|
-
if (this.#deliver(event.detail)) this.#resetWatchdog();
|
|
23220
|
+
if (this.#deliver(event.detail, senderOf(event))) this.#resetWatchdog();
|
|
23147
23221
|
}
|
|
23148
23222
|
};
|
|
23149
23223
|
this.#packetListener = packetListener;
|
|
@@ -23234,13 +23308,13 @@ var MulticastTransport = class {
|
|
|
23234
23308
|
* One packet from the subscription: filter, count, and hand it to the
|
|
23235
23309
|
* callback. Returns whether it was delivered; the caller notes liveness.
|
|
23236
23310
|
*/
|
|
23237
|
-
#deliver(detail) {
|
|
23311
|
+
#deliver(detail, sourceAddress) {
|
|
23238
23312
|
const packet = normalizeBytesEventDetail(detail);
|
|
23239
23313
|
if (!packet) {
|
|
23240
23314
|
this.#stats.packetsDroppedUnparseable++;
|
|
23241
23315
|
return false;
|
|
23242
23316
|
}
|
|
23243
|
-
if (!this.#acceptPacket(packet)) return false;
|
|
23317
|
+
if (!this.#acceptPacket(packet, sourceAddress)) return false;
|
|
23244
23318
|
this.#onPacket(packet, this.#context);
|
|
23245
23319
|
return true;
|
|
23246
23320
|
}
|
|
@@ -23281,11 +23355,15 @@ var MulticastTransport = class {
|
|
|
23281
23355
|
throw failures[0];
|
|
23282
23356
|
}
|
|
23283
23357
|
}
|
|
23284
|
-
/**
|
|
23285
|
-
|
|
23286
|
-
|
|
23358
|
+
/**
|
|
23359
|
+
* Feed a raw packet from an external source (e.g., AMT tunnel or ASM
|
|
23360
|
+
* subscription). `sourceAddress` is the datagram's sender when the source
|
|
23361
|
+
* knows it; see packetsDroppedForeignSender.
|
|
23362
|
+
*/
|
|
23363
|
+
feedPacket(data, context, sourceAddress) {
|
|
23364
|
+
if (this.#disposed) return;
|
|
23287
23365
|
const packet = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
23288
|
-
if (!this.#acceptPacket(packet)) return;
|
|
23366
|
+
if (!this.#acceptPacket(packet, sourceAddress)) return;
|
|
23289
23367
|
this.#onPacket(packet, context);
|
|
23290
23368
|
this.#resetWatchdog();
|
|
23291
23369
|
}
|
|
@@ -23435,6 +23513,10 @@ var MulticastTransport = class {
|
|
|
23435
23513
|
get withdrawnPacketIdDrops() {
|
|
23436
23514
|
return this.#withdrawnPacketIdDrops;
|
|
23437
23515
|
}
|
|
23516
|
+
/** The sender this join is locked to; undefined until one is known. */
|
|
23517
|
+
get sender() {
|
|
23518
|
+
return this.#sender;
|
|
23519
|
+
}
|
|
23438
23520
|
/** Whether a subscription is live: joined, and not yet released or replaced. */
|
|
23439
23521
|
get joined() {
|
|
23440
23522
|
return this.#subscription !== null;
|
|
@@ -23597,11 +23679,16 @@ var MulticastTransport = class {
|
|
|
23597
23679
|
this.#withdrawnPacketIds.clear();
|
|
23598
23680
|
}
|
|
23599
23681
|
/** The ingress filter; an accepted packet is counted here, once. */
|
|
23600
|
-
#acceptPacket(packet) {
|
|
23682
|
+
#acceptPacket(packet, sourceAddress) {
|
|
23683
|
+
const owner = this.#lockedTo?.() ?? this.#sender;
|
|
23684
|
+
if (sourceAddress !== void 0 && owner !== void 0 && sourceAddress !== owner) {
|
|
23685
|
+
this.#stats.packetsDroppedForeignSender++;
|
|
23686
|
+
return false;
|
|
23687
|
+
}
|
|
23601
23688
|
const packetId = readMMTPPacketId(packet);
|
|
23602
23689
|
if (packetId === null) {
|
|
23603
23690
|
if (!this.#packetIds) {
|
|
23604
|
-
this.#countAccepted(packet, null);
|
|
23691
|
+
this.#countAccepted(packet, null, sourceAddress);
|
|
23605
23692
|
return true;
|
|
23606
23693
|
}
|
|
23607
23694
|
this.#stats.packetsDroppedUnparseable++;
|
|
@@ -23612,14 +23699,15 @@ var MulticastTransport = class {
|
|
|
23612
23699
|
return false;
|
|
23613
23700
|
}
|
|
23614
23701
|
if (!this.#packetIds || this.#packetIds.has(packetId)) {
|
|
23615
|
-
this.#countAccepted(packet, packetId);
|
|
23702
|
+
this.#countAccepted(packet, packetId, sourceAddress);
|
|
23616
23703
|
return true;
|
|
23617
23704
|
}
|
|
23618
23705
|
this.#recordUnregisteredPidDrop(packetId);
|
|
23619
23706
|
return false;
|
|
23620
23707
|
}
|
|
23621
23708
|
/** Receive counters for one accepted packet; `packetId` is null when it has none. */
|
|
23622
|
-
#countAccepted(packet, packetId) {
|
|
23709
|
+
#countAccepted(packet, packetId, sourceAddress) {
|
|
23710
|
+
this.#sender ??= sourceAddress;
|
|
23623
23711
|
this.#stats.packetsReceived++;
|
|
23624
23712
|
this.#stats.bytesReceived += packet.byteLength;
|
|
23625
23713
|
if (packetId === null) return;
|
|
@@ -23690,6 +23778,7 @@ var MulticastTransport = class {
|
|
|
23690
23778
|
console.warn(
|
|
23691
23779
|
`[MulticastTransport] No packets for ${this.#failbackWindowMs}ms; failing back to MoQ`
|
|
23692
23780
|
);
|
|
23781
|
+
this.#sender = void 0;
|
|
23693
23782
|
this.#onFailback({ kind: "silence", windowMs: this.#failbackWindowMs });
|
|
23694
23783
|
}, delayMs);
|
|
23695
23784
|
}
|
|
@@ -26440,7 +26529,7 @@ function catalogAmtNetworkSource(networkSource) {
|
|
|
26440
26529
|
}
|
|
26441
26530
|
return void 0;
|
|
26442
26531
|
}
|
|
26443
|
-
function
|
|
26532
|
+
function intIn2(value, min, max) {
|
|
26444
26533
|
return Number.isInteger(value) && value >= min && value <= max;
|
|
26445
26534
|
}
|
|
26446
26535
|
function catalogAtsc3NetworkSource(networkSource) {
|
|
@@ -26448,330 +26537,18 @@ function catalogAtsc3NetworkSource(networkSource) {
|
|
|
26448
26537
|
for (const raw of entries) {
|
|
26449
26538
|
if (!raw || typeof raw !== "object") continue;
|
|
26450
26539
|
const ns = raw;
|
|
26451
|
-
if (ns.type !== "atsc3" || !
|
|
26540
|
+
if (ns.type !== "atsc3" || !intIn2(ns.frequency, 1, Number.MAX_SAFE_INTEGER)) continue;
|
|
26452
26541
|
const slsUri = typeof ns.slsUri === "string" ? ns.slsUri.trim() : "";
|
|
26453
26542
|
return {
|
|
26454
26543
|
frequency: ns.frequency,
|
|
26455
|
-
...
|
|
26456
|
-
...
|
|
26544
|
+
...intIn2(ns.plpId, 0, 63) ? { plpId: ns.plpId } : {},
|
|
26545
|
+
...intIn2(ns.serviceId, 0, 65535) ? { serviceId: ns.serviceId } : {},
|
|
26457
26546
|
...slsUri ? { slsUri } : {}
|
|
26458
26547
|
};
|
|
26459
26548
|
}
|
|
26460
26549
|
return void 0;
|
|
26461
26550
|
}
|
|
26462
26551
|
|
|
26463
|
-
// src/catalog-fec-params.ts
|
|
26464
|
-
var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
|
|
26465
|
-
function timescaleHz(value) {
|
|
26466
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
26467
|
-
}
|
|
26468
|
-
function catalogGroupDurationMs(rendition) {
|
|
26469
|
-
if (!rendition) return void 0;
|
|
26470
|
-
const ticks = numericField(rendition.groupDurationTicks);
|
|
26471
|
-
const ms = numericField(rendition.groupDurationMs);
|
|
26472
|
-
const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
|
|
26473
|
-
if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
|
|
26474
|
-
return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
|
|
26475
|
-
}
|
|
26476
|
-
return ms;
|
|
26477
|
-
}
|
|
26478
|
-
function resolveCatalogTrackGroupDurationMs(catalog, track) {
|
|
26479
|
-
const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
|
|
26480
|
-
return catalogGroupDurationMs(renditions?.[track.name]);
|
|
26481
|
-
}
|
|
26482
|
-
function positiveNumber(value) {
|
|
26483
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
26484
|
-
}
|
|
26485
|
-
function catalogKeyframeCadenceMs(rendition) {
|
|
26486
|
-
if (!rendition) return void 0;
|
|
26487
|
-
const timescale = positiveNumber(rendition.timescale);
|
|
26488
|
-
const ticksToMs = (ticks) => {
|
|
26489
|
-
const value = positiveNumber(ticks);
|
|
26490
|
-
return value !== void 0 && timescale !== void 0 ? value * 1e3 / timescale : void 0;
|
|
26491
|
-
};
|
|
26492
|
-
return ticksToMs(rendition.keyframeIntervalTicks) ?? positiveNumber(rendition.keyframeIntervalMs) ?? ticksToMs(rendition.keyframeDurationTicks) ?? positiveNumber(rendition.keyframeDurationMs) ?? ticksToMs(rendition.gopDurationTicks) ?? positiveNumber(rendition.gopDurationMs);
|
|
26493
|
-
}
|
|
26494
|
-
function catalogFecMediaUnitsPerSecond(type, rendition, endpointSampleRate) {
|
|
26495
|
-
if (type === "video") return positiveNumber(rendition?.framerate);
|
|
26496
|
-
const sampleRate = positiveNumber(endpointSampleRate ?? rendition?.sampleRate ?? rendition?.timescale);
|
|
26497
|
-
return sampleRate === void 0 ? void 0 : sampleRate / 1024;
|
|
26498
|
-
}
|
|
26499
|
-
var NO_FEC = {
|
|
26500
|
-
fecK: 0,
|
|
26501
|
-
fecRepairCount: 0,
|
|
26502
|
-
fecTotalRepairCount: 0,
|
|
26503
|
-
fecSymbolSize: 0,
|
|
26504
|
-
fecInterleaveMs: 0
|
|
26505
|
-
};
|
|
26506
|
-
function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
|
|
26507
|
-
const layers = params.repairLayers;
|
|
26508
|
-
if (layers === void 0 || layers.length === 0) return params.fecRepairCount;
|
|
26509
|
-
const joined = new Set(joinedRepairTrackNames);
|
|
26510
|
-
let total = 0;
|
|
26511
|
-
for (const layer of layers) {
|
|
26512
|
-
if (joined.has(layer.repairTrackName)) total += layer.repairSymbols;
|
|
26513
|
-
}
|
|
26514
|
-
return total;
|
|
26515
|
-
}
|
|
26516
|
-
function recordOf3(value) {
|
|
26517
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
26518
|
-
}
|
|
26519
|
-
function msfTracks(catalog) {
|
|
26520
|
-
const raw = catalog.tracks;
|
|
26521
|
-
if (!Array.isArray(raw)) return void 0;
|
|
26522
|
-
return { tracks: raw.map((t) => recordOf3(t) ?? {}) };
|
|
26523
|
-
}
|
|
26524
|
-
function resolveRepairLayers(catalog, source) {
|
|
26525
|
-
if (!catalog) return void 0;
|
|
26526
|
-
const sourceName = typeof source === "string" ? source : source.name;
|
|
26527
|
-
const hang = catalog;
|
|
26528
|
-
const roles = typeof source === "string" ? ["video", "audio"] : source.type === "audio" ? ["audio"] : source.type === "video" ? ["video"] : ["video", "audio"];
|
|
26529
|
-
const located = locateCatalogFecSource(catalog, sourceName, roles);
|
|
26530
|
-
if (located?.dialect === "hang") {
|
|
26531
|
-
const renditionPath = `$.${located.role}.renditions[${JSON.stringify(sourceName)}]`;
|
|
26532
|
-
const rendition = located.entry;
|
|
26533
|
-
rejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);
|
|
26534
|
-
const repairRenditions = recordOf3(hang.repair?.renditions) ?? {};
|
|
26535
|
-
const descriptors2 = Object.entries(repairRenditions).map(([name, entry]) => {
|
|
26536
|
-
const path = `$.repair.renditions[${JSON.stringify(name)}]`;
|
|
26537
|
-
const fields = recordOf3(entry);
|
|
26538
|
-
if (fields === void 0) {
|
|
26539
|
-
throwRepairContractError("track_repair_layer", path, "a repair rendition must be an object");
|
|
26540
|
-
}
|
|
26541
|
-
rejectRepairTrackGeometry(path, fields, throwRepairContractError);
|
|
26542
|
-
return describeRepairTrack(name, path, fields);
|
|
26543
|
-
});
|
|
26544
|
-
const fecOfRendition = (entry) => {
|
|
26545
|
-
const fec2 = recordOf3(recordOf3(entry)?.["fec"]);
|
|
26546
|
-
return fec2 !== void 0 && fec2["algorithm"] !== "none" ? fec2 : void 0;
|
|
26547
|
-
};
|
|
26548
|
-
const mediaRenditions = [
|
|
26549
|
-
...Object.values(recordOf3(hang.video?.renditions) ?? {}),
|
|
26550
|
-
...Object.values(recordOf3(hang.audio?.renditions) ?? {})
|
|
26551
|
-
];
|
|
26552
|
-
const reachability2 = repairReachability(mediaRenditions.map((entry) => recordOf3(entry)?.["fec"]));
|
|
26553
|
-
for (const d of descriptors2) {
|
|
26554
|
-
validateRepairTrackFields(d, throwRepairContractError);
|
|
26555
|
-
assertRepairTrackReachable(d, reachability2, throwRepairContractError);
|
|
26556
|
-
const target = dependsTarget(d);
|
|
26557
|
-
if (target === void 0) continue;
|
|
26558
|
-
const targetFec = fecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);
|
|
26559
|
-
if (targetFec === void 0) {
|
|
26560
|
-
throwRepairContractError(
|
|
26561
|
-
"track_repair_depends",
|
|
26562
|
-
`${d.path}.depends`,
|
|
26563
|
-
`${JSON.stringify(target)} is not a video or audio rendition with FEC`
|
|
26564
|
-
);
|
|
26565
|
-
}
|
|
26566
|
-
}
|
|
26567
|
-
const layers2 = assembleRepairLayers(
|
|
26568
|
-
geometryOf(sourceName, `${renditionPath}.fec`, recordOf3(rendition["fec"])),
|
|
26569
|
-
descriptors2,
|
|
26570
|
-
throwRepairContractError
|
|
26571
|
-
);
|
|
26572
|
-
return catalogFecUnavailable(rendition["fec"], `${renditionPath}.fec`) === void 0 ? layers2 : void 0;
|
|
26573
|
-
}
|
|
26574
|
-
const msf = msfTracks(catalog);
|
|
26575
|
-
if (!msf || located?.dialect !== "msf") return void 0;
|
|
26576
|
-
const msfIndex = located.index;
|
|
26577
|
-
const track = msf.tracks[msfIndex];
|
|
26578
|
-
const fec = recordOf3(track["fec"]);
|
|
26579
|
-
const packagingOf = (t) => t["packaging"];
|
|
26580
|
-
const fecOfTrack = (t) => {
|
|
26581
|
-
const trackFec = recordOf3(t?.["fec"]);
|
|
26582
|
-
return trackFec !== void 0 && trackFec["algorithm"] !== "none" ? trackFec : void 0;
|
|
26583
|
-
};
|
|
26584
|
-
const descriptors = [];
|
|
26585
|
-
const mediaTracks = [];
|
|
26586
|
-
msf.tracks.forEach((t, i) => {
|
|
26587
|
-
const path = `$.tracks[${i}]`;
|
|
26588
|
-
if (packagingOf(t) === "fec-repair") {
|
|
26589
|
-
rejectRepairTrackGeometry(path, t, throwRepairContractError);
|
|
26590
|
-
if (typeof t["name"] === "string") descriptors.push(describeRepairTrack(t["name"], path, t));
|
|
26591
|
-
return;
|
|
26592
|
-
}
|
|
26593
|
-
rejectRepairFieldsOnMediaTrack(path, t, throwRepairContractError);
|
|
26594
|
-
mediaTracks.push(t);
|
|
26595
|
-
});
|
|
26596
|
-
const reachability = repairReachability(mediaTracks.map((t) => t["fec"]));
|
|
26597
|
-
for (const d of descriptors) {
|
|
26598
|
-
validateRepairTrackFields(d, throwRepairContractError);
|
|
26599
|
-
assertRepairTrackReachable(d, reachability, throwRepairContractError);
|
|
26600
|
-
const target = dependsTarget(d);
|
|
26601
|
-
if (target === void 0) continue;
|
|
26602
|
-
const targetTrack = msf.tracks.find((t) => t["name"] === target);
|
|
26603
|
-
if (targetTrack === void 0 || packagingOf(targetTrack) !== "mmtp" || fecOfTrack(targetTrack) === void 0) {
|
|
26604
|
-
throwRepairContractError(
|
|
26605
|
-
"track_repair_depends",
|
|
26606
|
-
`${d.path}.depends`,
|
|
26607
|
-
`${JSON.stringify(target)} is not an mmtp track with FEC`
|
|
26608
|
-
);
|
|
26609
|
-
}
|
|
26610
|
-
}
|
|
26611
|
-
const layers = assembleRepairLayers(
|
|
26612
|
-
geometryOf(sourceName, `$.tracks[${msfIndex}].fec`, fec),
|
|
26613
|
-
descriptors,
|
|
26614
|
-
throwRepairContractError,
|
|
26615
|
-
{ layerZeroMustBeDeclared: true }
|
|
26616
|
-
);
|
|
26617
|
-
return catalogFecUnavailable(fec, `$.tracks[${msfIndex}].fec`) === void 0 ? layers : void 0;
|
|
26618
|
-
}
|
|
26619
|
-
function moqtPriority2(value) {
|
|
26620
|
-
return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
|
|
26621
|
-
}
|
|
26622
|
-
function catalogRepairTarget(catalog, source) {
|
|
26623
|
-
const repair = resolveRepairLayers(catalog, source);
|
|
26624
|
-
const layerZero = repair?.baseLayers[0];
|
|
26625
|
-
if (!catalog || !layerZero) return null;
|
|
26626
|
-
const sourceName = typeof source === "string" ? source : source.name;
|
|
26627
|
-
const msf = msfTracks(catalog);
|
|
26628
|
-
const entry = msf?.tracks.find((t) => t["name"] === sourceName);
|
|
26629
|
-
const hang = catalog;
|
|
26630
|
-
const roles = typeof source === "string" ? ["video", "audio"] : source.type === "video" ? ["video"] : source.type === "audio" ? ["audio"] : ["video", "audio"];
|
|
26631
|
-
const rendition = roles.map((role) => hang[role]?.renditions?.[sourceName]).find((r) => recordOf3(r) !== void 0);
|
|
26632
|
-
const fec = rendition === void 0 ? recordOf3(entry?.["fec"]) : recordOf3(rendition.fec);
|
|
26633
|
-
if (!fec || !(catalogFecInterleaveMs(fec) ?? 0)) return null;
|
|
26634
|
-
const sourcePriority = entry === void 0 ? null : moqtPriority2(entry["priority"]);
|
|
26635
|
-
return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
|
|
26636
|
-
}
|
|
26637
|
-
function geometryOf(name, path, fec) {
|
|
26638
|
-
return {
|
|
26639
|
-
name,
|
|
26640
|
-
path,
|
|
26641
|
-
algorithm: fec?.["algorithm"],
|
|
26642
|
-
sourceSymbols: fec?.["sourceSymbols"],
|
|
26643
|
-
repairSymbols: fec?.["repairSymbols"],
|
|
26644
|
-
repairTrack: fec?.["repairTrack"]
|
|
26645
|
-
};
|
|
26646
|
-
}
|
|
26647
|
-
function numericField(value) {
|
|
26648
|
-
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
26649
|
-
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
26650
|
-
}
|
|
26651
|
-
function nonNegativeField(value) {
|
|
26652
|
-
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
26653
|
-
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
26654
|
-
}
|
|
26655
|
-
function blockProfileUnavailable(value, path, declaresFecInstance) {
|
|
26656
|
-
try {
|
|
26657
|
-
readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
|
|
26658
|
-
return void 0;
|
|
26659
|
-
} catch (error) {
|
|
26660
|
-
if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
|
|
26661
|
-
throw error;
|
|
26662
|
-
}
|
|
26663
|
-
return Object.freeze({ code: error.code, path: error.path, message: error.message });
|
|
26664
|
-
}
|
|
26665
|
-
}
|
|
26666
|
-
function isoSpanUnavailable(fec, path, declaresFecInstance) {
|
|
26667
|
-
if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
|
|
26668
|
-
return Object.freeze({
|
|
26669
|
-
code: "track_fec",
|
|
26670
|
-
path,
|
|
26671
|
-
message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
|
|
26672
|
-
});
|
|
26673
|
-
}
|
|
26674
|
-
function catalogFecUnavailable(fec, fecPath) {
|
|
26675
|
-
if (catalogFecAvailable(fec)) return void 0;
|
|
26676
|
-
const block = recordOf3(fec);
|
|
26677
|
-
if (fecAlgorithmUnimplemented(block["algorithm"])) {
|
|
26678
|
-
return Object.freeze({
|
|
26679
|
-
code: "track_fec_algorithm_unimplemented",
|
|
26680
|
-
path: `${fecPath}.algorithm`,
|
|
26681
|
-
message: `algorithm ${JSON.stringify(block["algorithm"])} is not implemented: the track plays without FEC`
|
|
26682
|
-
});
|
|
26683
|
-
}
|
|
26684
|
-
const declaresFecInstance = block["algorithm"] !== "none";
|
|
26685
|
-
const reason = blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
|
|
26686
|
-
if (reason === void 0) {
|
|
26687
|
-
throw new Error(`catalogFecUnavailable: ${fecPath} is unavailable but no reason applies (rule drift)`);
|
|
26688
|
-
}
|
|
26689
|
-
return reason;
|
|
26690
|
-
}
|
|
26691
|
-
function fecParams(catalog, track, rendition, fps, sampleRate) {
|
|
26692
|
-
const mediaRate = sampleRate === void 0 ? { fps } : { fps, sampleRate };
|
|
26693
|
-
const fec = rendition?.fec;
|
|
26694
|
-
if (!fec || !track) return { ...NO_FEC, ...mediaRate };
|
|
26695
|
-
const repair = resolveRepairLayers(catalog, track);
|
|
26696
|
-
const fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;
|
|
26697
|
-
if (!catalogDeclaresFec(fec)) {
|
|
26698
|
-
if (catalogFecDeclaresUnconfigurableInstance(fec)) {
|
|
26699
|
-
const kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;
|
|
26700
|
-
throwRepairContractError(
|
|
26701
|
-
"track_fec",
|
|
26702
|
-
`${fecPath}.${kValid ? "symbolSize" : "sourceSymbols"}`,
|
|
26703
|
-
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"
|
|
26704
|
-
);
|
|
26705
|
-
}
|
|
26706
|
-
const fecUnavailable = catalogFecUnavailable(fec, fecPath);
|
|
26707
|
-
return fecUnavailable === void 0 ? { ...NO_FEC, ...mediaRate } : { ...NO_FEC, ...mediaRate, fecUnavailable };
|
|
26708
|
-
}
|
|
26709
|
-
const fecBlockProfile = readFecBlockProfile(fec.blockProfile, `${fecPath}.blockProfile`, true, throwRepairContractError);
|
|
26710
|
-
return {
|
|
26711
|
-
fecK: fec.sourceSymbols ?? 0,
|
|
26712
|
-
fecAlgorithm: fec.algorithm === "reed-solomon" ? "reed-solomon" : "raptorq",
|
|
26713
|
-
fecBlockProfile,
|
|
26714
|
-
fecRepairCount: fec.repairSymbols ?? 0,
|
|
26715
|
-
// The wire RSB_length: every base layer's packets carry the base total (§7.1).
|
|
26716
|
-
fecTotalRepairCount: repair?.totalBaseRepairSymbols ?? fec.repairSymbols ?? 0,
|
|
26717
|
-
fecSymbolSize: fec.symbolSize ?? 0,
|
|
26718
|
-
// Zero/absence leaves the elapsed deadline unknown; geometry stays in-band.
|
|
26719
|
-
fecInterleaveMs: catalogFecInterleaveMs(fec) ?? 0,
|
|
26720
|
-
// Additive, never a multiplier on the span: jitter belongs to the network
|
|
26721
|
-
// path and the span belongs to the encoder, so scaling one by the other
|
|
26722
|
-
// hands one network different slack per track. `numericField` rejects 0,
|
|
26723
|
-
// so it cannot be reused here — a declared 0 is a legitimate value.
|
|
26724
|
-
fecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),
|
|
26725
|
-
repairTrackName: fec.repairTrack,
|
|
26726
|
-
repairLayers: repair?.baseLayers,
|
|
26727
|
-
repairOverlays: repair?.overlays,
|
|
26728
|
-
...mediaRate
|
|
26729
|
-
};
|
|
26730
|
-
}
|
|
26731
|
-
function resolveCatalogTrackFec(catalog, tracks) {
|
|
26732
|
-
const videoTrackName = tracks.find((t) => t.type === "video")?.name;
|
|
26733
|
-
const audioTrackName = tracks.find((t) => t.type === "audio")?.name;
|
|
26734
|
-
const videoRenditions = catalog?.video?.renditions;
|
|
26735
|
-
const audioRenditions = catalog?.audio?.renditions;
|
|
26736
|
-
const video = videoTrackName ? videoRenditions?.[videoTrackName] : void 0;
|
|
26737
|
-
const audio = audioTrackName ? audioRenditions?.[audioTrackName] : void 0;
|
|
26738
|
-
const catalogFps = numericField(video?.framerate);
|
|
26739
|
-
const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
|
|
26740
|
-
const audioFps = sampleRate ? sampleRate / 1024 : void 0;
|
|
26741
|
-
const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, video, catalogFps);
|
|
26742
|
-
const audioParams = fecParams(
|
|
26743
|
-
catalog,
|
|
26744
|
-
audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
|
|
26745
|
-
audio,
|
|
26746
|
-
audioFps,
|
|
26747
|
-
sampleRate
|
|
26748
|
-
);
|
|
26749
|
-
const forTrack = (track) => {
|
|
26750
|
-
if (track.type === "audio") {
|
|
26751
|
-
if (track.name === audioTrackName) return audioParams;
|
|
26752
|
-
const rendition2 = audioRenditions?.[track.name];
|
|
26753
|
-
if (!rendition2) return { ...NO_FEC };
|
|
26754
|
-
const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
|
|
26755
|
-
return fecParams(catalog, { name: track.name, type: "audio" }, rendition2, rate ? rate / 1024 : void 0, rate);
|
|
26756
|
-
}
|
|
26757
|
-
if (track.name === videoTrackName) return videoParams;
|
|
26758
|
-
const rendition = videoRenditions?.[track.name];
|
|
26759
|
-
if (!rendition) return { ...NO_FEC };
|
|
26760
|
-
return fecParams(catalog, { name: track.name, type: "video" }, rendition, numericField(rendition.framerate));
|
|
26761
|
-
};
|
|
26762
|
-
return {
|
|
26763
|
-
videoTrackName,
|
|
26764
|
-
audioTrackName,
|
|
26765
|
-
missingVideoRendition: videoTrackName && videoRenditions && !video ? videoTrackName : void 0,
|
|
26766
|
-
missingAudioRendition: audioTrackName && audioRenditions && !audio ? audioTrackName : void 0,
|
|
26767
|
-
videoForKeyframeRepair: video,
|
|
26768
|
-
catalogFps,
|
|
26769
|
-
video: videoParams,
|
|
26770
|
-
audio: audioParams,
|
|
26771
|
-
forTrack
|
|
26772
|
-
};
|
|
26773
|
-
}
|
|
26774
|
-
|
|
26775
26552
|
// src/multicast-manager.ts
|
|
26776
26553
|
function freezeIngress(tracks, flow) {
|
|
26777
26554
|
return Object.freeze({
|
|
@@ -27128,20 +26905,25 @@ function altGroupKey(track) {
|
|
|
27128
26905
|
function repairTrackNamesBySource(tracks) {
|
|
27129
26906
|
const bySource = /* @__PURE__ */ new Map();
|
|
27130
26907
|
const add2 = (source, repair) => {
|
|
27131
|
-
if (!source || !repair) return;
|
|
26908
|
+
if (typeof source !== "string" || typeof repair !== "string" || !source || !repair) return;
|
|
27132
26909
|
const names = bySource.get(source) ?? /* @__PURE__ */ new Set();
|
|
27133
26910
|
names.add(repair);
|
|
27134
26911
|
bySource.set(source, names);
|
|
27135
26912
|
};
|
|
27136
26913
|
for (const track of tracks) {
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
|
|
27140
|
-
add2(track.depends[0], track.name);
|
|
26914
|
+
if (Array.isArray(track.fec)) {
|
|
26915
|
+
for (const instance of track.fec) add2(track.name, instance?.repairTrack);
|
|
27141
26916
|
}
|
|
26917
|
+
if (track.packaging === "fec-repair") add2(track.source, track.name);
|
|
27142
26918
|
}
|
|
27143
26919
|
return bySource;
|
|
27144
26920
|
}
|
|
26921
|
+
function boundRepairNames(binding) {
|
|
26922
|
+
if (binding?.status !== "ok") return [];
|
|
26923
|
+
const names = binding.layers.map((layer) => layer.repairTrackName);
|
|
26924
|
+
if (binding.overlay) names.push(binding.overlay.repairTrackName);
|
|
26925
|
+
return names;
|
|
26926
|
+
}
|
|
27145
26927
|
var MulticastManager = class {
|
|
27146
26928
|
#fec;
|
|
27147
26929
|
#router;
|
|
@@ -27567,7 +27349,7 @@ var MulticastManager = class {
|
|
|
27567
27349
|
jitterBufferMs: config.fecReorderToleranceMs
|
|
27568
27350
|
}) : void 0;
|
|
27569
27351
|
const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);
|
|
27570
|
-
const
|
|
27352
|
+
const geometry2 = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
|
|
27571
27353
|
trackKind,
|
|
27572
27354
|
framerate: config.fps,
|
|
27573
27355
|
interleaveDepthMs,
|
|
@@ -27584,7 +27366,7 @@ var MulticastManager = class {
|
|
|
27584
27366
|
timeoutMs: deliveryWindowMs,
|
|
27585
27367
|
...trackKind === void 0 ? {} : { trackKind },
|
|
27586
27368
|
...config.codec === void 0 ? {} : { codec: config.codec },
|
|
27587
|
-
...
|
|
27369
|
+
...geometry2 === void 0 ? {} : { geometry: geometry2 }
|
|
27588
27370
|
};
|
|
27589
27371
|
this.#fec.configureTrack(packetId, fecConfig, catalogTrackName);
|
|
27590
27372
|
this.#configureTrackMapper(packetId, fecConfig);
|
|
@@ -27704,10 +27486,6 @@ var MulticastManager = class {
|
|
|
27704
27486
|
onTrackPacket(packetId, cb) {
|
|
27705
27487
|
this.#router.onTrackPacket(packetId, cb);
|
|
27706
27488
|
}
|
|
27707
|
-
/** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
|
|
27708
|
-
onInitSegment(cb) {
|
|
27709
|
-
this.#router.onInitSegment(cb);
|
|
27710
|
-
}
|
|
27711
27489
|
onTrackInit(packetId, cb) {
|
|
27712
27490
|
this.#router.onTrackInit(packetId, cb);
|
|
27713
27491
|
}
|
|
@@ -27931,15 +27709,21 @@ var MulticastManager = class {
|
|
|
27931
27709
|
tracks: Object.freeze(endpoint.tracks.map(({ name, packetId }) => Object.freeze({ name, packetId }))),
|
|
27932
27710
|
...endpoint.flow === void 0 ? {} : { flow: endpoint.flow }
|
|
27933
27711
|
};
|
|
27934
|
-
|
|
27935
|
-
|
|
27712
|
+
let sender;
|
|
27713
|
+
const onPacket = (packet, sourceAddress) => {
|
|
27714
|
+
if (released) return;
|
|
27715
|
+
if (sourceAddress !== void 0 && sourceAddress !== (this.#transport.sender ?? (sender ??= sourceAddress))) return;
|
|
27716
|
+
this.#router.feedPacket(packet, "multicast", ingress);
|
|
27936
27717
|
};
|
|
27937
27718
|
const fail = (error) => console.warn(`[MulticastManager] repair group ${endpoint.group}:${endpoint.port} not joined`, error);
|
|
27938
27719
|
if (typeof provider === "function") {
|
|
27939
27720
|
Promise.resolve().then(() => provider(endpoint, onPacket)).then((release2) => released ? safely(release2) : releases.push(release2), fail);
|
|
27940
27721
|
continue;
|
|
27941
27722
|
}
|
|
27942
|
-
const transport = new MulticastTransport((packet) => onPacket(packet), {
|
|
27723
|
+
const transport = new MulticastTransport((packet) => onPacket(packet), {
|
|
27724
|
+
gateway: provider,
|
|
27725
|
+
lockedTo: () => this.#transport.sender
|
|
27726
|
+
});
|
|
27943
27727
|
releases.push(() => transport.dispose());
|
|
27944
27728
|
void transport.join({
|
|
27945
27729
|
sourceAddress: endpoint.source,
|
|
@@ -28218,8 +28002,8 @@ var MulticastManager = class {
|
|
|
28218
28002
|
* transport attribution in FEC path counters.
|
|
28219
28003
|
* The `router` accessor above is already public.
|
|
28220
28004
|
*/
|
|
28221
|
-
feedPacket(data, ingress) {
|
|
28222
|
-
this.#transport.feedPacket(data, ingress);
|
|
28005
|
+
feedPacket(data, ingress, sourceAddress) {
|
|
28006
|
+
this.#transport.feedPacket(data, ingress, sourceAddress);
|
|
28223
28007
|
}
|
|
28224
28008
|
/**
|
|
28225
28009
|
* Declare how packets given to feedPacket() reach this manager. A consumer
|
|
@@ -28252,20 +28036,18 @@ var MulticastManager = class {
|
|
|
28252
28036
|
* D-17: Builds altGroup → track-name index for getSwitchingSet() (repair legs excluded).
|
|
28253
28037
|
* D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
|
|
28254
28038
|
*
|
|
28255
|
-
* FEC
|
|
28256
|
-
*
|
|
28257
|
-
*
|
|
28258
|
-
*
|
|
28259
|
-
* its repair tracks
|
|
28260
|
-
*
|
|
28261
|
-
*
|
|
28262
|
-
*
|
|
28263
|
-
*
|
|
28264
|
-
*
|
|
28265
|
-
*
|
|
28266
|
-
*
|
|
28267
|
-
* Geometry a decoder could not configure (K, P, T, `repairTrack`,
|
|
28268
|
-
* algorithm) still throws.
|
|
28039
|
+
* FEC is per track and per instance (`resolveFecBindings`,
|
|
28040
|
+
* draft-ramadan-moq-fec-catalog-00 array, fec-core-00 10.3). A media track
|
|
28041
|
+
* whose base instance is not `ok` (an invalid instance, a profile other than
|
|
28042
|
+
* `iso-ssbg1-v1`, a draft-01 object `fec`) is consumed without FEC: it gets
|
|
28043
|
+
* no decoder, and none of its repair tracks is given an owner, so the router
|
|
28044
|
+
* drops their packets before any decoder. Those legs keep the `repair` role
|
|
28045
|
+
* and their owner's switching set. Neither the call nor any other track's
|
|
28046
|
+
* FEC fails on that account; `trackFecUnavailable(name)` reports the reason.
|
|
28047
|
+
* FEC a previous call configured on a packetId this call does not is
|
|
28048
|
+
* released (FECManager.releasePacket); frames its release gate held are
|
|
28049
|
+
* delivered in order, each gap stalled. FEC wired by hand (setFecDecoder) is
|
|
28050
|
+
* left alone.
|
|
28269
28051
|
*
|
|
28270
28052
|
* @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
|
|
28271
28053
|
* @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
|
|
@@ -28273,32 +28055,21 @@ var MulticastManager = class {
|
|
|
28273
28055
|
*/
|
|
28274
28056
|
configureMultiTrack(catalog, wasmDecoderSource) {
|
|
28275
28057
|
const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
|
|
28276
|
-
const
|
|
28277
|
-
|
|
28278
|
-
|
|
28279
|
-
|
|
28058
|
+
const bindings = resolveFecBindings({
|
|
28059
|
+
tracks: catalog.tracks.map(
|
|
28060
|
+
(track) => track.packaging === void 0 ? { ...track, packaging: "mmtp" } : track
|
|
28061
|
+
)
|
|
28062
|
+
});
|
|
28280
28063
|
const fecUnavailableByTrack = /* @__PURE__ */ new Map();
|
|
28281
|
-
for (
|
|
28282
|
-
|
|
28283
|
-
if (track.fec === void 0 || track.packaging === "fec-repair" || declaredRepairNames.has(track.name)) {
|
|
28284
|
-
continue;
|
|
28285
|
-
}
|
|
28286
|
-
const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);
|
|
28287
|
-
if (fecUnavailable === void 0) continue;
|
|
28288
|
-
fecUnavailableByTrack.set(track.name, fecUnavailable);
|
|
28289
|
-
const layerZero = track.fec.repairTrack;
|
|
28290
|
-
if (typeof layerZero === "string" && layerZero.length > 0) {
|
|
28291
|
-
const names = repairNamesBySource.get(track.name) ?? /* @__PURE__ */ new Set();
|
|
28292
|
-
names.add(layerZero);
|
|
28293
|
-
repairNamesBySource.set(track.name, names);
|
|
28294
|
-
}
|
|
28064
|
+
for (const binding of bindings.values()) {
|
|
28065
|
+
if (binding.status === "unavailable") fecUnavailableByTrack.set(binding.track, binding.reason);
|
|
28295
28066
|
}
|
|
28296
|
-
const
|
|
28297
|
-
|
|
28298
|
-
|
|
28299
|
-
|
|
28300
|
-
}
|
|
28067
|
+
const ownedRepairNames = /* @__PURE__ */ new Set();
|
|
28068
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
28069
|
+
for (const binding of bindings.values()) {
|
|
28070
|
+
for (const name of boundRepairNames(binding)) claimed.set(name, (claimed.get(name) ?? 0) + 1);
|
|
28301
28071
|
}
|
|
28072
|
+
for (const [name, count] of claimed) if (count === 1) ownedRepairNames.add(name);
|
|
28302
28073
|
const repairOwnerGroup = /* @__PURE__ */ new Map();
|
|
28303
28074
|
for (const track of catalog.tracks) {
|
|
28304
28075
|
for (const repairName of repairNamesBySource.get(track.name) ?? []) {
|
|
@@ -28344,15 +28115,8 @@ var MulticastManager = class {
|
|
|
28344
28115
|
const fecTrackIndexes = [];
|
|
28345
28116
|
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
28346
28117
|
const track = catalog.tracks[i];
|
|
28347
|
-
if (resolvedRoles[i] === "repair" || track.
|
|
28348
|
-
|
|
28349
|
-
if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {
|
|
28350
|
-
throw new Error(
|
|
28351
|
-
`[MulticastManager] invalid fec config for track '${track.name}'`
|
|
28352
|
-
);
|
|
28353
|
-
}
|
|
28354
|
-
if (unavailable) continue;
|
|
28355
|
-
if (track.packetId !== void 0) fecTrackIndexes.push(i);
|
|
28118
|
+
if (resolvedRoles[i] === "repair" || track.packetId === void 0) continue;
|
|
28119
|
+
if (bindings.get(track.name)?.status === "ok") fecTrackIndexes.push(i);
|
|
28356
28120
|
}
|
|
28357
28121
|
const fecDecoders = /* @__PURE__ */ new Map();
|
|
28358
28122
|
if (wasmDecoderSource !== void 0 && fecTrackIndexes.length > 0) {
|
|
@@ -28423,11 +28187,12 @@ var MulticastManager = class {
|
|
|
28423
28187
|
}
|
|
28424
28188
|
continue;
|
|
28425
28189
|
}
|
|
28426
|
-
|
|
28427
|
-
|
|
28190
|
+
const binding = bindings.get(track.name);
|
|
28191
|
+
if (binding?.status === "ok") {
|
|
28192
|
+
const fecCfg = binding.base;
|
|
28428
28193
|
if (track.packetId !== void 0) {
|
|
28429
|
-
for (const repairName of
|
|
28430
|
-
if (
|
|
28194
|
+
for (const repairName of boundRepairNames(binding)) {
|
|
28195
|
+
if (!ownedRepairNames.has(repairName)) continue;
|
|
28431
28196
|
const repairTrack = catalogTracksByName.get(repairName);
|
|
28432
28197
|
if (repairTrack?.packetId !== void 0) {
|
|
28433
28198
|
this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
|
|
@@ -28469,14 +28234,11 @@ var MulticastManager = class {
|
|
|
28469
28234
|
}
|
|
28470
28235
|
/**
|
|
28471
28236
|
* Why `configureMultiTrack` consumes the named catalog track without FEC
|
|
28472
|
-
* although its
|
|
28473
|
-
*
|
|
28474
|
-
*
|
|
28475
|
-
*
|
|
28476
|
-
*
|
|
28477
|
-
* resolver reports (`catalogFecUnavailable`). Undefined for every other
|
|
28478
|
-
* track, and for every track before the first call; each call replaces the
|
|
28479
|
-
* previous call's answers.
|
|
28237
|
+
* although it declares some: its base instance is not `ok`, with the
|
|
28238
|
+
* validator's code and path (`TrackFecUnavailable`), the same reason the
|
|
28239
|
+
* hang resolver reports (`catalogTrackFecUnavailable`). Undefined for every
|
|
28240
|
+
* other track, and for every track before the first call; each call
|
|
28241
|
+
* replaces the previous call's answers.
|
|
28480
28242
|
*/
|
|
28481
28243
|
trackFecUnavailable(trackName) {
|
|
28482
28244
|
return this.#fecUnavailableByTrack.get(trackName);
|
|
@@ -29503,33 +29265,44 @@ var MediaRouter = class {
|
|
|
29503
29265
|
};
|
|
29504
29266
|
|
|
29505
29267
|
// src/delta-fallback-config.ts
|
|
29506
|
-
|
|
29507
|
-
|
|
29508
|
-
|
|
29509
|
-
const
|
|
29510
|
-
|
|
29511
|
-
|
|
29512
|
-
|
|
29513
|
-
|
|
29514
|
-
|
|
29515
|
-
|
|
29516
|
-
|
|
29517
|
-
|
|
29518
|
-
|
|
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];
|
|
29519
29294
|
const config = {
|
|
29520
|
-
framerate
|
|
29521
|
-
sourceSymbols:
|
|
29522
|
-
repairSymbols:
|
|
29523
|
-
deltaFailWindowBlocks:
|
|
29524
|
-
deltaProbeIntervalMs:
|
|
29525
|
-
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
|
|
29526
29301
|
};
|
|
29527
|
-
assertDeltaFallbackConfig(config, `[delta-fallback] group ${
|
|
29528
|
-
return {
|
|
29529
|
-
}
|
|
29530
|
-
function isPositive(value) {
|
|
29531
|
-
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 };
|
|
29532
29304
|
}
|
|
29305
|
+
var repairOf = (b) => b.layers[0].repairTrackName;
|
|
29533
29306
|
|
|
29534
29307
|
// src/align.ts
|
|
29535
29308
|
function secondsFromTicks(ticks, timescale) {
|
|
@@ -30578,6 +30351,187 @@ var CmcdProducer = class {
|
|
|
30578
30351
|
}
|
|
30579
30352
|
};
|
|
30580
30353
|
|
|
30354
|
+
// src/repair-layers.ts
|
|
30355
|
+
var REPAIR_PRIORITY_MIN = 192;
|
|
30356
|
+
var REPAIR_PRIORITY_MAX = 255;
|
|
30357
|
+
var DEFAULT_REPAIR_PRIORITY = 240;
|
|
30358
|
+
var MAX_RAPTORQ_SOURCE_SYMBOLS = 56403;
|
|
30359
|
+
var MAX_REPAIR_SYMBOLS = 16777215;
|
|
30360
|
+
var MAX_ENCODING_SYMBOL_ID = 16777215;
|
|
30361
|
+
|
|
30362
|
+
// src/catalog-fec-params.ts
|
|
30363
|
+
var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
|
|
30364
|
+
function timescaleHz(value) {
|
|
30365
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
30366
|
+
}
|
|
30367
|
+
function catalogGroupDurationMs(rendition) {
|
|
30368
|
+
if (!rendition) return void 0;
|
|
30369
|
+
const ticks = numericField(rendition.groupDurationTicks);
|
|
30370
|
+
const ms = numericField(rendition.groupDurationMs);
|
|
30371
|
+
const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
|
|
30372
|
+
if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
|
|
30373
|
+
return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
|
|
30374
|
+
}
|
|
30375
|
+
return ms;
|
|
30376
|
+
}
|
|
30377
|
+
function resolveCatalogTrackGroupDurationMs(catalog, track) {
|
|
30378
|
+
const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
|
|
30379
|
+
return catalogGroupDurationMs(renditions?.[track.name]);
|
|
30380
|
+
}
|
|
30381
|
+
function positiveNumber(value) {
|
|
30382
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
30383
|
+
}
|
|
30384
|
+
function catalogKeyframeCadenceMs(rendition) {
|
|
30385
|
+
if (!rendition) return void 0;
|
|
30386
|
+
const timescale = positiveNumber(rendition.timescale);
|
|
30387
|
+
const ticksToMs = (ticks) => {
|
|
30388
|
+
const value = positiveNumber(ticks);
|
|
30389
|
+
return value !== void 0 && timescale !== void 0 ? value * 1e3 / timescale : void 0;
|
|
30390
|
+
};
|
|
30391
|
+
return ticksToMs(rendition.keyframeIntervalTicks) ?? positiveNumber(rendition.keyframeIntervalMs) ?? ticksToMs(rendition.keyframeDurationTicks) ?? positiveNumber(rendition.keyframeDurationMs) ?? ticksToMs(rendition.gopDurationTicks) ?? positiveNumber(rendition.gopDurationMs);
|
|
30392
|
+
}
|
|
30393
|
+
function catalogFecMediaUnitsPerSecond(type, rendition, endpointSampleRate) {
|
|
30394
|
+
if (type === "video") return positiveNumber(rendition?.framerate);
|
|
30395
|
+
const sampleRate = positiveNumber(endpointSampleRate ?? rendition?.sampleRate ?? rendition?.timescale);
|
|
30396
|
+
return sampleRate === void 0 ? void 0 : sampleRate / 1024;
|
|
30397
|
+
}
|
|
30398
|
+
var NO_FEC = {
|
|
30399
|
+
fecK: 0,
|
|
30400
|
+
fecRepairCount: 0,
|
|
30401
|
+
fecTotalRepairCount: 0,
|
|
30402
|
+
fecSymbolSize: 0,
|
|
30403
|
+
fecInterleaveMs: 0
|
|
30404
|
+
};
|
|
30405
|
+
function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
|
|
30406
|
+
const layers = params.repairLayers;
|
|
30407
|
+
if (layers === void 0 || layers.length === 0) return params.fecRepairCount;
|
|
30408
|
+
const joined = new Set(joinedRepairTrackNames);
|
|
30409
|
+
let total = 0;
|
|
30410
|
+
for (const layer of layers) {
|
|
30411
|
+
if (joined.has(layer.repairTrackName)) total += layer.repairSymbols;
|
|
30412
|
+
}
|
|
30413
|
+
return total;
|
|
30414
|
+
}
|
|
30415
|
+
function bindingOf(catalog, source) {
|
|
30416
|
+
if (!catalog) return void 0;
|
|
30417
|
+
const name = typeof source === "string" ? source : source.name;
|
|
30418
|
+
const binding = resolveFecBindings(catalog).get(name);
|
|
30419
|
+
if (binding === void 0 || typeof source === "string") return binding;
|
|
30420
|
+
const role = binding.pointer.split("/")[1];
|
|
30421
|
+
return role === "tracks" || role === source.type ? binding : void 0;
|
|
30422
|
+
}
|
|
30423
|
+
function catalogTrackFecUnavailable(catalog, source) {
|
|
30424
|
+
const binding = bindingOf(catalog, source);
|
|
30425
|
+
return binding?.status === "unavailable" ? binding.reason : void 0;
|
|
30426
|
+
}
|
|
30427
|
+
function resolveRepairLayers(catalog, source) {
|
|
30428
|
+
const binding = bindingOf(catalog, source);
|
|
30429
|
+
if (binding?.status !== "ok") return void 0;
|
|
30430
|
+
const overlays = binding.overlay ? [
|
|
30431
|
+
{
|
|
30432
|
+
repairTrackName: binding.overlay.repairTrackName,
|
|
30433
|
+
scope: "keyframe",
|
|
30434
|
+
sourceSymbols: binding.overlay.sourceSymbols,
|
|
30435
|
+
repairSymbols: binding.overlay.repairSymbols,
|
|
30436
|
+
priority: binding.overlay.priority
|
|
30437
|
+
}
|
|
30438
|
+
] : [];
|
|
30439
|
+
return {
|
|
30440
|
+
sourceTrackName: binding.track,
|
|
30441
|
+
baseLayers: binding.layers,
|
|
30442
|
+
overlays,
|
|
30443
|
+
totalBaseRepairSymbols: binding.totalBaseRepairSymbols
|
|
30444
|
+
};
|
|
30445
|
+
}
|
|
30446
|
+
function moqtPriority2(value) {
|
|
30447
|
+
return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
|
|
30448
|
+
}
|
|
30449
|
+
function catalogRepairTarget(catalog, source) {
|
|
30450
|
+
const binding = bindingOf(catalog, source);
|
|
30451
|
+
if (binding?.status !== "ok" || !(binding.base.interleaveDepthMs ?? 0)) return null;
|
|
30452
|
+
const layerZero = binding.layers[0];
|
|
30453
|
+
const m = /^\/tracks\/(\d+)$/.exec(binding.pointer);
|
|
30454
|
+
const tracks = catalog.tracks;
|
|
30455
|
+
const entry = m && Array.isArray(tracks) ? tracks[Number(m[1])] : void 0;
|
|
30456
|
+
const sourcePriority = entry ? moqtPriority2(entry["priority"]) : null;
|
|
30457
|
+
return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
|
|
30458
|
+
}
|
|
30459
|
+
function numericField(value) {
|
|
30460
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
30461
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
30462
|
+
}
|
|
30463
|
+
function nonNegativeField(value) {
|
|
30464
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
30465
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
30466
|
+
}
|
|
30467
|
+
function fecParams(catalog, track, fps, sampleRate) {
|
|
30468
|
+
const mediaRate = sampleRate === void 0 ? { fps } : { fps, sampleRate };
|
|
30469
|
+
const binding = track ? bindingOf(catalog, track) : void 0;
|
|
30470
|
+
if (binding === void 0) return { ...NO_FEC, ...mediaRate };
|
|
30471
|
+
if (binding.status === "unavailable") return { ...NO_FEC, ...mediaRate, fecUnavailable: binding.reason };
|
|
30472
|
+
const { base } = binding;
|
|
30473
|
+
const repair = resolveRepairLayers(catalog, track);
|
|
30474
|
+
return {
|
|
30475
|
+
fecK: base.sourceSymbols,
|
|
30476
|
+
fecAlgorithm: base.scheme,
|
|
30477
|
+
fecBlockProfile: parseFecBlockProfile(base.blockProfile, `${binding.pointer}/fec/blockProfile`),
|
|
30478
|
+
fecRepairCount: base.repairSymbols,
|
|
30479
|
+
// The wire RSB_length: every base layer's packets carry the base total.
|
|
30480
|
+
fecTotalRepairCount: binding.totalBaseRepairSymbols,
|
|
30481
|
+
fecSymbolSize: base.symbolSize,
|
|
30482
|
+
// Zero/absence leaves the elapsed deadline unknown; geometry stays in-band.
|
|
30483
|
+
fecInterleaveMs: catalogFecInterleaveMs(base) ?? 0,
|
|
30484
|
+
// Additive to the span, never a multiplier; a declared 0 stays 0.
|
|
30485
|
+
fecReorderToleranceMs: nonNegativeField(base.reorderToleranceMs),
|
|
30486
|
+
repairTrackName: base.repairTrack,
|
|
30487
|
+
repairLayers: repair.baseLayers,
|
|
30488
|
+
repairOverlays: repair.overlays,
|
|
30489
|
+
...mediaRate
|
|
30490
|
+
};
|
|
30491
|
+
}
|
|
30492
|
+
function resolveCatalogTrackFec(catalog, tracks) {
|
|
30493
|
+
const videoTrackName = tracks.find((t) => t.type === "video")?.name;
|
|
30494
|
+
const audioTrackName = tracks.find((t) => t.type === "audio")?.name;
|
|
30495
|
+
const videoRenditions = catalog?.video?.renditions;
|
|
30496
|
+
const audioRenditions = catalog?.audio?.renditions;
|
|
30497
|
+
const video = videoTrackName ? videoRenditions?.[videoTrackName] : void 0;
|
|
30498
|
+
const audio = audioTrackName ? audioRenditions?.[audioTrackName] : void 0;
|
|
30499
|
+
const catalogFps = numericField(video?.framerate);
|
|
30500
|
+
const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
|
|
30501
|
+
const audioFps = sampleRate ? sampleRate / 1024 : void 0;
|
|
30502
|
+
const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, catalogFps);
|
|
30503
|
+
const audioParams = fecParams(
|
|
30504
|
+
catalog,
|
|
30505
|
+
audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
|
|
30506
|
+
audioFps,
|
|
30507
|
+
sampleRate
|
|
30508
|
+
);
|
|
30509
|
+
const forTrack = (track) => {
|
|
30510
|
+
if (track.type === "audio") {
|
|
30511
|
+
if (track.name === audioTrackName) return audioParams;
|
|
30512
|
+
const rendition2 = audioRenditions?.[track.name];
|
|
30513
|
+
if (!rendition2) return { ...NO_FEC };
|
|
30514
|
+
const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
|
|
30515
|
+
return fecParams(catalog, { name: track.name, type: "audio" }, rate ? rate / 1024 : void 0, rate);
|
|
30516
|
+
}
|
|
30517
|
+
if (track.name === videoTrackName) return videoParams;
|
|
30518
|
+
const rendition = videoRenditions?.[track.name];
|
|
30519
|
+
if (!rendition) return { ...NO_FEC };
|
|
30520
|
+
return fecParams(catalog, { name: track.name, type: "video" }, numericField(rendition.framerate));
|
|
30521
|
+
};
|
|
30522
|
+
return {
|
|
30523
|
+
videoTrackName,
|
|
30524
|
+
audioTrackName,
|
|
30525
|
+
missingVideoRendition: videoTrackName && videoRenditions && !video ? videoTrackName : void 0,
|
|
30526
|
+
missingAudioRendition: audioTrackName && audioRenditions && !audio ? audioTrackName : void 0,
|
|
30527
|
+
videoForKeyframeRepair: video,
|
|
30528
|
+
catalogFps,
|
|
30529
|
+
video: videoParams,
|
|
30530
|
+
audio: audioParams,
|
|
30531
|
+
forTrack
|
|
30532
|
+
};
|
|
30533
|
+
}
|
|
30534
|
+
|
|
30581
30535
|
// src/mmtp-fec-session.ts
|
|
30582
30536
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
30583
30537
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
@@ -30615,20 +30569,20 @@ var MmtpFecSession = class {
|
|
|
30615
30569
|
failClosed: null
|
|
30616
30570
|
};
|
|
30617
30571
|
/**
|
|
30618
|
-
* @throws when the
|
|
30619
|
-
*
|
|
30620
|
-
* `reorderToleranceMs`. None is defaulted.
|
|
30572
|
+
* @throws when the instance lacks or misstates a field the session needs:
|
|
30573
|
+
* the scheme, the block profile, K, P_0 (`repairSymbols`), T,
|
|
30574
|
+
* `interleaveDepthMs` or `reorderToleranceMs`. None is defaulted.
|
|
30621
30575
|
*/
|
|
30622
30576
|
constructor(config) {
|
|
30623
30577
|
const fec = config?.fec;
|
|
30624
30578
|
if (!fec || typeof fec !== "object") {
|
|
30625
|
-
throw new Error("MmtpFecSession: the catalog fec
|
|
30579
|
+
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
30626
30580
|
}
|
|
30627
30581
|
this.blockProfile = parseFecBlockProfile(fec.blockProfile, "fec.blockProfile");
|
|
30628
|
-
if (fec.
|
|
30629
|
-
throw new Error(`MmtpFecSession: fec.
|
|
30582
|
+
if (fec.scheme !== "raptorq" && fec.scheme !== "reed-solomon") {
|
|
30583
|
+
throw new Error(`MmtpFecSession: fec.scheme ${JSON.stringify(fec.scheme)} is not implemented`);
|
|
30630
30584
|
}
|
|
30631
|
-
this.algorithm = fec.
|
|
30585
|
+
this.algorithm = fec.scheme;
|
|
30632
30586
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
30633
30587
|
this.repairSymbols = requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols");
|
|
30634
30588
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
@@ -31028,6 +30982,9 @@ function wireRepairFetch(mc, opts) {
|
|
|
31028
30982
|
};
|
|
31029
30983
|
const moot = (entry) => {
|
|
31030
30984
|
if (entry.priority !== 0) return false;
|
|
30985
|
+
const rttMs = readRtt();
|
|
30986
|
+
const sentAt = entry.running?.sentAt ?? performance.now();
|
|
30987
|
+
if (rttMs !== null && sentAt + rttMs < entry.playoutDeadlineAt) return false;
|
|
31031
30988
|
const last = lastSsId(entry);
|
|
31032
30989
|
if (last === void 0) return false;
|
|
31033
30990
|
try {
|
|
@@ -31122,7 +31079,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
31122
31079
|
bound: entry.bound,
|
|
31123
31080
|
...entry.range ? { range: true } : {}
|
|
31124
31081
|
});
|
|
31125
|
-
const run = { controller: new AbortController(), stopped: null };
|
|
31082
|
+
const run = { controller: new AbortController(), sentAt: now, stopped: null };
|
|
31126
31083
|
entry.running = run;
|
|
31127
31084
|
if (!entry.range) {
|
|
31128
31085
|
mc.fecManager.holdBlockForRepair(
|
|
@@ -31281,6 +31238,10 @@ function wireRepairFetch(mc, opts) {
|
|
|
31281
31238
|
);
|
|
31282
31239
|
if (promotedDeadline !== null && promotedDeadline.deadlineAt > entry2.deadlineAt) {
|
|
31283
31240
|
entry2.deadlineAt = promotedDeadline.deadlineAt;
|
|
31241
|
+
entry2.playoutDeadlineAt = Math.max(
|
|
31242
|
+
entry2.playoutDeadlineAt,
|
|
31243
|
+
promotedDeadline.deadlineAt - promotedDeadline.extensionMs
|
|
31244
|
+
);
|
|
31284
31245
|
entry2.anchorAt = promotedDeadline.anchorAt;
|
|
31285
31246
|
entry2.delayMs = promotedDeadline.delayMs;
|
|
31286
31247
|
entry2.bound = promotedDeadline.bound;
|
|
@@ -31321,15 +31282,15 @@ function wireRepairFetch(mc, opts) {
|
|
|
31321
31282
|
const retentionMs = mc.fecManager.terminalRetentionMsForPacket(packetId);
|
|
31322
31283
|
if (retentionMs === void 0) return null;
|
|
31323
31284
|
const ceilingAt = Math.min(anchor, earliestGatedFrameAt(packetId, catalogTrackName) ?? anchor) + retentionMs;
|
|
31324
|
-
const
|
|
31285
|
+
const at2 = (delay, delayBound) => {
|
|
31325
31286
|
const presentAt = Math.min(anchor + delay, ceilingAt);
|
|
31326
31287
|
if (waitDeadlineAt >= presentAt) return { deadlineAt: waitDeadlineAt, bound: "wait" };
|
|
31327
31288
|
return { deadlineAt: presentAt, bound: anchor + delay <= ceilingAt ? delayBound : "retention" };
|
|
31328
31289
|
};
|
|
31329
|
-
const base =
|
|
31290
|
+
const base = at2(delayMs, "presentation");
|
|
31330
31291
|
const budgetMs = keyframeBudgetEligible(blockClass, classifierMode(packetId, catalogTrackName)) && groupCanComplete(sbn, packetId, catalogTrackName) ? readKeyframeBudget(packetId, catalogTrackName) : null;
|
|
31331
31292
|
if (budgetMs === null || budgetMs <= delayMs) return { ...base, extensionMs: 0, anchorAt: anchor, delayMs };
|
|
31332
|
-
const extended =
|
|
31293
|
+
const extended = at2(budgetMs, "keyframe");
|
|
31333
31294
|
return {
|
|
31334
31295
|
...extended,
|
|
31335
31296
|
extensionMs: extended.deadlineAt - base.deadlineAt,
|
|
@@ -31339,8 +31300,8 @@ function wireRepairFetch(mc, opts) {
|
|
|
31339
31300
|
};
|
|
31340
31301
|
const earliestGatedFrameAt = (packetId, catalogTrackName) => {
|
|
31341
31302
|
try {
|
|
31342
|
-
const
|
|
31343
|
-
return
|
|
31303
|
+
const at2 = mc.earliestGatedFrameAtMs?.(packetId, catalogTrackName);
|
|
31304
|
+
return at2 !== void 0 && Number.isFinite(at2) ? at2 : void 0;
|
|
31344
31305
|
} catch {
|
|
31345
31306
|
return void 0;
|
|
31346
31307
|
}
|
|
@@ -31412,6 +31373,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
31412
31373
|
priority,
|
|
31413
31374
|
subscriberPriority: wire.subscriberPriority,
|
|
31414
31375
|
deadlineAt: deadline.deadlineAt,
|
|
31376
|
+
playoutDeadlineAt: deadline.deadlineAt - deadline.extensionMs,
|
|
31415
31377
|
anchorAt: deadline.anchorAt,
|
|
31416
31378
|
delayMs: deadline.delayMs,
|
|
31417
31379
|
bound: deadline.bound,
|
|
@@ -31481,6 +31443,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
31481
31443
|
priority,
|
|
31482
31444
|
subscriberPriority: wire.subscriberPriority,
|
|
31483
31445
|
deadlineAt: deadline.deadlineAt,
|
|
31446
|
+
playoutDeadlineAt: deadline.deadlineAt - deadline.extensionMs,
|
|
31484
31447
|
anchorAt: deadline.anchorAt,
|
|
31485
31448
|
delayMs: deadline.delayMs,
|
|
31486
31449
|
bound: deadline.bound,
|
|
@@ -31572,9 +31535,6 @@ export {
|
|
|
31572
31535
|
REPAIR_LIFECYCLE_LOG_PREFIX,
|
|
31573
31536
|
REPAIR_PRIORITY_MAX,
|
|
31574
31537
|
REPAIR_PRIORITY_MIN,
|
|
31575
|
-
REPAIR_TRACK_GEOMETRY_KEYS,
|
|
31576
|
-
REPAIR_TRACK_ONLY_KEYS,
|
|
31577
|
-
RepairContractError,
|
|
31578
31538
|
SOURCE_MEDIA_PRIORITY_MAX,
|
|
31579
31539
|
SampleCombineError,
|
|
31580
31540
|
SampleCombiner,
|
|
@@ -31587,8 +31547,6 @@ export {
|
|
|
31587
31547
|
annexBToAvcc,
|
|
31588
31548
|
archiveOrigin,
|
|
31589
31549
|
asSeconds,
|
|
31590
|
-
assembleRepairLayers,
|
|
31591
|
-
assertRepairTrackReachable,
|
|
31592
31550
|
avccToAnnexB,
|
|
31593
31551
|
buildAudioInitSegment,
|
|
31594
31552
|
buildHevcVideoInitSegment,
|
|
@@ -31596,14 +31554,12 @@ export {
|
|
|
31596
31554
|
catalogAmtNetworkSource,
|
|
31597
31555
|
catalogAtsc3NetworkSource,
|
|
31598
31556
|
catalogDeclaresFec,
|
|
31599
|
-
catalogFecAvailable,
|
|
31600
|
-
catalogFecDeclaresUnconfigurableInstance,
|
|
31601
31557
|
catalogFecInterleaveMs,
|
|
31602
31558
|
catalogFecMediaUnitsPerSecond,
|
|
31603
|
-
catalogFecUnavailable,
|
|
31604
31559
|
catalogGroupDurationMs,
|
|
31605
31560
|
catalogKeyframeCadenceMs,
|
|
31606
31561
|
catalogRepairTarget,
|
|
31562
|
+
catalogTrackFecUnavailable,
|
|
31607
31563
|
classifyH264Keyframe,
|
|
31608
31564
|
classifyHevcKeyframe,
|
|
31609
31565
|
classifyPacket,
|
|
@@ -31619,9 +31575,7 @@ export {
|
|
|
31619
31575
|
decodeLocHeader,
|
|
31620
31576
|
decodeLocTimestamp,
|
|
31621
31577
|
decodeTimestamp,
|
|
31622
|
-
dependsTarget,
|
|
31623
31578
|
deriveBlockClassifierGeometry,
|
|
31624
|
-
describeRepairTrack,
|
|
31625
31579
|
detectH264Keyframe,
|
|
31626
31580
|
detectHevcKeyframe,
|
|
31627
31581
|
driftCheck,
|
|
@@ -31638,7 +31592,6 @@ export {
|
|
|
31638
31592
|
extractHevcCodecDescription,
|
|
31639
31593
|
extractHevcParameterSets,
|
|
31640
31594
|
extractSpsPpsFromAvcC,
|
|
31641
|
-
fecAlgorithmUnimplemented,
|
|
31642
31595
|
findAnnexBStartCode,
|
|
31643
31596
|
forwardRepairLifecycleEvents,
|
|
31644
31597
|
frameSamplesFromInit,
|
|
@@ -31657,8 +31610,6 @@ export {
|
|
|
31657
31610
|
isCompleteAvccSample,
|
|
31658
31611
|
isCompleteFmp4DataUnit,
|
|
31659
31612
|
isFecBlockProfile,
|
|
31660
|
-
isFecCatalogConfig,
|
|
31661
|
-
isFecCatalogGeometry,
|
|
31662
31613
|
isFmp4InitSegment,
|
|
31663
31614
|
isHevcCodec,
|
|
31664
31615
|
isHevcIrapNalUnitType,
|
|
@@ -31672,7 +31623,6 @@ export {
|
|
|
31672
31623
|
isTrackTimingConfig,
|
|
31673
31624
|
joinedRepairSymbolCount,
|
|
31674
31625
|
keyframeIntervalMediaTicks,
|
|
31675
|
-
locateCatalogFecSource,
|
|
31676
31626
|
mergeOverrideIntoCmsfTracks,
|
|
31677
31627
|
mergeOverrideIntoHangRenditions,
|
|
31678
31628
|
mfuReassemblyTimeoutMs,
|
|
@@ -31691,15 +31641,11 @@ export {
|
|
|
31691
31641
|
parseOpusFrameSamples,
|
|
31692
31642
|
prepareHevcCmafSample,
|
|
31693
31643
|
processMMTPWithReassembly,
|
|
31694
|
-
readFecBlockProfile,
|
|
31695
|
-
rejectRepairFieldsOnMediaTrack,
|
|
31696
|
-
rejectRepairTrackGeometry,
|
|
31697
31644
|
reliableMoqMfuReassembly,
|
|
31698
31645
|
removeEmulationPrevention,
|
|
31699
31646
|
repairFetchRange,
|
|
31700
31647
|
repairFetchSubscriberPriority,
|
|
31701
31648
|
repairPriorityForClass,
|
|
31702
|
-
repairReachability,
|
|
31703
31649
|
resolveCatalogTrackFec,
|
|
31704
31650
|
resolveCatalogTrackGroupDurationMs,
|
|
31705
31651
|
resolveDeltaFallback,
|
|
@@ -31716,8 +31662,8 @@ export {
|
|
|
31716
31662
|
splitH264SampleNalUnits,
|
|
31717
31663
|
unwrapMulticastPacket,
|
|
31718
31664
|
validateCmsfMmtpCatalog,
|
|
31665
|
+
validateFecCatalog,
|
|
31719
31666
|
validateMulticastContainerContract,
|
|
31720
|
-
validateRepairTrackFields,
|
|
31721
31667
|
validateSwitchingSetTiming,
|
|
31722
31668
|
videoCodecFamily,
|
|
31723
31669
|
videoConcealModeForCodec,
|