@blockcast/mmt-container 0.5.0 → 1.0.0-main.3cb7359ea316

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +21 -16
  2. package/dist/browser-runtime.js +1024 -1078
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +33 -87
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +12 -434
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +48 -161
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +567 -592
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +6 -2
  13. package/dist/catalog-network-source.js.map +3 -3
  14. package/dist/delivery-catalog.js +12 -4
  15. package/dist/delivery-catalog.js.map +4 -4
  16. package/dist/delta-fallback-config.d.ts +21 -26
  17. package/dist/delta-fallback-config.d.ts.map +1 -1
  18. package/dist/delta-fallback-config.js +582 -24
  19. package/dist/delta-fallback-config.js.map +4 -4
  20. package/dist/fec-manager.js +6 -2
  21. package/dist/fec-manager.js.map +3 -3
  22. package/dist/fec-state.d.ts +5 -43
  23. package/dist/fec-state.d.ts.map +1 -1
  24. package/dist/fec-state.js +529 -34
  25. package/dist/fec-state.js.map +4 -4
  26. package/dist/fec-worker-setup.js +6 -2
  27. package/dist/fec-worker-setup.js.map +3 -3
  28. package/dist/held-layer-admission.js +6 -2
  29. package/dist/held-layer-admission.js.map +3 -3
  30. package/dist/index.d.ts +3 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5 -39
  33. package/dist/index.js.map +2 -2
  34. package/dist/iso-block-locator.js +6 -2
  35. package/dist/iso-block-locator.js.map +3 -3
  36. package/dist/mfu-reassembler.js +6 -2
  37. package/dist/mfu-reassembler.js.map +3 -3
  38. package/dist/mmtp-container-parser.js +6 -2
  39. package/dist/mmtp-container-parser.js.map +3 -3
  40. package/dist/mmtp-container-parser.no-modules.js +551 -452
  41. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  42. package/dist/mmtp-decode-pipeline.js +6 -2
  43. package/dist/mmtp-decode-pipeline.js.map +3 -3
  44. package/dist/mmtp-fec-session.d.ts +7 -6
  45. package/dist/mmtp-fec-session.d.ts.map +1 -1
  46. package/dist/mmtp-fec-session.js +13 -9
  47. package/dist/mmtp-fec-session.js.map +3 -3
  48. package/dist/mmtp-router.d.ts +7 -16
  49. package/dist/mmtp-router.d.ts.map +1 -1
  50. package/dist/mmtp-router.js +19 -29
  51. package/dist/mmtp-router.js.map +3 -3
  52. package/dist/multicast-auth-catalog.js +7 -3
  53. package/dist/multicast-auth-catalog.js.map +3 -3
  54. package/dist/multicast-input.js +7 -3
  55. package/dist/multicast-input.js.map +3 -3
  56. package/dist/multicast-manager.d.ts +29 -32
  57. package/dist/multicast-manager.d.ts.map +1 -1
  58. package/dist/multicast-manager.js +682 -253
  59. package/dist/multicast-manager.js.map +4 -4
  60. package/dist/multicast-transport.d.ts +23 -2
  61. package/dist/multicast-transport.d.ts.map +1 -1
  62. package/dist/multicast-transport.js +57 -16
  63. package/dist/multicast-transport.js.map +4 -4
  64. package/dist/multicast-unwrap.js +6 -2
  65. package/dist/multicast-unwrap.js.map +3 -3
  66. package/dist/no-modules.js +551 -452
  67. package/dist/no-modules.js.map +4 -4
  68. package/dist/phase14-catalog-override.d.ts +10 -5
  69. package/dist/phase14-catalog-override.d.ts.map +1 -1
  70. package/dist/phase14-catalog-override.js +35 -11
  71. package/dist/phase14-catalog-override.js.map +2 -2
  72. package/dist/repair-fetch.d.ts +3 -1
  73. package/dist/repair-fetch.d.ts.map +1 -1
  74. package/dist/repair-fetch.js +16 -3
  75. package/dist/repair-fetch.js.map +3 -3
  76. package/dist/repair-layers.d.ts +0 -123
  77. package/dist/repair-layers.d.ts.map +1 -1
  78. package/dist/repair-layers.js +1 -349
  79. package/dist/repair-layers.js.map +3 -3
  80. package/dist/signaling.js +6 -2
  81. package/dist/signaling.js.map +3 -3
  82. package/dist/timestamp-codec.js +6 -2
  83. package/dist/timestamp-codec.js.map +3 -3
  84. package/dist/wasm-mfu-reassembler.js +6 -2
  85. package/dist/wasm-mfu-reassembler.js.map +3 -3
  86. package/dist/wasm-mmtp-container-parser.js +6 -2
  87. package/dist/wasm-mmtp-container-parser.js.map +3 -3
  88. package/package.json +9 -5
  89. package/schemas/catalog.schema.json +53 -58
  90. package/src/carp-catalog.ts +39 -229
  91. package/src/catalog-fec-params.ts +130 -493
  92. package/src/delta-fallback-config.ts +63 -50
  93. package/src/fec-state.ts +7 -62
  94. package/src/index.ts +10 -30
  95. package/src/mmtp-fec-session.ts +11 -10
  96. package/src/mmtp-router.ts +14 -29
  97. package/src/multicast-manager.ts +89 -121
  98. package/src/multicast-transport.ts +65 -12
  99. package/src/phase14-catalog-override.ts +67 -16
  100. package/src/repair-fetch.ts +27 -3
  101. package/src/repair-layers.ts +7 -520
  102. package/test-vectors/catalog/README.md +39 -67
  103. package/test-vectors/catalog/fec-01-runtime/README.md +15 -0
  104. package/test-vectors/catalog/{negative/fec-block-profile-retired.json → fec-01-runtime/fec-iso-ssbg1.json} +1 -1
  105. package/test-vectors/catalog/fec-01-runtime/fec-layered-repair.json +106 -0
  106. package/test-vectors/catalog/{negative/fec-block-profile-unknown.json → fec-01-runtime/fec-multicast.json} +2 -1
  107. package/test-vectors/catalog/fec-01-runtime/multicast-native-ssm-no-network-source.json +44 -0
  108. package/test-vectors/catalog/negative/fec-block-profile-missing.json +24 -11
  109. package/test-vectors/catalog/negative/fec-iso-symbol-size-below-floor.json +13 -8
  110. package/test-vectors/catalog/negative/fec-reed-solomon-iso-symbol-size-13.json +13 -8
  111. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +20 -10
  112. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +20 -10
  113. package/test-vectors/catalog/negative/fec-repair-overlay-source-symbols-zero.json +25 -17
  114. package/test-vectors/catalog/negative/fec-repair-symbols-zero.json +13 -8
  115. package/test-vectors/catalog/negative/fec-source-symbols-zero.json +13 -8
  116. package/test-vectors/catalog/negative/fec-symbol-size-zero.json +13 -8
  117. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +18 -9
  118. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +13 -8
  119. package/test-vectors/catalog/positive/fec-algorithm-unimplemented.json +14 -9
  120. package/test-vectors/catalog/positive/fec-iso-ssbg1.json +25 -12
  121. package/test-vectors/catalog/positive/fec-layered-repair.json +79 -35
  122. package/test-vectors/catalog/positive/fec-multicast.json +26 -13
  123. package/test-vectors/catalog/positive/fec-reed-solomon-layered.json +29 -18
  124. package/test-vectors/catalog/positive/multicast-draft01-4-1-example.json +28 -20
  125. package/test-vectors/catalog/positive/multicast-draft01-7-2-6-example.json +14 -10
  126. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +22 -12
  127. package/test-vectors/catalog/section-codes.json +13 -22
  128. package/test-vectors/catalog-timing.json +2 -2
  129. package/test-vectors/catalog-validation.json +30 -243
  130. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +0 -28
  131. package/test-vectors/catalog/negative/fec-reed-solomon-k-plus-p-over-255.json +0 -39
  132. package/test-vectors/catalog/negative/fec-reed-solomon-overlay-over-255.json +0 -40
  133. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +0 -32
  134. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +0 -34
  135. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +0 -24
  136. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +0 -33
@@ -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 at = `.keys[${index}]`;
1025
+ const at2 = `.keys[${index}]`;
1099
1026
  if (!isObject(key) || Object.keys(key).some((member) => !KEY_MEMBERS.has(member))) {
1100
- return issue(at, "a key is exactly {kid, publicKey, firstEpoch}");
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(`${at}.kid`, "kid must be an unsigned 32-bit integer");
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(`${at}.publicKey`, "publicKey must be a 32-octet Ed25519 key in canonical unpadded base64url");
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(`${at}.firstEpoch`, "firstEpoch must be an unsigned 32-bit integer");
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(`${at}.kid`, `kid ${key["kid"]} is repeated; the catalog holds no key set`);
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(`${at}.firstEpoch`, `firstEpoch ${key["firstEpoch"]} is repeated; the catalog holds no key set`);
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, at) {
1050
+ function endpointAuthIssue(endpoint, mode, at2) {
1124
1051
  const auth = endpoint["auth"];
1125
1052
  if (!isObject(auth))
1126
- return { path: `${at}.auth`, message: "auth must be an object" };
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: `${at}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
1056
+ return { path: `${at2}.auth`, message: `auth has unknown member(s) ${unknown.join(", ")}` };
1130
1057
  if (!isIntegerIn(auth["manifestPort"], 1, 65535)) {
1131
- return { path: `${at}.auth.manifestPort`, message: "manifestPort must be an integer from 1 to 65535" };
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: `${at}.auth.streamId`, message: "streamId must be an unsigned 32-bit integer" };
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: `${at}.auth.senderAddress`, message: "senderAddress must not appear on an SSM endpoint; S is sourceAddress" };
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: `${at}.auth.senderAddress`, message: "a protected ASM endpoint requires senderAddress" };
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: `${at}.auth.maxAuthDelayMs`, message: "maxAuthDelayMs must be an integer from 50 to 1000" };
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: `${at}.auth.manifestTimerMs`, message: "manifestTimerMs must be an integer of at least 1" };
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: `${at}.auth.manifestTrack`, message: "manifestTrack must be a non-empty string" };
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: `${at}.bandwidth`, message: "a protected endpoint requires bandwidth, which bounds the receiver's hold queues" };
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 at = `$.multicast.endpoints[${index}]`;
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", at, "endpoint must be an object");
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", `${at}.protocol`, `protocol must be "ssm" or "asm"; a bearer such as ${JSON.stringify(protocol)} is a networkSource entry (draft -01 4.2)`);
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", `${at}.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");
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", `${at}.sourceAddress`, "sourceAddress must be a non-empty string");
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", `${at}.protocol`, `protocol ${JSON.stringify(protocol)} disagrees with sourceAddress, which selects ${mode}`);
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", `${at}.port`, "port must be an integer from 1 to 65535");
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, at);
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", `${at}.tracks[${refIndex}].name`, `track ${JSON.stringify(name)} declares namespace ${JSON.stringify(namespace)}, not the catalog track's ${JSON.stringify(referenceNamespace)}`);
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 at = `$.multicast.endpoints[${index}]`;
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: `${at}.auth.manifestPort`, message: "manifestPort must differ from the endpoint's own port", endpointIndex: index });
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: `${at}.port`, message: `(S, G, port) is shared with endpoint ${other}; one endpoint object per multicast tuple`, endpointIndex: index });
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: `${at}.auth.manifestPort`, message: `manifestPort is a port or manifestPort of endpoint ${other} on the same (S, G)`, endpointIndex: index });
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 positiveInteger3 = (input) => Number.isSafeInteger(input) && input > 0;
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 && !positiveInteger3(groupDurationMs)) {
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 && !positiveInteger3(groupDurationTicks)) {
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 && !positiveInteger3(keyframeIntervalTicks)) {
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 (!positiveInteger3(timescale)) {
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/catalog-fec-source.js
7337
- var ALL_ROLES2 = Object.freeze(["video", "audio"]);
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 positiveInteger2(value, path) {
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 = positiveInteger2(value, path);
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: positiveInteger2(
8659
+ bufferWindowSegments: positiveInteger(
8609
8660
  policy["bufferWindowSegments"],
8610
8661
  "multicast.deliveryPolicy.bufferWindowSegments"
8611
8662
  ),
8612
- requestTimeoutMs: positiveInteger2(policy["requestTimeoutMs"], "multicast.deliveryPolicy.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: positiveInteger2(fec["K"], `${path}.fec.K`),
8691
+ K: positiveInteger(fec["K"], `${path}.fec.K`),
8641
8692
  P: nonNegativeInteger(fec["P"], `${path}.fec.P`),
8642
- T: positiveInteger2(fec["T"], `${path}.fec.T`),
8643
- interleaveDepthMs: positiveInteger2(fec["interleaveDepthMs"], `${path}.fec.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 = positiveInteger2(mmtp["mediaUnitsPerObject"], `${path}.mmtp.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 = positiveInteger2(mmtp["mpuSequencesPerObject"], `${path}.mmtp.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: positiveInteger2(mmtp["timescale"], `${path}.mmtp.timescale`),
8718
- mediaUnitDurationTicks: positiveInteger2(
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
- for (const ot of override.tracks) {
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 role = ot.role ?? (ot.name.endsWith("/repair") ? void 0 : "video");
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
- name: ot.name,
9320
- packaging,
9321
- codec
9322
- };
9323
- if (mmtpMode !== void 0) entry.mmtpMode = mmtpMode;
9324
- if (ot.timescale !== void 0) entry.timescale = ot.timescale;
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 isPositiveSafeInteger2(value) {
12785
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
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(geometry, mediaTimescale) {
12885
- if (geometry.trackKind !== "video" || geometry.mode !== "classifying") return null;
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 } = geometry.keyframeInterval;
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(geometry) {
12923
- this.#geometry = geometry;
12992
+ constructor(geometry2) {
12993
+ this.#geometry = geometry2;
12924
12994
  this.#mode = "classifying";
12925
12995
  this.#reason = null;
12926
- this.reset(geometry);
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(geometry = this.#geometry) {
12947
- this.#geometry = 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 (geometry.trackKind === "video" && geometry.mode === "all-protected") {
13023
+ if (geometry2.trackKind === "video" && geometry2.mode === "all-protected") {
12954
13024
  this.#mode = "all-protected";
12955
- this.#reason = geometry.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 geometry = this.#geometry;
13072
- if (geometry.mode !== "classifying") return null;
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 >= geometry.gopFrames) {
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 >= geometry.interleaveUnits) {
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 at = starts[lo];
13139
- if (at !== void 0 && compareDataUnitStarts(at, start) === 0) {
13140
- if (at.sbn !== sbn) return true;
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 (at !== void 0 && at.sbn < sbn) return true;
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 geometry = this.#geometry;
13196
- if (geometry.trackKind !== "video" || geometry.mode !== "classifying") return "unknown";
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 = geometry.interleaveUnits - 1;
13199
- if (block.idxMax - span >= 1 && block.idxMin + span <= geometry.gopFrames - 1) {
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 = (geometry, packetId) => {
14667
+ const consider = (geometry2, packetId) => {
14598
14668
  if (!this.#hasCatalogTimingForPacket(packetId)) return;
14599
- if (geometry?.trackKind !== "video" || geometry.mode !== "classifying") return;
14600
- const { ticks, timescale } = geometry.keyframeInterval;
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, geometry] of this.#streamGeometries) {
14605
- consider(geometry, this.#parseStreamKey(streamKey).packetId);
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 geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName);
14854
- if (hasCatalogTiming && JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
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(geometry) {
19214
- assertPositiveMs("blockSpanMs", geometry.blockSpanMs);
19215
- assertPositiveMs("blockLifetimeMs", geometry.blockLifetimeMs);
19216
- const untilTerminalMs = geometry.blockLifetimeMs;
19217
- const terminalRecordMs = geometry.blockLifetimeMs;
19218
- return geometry.blockSpanMs + untilTerminalMs + terminalRecordMs;
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 at = a.authoredTiming;
19512
+ const at2 = a.authoredTiming;
19443
19513
  const bt = b.authoredTiming;
19444
- if (at !== void 0 && bt !== void 0) {
19445
- const left = at.presentationTime * BigInt(bt.mediaTimescale);
19446
- const right = bt.presentationTime * BigInt(at.mediaTimescale);
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 delivered for a packetId with no `onTrackInit` handler
19890
- * since construction, whether the deprecated aggregate `onInitSegment`
19891
- * callback then received them or nothing did. A supported deployment reads
19892
- * 0 here; a non-zero value is the evidence that blocks removing the
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
- this.#onInitSegment?.(data);
21544
+ console.log(`[MmtpRouter] Init rejected for ${track}: no onTrackInit handler`);
21545
+ return;
21489
21546
  }
21490
- console.log(
21491
- `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
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 packetListener = subscriptionDeliversPacketBatches(subscription) ? {
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
- /** Feed a raw packet from an external source (e.g., AMT tunnel or ASM subscription). */
23285
- feedPacket(data, context) {
23286
- if (this.#disposed) return;
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 intIn(value, min, max) {
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" || !intIn(ns.frequency, 1, Number.MAX_SAFE_INTEGER)) continue;
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
- ...intIn(ns.plpId, 0, 63) ? { plpId: ns.plpId } : {},
26456
- ...intIn(ns.serviceId, 0, 65535) ? { serviceId: ns.serviceId } : {},
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
- const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
27138
- if (typeof repairTrack === "string") add2(track.name, repairTrack);
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 geometry = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
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
- ...geometry === void 0 ? {} : { geometry }
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
- const onPacket = (packet) => {
27935
- if (!released) this.#router.feedPacket(packet, "multicast", ingress);
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), { gateway: provider });
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 availability is per track (draft-ramadan-moq-fec §5.1). A media track
28256
- * whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is
28257
- * implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span
28258
- * it cannot use, is consumed without FEC: it gets no decoder, and none of
28259
- * its repair tracks (layer 0 by `fec.repairTrack`, further layers and the
28260
- * overlay by `depends`) is given an owner, so the router drops their packets
28261
- * before any decoder. Those legs keep the `repair` role and their owner's
28262
- * switching set. Neither the call nor any other track's FEC fails on that
28263
- * account; `trackFecUnavailable(name)` reports the reason. FEC a previous
28264
- * call configured on a packetId this call does not is released
28265
- * (FECManager.releasePacket); frames its release gate held are delivered in
28266
- * order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.
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 declaredRepairNames = /* @__PURE__ */ new Set();
28277
- for (const names of repairNamesBySource.values()) {
28278
- for (const name of names) declaredRepairNames.add(name);
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 (let i = 0; i < catalog.tracks.length; i++) {
28282
- const track = catalog.tracks[i];
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 unownedRepairNames = /* @__PURE__ */ new Set();
28297
- for (const sourceName of fecUnavailableByTrack.keys()) {
28298
- for (const repairName of repairNamesBySource.get(sourceName) ?? []) {
28299
- unownedRepairNames.add(repairName);
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.fec === void 0) continue;
28348
- const unavailable = fecUnavailableByTrack.has(track.name);
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
- if (track.fec !== void 0 && !fecUnavailableByTrack.has(track.name)) {
28427
- const fecCfg = track.fec;
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 repairNamesBySource.get(track.name) ?? []) {
28430
- if (unownedRepairNames.has(repairName)) continue;
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 `fec` block declares one (draft-ramadan-moq-fec §5.1): no
28473
- * implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.
28474
- * the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an
28475
- * invalid `iso-ssbg1-v1` span (`track_fec`). Absent/zero spans keep in-band
28476
- * decoding enabled. The same reason the hang
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
- function resolveDeltaFallback(base, delta) {
29507
- const repair = base.fec?.repairTrack;
29508
- if (!repair) throw new Error(`[delta-fallback] base track "${base.name}" missing fec.repairTrack`);
29509
- const deltaFec = delta.fec;
29510
- if (!deltaFec?.repairTrack) throw new Error(`[delta-fallback] delta track "${delta.name}" missing fec.repairTrack`);
29511
- if (deltaFec.repairTrack !== repair)
29512
- throw new Error(
29513
- `[delta-fallback] inconsistent repair tracks: ${base.name} references "${repair}" but ${delta.name} references "${deltaFec.repairTrack}"`
29514
- );
29515
- if (!(typeof base.framerate === "number" && Number.isFinite(base.framerate) && base.framerate > 0))
29516
- throw new Error(`[delta-fallback] base track "${base.name}" missing positive framerate`);
29517
- if (!(isPositive(deltaFec.sourceSymbols) && isPositive(deltaFec.repairSymbols)))
29518
- throw new Error(`[delta-fallback] delta track "${delta.name}" missing positive fec.sourceSymbols/repairSymbols`);
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: base.framerate,
29521
- sourceSymbols: deltaFec.sourceSymbols,
29522
- repairSymbols: deltaFec.repairSymbols,
29523
- deltaFailWindowBlocks: base.fec?.deltaFailWindowBlocks ?? deltaFec.deltaFailWindowBlocks,
29524
- deltaProbeIntervalMs: base.fec?.deltaProbeIntervalMs ?? deltaFec.deltaProbeIntervalMs,
29525
- interleaveDepthMs: base.fec?.interleaveDepthMs ?? deltaFec.interleaveDepthMs
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 ${base.name}/${delta.name}`);
29528
- return { repairTrackName: repair, config };
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 catalog block lacks or misstates a field the session
30619
- * needs: the block profile, K, P_0 (`repairSymbols`), T, `interleaveDepthMs` or
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 block is required");
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.algorithm !== void 0 && fec.algorithm !== "raptorq" && fec.algorithm !== "reed-solomon") {
30629
- throw new Error(`MmtpFecSession: fec.algorithm ${JSON.stringify(fec.algorithm)} is not implemented`);
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.algorithm === "reed-solomon" ? "reed-solomon" : "raptorq";
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 at = (delay, delayBound) => {
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 = at(delayMs, "presentation");
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 = at(budgetMs, "keyframe");
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 at = mc.earliestGatedFrameAtMs?.(packetId, catalogTrackName);
31343
- return at !== void 0 && Number.isFinite(at) ? at : void 0;
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,