@blockcast/mmt-container 0.3.0 → 0.4.0-main.d6f2737828fc

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 (588) hide show
  1. package/README.md +22 -8
  2. package/dist/browser-runtime.js +1185 -1049
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +24 -8
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +21 -8
  7. package/dist/carp-catalog.js.map +2 -2
  8. package/dist/catalog-fec-params.d.ts +88 -28
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +59 -17
  11. package/dist/catalog-fec-params.js.map +3 -3
  12. package/dist/catalog-network-source.js +0 -1
  13. package/dist/catalog-network-source.js.map +2 -2
  14. package/dist/cmaf-block-assembler.d.ts +0 -2
  15. package/dist/cmaf-block-assembler.d.ts.map +1 -1
  16. package/dist/cmaf-block-assembler.js +0 -1
  17. package/dist/cmaf-block-assembler.js.map +2 -2
  18. package/dist/delta-fallback-config.d.ts +42 -0
  19. package/dist/delta-fallback-config.d.ts.map +1 -0
  20. package/dist/delta-fallback-config.js +59 -0
  21. package/dist/delta-fallback-config.js.map +7 -0
  22. package/dist/fec-manager.d.ts +35 -26
  23. package/dist/fec-manager.d.ts.map +1 -1
  24. package/dist/fec-manager.js +300 -273
  25. package/dist/fec-manager.js.map +2 -2
  26. package/dist/fec-state.d.ts +19 -31
  27. package/dist/fec-state.d.ts.map +1 -1
  28. package/dist/fec-state.js +3 -4
  29. package/dist/fec-state.js.map +2 -2
  30. package/dist/fec-worker-setup.d.ts +16 -6
  31. package/dist/fec-worker-setup.d.ts.map +1 -1
  32. package/dist/fec-worker-setup.js +72 -34
  33. package/dist/fec-worker-setup.js.map +2 -2
  34. package/dist/index.d.ts +4 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +12 -0
  37. package/dist/index.js.map +2 -2
  38. package/dist/iso-block-locator.js +0 -1
  39. package/dist/iso-block-locator.js.map +2 -2
  40. package/dist/media-router.d.ts +20 -8
  41. package/dist/media-router.d.ts.map +1 -1
  42. package/dist/media-router.js +46 -21
  43. package/dist/media-router.js.map +2 -2
  44. package/dist/mfu-reassembler.js +0 -1
  45. package/dist/mfu-reassembler.js.map +2 -2
  46. package/dist/mmtp-container-parser.d.ts +5 -0
  47. package/dist/mmtp-container-parser.d.ts.map +1 -1
  48. package/dist/mmtp-container-parser.js +22 -1
  49. package/dist/mmtp-container-parser.js.map +2 -2
  50. package/dist/mmtp-container-parser.no-modules.js +37 -9
  51. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  52. package/dist/mmtp-decode-pipeline.js +22 -1
  53. package/dist/mmtp-decode-pipeline.js.map +2 -2
  54. package/dist/mmtp-router.d.ts +36 -16
  55. package/dist/mmtp-router.d.ts.map +1 -1
  56. package/dist/mmtp-router.js +126 -213
  57. package/dist/mmtp-router.js.map +2 -2
  58. package/dist/multicast-input.js +0 -1
  59. package/dist/multicast-input.js.map +2 -2
  60. package/dist/multicast-manager.d.ts +33 -3
  61. package/dist/multicast-manager.d.ts.map +1 -1
  62. package/dist/multicast-manager.js +660 -529
  63. package/dist/multicast-manager.js.map +4 -4
  64. package/dist/multicast-transport.d.ts +1 -1
  65. package/dist/multicast-transport.d.ts.map +1 -1
  66. package/dist/multicast-transport.js +42 -14
  67. package/dist/multicast-transport.js.map +3 -3
  68. package/dist/multicast-unwrap.js +0 -1
  69. package/dist/multicast-unwrap.js.map +2 -2
  70. package/dist/no-modules.js +36 -8
  71. package/dist/no-modules.js.map +2 -2
  72. package/dist/repair-fetch.d.ts +30 -19
  73. package/dist/repair-fetch.d.ts.map +1 -1
  74. package/dist/repair-fetch.js +32 -18
  75. package/dist/repair-fetch.js.map +3 -3
  76. package/dist/repair-layers.d.ts +18 -7
  77. package/dist/repair-layers.d.ts.map +1 -1
  78. package/dist/repair-layers.js +11 -4
  79. package/dist/repair-layers.js.map +2 -2
  80. package/dist/signaling.js +0 -4
  81. package/dist/signaling.js.map +2 -2
  82. package/dist/timestamp-codec.js +0 -1
  83. package/dist/timestamp-codec.js.map +2 -2
  84. package/dist/track-session.d.ts +7 -0
  85. package/dist/track-session.d.ts.map +1 -1
  86. package/dist/track-session.js +15 -8
  87. package/dist/track-session.js.map +2 -2
  88. package/dist/wasm-mfu-reassembler.js +0 -1
  89. package/dist/wasm-mfu-reassembler.js.map +2 -2
  90. package/dist/wasm-mmtp-container-parser.js +22 -1
  91. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  92. package/package.json +8 -14
  93. package/schemas/catalog.schema.json +3 -3
  94. package/src/carp-catalog.ts +35 -13
  95. package/src/catalog-fec-params.ts +173 -64
  96. package/src/cmaf-block-assembler.ts +1 -4
  97. package/src/delta-fallback-config.ts +72 -0
  98. package/src/fec-manager.ts +413 -317
  99. package/src/fec-state.ts +20 -32
  100. package/src/fec-worker-setup.ts +105 -55
  101. package/src/index.ts +10 -0
  102. package/src/media-router.ts +59 -21
  103. package/src/mmtp-container-parser.ts +26 -0
  104. package/src/mmtp-router.ts +145 -303
  105. package/src/multicast-manager.ts +172 -60
  106. package/src/multicast-transport.ts +16 -9
  107. package/src/repair-fetch.ts +46 -27
  108. package/src/repair-layers.ts +28 -8
  109. package/src/track-session.ts +28 -9
  110. package/test-vectors/catalog/README.md +34 -17
  111. package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
  112. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +1 -1
  113. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +1 -1
  114. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +1 -1
  115. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +1 -1
  116. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +1 -1
  117. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +1 -1
  118. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +1 -1
  119. package/test-vectors/catalog/negative/multicast-network-source-empty.json +1 -1
  120. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +1 -1
  121. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +1 -1
  122. package/test-vectors/catalog/positive/fec-layered-repair.json +2 -2
  123. package/test-vectors/catalog/positive/fec-multicast.json +2 -1
  124. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
  125. package/test-vectors/catalog-timing.json +356 -0
  126. package/test-vectors/catalog-validation.json +40 -5
  127. package/node_modules/@blockcast/fec-worker/README.md +0 -223
  128. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  129. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +0 -38
  130. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +0 -1
  131. package/node_modules/@blockcast/fec-worker/dist/block-order.js +0 -78
  132. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +0 -1
  133. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +0 -93
  134. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +0 -1
  135. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +0 -346
  136. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +0 -1
  137. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +0 -380
  138. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +0 -1
  139. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +0 -11
  140. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +0 -1
  141. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +0 -14
  142. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +0 -1
  143. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +0 -859
  144. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +0 -7
  145. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +0 -10
  146. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +0 -1
  147. package/node_modules/@blockcast/fec-worker/dist/index.js +0 -8
  148. package/node_modules/@blockcast/fec-worker/dist/index.js.map +0 -1
  149. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +0 -20
  150. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +0 -1
  151. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +0 -370
  152. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +0 -7
  153. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +0 -104
  154. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +0 -1
  155. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +0 -181
  156. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +0 -1
  157. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +0 -194
  158. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +0 -1
  159. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +0 -62
  160. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +0 -1
  161. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +0 -9
  162. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +0 -1
  163. package/node_modules/@blockcast/fec-worker/dist/transfer.js +0 -13
  164. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +0 -1
  165. package/node_modules/@blockcast/fec-worker/package.json +0 -59
  166. package/node_modules/@blockcast/fec-worker/src/block-order.ts +0 -93
  167. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +0 -392
  168. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +0 -399
  169. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +0 -1502
  170. package/node_modules/@blockcast/fec-worker/src/index.ts +0 -35
  171. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +0 -589
  172. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +0 -222
  173. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +0 -209
  174. package/node_modules/@blockcast/fec-worker/src/transfer.ts +0 -12
  175. package/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  176. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  177. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  178. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  179. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  180. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  181. package/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  182. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  183. package/node_modules/@blockcast/mmt-base/README.md +0 -134
  184. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  185. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  186. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  187. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  188. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  189. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  190. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  191. package/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  192. package/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  193. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  194. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  195. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  196. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  197. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  198. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  199. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  200. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  201. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  202. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  203. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  204. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  205. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  206. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  207. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  208. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  209. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  210. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  211. package/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  212. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  213. package/node_modules/@blockcast/mmt-base/package.json +0 -49
  214. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  215. package/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  216. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  217. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  218. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  219. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  220. package/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  221. package/node_modules/@blockcast/mmt-fec/README.md +0 -271
  222. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  223. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +0 -82
  224. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +0 -1
  225. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +0 -296
  226. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +0 -1
  227. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +0 -106
  228. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +0 -1
  229. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +0 -288
  230. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +0 -1
  231. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +0 -46
  232. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +0 -1
  233. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +0 -96
  234. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +0 -1
  235. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +0 -33
  236. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +0 -1
  237. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +0 -57
  238. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +0 -1
  239. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +0 -205
  240. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +0 -1
  241. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +0 -240
  242. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +0 -1
  243. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +0 -28
  244. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +0 -1
  245. package/node_modules/@blockcast/mmt-fec/dist/index.js +0 -28
  246. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +0 -1
  247. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +0 -20
  248. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +0 -1
  249. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +0 -28
  250. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +0 -1
  251. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +0 -258
  252. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +0 -1
  253. package/node_modules/@blockcast/mmt-fec/dist/oti.js +0 -382
  254. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +0 -1
  255. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +0 -41
  256. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +0 -1
  257. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +0 -81
  258. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +0 -1
  259. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +0 -82
  260. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +0 -1
  261. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +0 -170
  262. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +0 -1
  263. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  264. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  265. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  266. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  267. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  268. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  269. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  270. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  271. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +0 -134
  272. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  273. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  274. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  275. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  276. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  277. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  278. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  279. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  280. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  281. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  282. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  283. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  284. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  285. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  286. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  287. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  288. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  289. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  290. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  291. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  292. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  293. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  294. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  295. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  296. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  297. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  298. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  299. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  300. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  301. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +0 -49
  302. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  309. package/node_modules/@blockcast/mmt-fec/package.json +0 -79
  310. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +0 -371
  311. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +0 -439
  312. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +0 -127
  313. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +0 -68
  314. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +0 -310
  315. package/node_modules/@blockcast/mmt-fec/src/index.ts +0 -94
  316. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +0 -34
  317. package/node_modules/@blockcast/mmt-fec/src/oti.ts +0 -463
  318. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +0 -101
  319. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +0 -195
  320. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  321. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +0 -28
  322. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +0 -1
  323. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +0 -128
  324. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +0 -1
  325. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +0 -4
  326. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +0 -1
  327. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +0 -4
  328. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +0 -1
  329. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +0 -37
  330. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +0 -1
  331. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +0 -179
  332. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +0 -1
  333. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +0 -99
  334. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +0 -1
  335. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +0 -492
  336. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +0 -1
  337. package/node_modules/@blockcast/mmt-manifest-verify/package.json +0 -45
  338. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +0 -171
  339. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +0 -3
  340. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +0 -230
  341. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +0 -669
  342. package/node_modules/@noble/hashes/LICENSE +0 -21
  343. package/node_modules/@noble/hashes/README.md +0 -521
  344. package/node_modules/@noble/hashes/_assert.d.ts +0 -17
  345. package/node_modules/@noble/hashes/_assert.d.ts.map +0 -1
  346. package/node_modules/@noble/hashes/_assert.js +0 -18
  347. package/node_modules/@noble/hashes/_assert.js.map +0 -1
  348. package/node_modules/@noble/hashes/_blake.d.ts +0 -14
  349. package/node_modules/@noble/hashes/_blake.d.ts.map +0 -1
  350. package/node_modules/@noble/hashes/_blake.js +0 -50
  351. package/node_modules/@noble/hashes/_blake.js.map +0 -1
  352. package/node_modules/@noble/hashes/_md.d.ts +0 -51
  353. package/node_modules/@noble/hashes/_md.d.ts.map +0 -1
  354. package/node_modules/@noble/hashes/_md.js +0 -162
  355. package/node_modules/@noble/hashes/_md.js.map +0 -1
  356. package/node_modules/@noble/hashes/_u64.d.ts +0 -55
  357. package/node_modules/@noble/hashes/_u64.d.ts.map +0 -1
  358. package/node_modules/@noble/hashes/_u64.js +0 -90
  359. package/node_modules/@noble/hashes/_u64.js.map +0 -1
  360. package/node_modules/@noble/hashes/argon2.d.ts +0 -32
  361. package/node_modules/@noble/hashes/argon2.d.ts.map +0 -1
  362. package/node_modules/@noble/hashes/argon2.js +0 -401
  363. package/node_modules/@noble/hashes/argon2.js.map +0 -1
  364. package/node_modules/@noble/hashes/blake1.d.ts +0 -106
  365. package/node_modules/@noble/hashes/blake1.d.ts.map +0 -1
  366. package/node_modules/@noble/hashes/blake1.js +0 -459
  367. package/node_modules/@noble/hashes/blake1.js.map +0 -1
  368. package/node_modules/@noble/hashes/blake2.d.ts +0 -116
  369. package/node_modules/@noble/hashes/blake2.d.ts.map +0 -1
  370. package/node_modules/@noble/hashes/blake2.js +0 -420
  371. package/node_modules/@noble/hashes/blake2.js.map +0 -1
  372. package/node_modules/@noble/hashes/blake2b.d.ts +0 -11
  373. package/node_modules/@noble/hashes/blake2b.d.ts.map +0 -1
  374. package/node_modules/@noble/hashes/blake2b.js +0 -14
  375. package/node_modules/@noble/hashes/blake2b.js.map +0 -1
  376. package/node_modules/@noble/hashes/blake2s.d.ts +0 -20
  377. package/node_modules/@noble/hashes/blake2s.d.ts.map +0 -1
  378. package/node_modules/@noble/hashes/blake2s.js +0 -24
  379. package/node_modules/@noble/hashes/blake2s.js.map +0 -1
  380. package/node_modules/@noble/hashes/blake3.d.ts +0 -54
  381. package/node_modules/@noble/hashes/blake3.d.ts.map +0 -1
  382. package/node_modules/@noble/hashes/blake3.js +0 -255
  383. package/node_modules/@noble/hashes/blake3.js.map +0 -1
  384. package/node_modules/@noble/hashes/crypto.d.ts +0 -2
  385. package/node_modules/@noble/hashes/crypto.d.ts.map +0 -1
  386. package/node_modules/@noble/hashes/crypto.js +0 -5
  387. package/node_modules/@noble/hashes/crypto.js.map +0 -1
  388. package/node_modules/@noble/hashes/cryptoNode.d.ts +0 -2
  389. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +0 -1
  390. package/node_modules/@noble/hashes/cryptoNode.js +0 -18
  391. package/node_modules/@noble/hashes/cryptoNode.js.map +0 -1
  392. package/node_modules/@noble/hashes/eskdf.d.ts +0 -47
  393. package/node_modules/@noble/hashes/eskdf.d.ts.map +0 -1
  394. package/node_modules/@noble/hashes/eskdf.js +0 -166
  395. package/node_modules/@noble/hashes/eskdf.js.map +0 -1
  396. package/node_modules/@noble/hashes/esm/_assert.d.ts +0 -17
  397. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +0 -1
  398. package/node_modules/@noble/hashes/esm/_assert.js +0 -15
  399. package/node_modules/@noble/hashes/esm/_assert.js.map +0 -1
  400. package/node_modules/@noble/hashes/esm/_blake.d.ts +0 -14
  401. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +0 -1
  402. package/node_modules/@noble/hashes/esm/_blake.js +0 -45
  403. package/node_modules/@noble/hashes/esm/_blake.js.map +0 -1
  404. package/node_modules/@noble/hashes/esm/_md.d.ts +0 -51
  405. package/node_modules/@noble/hashes/esm/_md.d.ts.map +0 -1
  406. package/node_modules/@noble/hashes/esm/_md.js +0 -155
  407. package/node_modules/@noble/hashes/esm/_md.js.map +0 -1
  408. package/node_modules/@noble/hashes/esm/_u64.d.ts +0 -55
  409. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +0 -1
  410. package/node_modules/@noble/hashes/esm/_u64.js +0 -67
  411. package/node_modules/@noble/hashes/esm/_u64.js.map +0 -1
  412. package/node_modules/@noble/hashes/esm/argon2.d.ts +0 -32
  413. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +0 -1
  414. package/node_modules/@noble/hashes/esm/argon2.js +0 -392
  415. package/node_modules/@noble/hashes/esm/argon2.js.map +0 -1
  416. package/node_modules/@noble/hashes/esm/blake1.d.ts +0 -106
  417. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +0 -1
  418. package/node_modules/@noble/hashes/esm/blake1.js +0 -452
  419. package/node_modules/@noble/hashes/esm/blake1.js.map +0 -1
  420. package/node_modules/@noble/hashes/esm/blake2.d.ts +0 -116
  421. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +0 -1
  422. package/node_modules/@noble/hashes/esm/blake2.js +0 -413
  423. package/node_modules/@noble/hashes/esm/blake2.js.map +0 -1
  424. package/node_modules/@noble/hashes/esm/blake2b.d.ts +0 -11
  425. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +0 -1
  426. package/node_modules/@noble/hashes/esm/blake2b.js +0 -11
  427. package/node_modules/@noble/hashes/esm/blake2b.js.map +0 -1
  428. package/node_modules/@noble/hashes/esm/blake2s.d.ts +0 -20
  429. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +0 -1
  430. package/node_modules/@noble/hashes/esm/blake2s.js +0 -21
  431. package/node_modules/@noble/hashes/esm/blake2s.js.map +0 -1
  432. package/node_modules/@noble/hashes/esm/blake3.d.ts +0 -54
  433. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +0 -1
  434. package/node_modules/@noble/hashes/esm/blake3.js +0 -251
  435. package/node_modules/@noble/hashes/esm/blake3.js.map +0 -1
  436. package/node_modules/@noble/hashes/esm/crypto.d.ts +0 -2
  437. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +0 -1
  438. package/node_modules/@noble/hashes/esm/crypto.js +0 -2
  439. package/node_modules/@noble/hashes/esm/crypto.js.map +0 -1
  440. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +0 -2
  441. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +0 -1
  442. package/node_modules/@noble/hashes/esm/cryptoNode.js +0 -15
  443. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +0 -1
  444. package/node_modules/@noble/hashes/esm/eskdf.d.ts +0 -47
  445. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +0 -1
  446. package/node_modules/@noble/hashes/esm/eskdf.js +0 -160
  447. package/node_modules/@noble/hashes/esm/eskdf.js.map +0 -1
  448. package/node_modules/@noble/hashes/esm/hkdf.d.ts +0 -36
  449. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +0 -1
  450. package/node_modules/@noble/hashes/esm/hkdf.js +0 -82
  451. package/node_modules/@noble/hashes/esm/hkdf.js.map +0 -1
  452. package/node_modules/@noble/hashes/esm/hmac.d.ts +0 -35
  453. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +0 -1
  454. package/node_modules/@noble/hashes/esm/hmac.js +0 -86
  455. package/node_modules/@noble/hashes/esm/hmac.js.map +0 -1
  456. package/node_modules/@noble/hashes/esm/index.d.ts +0 -2
  457. package/node_modules/@noble/hashes/esm/index.d.ts.map +0 -1
  458. package/node_modules/@noble/hashes/esm/index.js +0 -33
  459. package/node_modules/@noble/hashes/esm/index.js.map +0 -1
  460. package/node_modules/@noble/hashes/esm/legacy.d.ts +0 -71
  461. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +0 -1
  462. package/node_modules/@noble/hashes/esm/legacy.js +0 -281
  463. package/node_modules/@noble/hashes/esm/legacy.js.map +0 -1
  464. package/node_modules/@noble/hashes/esm/package.json +0 -10
  465. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +0 -23
  466. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +0 -1
  467. package/node_modules/@noble/hashes/esm/pbkdf2.js +0 -97
  468. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +0 -1
  469. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +0 -13
  470. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +0 -1
  471. package/node_modules/@noble/hashes/esm/ripemd160.js +0 -13
  472. package/node_modules/@noble/hashes/esm/ripemd160.js.map +0 -1
  473. package/node_modules/@noble/hashes/esm/scrypt.d.ts +0 -34
  474. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +0 -1
  475. package/node_modules/@noble/hashes/esm/scrypt.js +0 -228
  476. package/node_modules/@noble/hashes/esm/scrypt.js.map +0 -1
  477. package/node_modules/@noble/hashes/esm/sha1.d.ts +0 -11
  478. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +0 -1
  479. package/node_modules/@noble/hashes/esm/sha1.js +0 -11
  480. package/node_modules/@noble/hashes/esm/sha1.js.map +0 -1
  481. package/node_modules/@noble/hashes/esm/sha2.d.ts +0 -159
  482. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +0 -1
  483. package/node_modules/@noble/hashes/esm/sha2.js +0 -375
  484. package/node_modules/@noble/hashes/esm/sha2.js.map +0 -1
  485. package/node_modules/@noble/hashes/esm/sha256.d.ts +0 -20
  486. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +0 -1
  487. package/node_modules/@noble/hashes/esm/sha256.js +0 -20
  488. package/node_modules/@noble/hashes/esm/sha256.js.map +0 -1
  489. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +0 -142
  490. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +0 -1
  491. package/node_modules/@noble/hashes/esm/sha3-addons.js +0 -393
  492. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +0 -1
  493. package/node_modules/@noble/hashes/esm/sha3.d.ts +0 -53
  494. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +0 -1
  495. package/node_modules/@noble/hashes/esm/sha3.js +0 -234
  496. package/node_modules/@noble/hashes/esm/sha3.js.map +0 -1
  497. package/node_modules/@noble/hashes/esm/sha512.d.ts +0 -26
  498. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +0 -1
  499. package/node_modules/@noble/hashes/esm/sha512.js +0 -26
  500. package/node_modules/@noble/hashes/esm/sha512.js.map +0 -1
  501. package/node_modules/@noble/hashes/esm/utils.d.ts +0 -161
  502. package/node_modules/@noble/hashes/esm/utils.d.ts.map +0 -1
  503. package/node_modules/@noble/hashes/esm/utils.js +0 -281
  504. package/node_modules/@noble/hashes/esm/utils.js.map +0 -1
  505. package/node_modules/@noble/hashes/hkdf.d.ts +0 -36
  506. package/node_modules/@noble/hashes/hkdf.d.ts.map +0 -1
  507. package/node_modules/@noble/hashes/hkdf.js +0 -88
  508. package/node_modules/@noble/hashes/hkdf.js.map +0 -1
  509. package/node_modules/@noble/hashes/hmac.d.ts +0 -35
  510. package/node_modules/@noble/hashes/hmac.d.ts.map +0 -1
  511. package/node_modules/@noble/hashes/hmac.js +0 -91
  512. package/node_modules/@noble/hashes/hmac.js.map +0 -1
  513. package/node_modules/@noble/hashes/index.d.ts +0 -1
  514. package/node_modules/@noble/hashes/index.d.ts.map +0 -1
  515. package/node_modules/@noble/hashes/index.js +0 -33
  516. package/node_modules/@noble/hashes/index.js.map +0 -1
  517. package/node_modules/@noble/hashes/legacy.d.ts +0 -71
  518. package/node_modules/@noble/hashes/legacy.d.ts.map +0 -1
  519. package/node_modules/@noble/hashes/legacy.js +0 -287
  520. package/node_modules/@noble/hashes/legacy.js.map +0 -1
  521. package/node_modules/@noble/hashes/package.json +0 -266
  522. package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -23
  523. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +0 -1
  524. package/node_modules/@noble/hashes/pbkdf2.js +0 -101
  525. package/node_modules/@noble/hashes/pbkdf2.js.map +0 -1
  526. package/node_modules/@noble/hashes/ripemd160.d.ts +0 -13
  527. package/node_modules/@noble/hashes/ripemd160.d.ts.map +0 -1
  528. package/node_modules/@noble/hashes/ripemd160.js +0 -16
  529. package/node_modules/@noble/hashes/ripemd160.js.map +0 -1
  530. package/node_modules/@noble/hashes/scrypt.d.ts +0 -34
  531. package/node_modules/@noble/hashes/scrypt.d.ts.map +0 -1
  532. package/node_modules/@noble/hashes/scrypt.js +0 -232
  533. package/node_modules/@noble/hashes/scrypt.js.map +0 -1
  534. package/node_modules/@noble/hashes/sha1.d.ts +0 -11
  535. package/node_modules/@noble/hashes/sha1.d.ts.map +0 -1
  536. package/node_modules/@noble/hashes/sha1.js +0 -14
  537. package/node_modules/@noble/hashes/sha1.js.map +0 -1
  538. package/node_modules/@noble/hashes/sha2.d.ts +0 -159
  539. package/node_modules/@noble/hashes/sha2.d.ts.map +0 -1
  540. package/node_modules/@noble/hashes/sha2.js +0 -384
  541. package/node_modules/@noble/hashes/sha2.js.map +0 -1
  542. package/node_modules/@noble/hashes/sha256.d.ts +0 -20
  543. package/node_modules/@noble/hashes/sha256.d.ts.map +0 -1
  544. package/node_modules/@noble/hashes/sha256.js +0 -23
  545. package/node_modules/@noble/hashes/sha256.js.map +0 -1
  546. package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -142
  547. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +0 -1
  548. package/node_modules/@noble/hashes/sha3-addons.js +0 -402
  549. package/node_modules/@noble/hashes/sha3-addons.js.map +0 -1
  550. package/node_modules/@noble/hashes/sha3.d.ts +0 -53
  551. package/node_modules/@noble/hashes/sha3.d.ts.map +0 -1
  552. package/node_modules/@noble/hashes/sha3.js +0 -239
  553. package/node_modules/@noble/hashes/sha3.js.map +0 -1
  554. package/node_modules/@noble/hashes/sha512.d.ts +0 -26
  555. package/node_modules/@noble/hashes/sha512.d.ts.map +0 -1
  556. package/node_modules/@noble/hashes/sha512.js +0 -29
  557. package/node_modules/@noble/hashes/sha512.js.map +0 -1
  558. package/node_modules/@noble/hashes/src/_assert.ts +0 -22
  559. package/node_modules/@noble/hashes/src/_blake.ts +0 -50
  560. package/node_modules/@noble/hashes/src/_md.ts +0 -176
  561. package/node_modules/@noble/hashes/src/_u64.ts +0 -91
  562. package/node_modules/@noble/hashes/src/argon2.ts +0 -497
  563. package/node_modules/@noble/hashes/src/blake1.ts +0 -534
  564. package/node_modules/@noble/hashes/src/blake2.ts +0 -486
  565. package/node_modules/@noble/hashes/src/blake2b.ts +0 -10
  566. package/node_modules/@noble/hashes/src/blake2s.ts +0 -20
  567. package/node_modules/@noble/hashes/src/blake3.ts +0 -272
  568. package/node_modules/@noble/hashes/src/crypto.ts +0 -9
  569. package/node_modules/@noble/hashes/src/cryptoNode.ts +0 -15
  570. package/node_modules/@noble/hashes/src/eskdf.ts +0 -187
  571. package/node_modules/@noble/hashes/src/hkdf.ts +0 -88
  572. package/node_modules/@noble/hashes/src/hmac.ts +0 -94
  573. package/node_modules/@noble/hashes/src/index.ts +0 -31
  574. package/node_modules/@noble/hashes/src/legacy.ts +0 -293
  575. package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -122
  576. package/node_modules/@noble/hashes/src/ripemd160.ts +0 -12
  577. package/node_modules/@noble/hashes/src/scrypt.ts +0 -257
  578. package/node_modules/@noble/hashes/src/sha1.ts +0 -10
  579. package/node_modules/@noble/hashes/src/sha2.ts +0 -402
  580. package/node_modules/@noble/hashes/src/sha256.ts +0 -24
  581. package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -499
  582. package/node_modules/@noble/hashes/src/sha3.ts +0 -258
  583. package/node_modules/@noble/hashes/src/sha512.ts +0 -34
  584. package/node_modules/@noble/hashes/src/utils.ts +0 -395
  585. package/node_modules/@noble/hashes/utils.d.ts +0 -161
  586. package/node_modules/@noble/hashes/utils.d.ts.map +0 -1
  587. package/node_modules/@noble/hashes/utils.js +0 -313
  588. package/node_modules/@noble/hashes/utils.js.map +0 -1
@@ -87,6 +87,8 @@ var init_fec_worker_client = __esm({
87
87
  }
88
88
  /**
89
89
  * Configure the Worker with FEC parameters and WASM module.
90
+ * `interleaveMs: 0` is a valid no-span catalog value; in that mode the
91
+ * caller supplies a positive receiver-local `deliveryWindowMs`.
90
92
  * wasmModule is transferred (not neutered — WebAssembly.Module is shareable).
91
93
  * altaPublicKey is PKCS#8 SPKI DER bytes (self-describing — algorithm detected from OID).
92
94
  * Transferred (detached from sender).
@@ -139,8 +141,7 @@ var init_fec_worker_client = __esm({
139
141
  * Caller must not access data after this call — it is detached.
140
142
  *
141
143
  * Carries the wire-level Repair FEC Payload ID fields (ISO 23008-1 §C.5.3):
142
- * - ssStart: SS_ID of first source symbol in the block (= SBN * configured K
143
- * under `k-padded-v1`; any uint32, the block key, under `iso-ssbg1-v1`)
144
+ * - ssStart: SS_ID of first source symbol in the block (any uint32; the block key)
144
145
  * - ssbLength: authored source-symbol count (1..configured K)
145
146
  * - rsId: repair symbol index within the block
146
147
  */
@@ -190,7 +191,7 @@ var init_fec_worker_client = __esm({
190
191
  }
191
192
  /**
192
193
  * Trigger cleanup after a block's inactivity deadline. `currentSbn` is the
193
- * block key: the SBN under `k-padded-v1`, the SS_Start under `iso-ssbg1-v1`.
194
+ * block key, the block's SS_Start.
194
195
  *
195
196
  * While one queue-drain barrier is in flight, later SBNs are coalesced into
196
197
  * the next command without losing their individual acknowledgement identity.
@@ -902,8 +903,6 @@ function sourceFecPayloadIdOf(mmtpPacket, header) {
902
903
  ssId: (mmtpPacket[off] << 24 | mmtpPacket[off + 1] << 16 | mmtpPacket[off + 2] << 8 | mmtpPacket[off + 3]) >>> 0
903
904
  };
904
905
  }
905
- var MMTP_EXT_TYPE_ALTA_AUTH2 = MMTP_EXT_TYPE_ALTA_AUTH;
906
- var MMTP_EXT_TRAILER_HEADER_SIZE2 = MMTP_EXT_TRAILER_HEADER_SIZE;
907
906
  var extractAltaTrailer2 = extractAltaTrailer;
908
907
  var REPAIR_FEC_PID_SIZE = 13;
909
908
  function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
@@ -987,49 +986,21 @@ function serial32Precedes(a, b) {
987
986
  const distance = serial32Distance(a, b);
988
987
  return distance !== 0 && distance < SERIAL32_HALF;
989
988
  }
990
- function kStrideBlockStart(sbn, k) {
991
- assertUint32(sbn, "sbn");
992
- if (!Number.isInteger(k) || k < 1 || k >= SERIAL32_HALF) {
993
- throw new RangeError(`k must be a positive integer below 2^31 (got ${k})`);
994
- }
995
- const high = Math.floor(sbn / 65536);
996
- const low = sbn % 65536;
997
- return (high * k % SERIAL32_SPAN * 65536 + low * k) % SERIAL32_SPAN;
998
- }
999
- function kStrideBlockPrecedes(a, b, k) {
1000
- return serial32Precedes(kStrideBlockStart(a, k), kStrideBlockStart(b, k));
1001
- }
1002
- function kStrideBlocksBehind(a, b, k) {
1003
- const from = kStrideBlockStart(a, k);
1004
- const to = kStrideBlockStart(b, k);
1005
- if (!serial32Precedes(from, to))
1006
- return 0;
1007
- return Math.ceil(serial32Distance(from, to) / k);
1008
- }
1009
- function isKStrideBlock(sbn, k) {
1010
- return Number.isInteger(sbn) && sbn >= 0 && sbn < SERIAL32_SPAN && Number.isInteger(k) && k >= 1 && k < SERIAL32_HALF;
1011
- }
1012
- function kStrideNextBlock(sbn, k) {
1013
- assertUint32(sbn, "sbn");
1014
- kStrideBlockStart(sbn, k);
1015
- return (sbn + 1) * k >= SERIAL32_SPAN ? 0 : sbn + 1;
1016
- }
1017
989
 
1018
990
  // ../mmt-base/dist/fec-block-profile.js
1019
991
  var FEC_BLOCK_PROFILES = Object.freeze([
1020
- "k-padded-v1",
1021
992
  "iso-ssbg1-v1"
1022
993
  ]);
1023
994
  function isFecBlockProfile(value) {
1024
- return value === "k-padded-v1" || value === "iso-ssbg1-v1";
995
+ return value === "iso-ssbg1-v1";
1025
996
  }
1026
997
  function parseFecBlockProfile(value, where) {
1027
998
  if (isFecBlockProfile(value))
1028
999
  return value;
1029
1000
  if (value === void 0) {
1030
- throw new Error(`${where} is required: name the FEC block profile ("k-padded-v1" or "iso-ssbg1-v1")`);
1001
+ throw new Error(`${where} is required: name the FEC block profile ("iso-ssbg1-v1")`);
1031
1002
  }
1032
- throw new Error(`${where} must be "k-padded-v1" or "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
1003
+ throw new Error(`${where} must be "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
1033
1004
  }
1034
1005
  var SSBG1_LENGTH_FIELD_BYTES = 2;
1035
1006
  function ssbg1MaxPacketLength(symbolSize) {
@@ -6264,6 +6235,14 @@ var throwRepairContractError = (code, path, message) => {
6264
6235
  };
6265
6236
  function readFecBlockProfile(value, path, declaresFecInstance, fail) {
6266
6237
  if (value === void 0 && !declaresFecInstance) return void 0;
6238
+ if (isFecBlockProfile(value)) return value;
6239
+ if (typeof value === "string" && value.length > 0) {
6240
+ return fail(
6241
+ "track_fec_block_profile_unimplemented",
6242
+ path,
6243
+ `${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
6244
+ );
6245
+ }
6267
6246
  try {
6268
6247
  return parseFecBlockProfile(value, "blockProfile");
6269
6248
  } catch (error) {
@@ -6574,12 +6553,17 @@ function hasSwitchingSetRole(names) {
6574
6553
  return suffix === "base" || suffix === "delta";
6575
6554
  });
6576
6555
  }
6577
- function isFecCatalogConfig(obj) {
6556
+ function isFecCatalogGeometry(obj) {
6578
6557
  if (typeof obj !== "object" || obj === null) return false;
6579
6558
  const o = obj;
6580
6559
  const algorithm = o["algorithm"];
6581
6560
  const symbolSize = o["symbolSize"];
6582
- return typeof algorithm === "string" && (algorithm === "none" || algorithm === "raptorq" || algorithm === "reed-solomon") && 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 && (isFecBlockProfile(o["blockProfile"]) || algorithm === "none" && o["blockProfile"] === void 0);
6561
+ return typeof algorithm === "string" && (algorithm === "none" || algorithm === "raptorq" || algorithm === "reed-solomon") && 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;
6562
+ }
6563
+ function isFecCatalogConfig(obj) {
6564
+ if (!isFecCatalogGeometry(obj)) return false;
6565
+ const o = obj;
6566
+ return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
6583
6567
  }
6584
6568
  function isMulticastTrackObject(obj) {
6585
6569
  if (typeof obj !== "object" || obj === null) return false;
@@ -6718,7 +6702,7 @@ function validateCmsfMmtpCatalog(value) {
6718
6702
  const tracks = catalog["tracks"];
6719
6703
  const trackRecords = [];
6720
6704
  const positiveInteger2 = (input) => Number.isSafeInteger(input) && input > 0;
6721
- const positiveNumber = (input) => typeof input === "number" && Number.isFinite(input) && input > 0;
6705
+ const positiveNumber2 = (input) => typeof input === "number" && Number.isFinite(input) && input > 0;
6722
6706
  for (let index = 0; index < tracks.length; index++) {
6723
6707
  const raw = tracks[index];
6724
6708
  const path = `$.tracks[${index}]`;
@@ -6763,7 +6747,7 @@ function validateCmsfMmtpCatalog(value) {
6763
6747
  if (groupDurationTicks !== void 0 && !positiveInteger2(groupDurationTicks)) {
6764
6748
  catalogError("track_group_duration_ticks", `${path}.groupDurationTicks`, "must be a positive integer");
6765
6749
  }
6766
- if (keyframeIntervalMs !== void 0 && !positiveNumber(keyframeIntervalMs) || keyframeIntervalTicks !== void 0 && !positiveInteger2(keyframeIntervalTicks)) {
6750
+ if (keyframeIntervalMs !== void 0 && !positiveNumber2(keyframeIntervalMs) || keyframeIntervalTicks !== void 0 && !positiveInteger2(keyframeIntervalTicks)) {
6767
6751
  catalogError("track_keyframe_interval", path, "keyframe interval fields must be positive numbers");
6768
6752
  }
6769
6753
  if (packaging === "fec-repair") {
@@ -6811,8 +6795,8 @@ function validateCmsfMmtpCatalog(value) {
6811
6795
  "repair layers are self-describing fec-repair tracks (depends, repairLayer); fec.enhancementRepair is not supported"
6812
6796
  );
6813
6797
  }
6814
- if (config["interleaveDepthMs"] !== void 0 && !positiveInteger2(config["interleaveDepthMs"])) {
6815
- catalogError("track_fec", `${path}.fec.interleaveDepthMs`, "must be a positive integer");
6798
+ if (config["interleaveDepthMs"] !== void 0 && (!Number.isSafeInteger(config["interleaveDepthMs"]) || config["interleaveDepthMs"] < 0)) {
6799
+ catalogError("track_fec", `${path}.fec.interleaveDepthMs`, "must be a non-negative safe integer");
6816
6800
  }
6817
6801
  if (algorithm !== "none") {
6818
6802
  for (const field of ["sourceSymbols", "repairSymbols", "symbolSize"]) {
@@ -8689,7 +8673,6 @@ var CmafBlockAssembler = class {
8689
8673
  failedBlocks: 0
8690
8674
  };
8691
8675
  this.#config = {
8692
- k: config.k,
8693
8676
  timescale: config.timescale ?? 1e6,
8694
8677
  trackId: config.trackId ?? 1,
8695
8678
  trackType: config.trackType,
@@ -9518,49 +9501,6 @@ var TransportPool = class {
9518
9501
  };
9519
9502
 
9520
9503
  // ../fec/dist/block-processor.js
9521
- function hasUniformPaddingFrom(data, start) {
9522
- if (start >= data.length)
9523
- return false;
9524
- const pad = data[start];
9525
- if (pad !== 0 && pad !== 238)
9526
- return false;
9527
- for (let i = start + 1; i < data.length; i++) {
9528
- if (data[i] !== pad)
9529
- return false;
9530
- }
9531
- return true;
9532
- }
9533
- function maybeExtendAltaTrailer(symbolData, packetEnd) {
9534
- if (packetEnd + MMTP_EXT_TRAILER_HEADER_SIZE2 > symbolData.length)
9535
- return packetEnd;
9536
- const view2 = new DataView(symbolData.buffer, symbolData.byteOffset + packetEnd, symbolData.byteLength - packetEnd);
9537
- const extType = view2.getUint16(0, false);
9538
- if (extType !== MMTP_EXT_TYPE_ALTA_AUTH2)
9539
- return packetEnd;
9540
- const extLen = view2.getUint16(2, false);
9541
- const trailerEnd = packetEnd + MMTP_EXT_TRAILER_HEADER_SIZE2 + extLen;
9542
- return trailerEnd <= symbolData.length ? trailerEnd : packetEnd;
9543
- }
9544
- function trimDecodedSourceSymbol(symbolData) {
9545
- if (symbolData.length < MMTP_HEADER_SIZE2 + MPU_HEADER_SIZE2)
9546
- return null;
9547
- const headerLength = parseMMTPHeader(symbolData).headerLength;
9548
- if (symbolData.length < headerLength + MPU_HEADER_SIZE2)
9549
- return null;
9550
- const payload = symbolData.subarray(headerLength);
9551
- const mpu = parseMPUHeader(payload);
9552
- const trailingMpuHeaderSize = MPU_HEADER_SIZE2 - 2;
9553
- if (mpu.payloadLength < trailingMpuHeaderSize)
9554
- return null;
9555
- const declaredEnd = headerLength + 2 + mpu.payloadLength;
9556
- if (declaredEnd > symbolData.length)
9557
- return null;
9558
- const packetEnd = maybeExtendAltaTrailer(symbolData, declaredEnd);
9559
- if (packetEnd < symbolData.length && !hasUniformPaddingFrom(symbolData, packetEnd)) {
9560
- return null;
9561
- }
9562
- return symbolData.subarray(0, packetEnd);
9563
- }
9564
9504
  function emptyProcessResult() {
9565
9505
  return {
9566
9506
  symbols: [],
@@ -9624,17 +9564,6 @@ function parseProtectedPacket(packetData, hdr, index, bump) {
9624
9564
  rawSymbol: packetData
9625
9565
  };
9626
9566
  }
9627
- function parseKPaddedSymbol(symbolData, index, bump) {
9628
- const hdr = gateMpuHeader(symbolData, bump);
9629
- if (!hdr)
9630
- return null;
9631
- const packetData = trimDecodedSourceSymbol(symbolData);
9632
- if (!packetData) {
9633
- bump("bad_length");
9634
- return null;
9635
- }
9636
- return parseProtectedPacket(packetData, hdr, index, bump);
9637
- }
9638
9567
  function parseSsbg1Symbol(symbolData, index, bump) {
9639
9568
  const packetData = trimSsbg1SourceSymbol(symbolData);
9640
9569
  if (!packetData) {
@@ -9647,7 +9576,7 @@ function parseSsbg1Symbol(symbolData, index, bump) {
9647
9576
  return parseProtectedPacket(packetData, hdr, index, bump);
9648
9577
  }
9649
9578
  function processFecSymbol(symbolData, options) {
9650
- const blockProfile = parseFecBlockProfile(options?.blockProfile, "processFecSymbol options.blockProfile");
9579
+ parseFecBlockProfile(options?.blockProfile, "processFecSymbol options.blockProfile");
9651
9580
  const failReasons = {};
9652
9581
  let failedCount = 0;
9653
9582
  const bump = (reason) => {
@@ -9659,26 +9588,26 @@ function processFecSymbol(symbolData, options) {
9659
9588
  const index = options.index ?? 0;
9660
9589
  let parsed;
9661
9590
  try {
9662
- parsed = blockProfile === "iso-ssbg1-v1" ? parseSsbg1Symbol(symbolData, index, bump) : parseKPaddedSymbol(symbolData, index, bump);
9591
+ parsed = parseSsbg1Symbol(symbolData, index, bump);
9663
9592
  } catch {
9664
9593
  bump("exception");
9665
9594
  parsed = null;
9666
9595
  }
9667
9596
  return resultFromSymbols(parsed ? [parsed] : [], failedCount, failReasons, options.ownerPacketId);
9668
9597
  }
9669
- function requireSymbolSize(symbolSize, blockProfile) {
9598
+ function requireSymbolSize(symbolSize) {
9670
9599
  if (symbolSize === void 0) {
9671
9600
  throw new Error("processFecBlock options.symbolSize is required: name the catalog fec.symbolSize (T)");
9672
9601
  }
9673
9602
  if (typeof symbolSize !== "number" || !Number.isSafeInteger(symbolSize) || symbolSize <= 0) {
9674
9603
  throw new Error(`processFecBlock options.symbolSize must be a positive integer (got ${String(symbolSize)})`);
9675
9604
  }
9676
- if (blockProfile === "iso-ssbg1-v1" && symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {
9605
+ if (symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {
9677
9606
  throw new Error(`processFecBlock options.symbolSize must exceed the ${SSBG1_LENGTH_FIELD_BYTES}-byte ssbg_mode1 length field (got ${symbolSize})`);
9678
9607
  }
9679
9608
  return symbolSize;
9680
9609
  }
9681
- function requireSourceSymbolCount(k, blockLength, symbolSize, blockProfile) {
9610
+ function requireSourceSymbolCount(k, blockLength, symbolSize) {
9682
9611
  if (k === void 0) {
9683
9612
  if (blockLength % symbolSize !== 0) {
9684
9613
  throw new Error(`processFecBlock: ${blockLength} bytes is not a whole number of ${symbolSize}-byte symbols; pass options.k`);
@@ -9688,17 +9617,17 @@ function requireSourceSymbolCount(k, blockLength, symbolSize, blockProfile) {
9688
9617
  if (!Number.isSafeInteger(k) || k <= 0) {
9689
9618
  throw new Error(`processFecBlock options.k must be a positive integer (got ${k})`);
9690
9619
  }
9691
- if (blockProfile === "iso-ssbg1-v1" && blockLength !== k * symbolSize) {
9620
+ if (blockLength !== k * symbolSize) {
9692
9621
  throw new Error(`processFecBlock: an iso-ssbg1-v1 block of ${k} symbols of ${symbolSize} bytes is ${k * symbolSize} bytes (got ${blockLength})`);
9693
9622
  }
9694
9623
  return k;
9695
9624
  }
9696
9625
  function processFecBlock(blockData, options) {
9697
9626
  const blockProfile = parseFecBlockProfile(options?.blockProfile, "processFecBlock options.blockProfile");
9698
- const symbolSize = requireSymbolSize(options.symbolSize, blockProfile);
9627
+ const symbolSize = requireSymbolSize(options.symbolSize);
9699
9628
  if (!blockData || blockData.length === 0)
9700
9629
  return emptyProcessResult();
9701
- const k = requireSourceSymbolCount(options.k, blockData.length, symbolSize, blockProfile);
9630
+ const k = requireSourceSymbolCount(options.k, blockData.length, symbolSize);
9702
9631
  const failReasons = {};
9703
9632
  let failedCount = 0;
9704
9633
  const parsed = [];
@@ -9723,7 +9652,7 @@ function processFecBlock(blockData, options) {
9723
9652
  // ../fec/dist/oti.js
9724
9653
  function catalogFecInterleaveMs(fec) {
9725
9654
  const ms = fec?.interleaveDepthMs;
9726
- return typeof ms === "number" && ms > 0 && Number.isFinite(ms) ? ms : void 0;
9655
+ return typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0 ? ms : void 0;
9727
9656
  }
9728
9657
  function deriveFecInterleaveDepth(interleaveDepthMs, mediaUnitDurationMs) {
9729
9658
  if (!Number.isFinite(interleaveDepthMs) || interleaveDepthMs <= 0) {
@@ -9756,6 +9685,8 @@ function deriveFecRetentionBlocks(deliveryWindowMs, interleaveDepthMs) {
9756
9685
  throw new Error(`[FEC] retention of ${Math.ceil(deliveryWindowMs / interleaveDepthMs) + 1} blocks exceeds the 255 the decoder and the ISO/IEC 23008-1 AL-FEC signaling byte accept (deliveryWindowMs=${deliveryWindowMs}ms over interleaveDepthMs=${interleaveDepthMs}ms). Fix the catalog geometry: a span this short cannot carry a window this wide.`);
9757
9686
  }
9758
9687
  var MAX_FEC_RETENTION_BLOCKS = 255;
9688
+ var LOCAL_FEC_RETENTION_BLOCKS = 2;
9689
+ var LOCAL_FEC_TIMEOUT_MS = 500;
9759
9690
  function tryDeriveFecRetentionBlocks(deliveryWindowMs, interleaveDepthMs) {
9760
9691
  if (!Number.isFinite(interleaveDepthMs) || !(interleaveDepthMs > 0))
9761
9692
  return void 0;
@@ -9777,7 +9708,7 @@ var NO_FEC_INSTANCE = /* @__PURE__ */ Symbol("no FEC instance");
9777
9708
  var FecBlockMapper = class {
9778
9709
  /** Media units (GOPs/frames) per FEC block, recovered with positive round-half-up semantics. */
9779
9710
  d;
9780
- /** K: source symbols per block (`k-padded-v1`) or the maximum SSB_length (`iso-ssbg1-v1`). */
9711
+ /** K: the maximum SSB_length of a block (catalog `fec.sourceSymbols`). */
9781
9712
  k;
9782
9713
  /** Block time span (ms) — used as pacing threshold */
9783
9714
  interleaveDepthMs;
@@ -9803,21 +9734,14 @@ var FecBlockMapper = class {
9803
9734
  this.d = deriveFecInterleaveDepth(config.interleaveDepthMs, config.gopDurationMs);
9804
9735
  }
9805
9736
  /**
9806
- * The `k-padded-v1` locator: SS_ID → (SBN, ESI) with `SBN = floor(SS_ID / K)`
9807
- * and `ESI = SS_ID mod K`, for MMTP over multicast UDP where the Source FEC
9808
- * Payload ID carries SS_ID directly.
9737
+ * SS_ID → (SBN, ESI). Only {@link NullBlockMapper} answers: a stream with an
9738
+ * FEC instance is `iso-ssbg1-v1`, whose blocks are located by
9739
+ * `(SS_Start, SSB_length)` range membership and never from SS_ID alone.
9809
9740
  *
9810
- * @throws under `iso-ssbg1-v1`, where a block is located by
9811
- * `(SS_Start, SSB_length)` range membership and never from SS_ID alone
9741
+ * @throws for every stream with an FEC instance
9812
9742
  */
9813
- fromSsId(ssId) {
9814
- if (this.blockProfile !== "k-padded-v1") {
9815
- throw new Error(`fromSsId is the k-padded-v1 locator; a ${this.blockProfile ?? "non-FEC"} stream locates a block by (SS_Start, SSB_length) range membership, never from SS_ID alone`);
9816
- }
9817
- return {
9818
- sbn: Math.floor(ssId / this.k),
9819
- esi: ssId % this.k
9820
- };
9743
+ fromSsId(_ssId) {
9744
+ throw new Error(`fromSsId: a ${this.blockProfile ?? "non-FEC"} stream locates a block by (SS_Start, SSB_length) range membership, never from SS_ID alone`);
9821
9745
  }
9822
9746
  /**
9823
9747
  * Pacing threshold: one full block duration = interleaveDepthMs.
@@ -9901,20 +9825,22 @@ function computeFecLossWindowMs(fecInterleaveMs, fecK, jitterBufferMs) {
9901
9825
  });
9902
9826
  }
9903
9827
  function computeFecDeliveryWindowMs(config) {
9904
- const fecInterleaveMs = config.interleaveDepthMs;
9905
9828
  const fecK = config.sourceSymbols;
9906
- if (!Number.isFinite(fecInterleaveMs) || fecInterleaveMs <= 0) {
9829
+ if (!Number.isFinite(fecK) || fecK <= 0) {
9907
9830
  throw new Error(
9908
- `[TrackSession] fecInterleaveMs=${fecInterleaveMs}. Derive a positive FEC interleave window from the stream catalog.`
9831
+ `[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
9909
9832
  );
9910
9833
  }
9911
- if (!Number.isFinite(fecK) || fecK <= 0) {
9834
+ return fecSpanDeliveryWindowMs(config.interleaveDepthMs, config.jitterBufferMs);
9835
+ }
9836
+ function fecSpanDeliveryWindowMs(interleaveDepthMs, jitterBufferMs) {
9837
+ if (!Number.isFinite(interleaveDepthMs) || interleaveDepthMs <= 0) {
9912
9838
  throw new Error(
9913
- `[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
9839
+ `[TrackSession] fecInterleaveMs=${interleaveDepthMs}. Derive a positive FEC interleave window from the stream catalog.`
9914
9840
  );
9915
9841
  }
9916
- if (config.jitterBufferMs === void 0) return Math.ceil(fecInterleaveMs);
9917
- return fecBlockBudgetMs(fecInterleaveMs, config.jitterBufferMs);
9842
+ if (jitterBufferMs === void 0) return Math.ceil(interleaveDepthMs);
9843
+ return fecBlockBudgetMs(interleaveDepthMs, jitterBufferMs);
9918
9844
  }
9919
9845
  function computeFecDeliveryWindowMediaUnits(config, timestampTimescale) {
9920
9846
  if (!Number.isFinite(timestampTimescale) || timestampTimescale <= 0) {
@@ -10075,6 +10001,10 @@ var TrackSession = class {
10075
10001
  /** multicast/amt → failing: loss detected, start hysteresis timer (Claim 9f). */
10076
10002
  lossDetected() {
10077
10003
  if (this.#state !== "multicast" && this.#state !== "amt") return;
10004
+ if (this.config.fecInterleaveMs === 0 || this.config.fecInterleaveMs === void 0) {
10005
+ this.transportFailed();
10006
+ return;
10007
+ }
10078
10008
  this.#previousPrimary = this.#state;
10079
10009
  this.#transition("failing");
10080
10010
  const hysteresisMs = computeFecLossWindowMs(
@@ -10881,7 +10811,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
10881
10811
  };
10882
10812
 
10883
10813
  // src/fec-state.ts
10884
- var MIN_INTERLEAVE_DEPTH_MS = 33;
10814
+ var MIN_INTERLEAVE_DEPTH_MS = 1;
10885
10815
  var FEC_INACTIVE_REASONS = Object.freeze([
10886
10816
  "no-catalog-fec",
10887
10817
  "reliable-moq",
@@ -10908,7 +10838,7 @@ function isoBlockSpanUsable(fec) {
10908
10838
  const block = recordOf(fec);
10909
10839
  if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1") return true;
10910
10840
  const ms = block["interleaveDepthMs"];
10911
- return typeof ms === "number" && Number.isFinite(ms) && ms >= MIN_INTERLEAVE_DEPTH_MS;
10841
+ return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
10912
10842
  }
10913
10843
  function requireBoolean(value, field) {
10914
10844
  if (typeof value !== "boolean") {
@@ -11442,7 +11372,6 @@ var FECManager = class _FECManager {
11442
11372
  constructor() {
11443
11373
  // Per-track WASM RaptorQ decoders, keyed by physical stream identity.
11444
11374
  this.#fecDecoders = /* @__PURE__ */ new Map();
11445
- /** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */
11446
11375
  // Per-track Worker-backed FEC decoders, keyed by physical stream identity.
11447
11376
  // Worker-backed tracks use async decode — recovered frames arrive via onFrame callback,
11448
11377
  // not synchronous return from feedSourceSymbol/feedRepairSymbol.
@@ -11457,10 +11386,9 @@ var FECManager = class _FECManager {
11457
11386
  // source/repair symbols do not split across two RaptorQ states.
11458
11387
  this.#syncDecoderBlockOwners = /* @__PURE__ */ new Map();
11459
11388
  this.#decoderBlockOwners = /* @__PURE__ */ new WeakMap();
11460
- this.#fecK = 4;
11461
11389
  this.#fecSymbolSize = 0;
11462
- this.#fecTimeoutMs = 500;
11463
- // default 500ms; refined by setFramerate() when fps known
11390
+ this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
11391
+ // local default; refined by setFramerate() when fps known
11464
11392
  this.#trackConfigs = /* @__PURE__ */ new Map();
11465
11393
  this.#fecStats = newFecCounters();
11466
11394
  this.#fecTrackStats = /* @__PURE__ */ new Map();
@@ -11500,10 +11428,10 @@ var FECManager = class _FECManager {
11500
11428
  this.#blockSourceEsis = /* @__PURE__ */ new Map();
11501
11429
  // Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
11502
11430
  // first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
11503
- // media units, usually before it holds K symbols, and fec-raptorq
11504
- // force_complete() encodes it as a K-symbol RaptorQ block whose ESIs
11505
- // SSB_length..K-1 are zero padding that is never sent. Such a block is clean
11506
- // at SSB_length source arrivals and recoverable from SSB_length received
11431
+ // media units, usually before it holds K symbols; under iso-ssbg1-v1
11432
+ // (ssbg_mode1) that block is K' = SSB_length symbols, its repair ESIs start
11433
+ // at SSB_length, and K is only the maximum. Such a block is clean at
11434
+ // SSB_length source arrivals and recoverable from SSB_length received
11507
11435
  // symbols; waiting for K fails every one of them.
11508
11436
  this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
11509
11437
  this.#ssbLengthConflictCount = 0;
@@ -11620,14 +11548,16 @@ var FECManager = class _FECManager {
11620
11548
  this.#resolvedBlockClasses = /* @__PURE__ */ new Map();
11621
11549
  // Blocks already reported through onBlockRepaired (once per block).
11622
11550
  this.#repairedBlocks = /* @__PURE__ */ new Set();
11623
- // Blocks created without any symbol because every symbol vanished.
11551
+ // Blocks created before any symbol of theirs was tracked: the repair-less
11552
+ // spans placeIsoRepair materializes (onBlockVanished).
11624
11553
  this.#materializedBlocks = /* @__PURE__ */ new Set();
11625
11554
  // packetIds whose ordered-relay horizon became underivable and fell back to
11626
11555
  // the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once
11627
11556
  // per stream. Not only throws: the ordinary out-of-range case is detected by
11628
11557
  // arithmetic and never throws at all.
11629
11558
  this.#orderedHorizonWarnedFor = /* @__PURE__ */ new Set();
11630
- // Highest SBN seen per stream and when, for vanished-block detection.
11559
+ // Newest block start (SS_Start) seen per stream and when: the frontier for
11560
+ // source-discontinuity detection and isoSpanResolution.
11631
11561
  this.#highestSbn = /* @__PURE__ */ new Map();
11632
11562
  // iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a
11633
11563
  // block only once a repair symbol stated the block's (SS_Start, SSB_length).
@@ -11721,7 +11651,10 @@ var FECManager = class _FECManager {
11721
11651
  #releasedWorkerClients;
11722
11652
  #syncDecoderBlockOwners;
11723
11653
  #decoderBlockOwners;
11724
- #fecK;
11654
+ // The stream-global K a configure() call stated, reported as fecStats.k.
11655
+ // It never resolves a packetId: a packetId has only its own track config
11656
+ // (BLO-45953), and K does not feed the delivery-window math.
11657
+ #streamK;
11725
11658
  #fecSymbolSize;
11726
11659
  #fecTimeoutMs;
11727
11660
  #trackConfigs;
@@ -11883,7 +11816,8 @@ var FECManager = class _FECManager {
11883
11816
  this.#completedBlocks.add(key);
11884
11817
  if (clean) this.#cleanlyCompletedBlocks.add(key);
11885
11818
  this.#indexBlockKey(key, sbn, packetId);
11886
- const expiresAtMs = nowMs + this.terminalRetentionMsForPacket(packetId);
11819
+ const config = this.#trackConfig(packetId);
11820
+ const expiresAtMs = nowMs + config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
11887
11821
  this.#terminalExpiryMs.set(key, expiresAtMs);
11888
11822
  let queue = this.#terminalQueues.get(packetId);
11889
11823
  if (!queue) {
@@ -11919,8 +11853,9 @@ var FECManager = class _FECManager {
11919
11853
  * retention the Worker and the WASM decoder are configured with, because it
11920
11854
  * comes from the same function they do (`deriveFecRetentionBlocks`, over the
11921
11855
  * block's delivery window and its span). The decoder retires every block
11922
- * more than this many SBNs behind the newest one it saw (`cleanup_by_sbn`),
11923
- * so settling earlier than this would decide a block the decoder would still
11856
+ * more than this many blocks behind the newest SS_Start it saw, counted
11857
+ * from below by `isoBlocksBehindLowerBound` (`cleanup_by_ss_start`), so
11858
+ * settling earlier than this would decide a block the decoder would still
11924
11859
  * have recovered.
11925
11860
  *
11926
11861
  * This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA
@@ -11933,6 +11868,14 @@ var FECManager = class _FECManager {
11933
11868
  * derivation; do not re-derive a horizon at either call site.
11934
11869
  */
11935
11870
  #orderedHorizonBlocksFor(packetId, config) {
11871
+ if (config.interleaveDepthMs === 0) {
11872
+ if (Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
11873
+ return LOCAL_FEC_RETENTION_BLOCKS;
11874
+ }
11875
+ throw new Error(
11876
+ `[FECManager] packetId=${packetId}: local FEC cleanup requires a positive timeout (got ${config.timeoutMs})`
11877
+ );
11878
+ }
11936
11879
  if (!_FECManager.#hasOrderedGeometry(config)) {
11937
11880
  throw new Error(
11938
11881
  `[FECManager] packetId=${packetId}: ordered-relay settlement derives the decoder's interleave horizon from the catalog interleaveDepthMs (>= ${MIN_INTERLEAVE_DEPTH_MS}ms), the media-unit rate and the block delivery window; got interleaveDepthMs=${config.interleaveDepthMs} fps=${String(config.fps)} timeoutMs=${config.timeoutMs}.`
@@ -11946,7 +11889,7 @@ var FECManager = class _FECManager {
11946
11889
  * two can never disagree about which streams settle by stream order.
11947
11890
  */
11948
11891
  static #hasOrderedGeometry(config) {
11949
- return config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS && // `fps` is no longer an input to the horizon, but it still gates WHICH
11892
+ return _FECManager.#hasCatalogTiming(config) && // `fps` is no longer an input to the horizon, but it still gates WHICH
11950
11893
  // streams settle by stream order at all: a track with no usable
11951
11894
  // media-unit rate fell back to datagram semantics before this and must
11952
11895
  // keep doing so. Removing this widens ordered-relay settlement to streams
@@ -11986,6 +11929,9 @@ var FECManager = class _FECManager {
11986
11929
  * argument: narrower IS the failing direction.
11987
11930
  */
11988
11931
  #derivableOrderedHorizon(config, packetId) {
11932
+ if (config.interleaveDepthMs === 0) {
11933
+ return Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? LOCAL_FEC_RETENTION_BLOCKS : void 0;
11934
+ }
11989
11935
  if (!_FECManager.#hasOrderedGeometry(config)) return void 0;
11990
11936
  try {
11991
11937
  const blocks = tryDeriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs);
@@ -12018,19 +11964,20 @@ var FECManager = class _FECManager {
12018
11964
  /**
12019
11965
  * The stream's interleave horizon, or undefined when its geometry cannot
12020
11966
  * define one. Two different streams reach that case, with different causes:
12021
- * a packetId that was never configured, and so resolves to the manager
12022
- * defaults (explicitly configured tracks are refused an underivable geometry
11967
+ * a packetId that was never configured, which has no geometry at all
11968
+ * (explicitly configured tracks are refused an underivable geometry
12023
11969
  * at config time — #upsertTrackConfig, setMulticastSymbolDelivery); or a
12024
11970
  * configured track whose timeoutMs was later widened past the 255-block
12025
11971
  * ceiling without re-validating, which #derivableOrderedHorizon catches
12026
11972
  * rather than throwing from the packet-feed path. Either way the stream
12027
- * keeps datagram semantics instead of throwing. The manager defaults can
11973
+ * keeps datagram semantics instead of throwing. A track's configuration can
12028
11974
  * also change under a stream that already holds blocks, so a caller cannot
12029
11975
  * assume a horizon that was defined when a block was held is still defined
12030
11976
  * (see #advanceOrderedFrontier).
12031
11977
  */
12032
11978
  #orderedHorizonBlocks(packetId) {
12033
- return this.#derivableOrderedHorizon(this.#resolveTrackConfig(packetId), packetId);
11979
+ const config = this.#trackConfigs.get(packetId);
11980
+ return config === void 0 ? void 0 : this.#derivableOrderedHorizon(config, packetId);
12034
11981
  }
12035
11982
  /** True while a block of an ordered-relay stream can still receive symbols. */
12036
11983
  #withinOrderedHorizon(key) {
@@ -12064,7 +12011,7 @@ var FECManager = class _FECManager {
12064
12011
  */
12065
12012
  #advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName) {
12066
12013
  const previous = this.#orderedFrontiers.get(streamKey);
12067
- const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn, packetId) ? sbn : previous;
12014
+ const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn) ? sbn : previous;
12068
12015
  if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier);
12069
12016
  const pending = this.#orderedPendingBlocks.get(streamKey);
12070
12017
  if (!pending) return;
@@ -12099,7 +12046,7 @@ var FECManager = class _FECManager {
12099
12046
  if (this.#completedBlocks.has(key) || this.#pendingWorkerCleanups.has(key) || this.#repairRequestedBlocks.has(key)) return;
12100
12047
  const authoredLength = this.#blockAuthoredLengths.get(key);
12101
12048
  const received = (this.#blockSourceCounts.get(key) ?? 0) + (this.#blockRepairRsIds.get(key)?.size ?? 0);
12102
- if (this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
12049
+ if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
12103
12050
  this.#requestRepair(sbn, packetId, catalogTrackName, true);
12104
12051
  return;
12105
12052
  }
@@ -12255,7 +12202,7 @@ var FECManager = class _FECManager {
12255
12202
  }
12256
12203
  #requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
12257
12204
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
12258
- if (this.#completedBlocks.has(key) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
12205
+ if (this.#completedBlocks.has(key) || !this.#hasCatalogTimingForPacket(packetId) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
12259
12206
  const deadline = this.#repairDeadlines.get(key);
12260
12207
  if (deadline?.handle != null) clearTimeout(deadline.handle);
12261
12208
  this.#repairDeadlines.set(key, null);
@@ -12335,6 +12282,7 @@ var FECManager = class _FECManager {
12335
12282
  }
12336
12283
  /** A block's live class without creating classifier state for the stream. */
12337
12284
  #peekClass(sbn, packetId, catalogTrackName) {
12285
+ if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
12338
12286
  const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName));
12339
12287
  if (classifier) return classifier.classOf(sbn);
12340
12288
  return this.#geometryFor(packetId, catalogTrackName).trackKind === "audio" ? "audio" : "unknown";
@@ -12356,8 +12304,8 @@ var FECManager = class _FECManager {
12356
12304
  // Deliberately one block tighter than deriveFecRetentionBlocks over the same
12357
12305
  // inputs (that function's `+ 1`). Different question: the retention horizon
12358
12306
  // asks how long the decoder still ACCEPTS symbols for a block, this asks how
12359
- // far back a block that vanished entirely may still be repairable (see
12360
- // #noteSbnSeen).
12307
+ // many blocks a stream can live behind (or ahead of) its frontier before a
12308
+ // source symbol there is a discontinuity rather than loss (see #noteSbnSeen).
12361
12309
  // The extra block of retention is slack for a block still being fed; a block
12362
12310
  // nothing has been heard from does not get it. Not the units defect — but it
12363
12311
  // is the next place the two can drift, so change both or neither.
@@ -12366,27 +12314,28 @@ var FECManager = class _FECManager {
12366
12314
  return Math.ceil(config.timeoutMs / config.interleaveDepthMs);
12367
12315
  }
12368
12316
  /**
12369
- * Record that a symbol for block `sbn` arrived and materialise any blocks
12370
- * that vanished entirely between the stream's highest SBN and this one.
12317
+ * Record that a symbol for block `sbn` (the block's SS_Start) arrived and
12318
+ * advance the stream's frontier, the newest block start seen.
12371
12319
  *
12372
- * The newest ceil(timeoutMs / interleaveDepthMs) vanished blocks may still
12373
- * be repairable, so they get the normal repair and fail deadlines, armed
12374
- * from now. Older ones are already past their fail bound and fail now, as
12375
- * class `unknown`. A SOURCE symbol whose SBN goes back further than one
12376
- * live window, or jumps further than the stream could have produced since
12377
- * its last symbol, is a source discontinuity, not loss. A repair symbol can
12378
- * only advance the frontier: a D-28 fetch legitimately answers for an old
12379
- * block, so it never signals a discontinuity.
12320
+ * A SOURCE symbol whose block goes back further than one live window, or
12321
+ * jumps further than the stream could have produced since its last symbol,
12322
+ * is a source discontinuity, not loss. A repair symbol can only advance the
12323
+ * frontier: a D-28 fetch legitimately answers for an old block, so it never
12324
+ * signals a discontinuity.
12380
12325
  *
12381
12326
  * The producer closes a block at its Dth media-unit boundary OR when K
12382
- * source symbols filled it, so one interleave span can start several
12383
- * blocks (every large keyframe does). What bounds a forward jump is
12384
- * therefore packet evidence, not a block rate: with the MMTP
12385
- * packet_sequence_number (contiguous per packet_id, ISO/IEC 23008-1) of
12386
- * both source symbols, every block strictly between them holds at least one
12387
- * of the packets sent strictly between them, and K-closes can add at most
12388
- * one block per K of those packets on top of the D-closes elapsed time
12389
- * allows. Without it, only the D-close rate is known.
12327
+ * source symbols filled it, so one interleave span can start several blocks
12328
+ * (every large keyframe does). What bounds a forward jump is therefore
12329
+ * packet evidence, not a block rate: SS_ID advances by one per FEC source
12330
+ * packet (ISO/IEC 23008-1 C.5.2) and the MMTP packet_sequence_number by one
12331
+ * per packet of the packet_id, so with both source symbols'
12332
+ * packet_sequence_number a block start can move at most the packets sent
12333
+ * between them plus the anchor's own offset into its block (< K). Without
12334
+ * it, only the D-close rate is known.
12335
+ *
12336
+ * Vanished blocks are not enumerated here: their starts are unknown until a
12337
+ * repair states them. The SS_IDs between two stated blocks become one
12338
+ * repair-less span when the later block's repair arrives (placeIsoRepair).
12390
12339
  */
12391
12340
  #noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber) {
12392
12341
  const streamKey = this.#streamKey(packetId, catalogTrackName);
@@ -12395,7 +12344,7 @@ var FECManager = class _FECManager {
12395
12344
  const anchor = this.#sourceAnchors.get(streamKey);
12396
12345
  const psn = symbol === "source" && packetSequenceNumber !== void 0 && Number.isSafeInteger(packetSequenceNumber) && packetSequenceNumber >= 0 && packetSequenceNumber <= 4294967295 ? packetSequenceNumber : void 0;
12397
12346
  const anchorHere = () => {
12398
- if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn, packetId))) {
12347
+ if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn))) {
12399
12348
  this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now });
12400
12349
  }
12401
12350
  };
@@ -12411,9 +12360,9 @@ var FECManager = class _FECManager {
12411
12360
  anchorHere();
12412
12361
  return;
12413
12362
  }
12414
- const config = this.#resolveTrackConfig(packetId);
12363
+ const config = this.#trackConfig(packetId);
12415
12364
  const window2 = this.#liveWindowBlocks(config);
12416
- if (!this.#blockPrecedes(previous.sbn, sbn, packetId)) {
12365
+ if (!this.#blockPrecedes(previous.sbn, sbn)) {
12417
12366
  if (symbol === "source" && window2 !== null && this.#blocksBehind(sbn, previous.sbn, packetId) > window2) {
12418
12367
  discontinuity();
12419
12368
  return;
@@ -12424,15 +12373,9 @@ var FECManager = class _FECManager {
12424
12373
  const missing = this.#blocksBehind(previous.sbn, sbn, packetId) - 1;
12425
12374
  let plausible = true;
12426
12375
  if (window2 !== null) {
12427
- if (this.#isIso(packetId) && psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
12376
+ if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn) && psn !== anchor.psn) {
12428
12377
  const sent = psn - anchor.psn >>> 0;
12429
12378
  plausible = sent <= 2147483647 && serial32Distance(anchor.sbn, sbn) <= sent + config.k;
12430
- } else if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
12431
- const sent = psn - anchor.psn >>> 0;
12432
- const between = this.#blocksBehind(anchor.sbn, sbn, packetId) - 1;
12433
- const dCloses = window2 + Math.ceil((now - anchor.atMs) / config.interleaveDepthMs);
12434
- const kCloses = config.k > 0 ? Math.ceil(sent / config.k) : sent;
12435
- plausible = sent <= 2147483647 && between <= sent - 1 && between <= dCloses + kCloses;
12436
12379
  } else {
12437
12380
  plausible = missing <= window2 + Math.ceil((now - previous.atMs) / config.interleaveDepthMs);
12438
12381
  }
@@ -12444,48 +12387,31 @@ var FECManager = class _FECManager {
12444
12387
  }
12445
12388
  this.#highestSbn.set(streamKey, { sbn, atMs: now });
12446
12389
  anchorHere();
12447
- if (this.#isIso(packetId)) return;
12448
- if (missing <= 0 || window2 === null) return;
12449
- let vanished = previous.sbn;
12450
- for (let i = 0; i < missing; i++) {
12451
- vanished = this.#nextBlock(vanished, packetId);
12452
- this.#materializeBlock(
12453
- vanished,
12454
- packetId,
12455
- catalogTrackName,
12456
- this.#blocksBehind(vanished, sbn, packetId) <= window2
12457
- );
12458
- }
12459
12390
  }
12460
12391
  /*
12461
- * Block order for one stream (BLO-35597 iso:5). SS_ID is a 32-bit serial
12462
- * number (ISO/IEC 23008-1 C.5.2), so blocks are ordered by RFC 1982
12463
- * comparison of their first SS_ID (sbn * K mod 2^32). That is identical to
12464
- * comparing SBNs with < for every stream that has not wrapped SS_ID; only
12465
- * the order across the wrap changes. A block outside K-stride geometry has
12466
- * no SS_ID position, so it keeps the SBN order it always had.
12467
- */
12468
- #blockPrecedes(a, b, packetId) {
12469
- if (this.#isIso(packetId)) return serial32Precedes(a, b);
12470
- const k = this.kForPacket(packetId);
12471
- return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlockPrecedes(a, b, k) : a < b;
12472
- }
12473
- /** Blocks `a` lies behind `b` (0 when it does not precede it). */
12474
- #blocksBehind(a, b, packetId) {
12475
- if (this.#isIso(packetId)) return isoBlocksBehindLowerBound(a, b, this.kForPacket(packetId));
12476
- const k = this.kForPacket(packetId);
12477
- return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlocksBehind(a, b, k) : Math.max(0, b - a);
12392
+ * Block order for one stream (BLO-35597 iso:5). A block is keyed by its
12393
+ * SS_Start and SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2), so
12394
+ * blocks are ordered by RFC 1982 comparison of their starts. Only configured
12395
+ * streams hold blocks: every entry point refuses an unconfigured packetId.
12396
+ */
12397
+ #blockPrecedes(a, b) {
12398
+ return serial32Precedes(a, b);
12478
12399
  }
12479
- /** The block after `sbn`, stepping to block 0 where SS_ID wraps. */
12480
- #nextBlock(sbn, packetId) {
12481
- const k = this.kForPacket(packetId);
12482
- return isKStrideBlock(sbn, k) ? kStrideNextBlock(sbn, k) : sbn + 1;
12400
+ /**
12401
+ * Blocks `a` lies behind `b` (0 when it does not precede it). Blocks vary
12402
+ * in length (K' = SSB_length <= K), so only a lower bound is known: the one
12403
+ * metric the decoder and the worker retire by too.
12404
+ */
12405
+ #blocksBehind(a, b, packetId) {
12406
+ return isoBlocksBehindLowerBound(a, b, this.#trackConfig(packetId).k);
12483
12407
  }
12484
12408
  #materializeBlock(sbn, packetId, catalogTrackName, repairable) {
12485
12409
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
12486
12410
  if (this.#blockResolutionFor(key) !== "untracked") return;
12487
12411
  this.#materializedBlocks.add(key);
12488
- this.#classifierFor(packetId, catalogTrackName).touch(sbn);
12412
+ if (this.#hasCatalogTimingForPacket(packetId)) {
12413
+ this.#classifierFor(packetId, catalogTrackName).touch(sbn);
12414
+ }
12489
12415
  this.onBlockVanished?.(sbn, packetId, catalogTrackName);
12490
12416
  if (repairable) {
12491
12417
  this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
@@ -12524,21 +12450,39 @@ var FECManager = class _FECManager {
12524
12450
  });
12525
12451
  if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32);
12526
12452
  }
12527
- #defaultTrackConfig() {
12528
- return {
12529
- k: this.#fecK,
12530
- symbolSize: this.#fecSymbolSize,
12531
- interleaveDepthMs: this.#fecInterleaveDepthMs,
12532
- fps: this.#fecFps,
12533
- timeoutMs: this.#fecTimeoutMs
12534
- };
12453
+ /**
12454
+ * A configured packetId's track config. There is no manager-wide default
12455
+ * (BLO-45953): an unconfigured packetId does no FEC work, so every caller
12456
+ * here is reached only behind a configuration gate and this throw marks a
12457
+ * broken invariant, never a packet-path outcome.
12458
+ */
12459
+ #trackConfig(packetId) {
12460
+ const config = this.#trackConfigs.get(packetId);
12461
+ if (config === void 0) {
12462
+ throw new Error(
12463
+ `[FECManager] packetId=${packetId} was never configured; configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first`
12464
+ );
12465
+ }
12466
+ return config;
12535
12467
  }
12536
- #resolveTrackConfig(packetId) {
12537
- return this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig();
12468
+ /**
12469
+ * Whether this track has the catalog's elapsed-time FEC policy. A zero span
12470
+ * is a deliberate no-deadline signal; it still leaves the wire geometry
12471
+ * usable for in-band decode, but it cannot drive classification, repair
12472
+ * fetches or layer adaptation.
12473
+ */
12474
+ static #hasCatalogTiming(config) {
12475
+ return Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS;
12476
+ }
12477
+ #hasCatalogTimingForPacket(packetId) {
12478
+ const config = this.#trackConfigs.get(packetId);
12479
+ return config !== void 0 && _FECManager.#hasCatalogTiming(config);
12538
12480
  }
12539
12481
  #computeTimeout(config) {
12540
- if (config.timeoutMs && config.timeoutMs > 0) return config.timeoutMs;
12541
- if (config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
12482
+ if (typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
12483
+ return config.timeoutMs;
12484
+ }
12485
+ if (Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
12542
12486
  return Math.max(
12543
12487
  this.#fecTimeoutMs,
12544
12488
  computeFecDeliveryWindowMs({
@@ -12563,14 +12507,17 @@ var FECManager = class _FECManager {
12563
12507
  */
12564
12508
  #refreshDeliveryGapWindow() {
12565
12509
  let windowMs;
12566
- const consider = (geometry) => {
12510
+ const consider = (geometry, packetId) => {
12511
+ if (!this.#hasCatalogTimingForPacket(packetId)) return;
12567
12512
  if (geometry?.trackKind !== "video" || geometry.mode !== "classifying") return;
12568
12513
  const { ticks, timescale } = geometry.keyframeInterval;
12569
12514
  const ms = ticks / timescale * 1e3;
12570
12515
  if (Number.isFinite(ms) && ms > 0 && (windowMs === void 0 || ms > windowMs)) windowMs = ms;
12571
12516
  };
12572
- for (const geometry of this.#streamGeometries.values()) consider(geometry);
12573
- for (const config of this.#trackConfigs.values()) consider(config.geometry);
12517
+ for (const [streamKey, geometry] of this.#streamGeometries) {
12518
+ consider(geometry, this.#parseStreamKey(streamKey).packetId);
12519
+ }
12520
+ for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId);
12574
12521
  this.#deliveryGaps.setWindowMs(windowMs);
12575
12522
  }
12576
12523
  /**
@@ -12598,6 +12545,7 @@ var FECManager = class _FECManager {
12598
12545
  return Math.max(config.timeoutMs, Math.ceil(config.interleaveDepthMs + gapMs));
12599
12546
  }
12600
12547
  #repairDeadlineMs(config, waitMs = this.#multicastWaitMs(config)) {
12548
+ if (!_FECManager.#hasCatalogTiming(config)) return waitMs;
12601
12549
  const halfFailBoundMs = Math.ceil(waitMs / 2);
12602
12550
  if (config.k > 0 && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS && config.fps !== void 0 && Number.isFinite(config.fps) && config.fps > 0) {
12603
12551
  const mediaUnitMs = 1e3 / config.fps;
@@ -12612,10 +12560,12 @@ var FECManager = class _FECManager {
12612
12560
  }
12613
12561
  /**
12614
12562
  * One block lifetime: the inactivity fail bound plus the D-28 repair grace,
12615
- * at the widest wait the catalog allows (#maxMulticastWaitMs). Everything
12616
- * derived from it (terminal retention, the release gate's hold ceiling, the
12617
- * duplicate ledgers, the owed-cleanup bound) is compared across time, so it
12618
- * must not follow the live measurement.
12563
+ * at the widest wait the catalog allows (#maxMulticastWaitMs). For a track
12564
+ * without a catalog span this is receiver-local cleanup time only; no repair
12565
+ * grace is added. Everything derived from it (terminal retention, the
12566
+ * release gate's hold ceiling, the duplicate ledgers, the owed-cleanup
12567
+ * bound) is compared across time, so it must not follow the live
12568
+ * measurement.
12619
12569
  */
12620
12570
  #blockLifetimeMs(config) {
12621
12571
  const waitMs = this.#maxMulticastWaitMs(config);
@@ -12631,6 +12581,7 @@ var FECManager = class _FECManager {
12631
12581
  return Math.min(waitMs + this.#repairGraceMs(config, waitMs), this.#blockLifetimeMs(config));
12632
12582
  }
12633
12583
  #repairGraceMs(config, waitMs) {
12584
+ if (!_FECManager.#hasCatalogTiming(config)) return 0;
12634
12585
  return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs));
12635
12586
  }
12636
12587
  #armBlockTimeout(sbn, packetId, catalogTrackName, delayMs) {
@@ -12717,51 +12668,72 @@ var FECManager = class _FECManager {
12717
12668
  * stream; every other field stays per packetId.
12718
12669
  */
12719
12670
  #upsertTrackConfig(packetId, config, catalogTrackName) {
12720
- const previous = this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig();
12671
+ const previousTrack = this.#trackConfigs.get(packetId);
12672
+ const k = config.k ?? previousTrack?.k;
12673
+ if (k === void 0 || !Number.isSafeInteger(k) || k <= 0) {
12674
+ throw new Error(
12675
+ `[FECManager] packetId=${packetId}: fec.k=${String(k)} must be a positive integer; configure the track from its catalog`
12676
+ );
12677
+ }
12678
+ const previous = previousTrack ?? {};
12721
12679
  const streamScopedGeometry = catalogTrackName !== void 0 && config.geometry !== void 0 ? config.geometry : void 0;
12722
12680
  if (streamScopedGeometry !== void 0) {
12723
12681
  const { geometry: _streamGeometry, ...packetLevel } = config;
12724
12682
  config = packetLevel;
12725
12683
  }
12726
12684
  const timingChanged = config.k !== void 0 || config.interleaveDepthMs !== void 0 || config.fps !== void 0;
12685
+ if (config.timeoutMs !== void 0 && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
12686
+ throw new Error(
12687
+ `[FECManager] packetId=${packetId}: timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
12688
+ );
12689
+ }
12727
12690
  if (config.blockProfile !== void 0) {
12728
12691
  parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`);
12729
12692
  }
12730
12693
  const blockProfile = config.blockProfile ?? previous.blockProfile;
12731
12694
  if (blockProfile === void 0) {
12732
12695
  throw new Error(
12733
- `[FECManager] packetId=${packetId}: fec.blockProfile is required ("k-padded-v1" or "iso-ssbg1-v1"); configure the track from its catalog`
12696
+ `[FECManager] packetId=${packetId}: fec.blockProfile is required ("iso-ssbg1-v1"); configure the track from its catalog`
12734
12697
  );
12735
12698
  }
12736
- if (blockProfile === "iso-ssbg1-v1") {
12737
- const ownInterleaveDepthMs = config.interleaveDepthMs ?? this.#trackConfigs.get(packetId)?.interleaveDepthMs;
12738
- if (ownInterleaveDepthMs === void 0 || !Number.isFinite(ownInterleaveDepthMs) || ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
12739
- throw new Error(
12740
- `[FECManager] packetId=${packetId}: iso-ssbg1-v1 requires the catalog fec.interleaveDepthMs (>= ${MIN_INTERLEAVE_DEPTH_MS} ms; got ${String(ownInterleaveDepthMs)}): it bounds how long block location keeps stated blocks and held sources`
12741
- );
12742
- }
12699
+ const ownInterleaveDepthMs = config.interleaveDepthMs ?? previousTrack?.interleaveDepthMs ?? 0;
12700
+ if (!Number.isSafeInteger(ownInterleaveDepthMs) || ownInterleaveDepthMs !== 0 && ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
12701
+ throw new Error(
12702
+ `[FECManager] packetId=${packetId}: fec.interleaveDepthMs must be absent, 0, or a finite value >= ${MIN_INTERLEAVE_DEPTH_MS} ms (got ${String(ownInterleaveDepthMs)})`
12703
+ );
12743
12704
  }
12705
+ const hasCatalogTiming = _FECManager.#hasCatalogTiming({ interleaveDepthMs: ownInterleaveDepthMs });
12744
12706
  const next = {
12745
12707
  blockProfile,
12746
- k: config.k ?? previous.k,
12747
- symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize : previous.symbolSize,
12748
- interleaveDepthMs: config.interleaveDepthMs ?? previous.interleaveDepthMs,
12749
- fps: config.fps ?? previous.fps,
12750
- timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),
12708
+ k,
12709
+ symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize : previousTrack?.symbolSize ?? 0,
12710
+ interleaveDepthMs: ownInterleaveDepthMs,
12711
+ fps: config.fps ?? previous.fps ?? this.#fecFps,
12712
+ timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs ?? 0),
12751
12713
  ...(config.trackKind ?? previous.trackKind) === void 0 ? {} : { trackKind: config.trackKind ?? previous.trackKind },
12752
12714
  ...(config.codec ?? previous.codec) === void 0 ? {} : { codec: config.codec ?? previous.codec },
12753
12715
  ...(config.geometry ?? previous.geometry) === void 0 ? {} : { geometry: config.geometry ?? previous.geometry }
12754
12716
  };
12755
- next.timeoutMs = this.#computeTimeout(next);
12717
+ if (!hasCatalogTiming) {
12718
+ next.timeoutMs = config.timeoutMs !== void 0 && config.timeoutMs > 0 ? config.timeoutMs : previousTrack !== void 0 && previousTrack.interleaveDepthMs === 0 ? previousTrack.timeoutMs : LOCAL_FEC_TIMEOUT_MS;
12719
+ } else {
12720
+ next.timeoutMs = this.#computeTimeout(next);
12721
+ }
12756
12722
  if (this.#multicastSymbolDelivery === "ordered-relay") {
12757
12723
  this.#orderedHorizonBlocksFor(packetId, next);
12758
12724
  }
12759
- if (previous.blockProfile !== void 0 && (previous.blockProfile !== blockProfile || blockProfile === "iso-ssbg1-v1" && previous.k !== next.k)) {
12725
+ const timingPolicyChanged = _FECManager.#hasCatalogTiming({ interleaveDepthMs: previous.interleaveDepthMs ?? 0 }) !== hasCatalogTiming;
12726
+ if (timingPolicyChanged && !hasCatalogTiming) {
12727
+ for (const key of [...this.#repairDeadlines.keys()]) {
12728
+ if (this.#belongsToPacket(key, packetId)) this.#clearRepairDeadline(key);
12729
+ }
12730
+ }
12731
+ if (previous.blockProfile !== void 0 && previous.k !== next.k) {
12760
12732
  this.#dropIsoLocators(packetId);
12761
12733
  }
12762
12734
  this.#trackConfigs.set(packetId, next);
12763
12735
  this.#refreshDeliveryGapWindow();
12764
- let policyChanged = config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
12736
+ let policyChanged = timingPolicyChanged || config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
12765
12737
  if (streamScopedGeometry !== void 0) {
12766
12738
  const streamKey = this.#streamKey(packetId, catalogTrackName);
12767
12739
  if (JSON.stringify(streamScopedGeometry) !== JSON.stringify(this.#streamGeometries.get(streamKey))) {
@@ -12776,20 +12748,14 @@ var FECManager = class _FECManager {
12776
12748
  if (!this.#belongsToPacket(key, packetId)) continue;
12777
12749
  const stream = this.#parseStreamKey(key);
12778
12750
  const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName);
12779
- if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
12751
+ if (hasCatalogTiming && JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
12780
12752
  this.#classifiers.delete(key);
12753
+ if (!hasCatalogTiming) continue;
12781
12754
  this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire());
12782
12755
  }
12783
12756
  }
12784
12757
  return next;
12785
12758
  }
12786
- #normalizeSymbolData(packetId, data) {
12787
- const symbolSize = this.symbolSizeForPacket(packetId);
12788
- if (symbolSize <= 0 || data.byteLength === symbolSize) return data;
12789
- const normalized = new Uint8Array(symbolSize);
12790
- normalized.set(data.subarray(0, symbolSize));
12791
- return normalized;
12792
- }
12793
12759
  #blockKeysFor(sbn, packetId, catalogTrackName) {
12794
12760
  if (packetId != null && catalogTrackName !== void 0) {
12795
12761
  return [this.#blockKey(sbn, packetId, catalogTrackName)];
@@ -12850,7 +12816,7 @@ var FECManager = class _FECManager {
12850
12816
  ...stats,
12851
12817
  packetId,
12852
12818
  blocksComplete: trackTotal,
12853
- k: this.#resolveTrackConfig(packetId).k,
12819
+ k: this.#trackConfigs.get(packetId)?.k,
12854
12820
  recoveryRate: trackAffected > 0 ? stats.blocksRecovered / trackAffected * 100 : 100,
12855
12821
  packetLossRate: trackTotal > 0 ? trackAffected / trackTotal * 100 : 0
12856
12822
  };
@@ -12862,7 +12828,7 @@ var FECManager = class _FECManager {
12862
12828
  (block) => block.multicastSymbols > 0 && block.moqSymbols > 0
12863
12829
  ).length,
12864
12830
  recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),
12865
- k: this.#fecK,
12831
+ k: this.#streamK,
12866
12832
  // True recovery rate: of blocks that HAD loss, how many were recovered?
12867
12833
  recoveryRate: affected > 0 ? s.blocksRecovered / affected * 100 : 100,
12868
12834
  packetLossRate: total > 0 ? affected / total * 100 : 0,
@@ -13157,15 +13123,20 @@ var FECManager = class _FECManager {
13157
13123
  }
13158
13124
  }
13159
13125
  }
13160
- /** Record a transport symbol and reject later duplicates of the same packetId/SBN/ESI. */
13126
+ /**
13127
+ * Record a transport symbol and reject later duplicates of the same
13128
+ * packetId/SBN/ESI. `sbn` is the block's SS_Start. A packetId with no
13129
+ * configured block profile has no block geometry: its symbol is refused
13130
+ * (false) and no block state is created for it.
13131
+ */
13161
13132
  feedSymbolFromSource(packetId, sbn, esi, _data, source, catalogTrackName) {
13133
+ if (!this.#hasBlockProfile(packetId)) return false;
13162
13134
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
13163
13135
  const streamKey = this.#streamKey(packetId, catalogTrackName);
13164
13136
  if (source === "multicast" && this.#multicastSymbolDelivery === "ordered-relay") {
13165
13137
  this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName);
13166
13138
  }
13167
- const k = this.kForPacket(packetId);
13168
- const sourceSymbols = this.#isIso(packetId) ? this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? k : k;
13139
+ const sourceSymbols = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? this.#trackConfig(packetId).k;
13169
13140
  const isSourceSymbol = esi >= 0 && esi < sourceSymbols;
13170
13141
  const isStartupPartial = this.#startupPartialBlocks.has(blockKey);
13171
13142
  const isReadyMulticastRepair = this.#startupRequireRepairReady && source === "multicast" && !isSourceSymbol && this.#startupRepairReadyPackets.has(streamKey);
@@ -13283,14 +13254,14 @@ var FECManager = class _FECManager {
13283
13254
  for (const [key] of this.#activeBlocks) {
13284
13255
  const { packetId, sbn } = this.#parseBlockKey(key);
13285
13256
  const count = this.#blockSourceCounts.get(key) ?? 0;
13286
- const k = this.#blockAuthoredLengths.get(key) ?? this.kForPacket(packetId);
13257
+ const k = this.#blockAuthoredLengths.get(key) ?? this.#trackConfig(packetId).k;
13287
13258
  blocks.push({ sbn, packetId, state: "collecting", sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });
13288
13259
  }
13289
13260
  const completed = [...this.#completedBlocks].slice(-16);
13290
13261
  for (const key of completed) {
13291
13262
  const { packetId, sbn } = this.#parseBlockKey(key);
13292
13263
  if (!this.#activeBlocks.has(key)) {
13293
- const k = this.kForPacket(packetId);
13264
+ const k = this.#trackConfig(packetId).k;
13294
13265
  blocks.push({ sbn, packetId, state: "complete", sourceCount: k, k, progress: 1 });
13295
13266
  }
13296
13267
  }
@@ -13301,25 +13272,23 @@ var FECManager = class _FECManager {
13301
13272
  get wasmDecoder() {
13302
13273
  return this.#fecDecoders.values().next().value?.client ?? null;
13303
13274
  }
13304
- get k() {
13305
- return this.#fecK;
13306
- }
13275
+ /** A configured packetId's K; undefined for an unconfigured one (no FEC work). */
13307
13276
  kForPacket(packetId) {
13308
- return this.#resolveTrackConfig(packetId).k;
13277
+ return this.#trackConfigs.get(packetId)?.k;
13309
13278
  }
13310
13279
  /** FEC symbol size in bytes. */
13311
13280
  get symbolSize() {
13312
13281
  return this.#fecSymbolSize;
13313
13282
  }
13314
13283
  symbolSizeForPacket(packetId) {
13315
- return this.#resolveTrackConfig(packetId).symbolSize;
13284
+ return this.#trackConfigs.get(packetId)?.symbolSize;
13316
13285
  }
13317
13286
  /** FEC block span in milliseconds (0 = no interleaving). */
13318
13287
  get interleaveDepthMs() {
13319
13288
  return this.#fecInterleaveDepthMs;
13320
13289
  }
13321
13290
  interleaveDepthMsForPacket(packetId) {
13322
- return this.#resolveTrackConfig(packetId).interleaveDepthMs;
13291
+ return this.#trackConfigs.get(packetId)?.interleaveDepthMs;
13323
13292
  }
13324
13293
  /**
13325
13294
  * Record one multicast MMTP packet arrival (every packet of the leg, FEC
@@ -13337,7 +13306,8 @@ var FECManager = class _FECManager {
13337
13306
  * plus the larger of the catalog tolerance and the measured delivery gap.
13338
13307
  */
13339
13308
  timeoutMsForPacket(packetId) {
13340
- return this.#multicastWaitMs(this.#resolveTrackConfig(packetId));
13309
+ const config = this.#trackConfigs.get(packetId);
13310
+ return config === void 0 ? void 0 : this.#multicastWaitMs(config);
13341
13311
  }
13342
13312
  /**
13343
13313
  * Largest multicast delivery gap measured over the current window (ms), and
@@ -13393,27 +13363,31 @@ var FECManager = class _FECManager {
13393
13363
  }
13394
13364
  /** Resolved per-track D-28 repair request deadline in milliseconds. */
13395
13365
  repairDeadlineMsForPacket(packetId) {
13396
- return this.#repairDeadlineMs(this.#resolveTrackConfig(packetId));
13366
+ const config = this.#trackConfigs.get(packetId);
13367
+ return config === void 0 ? void 0 : this.#repairDeadlineMs(config);
13397
13368
  }
13398
13369
  /**
13399
13370
  * One block lifetime: the longest a block stays open after its last symbol
13400
13371
  * (inactivity fail bound plus D-28 repair grace).
13401
13372
  */
13402
13373
  blockLifetimeMsForPacket(packetId) {
13403
- return this.#blockLifetimeMs(this.#resolveTrackConfig(packetId));
13374
+ const config = this.#trackConfigs.get(packetId);
13375
+ return config === void 0 ? void 0 : this.#blockLifetimeMs(config);
13404
13376
  }
13405
13377
  /**
13406
13378
  * How long a terminal (clean, recovered or failed) block stays authoritative,
13407
13379
  * for late symbols and for the in-order release gate. It is also the gate's
13408
13380
  * hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as
13409
13381
  * untracked, which the gate counts as lost, so no frame may still be waiting
13410
- * on a block that can retire. The window is one interleave depth for a gap's
13411
- * last block to finish arriving, one block lifetime to fail it, and one more
13412
- * for an owed Worker cleanup that can keep a failing block pending. Every
13413
- * term comes from the track's catalog geometry; there is no separate cap.
13382
+ * on a block that can retire. With a positive catalog span the window is one
13383
+ * interleave depth for a gap's last block to finish arriving, one block
13384
+ * lifetime to fail it, and one more for an owed Worker cleanup. With an
13385
+ * absent/zero span it is a receiver-local cleanup bound only; it creates no
13386
+ * catalog FEC deadline.
13414
13387
  */
13415
13388
  terminalRetentionMsForPacket(packetId) {
13416
- const config = this.#resolveTrackConfig(packetId);
13389
+ const config = this.#trackConfigs.get(packetId);
13390
+ if (config === void 0) return void 0;
13417
13391
  return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
13418
13392
  }
13419
13393
  /** Remaining fetch budget for a specific repair-requested block. */
@@ -13424,7 +13398,8 @@ var FECManager = class _FECManager {
13424
13398
  if (deadlineMs !== void 0) {
13425
13399
  return Math.max(1, Math.ceil(deadlineMs - performance.now()));
13426
13400
  }
13427
- const trackConfig = this.#resolveTrackConfig(packetId);
13401
+ const trackConfig = this.#trackConfigs.get(packetId);
13402
+ if (trackConfig === void 0) return void 0;
13428
13403
  return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig));
13429
13404
  }
13430
13405
  /** True once configureTrack/setFecDecoder/setFecWorker configured this packetId. */
@@ -13435,14 +13410,19 @@ var FECManager = class _FECManager {
13435
13410
  blockProfileForPacket(packetId) {
13436
13411
  return this.#trackConfigs.get(packetId)?.blockProfile;
13437
13412
  }
13438
- #isIso(packetId) {
13439
- return this.#trackConfigs.get(packetId)?.blockProfile === "iso-ssbg1-v1";
13413
+ /**
13414
+ * Whether a packetId has a configured block geometry. iso-ssbg1-v1 is the
13415
+ * only profile, so every configured track has it; a packetId never
13416
+ * configured (or released) has none and gets no FEC block tracking.
13417
+ */
13418
+ #hasBlockProfile(packetId) {
13419
+ return this.#trackConfigs.get(packetId)?.blockProfile !== void 0;
13440
13420
  }
13441
13421
  #isoLocatorFor(packetId, catalogTrackName) {
13442
13422
  const streamKey = this.#streamKey(packetId, catalogTrackName);
13443
13423
  let locator = this.#isoLocators.get(streamKey);
13444
13424
  if (!locator) {
13445
- locator = new IsoBlockLocator(this.kForPacket(packetId), this.#isoRetentionBlocks(packetId));
13425
+ locator = new IsoBlockLocator(this.#trackConfig(packetId).k, this.#isoRetentionBlocks(packetId));
13446
13426
  this.#isoLocators.set(streamKey, locator);
13447
13427
  }
13448
13428
  return locator;
@@ -13473,12 +13453,14 @@ var FECManager = class _FECManager {
13473
13453
  */
13474
13454
  #isoRetentionBlocks(packetId) {
13475
13455
  const config = this.#trackConfigs.get(packetId);
13476
- if (config?.blockProfile !== "iso-ssbg1-v1" || !(config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS)) {
13456
+ if (config?.blockProfile === void 0) {
13477
13457
  throw new Error(
13478
- `[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block span to derive block-location retention from`
13458
+ `[FECManager] packetId=${packetId} has no configured iso-ssbg1-v1 block profile`
13479
13459
  );
13480
13460
  }
13481
- const blocks = Math.ceil(this.terminalRetentionMsForPacket(packetId) / config.interleaveDepthMs) + 1;
13461
+ if (!_FECManager.#hasCatalogTiming(config)) return LOCAL_FEC_RETENTION_BLOCKS;
13462
+ const terminalRetentionMs = config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
13463
+ const blocks = Math.ceil(terminalRetentionMs / config.interleaveDepthMs) + 1;
13482
13464
  if (!Number.isSafeInteger(blocks) || blocks <= 0) {
13483
13465
  throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`);
13484
13466
  }
@@ -13486,7 +13468,8 @@ var FECManager = class _FECManager {
13486
13468
  }
13487
13469
  /** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */
13488
13470
  isoSourceFits(packetId, packetLength) {
13489
- return ssbg1FitsSymbol(packetLength, this.symbolSizeForPacket(packetId));
13471
+ const symbolSize = this.#trackConfigs.get(packetId)?.symbolSize;
13472
+ return symbolSize !== void 0 && ssbg1FitsSymbol(packetLength, symbolSize);
13490
13473
  }
13491
13474
  /**
13492
13475
  * Locate an iso-ssbg1-v1 source by range membership in a block a repair
@@ -13547,11 +13530,11 @@ var FECManager = class _FECManager {
13547
13530
  * that could hold them is pending or not yet stated by a repair, else the
13548
13531
  * starts of the blocks that failed or vanished.
13549
13532
  *
13550
- * `untracked` reads exactly as in the k-padded-v1 release gate
13551
- * (MmtpRouter #createFecReleaseGate): a block holding either released
13552
- * frame's SS_ID (`fromSsId` or `toSsId`) delivered that frame, so untracked
13553
- * there means it resolved and retired; a block starting after the stream's frontier (`highestSbn`,
13554
- * an SS_Start here) may still be heard from; a block at or behind the
13533
+ * `untracked` reads as the release gate reads it (MmtpRouter
13534
+ * #createFecReleaseGate): a block holding either released frame's SS_ID
13535
+ * (`fromSsId` or `toSsId`) delivered that frame, so untracked there means it
13536
+ * resolved and retired; a block starting after the stream's frontier
13537
+ * (`highestSbn`, an SS_Start) may still be heard from; a block at or behind the
13555
13538
  * frontier that no symbol of this manager ever reached vanished, and its
13556
13539
  * media is lost.
13557
13540
  */
@@ -13619,6 +13602,7 @@ var FECManager = class _FECManager {
13619
13602
  }
13620
13603
  /** Current classifier mode of a stream. */
13621
13604
  classifierModeFor(packetId, catalogTrackName) {
13605
+ if (!this.#hasCatalogTimingForPacket(packetId)) return { mode: "all-protected", reason: null };
13622
13606
  const classifier = this.#classifierFor(packetId, catalogTrackName);
13623
13607
  return { mode: classifier.mode, reason: classifier.reason };
13624
13608
  }
@@ -13628,10 +13612,12 @@ var FECManager = class _FECManager {
13628
13612
  * it does not. A stream with no classifier yet has shown nothing against it.
13629
13613
  */
13630
13614
  mpuBoundsRapsFor(packetId, catalogTrackName) {
13615
+ if (!this.#hasCatalogTimingForPacket(packetId)) return false;
13631
13616
  return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true;
13632
13617
  }
13633
13618
  /** A block's class now, or frozen at its resolution. */
13634
13619
  blockClassFor(sbn, packetId, catalogTrackName) {
13620
+ if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
13635
13621
  const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName));
13636
13622
  if (frozen !== void 0) return frozen;
13637
13623
  return this.#peekClass(sbn, packetId, catalogTrackName);
@@ -13653,7 +13639,7 @@ var FECManager = class _FECManager {
13653
13639
  * invariant (consecutive MPUs exactly one keyframe interval apart).
13654
13640
  */
13655
13641
  noteReleasedRap(packetId, catalogTrackName, mpu, presentationTime, mediaTimescale) {
13656
- if (!this.#trackConfigs.has(packetId)) return;
13642
+ if (!this.#trackConfigs.has(packetId) || !this.#hasCatalogTimingForPacket(packetId)) return;
13657
13643
  this.#applyClassifierChanges(
13658
13644
  packetId,
13659
13645
  catalogTrackName,
@@ -13669,27 +13655,34 @@ var FECManager = class _FECManager {
13669
13655
  // -----------------------------------------------------------------------
13670
13656
  /** Configure FEC parameters. */
13671
13657
  configure(config) {
13672
- if (config.k != null) this.#fecK = config.k;
13658
+ if (config.interleaveDepthMs != null && (!Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < 0)) {
13659
+ throw new Error(
13660
+ `[FECManager] interleaveDepthMs=${config.interleaveDepthMs} must be a non-negative safe integer in milliseconds`
13661
+ );
13662
+ }
13663
+ if (config.timeoutMs != null && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
13664
+ throw new Error(
13665
+ `[FECManager] timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
13666
+ );
13667
+ }
13668
+ if (config.k != null && (!Number.isSafeInteger(config.k) || config.k <= 0)) {
13669
+ throw new Error(`[FECManager] k=${config.k} must be a positive integer`);
13670
+ }
13671
+ if (config.k != null) this.#streamK = config.k;
13673
13672
  if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize;
13674
13673
  if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs;
13675
13674
  if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {
13676
13675
  this.#fecFps = config.fps;
13677
13676
  }
13678
- if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
13679
- console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (\xA75.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`);
13680
- }
13681
13677
  if (config.timeoutMs != null && config.timeoutMs > 0) {
13682
13678
  this.#fecTimeoutMs = config.timeoutMs;
13683
- } else if (config.interleaveDepthMs != null && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
13684
- this.#fecTimeoutMs = computeFecDeliveryWindowMs({
13685
- interleaveDepthMs: config.interleaveDepthMs,
13686
- sourceSymbols: this.#fecK,
13687
- symbolSize: this.#fecSymbolSize,
13688
- mediaUnitsPerSecond: config.fps
13689
- });
13679
+ } else if (config.interleaveDepthMs === 0) {
13680
+ this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
13681
+ } else if (config.interleaveDepthMs != null && Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
13682
+ this.#fecTimeoutMs = fecSpanDeliveryWindowMs(config.interleaveDepthMs);
13690
13683
  }
13691
13684
  for (const [packetId, track] of this.#trackConfigs) {
13692
- if (track.timeoutMs < this.#fecTimeoutMs) {
13685
+ if (_FECManager.#hasCatalogTiming(track) && track.timeoutMs < this.#fecTimeoutMs) {
13693
13686
  this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs });
13694
13687
  }
13695
13688
  }
@@ -13748,7 +13741,7 @@ var FECManager = class _FECManager {
13748
13741
  );
13749
13742
  }
13750
13743
  if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName);
13751
- const workerK = this.#resolveTrackConfig(packetId).k;
13744
+ const workerK = this.#trackConfig(packetId).k;
13752
13745
  this.#workerClients.set(streamKey, client);
13753
13746
  this.#releasedWorkerClients.delete(client);
13754
13747
  client.onFrame((data, meta) => {
@@ -13928,7 +13921,7 @@ var FECManager = class _FECManager {
13928
13921
  return;
13929
13922
  }
13930
13923
  this.#settleOwedCleanup(key, "cleanup-ack-timeout");
13931
- }, Math.ceil(this.#liveBlockLifetimeMs(this.#resolveTrackConfig(packetId))))
13924
+ }, Math.ceil(this.#liveBlockLifetimeMs(this.#trackConfig(packetId))))
13932
13925
  };
13933
13926
  this.#pendingWorkerCleanupBounds.set(key, bound);
13934
13927
  }
@@ -13969,16 +13962,11 @@ var FECManager = class _FECManager {
13969
13962
  if (this.#fpsDetected || fps <= 0) return;
13970
13963
  this.#fpsDetected = true;
13971
13964
  this.#fecFps = fps;
13972
- if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
13973
- this.#fecTimeoutMs = computeFecDeliveryWindowMs({
13974
- interleaveDepthMs: this.#fecInterleaveDepthMs,
13975
- sourceSymbols: this.#fecK,
13976
- symbolSize: this.#fecSymbolSize,
13977
- mediaUnitsPerSecond: fps
13978
- });
13965
+ if (Number.isSafeInteger(this.#fecInterleaveDepthMs) && this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
13966
+ this.#fecTimeoutMs = fecSpanDeliveryWindowMs(this.#fecInterleaveDepthMs);
13979
13967
  }
13980
13968
  for (const [packetId, config] of this.#trackConfigs) {
13981
- if (config.fps !== void 0 || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
13969
+ if (config.fps !== void 0 || !Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
13982
13970
  this.#trackConfigs.set(packetId, {
13983
13971
  ...config,
13984
13972
  fps,
@@ -14010,35 +13998,28 @@ var FECManager = class _FECManager {
14010
13998
  feedSourceSymbol(packetId, ssId, symbolData, alta, catalogTrackName) {
14011
13999
  this.#fecStats.sourcePackets++;
14012
14000
  this.#statsForPacket(packetId).sourcePackets++;
14013
- let sbn;
14014
- let fecData;
14015
- if (this.#isIso(packetId)) {
14016
- const locator = this.#isoLocatorFor(packetId, catalogTrackName);
14017
- const located = locator.locate(ssId);
14018
- if (located === null) {
14019
- throw new Error(
14020
- `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
14021
- );
14022
- }
14023
- locator.observe(ssId);
14024
- if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {
14025
- this.#isoSourceOversizeCount++;
14026
- return null;
14027
- }
14028
- sbn = located.block.start;
14029
- fecData = symbolData;
14030
- } else {
14031
- fecData = this.#normalizeSymbolData(packetId, symbolData);
14032
- sbn = Math.floor(ssId / this.kForPacket(packetId));
14001
+ if (!this.#hasBlockProfile(packetId)) return null;
14002
+ const locator = this.#isoLocatorFor(packetId, catalogTrackName);
14003
+ const located = locator.locate(ssId);
14004
+ if (located === null) {
14005
+ throw new Error(
14006
+ `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
14007
+ );
14033
14008
  }
14009
+ locator.observe(ssId);
14010
+ if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) {
14011
+ this.#isoSourceOversizeCount++;
14012
+ return null;
14013
+ }
14014
+ const sbn = located.block.start;
14034
14015
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
14035
14016
  const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
14036
14017
  const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
14037
14018
  if (workerClient && !syncBlockOwner) {
14038
14019
  if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null;
14039
- const buf = fecData !== symbolData ? fecData.buffer : fecData.buffer.slice(
14040
- fecData.byteOffset,
14041
- fecData.byteOffset + fecData.byteLength
14020
+ const buf = symbolData.buffer.slice(
14021
+ symbolData.byteOffset,
14022
+ symbolData.byteOffset + symbolData.byteLength
14042
14023
  );
14043
14024
  if (alta) {
14044
14025
  const authBuf = alta.auth.buffer.slice(
@@ -14061,7 +14042,7 @@ var FECManager = class _FECManager {
14061
14042
  this.#syncDecoderBlockOwners.set(blockKey, client);
14062
14043
  let recovered = null;
14063
14044
  try {
14064
- recovered = client.add_source(ssId, fecData, performance.now());
14045
+ recovered = client.add_source(ssId, symbolData, performance.now());
14065
14046
  } catch {
14066
14047
  }
14067
14048
  this.#retireIsoUnplaced(client, packetId, catalogTrackName);
@@ -14080,7 +14061,8 @@ var FECManager = class _FECManager {
14080
14061
  feedRepairSymbol(packetId, ssStart, ssbLength, rsId, repairData, catalogTrackName) {
14081
14062
  this.#fecStats.repairPackets++;
14082
14063
  this.#statsForPacket(packetId).repairPackets++;
14083
- const symbolSize = this.symbolSizeForPacket(packetId);
14064
+ if (!this.#hasBlockProfile(packetId)) return null;
14065
+ const symbolSize = this.#trackConfig(packetId).symbolSize;
14084
14066
  if (symbolSize > 0 && repairData.byteLength !== symbolSize) {
14085
14067
  this.#repairSymbolSizeMismatchCount++;
14086
14068
  if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {
@@ -14091,18 +14073,12 @@ var FECManager = class _FECManager {
14091
14073
  return null;
14092
14074
  }
14093
14075
  const fecData = repairData;
14094
- const k = this.kForPacket(packetId);
14095
- let sbn;
14096
- if (this.#isIso(packetId)) {
14097
- const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart);
14098
- if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
14099
- return null;
14100
- }
14101
- sbn = ssStart;
14102
- } else {
14103
- if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
14104
- sbn = Math.floor(ssStart / k);
14076
+ const k = this.#trackConfig(packetId).k;
14077
+ const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart);
14078
+ if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
14079
+ return null;
14105
14080
  }
14081
+ const sbn = ssStart;
14106
14082
  this.#noteSbnSeen(sbn, packetId, catalogTrackName, "repair");
14107
14083
  this.noteBlockActivity(sbn, packetId, catalogTrackName);
14108
14084
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
@@ -14140,8 +14116,9 @@ var FECManager = class _FECManager {
14140
14116
  * Record a block's authored SSB_length from a repair symbol. Returns false,
14141
14117
  * leaving the block unchanged, when the symbol contradicts the block: a
14142
14118
  * different SSB_length than an earlier repair symbol declared is dropped,
14143
- * and an SSB_length that places an already-received source ESI inside the
14144
- * zero padding fails the block, since its geometry can no longer be trusted.
14119
+ * and an SSB_length that places an already-received source ESI at or past
14120
+ * the block's end fails the block, since its geometry can no longer be
14121
+ * trusted.
14145
14122
  */
14146
14123
  #noteAuthoredLength(blockKey, sbn, packetId, ssbLength, catalogTrackName) {
14147
14124
  const known = this.#blockAuthoredLengths.get(blockKey);
@@ -14208,7 +14185,7 @@ var FECManager = class _FECManager {
14208
14185
  * a sweep tied to expiry alone would never run there.
14209
14186
  */
14210
14187
  #retireIsoUnplaced(decoder, packetId, catalogTrackName) {
14211
- if (!this.#isIso(packetId)) return;
14188
+ if (!this.#hasBlockProfile(packetId)) return;
14212
14189
  const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest;
14213
14190
  if (newest === void 0) return;
14214
14191
  try {
@@ -14253,11 +14230,13 @@ var FECManager = class _FECManager {
14253
14230
  this.#failBlock(sbn, packetId, catalogTrackName);
14254
14231
  return;
14255
14232
  }
14256
- this.#applyClassifierChanges(
14257
- packetId,
14258
- catalogTrackName,
14259
- this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence)
14260
- );
14233
+ if (_FECManager.#hasCatalogTiming(trackConfig)) {
14234
+ this.#applyClassifierChanges(
14235
+ packetId,
14236
+ catalogTrackName,
14237
+ this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence)
14238
+ );
14239
+ }
14261
14240
  const count = (this.#blockSourceCounts.get(key) ?? 0) + 1;
14262
14241
  this.#blockSourceCounts.set(key, count);
14263
14242
  if (count === 1) this.#indexBlockKey(key, sbn, packetId);
@@ -14301,6 +14280,7 @@ var FECManager = class _FECManager {
14301
14280
  }
14302
14281
  /** Refresh deadlines after accepted block activity. */
14303
14282
  noteBlockActivity(sbn, packetId, catalogTrackName, sourceProgress = false) {
14283
+ if (!this.#trackConfigs.has(packetId)) return;
14304
14284
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
14305
14285
  if (this.#completedBlocks.has(key)) return;
14306
14286
  if (this.#pendingWorkerCleanups.has(key)) return;
@@ -14310,13 +14290,13 @@ var FECManager = class _FECManager {
14310
14290
  this.#holdForOrderedSettlement(key, sbn, packetId, catalogTrackName);
14311
14291
  return;
14312
14292
  }
14313
- const trackConfig = this.#resolveTrackConfig(packetId);
14293
+ const trackConfig = this.#trackConfig(packetId);
14314
14294
  const timeoutMs = this.#multicastWaitMs(trackConfig);
14315
14295
  if (!(timeoutMs > 0)) {
14316
14296
  throw new Error(`[FECManager] packetId=${packetId} has no positive fail bound (timeoutMs=${timeoutMs})`);
14317
14297
  }
14318
14298
  const repairDeadline = this.#repairDeadlines.get(key);
14319
- if (this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
14299
+ if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
14320
14300
  this.#armRepairDeadline(
14321
14301
  key,
14322
14302
  sbn,
@@ -14386,6 +14366,7 @@ var FECManager = class _FECManager {
14386
14366
  }
14387
14367
  /** Mark a block as completed externally (e.g., after routing recovered data). */
14388
14368
  markBlockCompleted(sbn, packetId, catalogTrackName) {
14369
+ if (packetId != null && !this.#trackConfigs.has(packetId)) return;
14389
14370
  for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {
14390
14371
  this.#resolveCompleted(key);
14391
14372
  }
@@ -15422,6 +15403,21 @@ var FecReleaseSequencer = class {
15422
15403
  this.#streams.delete(key);
15423
15404
  }
15424
15405
  }
15406
+ /**
15407
+ * The packet's FEC ended, so no block will resolve a held gap: release its
15408
+ * frames in order, each gap stalled as a discontinuity, and forget its
15409
+ * streams, so FEC configured on it again starts from a fresh one.
15410
+ */
15411
+ endPacket(packetId) {
15412
+ for (const [key, stream] of this.#streams) {
15413
+ if (stream.packetId !== packetId) continue;
15414
+ while (stream.buf.length > 0 && stream.last !== null) {
15415
+ stream.discontinuity = true;
15416
+ this.#drain(stream);
15417
+ }
15418
+ this.#streams.delete(key);
15419
+ }
15420
+ }
15425
15421
  discardPacket(packetId) {
15426
15422
  for (const [key, stream] of this.#streams) {
15427
15423
  if (stream.packetId !== packetId) continue;
@@ -15804,6 +15800,13 @@ var MmtpContainerParser = class _MmtpContainerParser {
15804
15800
  markFecDiscontinuity(packetId, catalogTrackName) {
15805
15801
  this.#fecSequencer?.markDiscontinuity(this.#streamKey(packetId, catalogTrackName));
15806
15802
  }
15803
+ /**
15804
+ * The FEC of a packet ID ended: release the frames the FEC release gate
15805
+ * holds for it in order, each gap stalled, and forget its gated streams.
15806
+ */
15807
+ endFecRelease(packetId) {
15808
+ this.#fecSequencer?.endPacket(packetId);
15809
+ }
15807
15810
  /**
15808
15811
  * MPU and data-unit identity of one MMTP packet, parsed in place (no copy,
15809
15812
  * no reassembly). Null for non-MPU packets and packets that do not parse.
@@ -17137,6 +17140,8 @@ var MmtpRouter = class {
17137
17140
  */
17138
17141
  #onPublisherRestart;
17139
17142
  #publisherRestarts = 0;
17143
+ /** Inits delivered with no onTrackInit handler for their packetId (MOQT-01). */
17144
+ #unmappedInitDeliveries = 0;
17140
17145
  // FEC Payload IDs left unread because their (owner) packetId had no
17141
17146
  // configured fec.blockProfile (see #noteUnreadableFecPayloadId).
17142
17147
  #fecPayloadIdsWithoutProfile = 0;
@@ -17333,15 +17338,24 @@ var MmtpRouter = class {
17333
17338
  return NO_FEC_GEOMETRY_HORIZON;
17334
17339
  }
17335
17340
  const blockSpanMs = fec.interleaveDepthMsForPacket(packetId);
17341
+ const blockLifetimeMs = fec.blockLifetimeMsForPacket(packetId);
17342
+ if (blockSpanMs === void 0 || blockLifetimeMs === void 0) return NO_FEC_GEOMETRY_HORIZON;
17336
17343
  if (blockSpanMs === 0) return NO_BLOCK_SPAN_HORIZON;
17337
17344
  return {
17338
17345
  kind: "fec-horizon",
17339
- retentionMs: duplicateRetentionMs({
17340
- blockSpanMs,
17341
- blockLifetimeMs: fec.blockLifetimeMsForPacket(packetId)
17342
- })
17346
+ retentionMs: duplicateRetentionMs({ blockSpanMs, blockLifetimeMs })
17343
17347
  };
17344
17348
  }
17349
+ /**
17350
+ * Init segments delivered for a packetId with no `onTrackInit` handler
17351
+ * since construction, whether the deprecated aggregate `onInitSegment`
17352
+ * callback then received them or nothing did. A supported deployment reads
17353
+ * 0 here; a non-zero value is the evidence that blocks removing the
17354
+ * aggregate callback (MOQT-01, BLO-21181).
17355
+ */
17356
+ get unmappedInitDeliveryCount() {
17357
+ return this.#unmappedInitDeliveries;
17358
+ }
17345
17359
  /** Publisher restarts detected on the multicast leg since construction. */
17346
17360
  get publisherRestartCount() {
17347
17361
  return this.#publisherRestarts;
@@ -17429,7 +17443,7 @@ var MmtpRouter = class {
17429
17443
  /** Update MFU reassembly timeout. Source and FEC-recovery paths share the
17430
17444
  * parser's single reassembler (BLO-8151), so one update covers both. */
17431
17445
  setMfuTimeoutMs(ms) {
17432
- if (ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
17446
+ if (ms !== null && ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
17433
17447
  this.#parser.setMfuTimeoutMs(ms);
17434
17448
  }
17435
17449
  /** Update parser frame reordering for unordered multicast delivery.
@@ -17499,7 +17513,15 @@ var MmtpRouter = class {
17499
17513
  onTrackPacket(packetId, cb) {
17500
17514
  this.#trackPacketCallbacks.set(packetId, cb);
17501
17515
  }
17502
- /** Register an init segment callback (ftyp+moov from MMTP MPU metadata). */
17516
+ /**
17517
+ * Register an init segment callback (ftyp+moov from MMTP MPU metadata).
17518
+ * Receives an init only for a packetId with no `onTrackInit` handler.
17519
+ *
17520
+ * @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
17521
+ * removed in the 1.0.0 major, no earlier than one minor release after this
17522
+ * deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
17523
+ * deliveries with `unmappedInitDeliveryCount`.
17524
+ */
17503
17525
  onInitSegment(cb) {
17504
17526
  this.#onInitSegment = cb;
17505
17527
  }
@@ -17513,17 +17535,16 @@ var MmtpRouter = class {
17513
17535
  * When active, source frames are buffered per FEC block and emitted as
17514
17536
  * multi-sample CMAF segments instead of per-frame callbacks. */
17515
17537
  enableMseAssembler(packetId, config, catalogTrackName) {
17516
- const k = this.#kForPacket(packetId);
17517
17538
  const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);
17518
17539
  const previous = this.#mseAssemblers.get(assemblerKey);
17519
17540
  if (previous) {
17520
17541
  previous.flush();
17521
17542
  previous.dispose();
17522
17543
  }
17523
- const assembler = new CmafBlockAssembler({ ...config, k });
17544
+ const assembler = new CmafBlockAssembler(config);
17524
17545
  this.#mseAssemblers.set(assemblerKey, assembler);
17525
17546
  console.log(
17526
- `[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (K=${k}, ${config.trackType})`
17547
+ `[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${config.trackType})`
17527
17548
  );
17528
17549
  return assembler;
17529
17550
  }
@@ -17632,6 +17653,21 @@ var MmtpRouter = class {
17632
17653
  }
17633
17654
  this.#fecMappersByPacketId.set(packetId, mapper);
17634
17655
  }
17656
+ /**
17657
+ * Release the router side of a packetId whose FEC is ending: drop its block
17658
+ * mapper and end its FEC release gating. Frames the gate holds are released
17659
+ * in order, each gap stalled (no block will resolve it), not stranded. Call
17660
+ * before FECManager.releasePacket, while the gate can still read its blocks.
17661
+ */
17662
+ releaseFecPacket(packetId) {
17663
+ this.#fecMappersByPacketId.delete(packetId);
17664
+ this.#withDeferredFecPokes(() => {
17665
+ this.#parser.endFecRelease(packetId);
17666
+ for (const [key, poke] of this.#pendingFecPokes) {
17667
+ if (poke.packetId === packetId) this.#pendingFecPokes.delete(key);
17668
+ }
17669
+ });
17670
+ }
17635
17671
  registerManifestTrack(context) {
17636
17672
  if (!this.#manifestVerifier) throw new Error("manifest verifier is not configured");
17637
17673
  this.#manifestVerifier.registerTrack(context);
@@ -17751,87 +17787,21 @@ var MmtpRouter = class {
17751
17787
  this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
17752
17788
  return;
17753
17789
  }
17754
- if (this.#isIsoPacket(ownerPacketId)) {
17755
- this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
17756
- return;
17757
- }
17758
- const repairSbn = this.#deriveRepairSbn(parsed.ssStart, parsed.ssbLength, ownerPacketId);
17759
- if (repairSbn === null) return;
17760
- const repairEsi = this.#kForPacket(ownerPacketId) + parsed.rsId;
17761
- if (!this.#feedFecTransportSymbol(
17762
- ownerPacketId,
17763
- repairSbn,
17764
- repairEsi,
17765
- parsed.symbolData,
17766
- "moq",
17767
- catalogTrackName
17768
- )) return;
17769
- const recovered = catalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
17770
- ownerPacketId,
17771
- parsed.ssStart,
17772
- parsed.ssbLength,
17773
- parsed.rsId,
17774
- parsed.symbolData
17775
- ) : this.#fecManager.feedRepairSymbol(
17776
- ownerPacketId,
17777
- parsed.ssStart,
17778
- parsed.ssbLength,
17779
- parsed.rsId,
17780
- parsed.symbolData,
17781
- catalogTrackName
17782
- );
17783
- if (recovered) {
17784
- const alreadyCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
17785
- if (catalogTrackName === void 0) {
17786
- this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
17787
- } else {
17788
- this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId, catalogTrackName);
17789
- }
17790
- if (!alreadyCompleted) {
17791
- this.#fecManager.countRecovery(ownerPacketId, repairSbn, catalogTrackName);
17792
- if (catalogTrackName === void 0) {
17793
- this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
17794
- } else {
17795
- this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
17796
- }
17797
- this.#routeRecoveredBlock(
17798
- repairSbn,
17799
- recovered,
17800
- ownerPacketId,
17801
- catalogTrackName
17802
- );
17803
- }
17804
- }
17790
+ this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
17805
17791
  }
17806
17792
  }
17807
- // ── SBN/ESI derivation ──────────────────────────────────────────
17808
- #kForPacket(packetId) {
17809
- if (packetId != null)
17810
- return this.#fecManager?.kForPacket?.(packetId) ?? this.#fecMappersByPacketId.get(packetId)?.k ?? this.#fecManager?.k ?? 4;
17811
- return this.#defaultFecMapper?.k ?? this.#fecManager?.k ?? 4;
17812
- }
17793
+ // ── Packet FEC geometry and block profile ───────────────────────
17813
17794
  #symbolSizeForPacket(packetId) {
17814
- if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? this.#fecManager?.symbolSize ?? 0;
17795
+ if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? 0;
17815
17796
  return this.#fecManager?.symbolSize ?? 0;
17816
17797
  }
17817
- /** Derive SBN/ESI from SS_ID using packet-scoped FEC geometry. */
17818
- #deriveSbnEsi(ssId, packetId) {
17819
- const mapper = packetId == null ? this.#defaultFecMapper : this.#fecMappersByPacketId.get(packetId) ?? null;
17820
- if (mapper) return mapper.fromSsId(ssId);
17821
- const k = this.#kForPacket(packetId);
17822
- return { sbn: Math.floor(ssId / k), esi: ssId % k };
17823
- }
17824
- #deriveRepairSbn(ssStart, ssbLength, packetId) {
17825
- const k = this.#kForPacket(packetId);
17826
- if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
17827
- return Math.floor(ssStart / k);
17828
- }
17829
17798
  /**
17830
17799
  * The configured block profile of a packetId, or undefined while it is
17831
17800
  * unconfigured. With an FEC manager its per-packet configuration is the
17832
17801
  * only source. A router without one still recovers symbols through its
17833
17802
  * registered mapper, built with the catalog's explicit profile: the
17834
- * packet's own mapper, else the default one (the #deriveSbnEsi rule).
17803
+ * packet's own mapper, else the default one. A NullBlockMapper (a stream
17804
+ * without an FEC instance) states none.
17835
17805
  */
17836
17806
  #configuredBlockProfile(packetId) {
17837
17807
  if (this.#fecManager) return this.#fecManager.blockProfileForPacket(packetId);
@@ -17839,9 +17809,10 @@ var MmtpRouter = class {
17839
17809
  return mapper?.blockProfile ?? void 0;
17840
17810
  }
17841
17811
  /**
17842
- * The block profile an FEC coordinate of `packetId` is read under. Throws
17843
- * while none is configured: the two profiles carry identical wire tuples
17844
- * with different block math, so there is no default to fall back to.
17812
+ * The block profile an FEC coordinate of `packetId` is read under:
17813
+ * `iso-ssbg1-v1`, the only one (`k-padded-v1` is retired and refused).
17814
+ * Throws while none is configured: such a packet has no FEC geometry, and
17815
+ * nothing is inferred from the wire or defaulted.
17845
17816
  * Packet ingress leaves such a packet's FEC Payload ID unread first
17846
17817
  * (#noteUnreadableFecPayloadId); a throw here is a wiring fault on a path
17847
17818
  * that cannot hold an unconfigured packet.
@@ -17855,8 +17826,16 @@ var MmtpRouter = class {
17855
17826
  }
17856
17827
  return profile;
17857
17828
  }
17858
- #isIsoPacket(packetId) {
17859
- return this.#blockProfileFor(packetId) === "iso-ssbg1-v1";
17829
+ /**
17830
+ * The block coordinates (SS_Start, ESI) of an FEC source: range membership
17831
+ * in a block a repair already stated (ISO/IEC 23008-1 C.5.3). Null while no
17832
+ * repair has stated its block, while its packetId has no configured block
17833
+ * profile, and without an FEC manager: SS_ID alone locates no block, so
17834
+ * nothing is derived from it.
17835
+ */
17836
+ #locateFecSource(packetId, catalogTrackName, ssId) {
17837
+ if (this.#configuredBlockProfile(packetId) === void 0) return null;
17838
+ return this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId) ?? null;
17860
17839
  }
17861
17840
  /**
17862
17841
  * Explicit refusal to read an FEC Payload ID: its (owner) packetId has no
@@ -17875,21 +17854,20 @@ var MmtpRouter = class {
17875
17854
  }
17876
17855
  }
17877
17856
  /**
17878
- * The release-gate anchor of a recovered symbol: its block (SBN) under
17879
- * k-padded-v1, its own SS_ID under iso-ssbg1-v1, where a frame is gated by
17880
- * the SS_IDs between it and the last released frame.
17857
+ * The release-gate anchor of a recovered symbol: its own SS_ID, SS_Start +
17858
+ * ESI modulo 2^32 (C.5.2), where a frame is gated by the SS_IDs between it
17859
+ * and the last released frame.
17881
17860
  */
17882
- #fecAnchorForRecovered(ownerPacketId, blockId, index) {
17883
- return ownerPacketId !== void 0 && this.#isIsoPacket(ownerPacketId) ? serial32Add(blockId, index) : blockId;
17861
+ #fecAnchorForRecovered(blockStart, index) {
17862
+ return serial32Add(blockStart, index);
17884
17863
  }
17885
17864
  /**
17886
17865
  * One iso-ssbg1-v1 repair symbol (ISO/IEC 23008-1 C.5.3). It states the
17887
17866
  * block `[SS_Start, SS_Start + SSB_length)`; repair ESI = SSB_length +
17888
17867
  * RS_ID (K' = SSB_length). The held sources it placed (and those of a
17889
17868
  * repair-less span in front of it) are fed as first-arrival sources, then
17890
- * the repair, the order they had on the wire, so a loss-free block is clean
17891
- * on its repair exactly as under k-padded-v1. A repair contradicting a known
17892
- * block is dropped.
17869
+ * the repair, the order they had on the wire, so a loss-free block completes
17870
+ * clean on its repair. A repair contradicting a known block is dropped.
17893
17871
  */
17894
17872
  #handleIsoRepair(ownerPacketId, catalogTrackName, repair, source) {
17895
17873
  const fec = this.#fecManager;
@@ -17981,7 +17959,8 @@ var MmtpRouter = class {
17981
17959
  return null;
17982
17960
  }
17983
17961
  /** Route a recovered FEC block through the parser + dedup pipeline.
17984
- * Sync WASM decoders return K fixed-width source symbols concatenated. */
17962
+ * Sync WASM decoders return the block's K' = SSB_length fixed-width
17963
+ * ssbg_mode1 source symbols concatenated. */
17985
17964
  routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
17986
17965
  this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
17987
17966
  }
@@ -18449,29 +18428,19 @@ var MmtpRouter = class {
18449
18428
  resolvedLoss: (packetId, catalogTrackName, fromSbn, toSbn, releasedSbn) => {
18450
18429
  const fec = this.#fecManager;
18451
18430
  if (!fec?.blockResolution) return [];
18452
- if (this.#isIsoPacket(packetId)) {
18453
- const head = fromSbn === releasedSbn ? toSbn : fromSbn;
18454
- return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
18455
- }
18456
- const highest = fec.highestSbn?.(packetId, catalogTrackName);
18457
- const lost = [];
18458
- for (let sbn = fromSbn; sbn <= toSbn; sbn++) {
18459
- const resolution = fec.blockResolution(sbn, packetId, catalogTrackName);
18460
- if (resolution === "pending") return null;
18461
- if (resolution === "failed") {
18462
- lost.push(sbn);
18463
- } else if (resolution === "untracked") {
18464
- if (sbn === releasedSbn) continue;
18465
- if (highest === void 0 || sbn > highest) return null;
18466
- lost.push(sbn);
18467
- }
18468
- }
18469
- return lost;
18431
+ const head = fromSbn === releasedSbn ? toSbn : fromSbn;
18432
+ return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
18470
18433
  },
18471
18434
  // The retention window itself: a frame is never still held when a block
18472
18435
  // it waits on retires. Only consulted while resolvedLoss holds a gap,
18473
18436
  // which needs blockResolution, so the check above guarantees the method.
18474
- holdLimitMs: (packetId) => this.#fecManager?.terminalRetentionMsForPacket?.(packetId) ?? Infinity,
18437
+ // A packetId the manager no longer has configured (released) has no
18438
+ // block left to wait on: release its frames now (BLO-45953).
18439
+ holdLimitMs: (packetId) => {
18440
+ const fec = this.#fecManager;
18441
+ if (!fec?.terminalRetentionMsForPacket) return Infinity;
18442
+ return fec.terminalRetentionMsForPacket(packetId) ?? 0;
18443
+ },
18475
18444
  decide: (before, after, lostBlocks, discontinuity) => {
18476
18445
  const decision = this.#decideGap(before, after, lostBlocks, discontinuity);
18477
18446
  this.#onFrameGap?.(decision);
@@ -18533,9 +18502,10 @@ var MmtpRouter = class {
18533
18502
  }
18534
18503
  /**
18535
18504
  * The most media units a gap between two released frames can hold: the
18536
- * FEC blocks from `before`'s SBN to `after`'s, times D, the media units one
18537
- * block touches (ceil(interleaveDepthMs / unit duration), §5.1). Null when
18538
- * either SBN, the unit duration or the catalog interleave is unknown.
18505
+ * FEC blocks between `before`'s anchor and `after`'s, times D, the media
18506
+ * units one block touches (ceil(interleaveDepthMs / unit duration), §5.1).
18507
+ * Null when either anchor, the unit duration or the catalog interleave is
18508
+ * unknown.
18539
18509
  */
18540
18510
  #maxMissingUnits(before, after) {
18541
18511
  const from = before.fecSbn;
@@ -18548,7 +18518,7 @@ var MmtpRouter = class {
18548
18518
  const denominator = BigInt(timing.durationTicks) * 1000n;
18549
18519
  const unitsPerBlock = (numerator + denominator - 1n) / denominator;
18550
18520
  const blocks = BigInt(
18551
- (this.#isIsoPacket(after.packetId) ? serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from) : Math.abs(to - from)) + 1
18521
+ (serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from)) + 1
18552
18522
  );
18553
18523
  const bound = unitsPerBlock * blocks;
18554
18524
  return bound > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(bound);
@@ -18598,8 +18568,9 @@ var MmtpRouter = class {
18598
18568
  const cb = this.#trackInitCallbacks.get(packetId);
18599
18569
  if (cb) {
18600
18570
  cb(data);
18601
- } else if (this.#onInitSegment) {
18602
- this.#onInitSegment(data);
18571
+ } else {
18572
+ this.#unmappedInitDeliveries++;
18573
+ this.#onInitSegment?.(data);
18603
18574
  }
18604
18575
  console.log(
18605
18576
  `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
@@ -18641,9 +18612,9 @@ var MmtpRouter = class {
18641
18612
  if (!info.isRepair && this.#manifestVerifier && !manifestAdmitted) {
18642
18613
  let identity = manifestIdentity;
18643
18614
  if (!identity && info.fecPayloadId && !fecUnreadable && (this.#resolveManifestIdentityForRoute || this.#resolveManifestIdentity)) {
18644
- const coordinates = this.#deriveSbnEsi(info.fecPayloadId.ssId, info.packetId);
18645
18615
  const catalogTrackName = catalogTrackNameForPacket(ingress, info.packetId);
18646
- identity = (this.#resolveManifestIdentityForRoute ? this.#resolveManifestIdentityForRoute({
18616
+ const coordinates = this.#locateFecSource(info.packetId, catalogTrackName, info.fecPayloadId.ssId);
18617
+ identity = coordinates === null ? void 0 : (this.#resolveManifestIdentityForRoute ? this.#resolveManifestIdentityForRoute({
18647
18618
  packetId: info.packetId,
18648
18619
  sbn: coordinates.sbn,
18649
18620
  esi: coordinates.esi,
@@ -18695,79 +18666,7 @@ var MmtpRouter = class {
18695
18666
  this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
18696
18667
  return;
18697
18668
  }
18698
- if (this.#isIsoPacket(ownerPacketId)) {
18699
- this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
18700
- return;
18701
- }
18702
- const repairSbn = this.#deriveRepairSbn(repair.ssStart, repair.ssbLength, ownerPacketId);
18703
- if (repairSbn === null) return;
18704
- const repairEsi = this.#kForPacket(ownerPacketId) + repair.rsId;
18705
- if (!this.#feedFecTransportSymbol(
18706
- ownerPacketId,
18707
- repairSbn,
18708
- repairEsi,
18709
- repair.symbolData,
18710
- source,
18711
- catalogTrackNameForPacket(ingress, ownerPacketId)
18712
- )) return;
18713
- const repairCatalogTrackName = catalogTrackNameForPacket(ingress, ownerPacketId);
18714
- if (repairCatalogTrackName === void 0) {
18715
- this.#fecManager.noteBlockActivity?.(repairSbn, ownerPacketId);
18716
- } else {
18717
- this.#fecManager.noteBlockActivity?.(
18718
- repairSbn,
18719
- ownerPacketId,
18720
- repairCatalogTrackName
18721
- );
18722
- }
18723
- const recovered = repairCatalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
18724
- ownerPacketId,
18725
- repair.ssStart,
18726
- repair.ssbLength,
18727
- repair.rsId,
18728
- repair.symbolData
18729
- ) : this.#fecManager.feedRepairSymbol(
18730
- ownerPacketId,
18731
- repair.ssStart,
18732
- repair.ssbLength,
18733
- repair.rsId,
18734
- repair.symbolData,
18735
- repairCatalogTrackName
18736
- );
18737
- if (recovered) {
18738
- const alreadyCompleted = repairCatalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(
18739
- repairSbn,
18740
- ownerPacketId,
18741
- repairCatalogTrackName
18742
- );
18743
- if (repairCatalogTrackName === void 0) {
18744
- this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
18745
- } else {
18746
- this.#fecManager.cancelBlockTimeout(
18747
- repairSbn,
18748
- ownerPacketId,
18749
- repairCatalogTrackName
18750
- );
18751
- }
18752
- if (!alreadyCompleted) {
18753
- this.#fecManager.countRecovery(ownerPacketId, repairSbn, repairCatalogTrackName);
18754
- if (repairCatalogTrackName === void 0) {
18755
- this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
18756
- } else {
18757
- this.#fecManager.markBlockCompleted(
18758
- repairSbn,
18759
- ownerPacketId,
18760
- repairCatalogTrackName
18761
- );
18762
- }
18763
- this.#routeRecoveredBlock(
18764
- repairSbn,
18765
- recovered,
18766
- ownerPacketId,
18767
- catalogTrackNameForPacket(ingress, ownerPacketId)
18768
- );
18769
- }
18770
- }
18669
+ this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
18771
18670
  }
18772
18671
  } else {
18773
18672
  if (this.#classifyPacketId(info.packetId) === null) return;
@@ -18791,8 +18690,8 @@ var MmtpRouter = class {
18791
18690
  }
18792
18691
  const duplicates = this.#stream(info.packetId, catalogTrackName).duplicates;
18793
18692
  const fecPayloadId = fecUnreadable ? null : info.fecPayloadId;
18794
- const isoSource = this.#fecManager !== null && fecPayloadId != null && this.#isIsoPacket(info.packetId);
18795
- const coords = this.#fecManager && fecPayloadId ? isoSource ? this.#fecManager.locateIsoSource?.(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? void 0 : this.#deriveSbnEsi(fecPayloadId.ssId, info.packetId) : void 0;
18693
+ const fecSource = this.#fecManager !== null && fecPayloadId != null;
18694
+ const coords = fecSource && fecPayloadId ? this.#locateFecSource(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? void 0 : void 0;
18796
18695
  duplicates.advance(coords?.sbn);
18797
18696
  if (source === "multicast" && !overlapCopy && !releasedFromQuarantine && catalogTrackName !== void 0 && duplicates.predatesHorizon(info.sequenceNumber, fecPayloadId?.ssId)) {
18798
18697
  this.#publisherRestarts++;
@@ -18807,9 +18706,8 @@ var MmtpRouter = class {
18807
18706
  }
18808
18707
  if (this.#fecManager && fecPayloadId && coords) {
18809
18708
  const fecId = fecPayloadId;
18810
- const symbolSize = this.#symbolSizeForPacket(info.packetId);
18811
18709
  const rawSymbol = packet.subarray(0, packet.length - 4);
18812
- const oversized = isoSource ? !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false) : symbolSize > 0 && rawSymbol.length > symbolSize;
18710
+ const oversized = !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false);
18813
18711
  let overlapBlockCompleted = false;
18814
18712
  if (overlapCopy) {
18815
18713
  overlapBlockCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(coords.sbn, info.packetId) : this.#fecManager.isBlockCompleted(
@@ -18844,7 +18742,7 @@ var MmtpRouter = class {
18844
18742
  }
18845
18743
  parseSourceSymbol = (overlapCopy ? !overlapBlockCompleted : sourceSymbolAccepted) && !duplicates.parsedSymbols.has(fecId.ssId);
18846
18744
  fecInfo = { sbn: coords.sbn, esi: coords.esi, oversized };
18847
- } else if (isoSource && this.#fecManager && fecPayloadId) {
18745
+ } else if (fecSource && this.#fecManager && fecPayloadId) {
18848
18746
  const ssId = fecPayloadId.ssId;
18849
18747
  if (overlapCopy) {
18850
18748
  parseSourceSymbol = !duplicates.parsedSymbols.has(ssId);
@@ -18878,12 +18776,13 @@ var MmtpRouter = class {
18878
18776
  this.#feedViaParser(packet, info, fecInfo, {
18879
18777
  source,
18880
18778
  catalogTrackName,
18881
- ...isoSource && info.fecPayloadId ? { fecSbn: info.fecPayloadId.ssId } : fecInfo === void 0 ? {} : { fecSbn: fecInfo.sbn }
18779
+ // An FEC source frame is anchored by its own SS_ID (C.5.2).
18780
+ ...fecSource && fecPayloadId ? { fecSbn: fecPayloadId.ssId } : {}
18882
18781
  }, parsed);
18883
18782
  }
18884
18783
  if (this.#classifyPacketId(info.packetId) === null) return;
18885
- if (this.#fecManager && fecPayloadId && sourceSymbolAccepted && sourceSymbolTrackable) {
18886
- const { sbn, esi } = isoSource && coords ? coords : this.#deriveSbnEsi(fecPayloadId.ssId, info.packetId);
18784
+ if (this.#fecManager && fecPayloadId && coords && sourceSymbolAccepted && sourceSymbolTrackable) {
18785
+ const { sbn, esi } = coords;
18887
18786
  if (this.#classifyPacketId(info.packetId) !== null && this.#fecManager.isTrackConfigured?.(info.packetId) !== false) {
18888
18787
  this.#fecManager.trackBlock(
18889
18788
  sbn,
@@ -19317,7 +19216,6 @@ var MmtpRouter = class {
19317
19216
  // ── Private: FEC recovery routing ───────────────────────────────
19318
19217
  /** Route WASM-recovered block through appropriate path based on container kind. */
19319
19218
  #routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName) {
19320
- const k = this.#kForPacket(ownerPacketId);
19321
19219
  const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
19322
19220
  if (ownerPacketId === void 0) {
19323
19221
  throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
@@ -19330,7 +19228,6 @@ var MmtpRouter = class {
19330
19228
  }
19331
19229
  const symbolSize = packetSymbolSize;
19332
19230
  const result = processFecBlock(recovered, {
19333
- ...blockProfile === "k-padded-v1" ? { k } : {},
19334
19231
  symbolSize,
19335
19232
  ownerPacketId,
19336
19233
  blockProfile
@@ -19410,8 +19307,7 @@ var MmtpRouter = class {
19410
19307
  const admission = verifier.admitObject(
19411
19308
  {
19412
19309
  ...identity,
19413
- packetId: ownerPacketId,
19414
- ...ownerPacketId !== void 0 && this.#isIsoPacket(ownerPacketId) ? {} : { ssId: blockId * this.#kForPacket(ownerPacketId) + symbol.index }
19310
+ packetId: ownerPacketId
19415
19311
  },
19416
19312
  symbol.rawSymbol,
19417
19313
  () => {
@@ -19514,7 +19410,7 @@ var MmtpRouter = class {
19514
19410
  rapFlag: sym.fragment.rapFlag,
19515
19411
  recovered: true,
19516
19412
  deliveryProvenance,
19517
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
19413
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
19518
19414
  ...catalogTrackName === void 0 ? {} : { catalogTrackName }
19519
19415
  });
19520
19416
  continue;
@@ -19626,7 +19522,7 @@ var MmtpRouter = class {
19626
19522
  rapFlag: assembled.rapFlag,
19627
19523
  recovered: true,
19628
19524
  ...assembled.deliveryProvenance === void 0 ? {} : { deliveryProvenance: assembled.deliveryProvenance },
19629
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
19525
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
19630
19526
  ...recoveredCatalogTrackName === void 0 ? {} : { catalogTrackName: recoveredCatalogTrackName }
19631
19527
  });
19632
19528
  continue;
@@ -19763,7 +19659,7 @@ var MmtpRouter = class {
19763
19659
  rapFlag: sym.fragment.rapFlag,
19764
19660
  recovered: true,
19765
19661
  deliveryProvenance,
19766
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
19662
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
19767
19663
  ...catalogTrackName === void 0 ? {} : { catalogTrackName }
19768
19664
  });
19769
19665
  } else {
@@ -19842,7 +19738,7 @@ var MmtpRouter = class {
19842
19738
  rapFlag: assembled.rapFlag,
19843
19739
  recovered: true,
19844
19740
  deliveryProvenance,
19845
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
19741
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
19846
19742
  ...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
19847
19743
  });
19848
19744
  }
@@ -19877,7 +19773,7 @@ var MmtpRouter = class {
19877
19773
  ...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
19878
19774
  recovered: true,
19879
19775
  deliveryProvenance,
19880
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
19776
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
19881
19777
  ...catalogTrackName === void 0 ? {} : { catalogTrackName }
19882
19778
  });
19883
19779
  continue;
@@ -20225,22 +20121,22 @@ var MoqFecAlgorithm;
20225
20121
  MoqFecAlgorithm2[MoqFecAlgorithm2["RaptorQ"] = 2] = "RaptorQ";
20226
20122
  })(MoqFecAlgorithm || (MoqFecAlgorithm = {}));
20227
20123
 
20228
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-alta-parse/dist/index.js
20124
+ // node_modules/@blockcast/mmt-alta-parse/dist/index.js
20229
20125
  var MPU_HEADER_SIZE3 = 8;
20230
20126
  var MPU_LENGTH_FIELD_SIZE3 = 2;
20231
20127
  var MPU_HEADER_REMAINDER_SIZE3 = MPU_HEADER_SIZE3 - MPU_LENGTH_FIELD_SIZE3;
20232
20128
 
20233
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/mmtp.js
20129
+ // node_modules/@blockcast/mmt-base/dist/mmtp.js
20234
20130
  var MPU_HEADER_SIZE4 = 8;
20235
20131
  var MPU_LENGTH_FIELD_SIZE4 = 2;
20236
20132
  var MPU_HEADER_REMAINDER_SIZE4 = MPU_HEADER_SIZE4 - MPU_LENGTH_FIELD_SIZE4;
20237
20133
  var MMTP_SIGNALING_PACKET_ID2 = 0;
20238
20134
  var MMTP_PACKET_ID_MAX2 = 65535;
20239
20135
 
20240
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/net-constants.js
20136
+ // node_modules/@blockcast/mmt-base/dist/net-constants.js
20241
20137
  var DEFAULT_AMT_RELAY_PORT2 = 2268;
20242
20138
 
20243
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/ntp-short.js
20139
+ // node_modules/@blockcast/mmt-base/dist/ntp-short.js
20244
20140
  var NTP_SHORT_BITS2 = 32;
20245
20141
  var NTP_SHORT_MODULUS2 = 2 ** NTP_SHORT_BITS2;
20246
20142
  var NTP_SHORT_HALF_RANGE2 = 2 ** (NTP_SHORT_BITS2 - 1);
@@ -20261,9 +20157,8 @@ function compareNtpShort(a, b) {
20261
20157
  return "unordered";
20262
20158
  }
20263
20159
 
20264
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js
20160
+ // node_modules/@blockcast/mmt-base/dist/fec-block-profile.js
20265
20161
  var FEC_BLOCK_PROFILES2 = Object.freeze([
20266
- "k-padded-v1",
20267
20162
  "iso-ssbg1-v1"
20268
20163
  ]);
20269
20164
 
@@ -20655,10 +20550,12 @@ var MulticastOrchestrator = class {
20655
20550
  }
20656
20551
  onGoodput(sample) {
20657
20552
  const bitrate = sample.bitrate;
20658
- if (!this.#current || this.options.adaptive === false)
20553
+ if (!this.#current)
20659
20554
  return void 0;
20660
20555
  if (this.#state === "joining")
20661
20556
  return this.poll();
20557
+ if (this.options.adaptive === false)
20558
+ return void 0;
20662
20559
  if (bitrate === void 0 || !Number.isFinite(bitrate))
20663
20560
  return void 0;
20664
20561
  this.#estimate = bitrate;
@@ -20725,6 +20622,27 @@ var MulticastOrchestrator = class {
20725
20622
  this.#favored = void 0;
20726
20623
  return { kind: "join-target", current: this.#current, target: desired };
20727
20624
  }
20625
+ /**
20626
+ * Join `target` because the viewer selected it, not because goodput or
20627
+ * headroom ranked it. It runs whether or not the orchestrator adapts, and
20628
+ * takes the path an adaptive join takes: commit on the target's RAP at or
20629
+ * beyond the high-water mark, abandon at the join deadline. While it is
20630
+ * pending nothing re-ranks the ladder; goodput only advances the deadline.
20631
+ *
20632
+ * Returns undefined, changing nothing, while another join is pending (cancel
20633
+ * it first), when `target` is the current tier, or when it carries no media.
20634
+ */
20635
+ select(target) {
20636
+ if (!this.#current || this.#state === "joining" || !carriesMedia(target))
20637
+ return void 0;
20638
+ if (endpointKey(target) === endpointKey(this.#current))
20639
+ return void 0;
20640
+ this.#state = "joining";
20641
+ this.#target = target;
20642
+ this.#joinStartedAt = this.#now();
20643
+ this.#favored = void 0;
20644
+ return { kind: "join-target", current: this.#current, target };
20645
+ }
20728
20646
  /**
20729
20647
  * Feed a packet from the active tier (to advance the high-water mark) or
20730
20648
  * from the in-flight target (to find a committable RAP).
@@ -21764,7 +21682,7 @@ var MulticastTransport = class _MulticastTransport {
21764
21682
  // inside the window, instead of a clear+set pair on every packet.
21765
21683
  #watchdog = null;
21766
21684
  #lastPacketAtMs = 0;
21767
- #failbackWindowMs = null;
21685
+ #failbackWindowMs;
21768
21686
  #onTakeover = null;
21769
21687
  #onFailback = null;
21770
21688
  // Data-plane-live edge: the first packet this join accepted. Everything
@@ -22069,14 +21987,20 @@ var MulticastTransport = class _MulticastTransport {
22069
21987
  return this.#dataPlaneLive;
22070
21988
  }
22071
21989
  setFailbackWindowMs(windowMs) {
22072
- if (!Number.isFinite(windowMs) || windowMs <= 0) {
21990
+ if (windowMs !== null && (!Number.isFinite(windowMs) || windowMs <= 0)) {
22073
21991
  throw new Error(
22074
21992
  `[MulticastTransport] failbackWindowMs=${windowMs}. Derive a positive failback window from the stream catalog.`
22075
21993
  );
22076
21994
  }
22077
21995
  this.#failbackWindowMs = windowMs;
22078
- if (this.#watchdog !== null) {
22079
- clearTimeout(this.#watchdog);
21996
+ if (windowMs === null) {
21997
+ if (this.#watchdog !== null) clearTimeout(this.#watchdog);
21998
+ this.#watchdog = null;
21999
+ return;
22000
+ }
22001
+ if (this.#watchdog !== null) clearTimeout(this.#watchdog);
22002
+ this.#watchdog = null;
22003
+ if (this.#dataPlaneLive && this.#onFailback) {
22080
22004
  const silentMs = performance.now() - this.#lastPacketAtMs;
22081
22005
  this.#armWatchdog(Math.max(0, Math.ceil(windowMs - silentMs)));
22082
22006
  }
@@ -22402,7 +22326,8 @@ var MulticastTransport = class _MulticastTransport {
22402
22326
  }
22403
22327
  if (this.#watchdog !== null) return;
22404
22328
  if (!this.#onFailback) return;
22405
- if (this.#failbackWindowMs === null) {
22329
+ if (this.#failbackWindowMs === null) return;
22330
+ if (this.#failbackWindowMs === void 0) {
22406
22331
  throw new Error(
22407
22332
  "[MulticastTransport] Failback watchdog requires a catalog-derived window"
22408
22333
  );
@@ -22412,7 +22337,7 @@ var MulticastTransport = class _MulticastTransport {
22412
22337
  #armWatchdog(delayMs) {
22413
22338
  this.#watchdog = setTimeout(() => {
22414
22339
  this.#watchdog = null;
22415
- if (this.#disposed || !this.#onFailback || this.#failbackWindowMs === null) return;
22340
+ if (this.#disposed || !this.#onFailback || this.#failbackWindowMs == null) return;
22416
22341
  const silentMs = performance.now() - this.#lastPacketAtMs;
22417
22342
  if (silentMs < this.#failbackWindowMs) {
22418
22343
  this.#armWatchdog(Math.ceil(this.#failbackWindowMs - silentMs));
@@ -22452,18 +22377,320 @@ function catalogAmtNetworkSource(networkSource) {
22452
22377
  return void 0;
22453
22378
  }
22454
22379
 
22455
- // src/multicast-manager.ts
22456
- function freezeIngress(tracks) {
22457
- return Object.freeze({
22458
- tracks: Object.freeze(
22459
- [...tracks].map(({ name, packetId }) => Object.freeze({ name, packetId }))
22460
- )
22461
- });
22380
+ // src/catalog-fec-params.ts
22381
+ var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
22382
+ function timescaleHz(value) {
22383
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
22462
22384
  }
22463
- function sameIdSet(a, b) {
22464
- if (a.size !== b.size) return false;
22465
- for (const id of a) if (!b.has(id)) return false;
22466
- return true;
22385
+ function catalogGroupDurationMs(rendition) {
22386
+ if (!rendition) return void 0;
22387
+ const ticks = numericField(rendition.groupDurationTicks);
22388
+ const ms = numericField(rendition.groupDurationMs);
22389
+ const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
22390
+ if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
22391
+ return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
22392
+ }
22393
+ return ms;
22394
+ }
22395
+ function resolveCatalogTrackGroupDurationMs(catalog, track) {
22396
+ const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
22397
+ return catalogGroupDurationMs(renditions?.[track.name]);
22398
+ }
22399
+ function positiveNumber(value) {
22400
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
22401
+ }
22402
+ function catalogKeyframeCadenceMs(rendition) {
22403
+ if (!rendition) return void 0;
22404
+ const timescale = positiveNumber(rendition.timescale);
22405
+ const ticksToMs = (ticks) => {
22406
+ const value = positiveNumber(ticks);
22407
+ return value !== void 0 && timescale !== void 0 ? value * 1e3 / timescale : void 0;
22408
+ };
22409
+ return ticksToMs(rendition.keyframeIntervalTicks) ?? positiveNumber(rendition.keyframeIntervalMs) ?? ticksToMs(rendition.keyframeDurationTicks) ?? positiveNumber(rendition.keyframeDurationMs) ?? ticksToMs(rendition.gopDurationTicks) ?? positiveNumber(rendition.gopDurationMs);
22410
+ }
22411
+ function catalogFecMediaUnitsPerSecond(type, rendition, endpointSampleRate) {
22412
+ if (type === "video") return positiveNumber(rendition?.framerate);
22413
+ const sampleRate = positiveNumber(endpointSampleRate ?? rendition?.sampleRate ?? rendition?.timescale);
22414
+ return sampleRate === void 0 ? void 0 : sampleRate / 1024;
22415
+ }
22416
+ var NO_FEC = {
22417
+ fecK: 0,
22418
+ fecRepairCount: 0,
22419
+ fecTotalRepairCount: 0,
22420
+ fecSymbolSize: 0,
22421
+ fecInterleaveMs: 0
22422
+ };
22423
+ function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
22424
+ const layers = params.repairLayers;
22425
+ if (layers === void 0 || layers.length === 0) return params.fecRepairCount;
22426
+ const joined = new Set(joinedRepairTrackNames);
22427
+ let total = 0;
22428
+ for (const layer of layers) {
22429
+ if (joined.has(layer.repairTrackName)) total += layer.repairSymbols;
22430
+ }
22431
+ return total;
22432
+ }
22433
+ function recordOf2(value) {
22434
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
22435
+ }
22436
+ function msfTracks(catalog) {
22437
+ const raw = catalog.tracks;
22438
+ if (!Array.isArray(raw)) return void 0;
22439
+ return { tracks: raw.map((t) => recordOf2(t) ?? {}) };
22440
+ }
22441
+ function resolveRepairLayers(catalog, source) {
22442
+ if (!catalog) return void 0;
22443
+ const sourceName = typeof source === "string" ? source : source.name;
22444
+ const hang = catalog;
22445
+ const roles = typeof source === "string" ? ["video", "audio"] : source.type === "audio" ? ["audio"] : source.type === "video" ? ["video"] : ["video", "audio"];
22446
+ const role = roles.find((r) => recordOf2(hang[r]?.renditions?.[sourceName]) !== void 0);
22447
+ if (role !== void 0) {
22448
+ const renditionPath = `$.${role}.renditions[${JSON.stringify(sourceName)}]`;
22449
+ const rendition = recordOf2(hang[role].renditions[sourceName]);
22450
+ rejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);
22451
+ const repairRenditions = recordOf2(hang.repair?.renditions) ?? {};
22452
+ const descriptors2 = Object.entries(repairRenditions).map(([name, entry]) => {
22453
+ const path = `$.repair.renditions[${JSON.stringify(name)}]`;
22454
+ const fields = recordOf2(entry);
22455
+ if (fields === void 0) {
22456
+ throwRepairContractError("track_repair_layer", path, "a repair rendition must be an object");
22457
+ }
22458
+ rejectRepairTrackGeometry(path, fields, throwRepairContractError);
22459
+ return describeRepairTrack(name, path, fields);
22460
+ });
22461
+ const fecOfRendition = (entry) => {
22462
+ const fec2 = recordOf2(recordOf2(entry)?.["fec"]);
22463
+ return fec2 !== void 0 && fec2["algorithm"] !== "none" ? fec2 : void 0;
22464
+ };
22465
+ const mediaRenditions = [
22466
+ ...Object.values(recordOf2(hang.video?.renditions) ?? {}),
22467
+ ...Object.values(recordOf2(hang.audio?.renditions) ?? {})
22468
+ ];
22469
+ const reachability2 = repairReachability(mediaRenditions.map((entry) => recordOf2(entry)?.["fec"]));
22470
+ for (const d of descriptors2) {
22471
+ validateRepairTrackFields(d, throwRepairContractError);
22472
+ assertRepairTrackReachable(d, reachability2, throwRepairContractError);
22473
+ const target = dependsTarget(d);
22474
+ if (target === void 0) continue;
22475
+ const targetFec = fecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);
22476
+ if (targetFec === void 0) {
22477
+ throwRepairContractError(
22478
+ "track_repair_depends",
22479
+ `${d.path}.depends`,
22480
+ `${JSON.stringify(target)} is not a video or audio rendition with FEC`
22481
+ );
22482
+ }
22483
+ }
22484
+ const layers2 = assembleRepairLayers(
22485
+ geometryOf(sourceName, `${renditionPath}.fec`, recordOf2(rendition["fec"])),
22486
+ descriptors2,
22487
+ throwRepairContractError
22488
+ );
22489
+ return catalogFecUnavailable(rendition["fec"], `${renditionPath}.fec`) === void 0 ? layers2 : void 0;
22490
+ }
22491
+ const msf = msfTracks(catalog);
22492
+ const msfIndex = msf ? msf.tracks.findIndex((t) => t["name"] === sourceName) : -1;
22493
+ if (!msf || msfIndex < 0) return void 0;
22494
+ const track = msf.tracks[msfIndex];
22495
+ const fec = recordOf2(track["fec"]);
22496
+ const packagingOf = (t) => t["packaging"];
22497
+ const fecOfTrack = (t) => {
22498
+ const trackFec = recordOf2(t?.["fec"]);
22499
+ return trackFec !== void 0 && trackFec["algorithm"] !== "none" ? trackFec : void 0;
22500
+ };
22501
+ const descriptors = [];
22502
+ const mediaTracks = [];
22503
+ msf.tracks.forEach((t, i) => {
22504
+ const path = `$.tracks[${i}]`;
22505
+ if (packagingOf(t) === "fec-repair") {
22506
+ rejectRepairTrackGeometry(path, t, throwRepairContractError);
22507
+ if (typeof t["name"] === "string") descriptors.push(describeRepairTrack(t["name"], path, t));
22508
+ return;
22509
+ }
22510
+ rejectRepairFieldsOnMediaTrack(path, t, throwRepairContractError);
22511
+ mediaTracks.push(t);
22512
+ });
22513
+ const reachability = repairReachability(mediaTracks.map((t) => t["fec"]));
22514
+ for (const d of descriptors) {
22515
+ validateRepairTrackFields(d, throwRepairContractError);
22516
+ assertRepairTrackReachable(d, reachability, throwRepairContractError);
22517
+ const target = dependsTarget(d);
22518
+ if (target === void 0) continue;
22519
+ const targetTrack = msf.tracks.find((t) => t["name"] === target);
22520
+ if (targetTrack === void 0 || packagingOf(targetTrack) !== "mmtp" || fecOfTrack(targetTrack) === void 0) {
22521
+ throwRepairContractError(
22522
+ "track_repair_depends",
22523
+ `${d.path}.depends`,
22524
+ `${JSON.stringify(target)} is not an mmtp track with FEC`
22525
+ );
22526
+ }
22527
+ }
22528
+ const layers = assembleRepairLayers(
22529
+ geometryOf(sourceName, `$.tracks[${msfIndex}].fec`, fec),
22530
+ descriptors,
22531
+ throwRepairContractError,
22532
+ { layerZeroMustBeDeclared: true }
22533
+ );
22534
+ return catalogFecUnavailable(fec, `$.tracks[${msfIndex}].fec`) === void 0 ? layers : void 0;
22535
+ }
22536
+ function moqtPriority2(value) {
22537
+ return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
22538
+ }
22539
+ function catalogRepairTarget(catalog, source) {
22540
+ const repair = resolveRepairLayers(catalog, source);
22541
+ const layerZero = repair?.baseLayers[0];
22542
+ if (!catalog || !layerZero) return null;
22543
+ const sourceName = typeof source === "string" ? source : source.name;
22544
+ const msf = msfTracks(catalog);
22545
+ const entry = msf?.tracks.find((t) => t["name"] === sourceName);
22546
+ const hang = catalog;
22547
+ const roles = typeof source === "string" ? ["video", "audio"] : source.type === "video" ? ["video"] : source.type === "audio" ? ["audio"] : ["video", "audio"];
22548
+ const rendition = roles.map((role) => hang[role]?.renditions?.[sourceName]).find((r) => recordOf2(r) !== void 0);
22549
+ const fec = rendition === void 0 ? recordOf2(entry?.["fec"]) : recordOf2(rendition.fec);
22550
+ if (!fec || !(catalogFecInterleaveMs(fec) ?? 0)) return null;
22551
+ const sourcePriority = entry === void 0 ? null : moqtPriority2(entry["priority"]);
22552
+ return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
22553
+ }
22554
+ function geometryOf(name, path, fec) {
22555
+ return {
22556
+ name,
22557
+ path,
22558
+ algorithm: fec?.["algorithm"],
22559
+ sourceSymbols: fec?.["sourceSymbols"],
22560
+ repairSymbols: fec?.["repairSymbols"],
22561
+ repairTrack: fec?.["repairTrack"]
22562
+ };
22563
+ }
22564
+ function numericField(value) {
22565
+ const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
22566
+ return Number.isFinite(n) && n > 0 ? n : void 0;
22567
+ }
22568
+ function nonNegativeField(value) {
22569
+ const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
22570
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
22571
+ }
22572
+ function blockProfileUnavailable(value, path, declaresFecInstance) {
22573
+ try {
22574
+ readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
22575
+ return void 0;
22576
+ } catch (error) {
22577
+ if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
22578
+ throw error;
22579
+ }
22580
+ return Object.freeze({ code: error.code, path: error.path, message: error.message });
22581
+ }
22582
+ }
22583
+ function isoSpanUnavailable(fec, path, declaresFecInstance) {
22584
+ if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
22585
+ return Object.freeze({
22586
+ code: "track_fec",
22587
+ path,
22588
+ message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
22589
+ });
22590
+ }
22591
+ function catalogFecUnavailable(fec, fecPath) {
22592
+ const block = recordOf2(fec);
22593
+ if (block === void 0) return void 0;
22594
+ const declaresFecInstance = block["algorithm"] !== "none";
22595
+ return blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
22596
+ }
22597
+ function fecParams(catalog, track, rendition, fps, sampleRate) {
22598
+ const mediaRate = sampleRate === void 0 ? { fps } : { fps, sampleRate };
22599
+ const fec = rendition?.fec;
22600
+ if (!fec || !track) return { ...NO_FEC, ...mediaRate };
22601
+ const repair = resolveRepairLayers(catalog, track);
22602
+ const fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;
22603
+ if (!catalogDeclaresFec(fec)) {
22604
+ const declaresFecInstance = fec.algorithm !== "none";
22605
+ const kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;
22606
+ const tValid = Number.isSafeInteger(fec.symbolSize) && (fec.symbolSize ?? 0) > 0;
22607
+ if (declaresFecInstance && !(kValid && tValid)) {
22608
+ throwRepairContractError(
22609
+ "track_fec",
22610
+ `${fecPath}.${kValid ? "symbolSize" : "sourceSymbols"}`,
22611
+ "must be a positive integer"
22612
+ );
22613
+ }
22614
+ const fecUnavailable = catalogFecUnavailable(fec, fecPath);
22615
+ return fecUnavailable === void 0 ? { ...NO_FEC, ...mediaRate } : { ...NO_FEC, ...mediaRate, fecUnavailable };
22616
+ }
22617
+ const fecBlockProfile = readFecBlockProfile(fec.blockProfile, `${fecPath}.blockProfile`, true, throwRepairContractError);
22618
+ return {
22619
+ fecK: fec.sourceSymbols ?? 0,
22620
+ fecBlockProfile,
22621
+ fecRepairCount: fec.repairSymbols ?? 0,
22622
+ // The wire RSB_length: every base layer's packets carry the base total (§7.1).
22623
+ fecTotalRepairCount: repair?.totalBaseRepairSymbols ?? fec.repairSymbols ?? 0,
22624
+ fecSymbolSize: fec.symbolSize ?? 0,
22625
+ // Zero/absence leaves the elapsed deadline unknown; geometry stays in-band.
22626
+ fecInterleaveMs: catalogFecInterleaveMs(fec) ?? 0,
22627
+ // Additive, never a multiplier on the span: jitter belongs to the network
22628
+ // path and the span belongs to the encoder, so scaling one by the other
22629
+ // hands one network different slack per track. `numericField` rejects 0,
22630
+ // so it cannot be reused here — a declared 0 is a legitimate value.
22631
+ fecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),
22632
+ repairTrackName: fec.repairTrack,
22633
+ repairLayers: repair?.baseLayers,
22634
+ repairOverlays: repair?.overlays,
22635
+ ...mediaRate
22636
+ };
22637
+ }
22638
+ function resolveCatalogTrackFec(catalog, tracks) {
22639
+ const videoTrackName = tracks.find((t) => t.type === "video")?.name;
22640
+ const audioTrackName = tracks.find((t) => t.type === "audio")?.name;
22641
+ const videoRenditions = catalog?.video?.renditions;
22642
+ const audioRenditions = catalog?.audio?.renditions;
22643
+ const video = videoTrackName ? videoRenditions?.[videoTrackName] : void 0;
22644
+ const audio = audioTrackName ? audioRenditions?.[audioTrackName] : void 0;
22645
+ const catalogFps = numericField(video?.framerate);
22646
+ const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
22647
+ const audioFps = sampleRate ? sampleRate / 1024 : void 0;
22648
+ const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, video, catalogFps);
22649
+ const audioParams = fecParams(
22650
+ catalog,
22651
+ audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
22652
+ audio,
22653
+ audioFps,
22654
+ sampleRate
22655
+ );
22656
+ const forTrack = (track) => {
22657
+ if (track.type === "audio") {
22658
+ if (track.name === audioTrackName) return audioParams;
22659
+ const rendition2 = audioRenditions?.[track.name];
22660
+ if (!rendition2) return { ...NO_FEC };
22661
+ const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
22662
+ return fecParams(catalog, { name: track.name, type: "audio" }, rendition2, rate ? rate / 1024 : void 0, rate);
22663
+ }
22664
+ if (track.name === videoTrackName) return videoParams;
22665
+ const rendition = videoRenditions?.[track.name];
22666
+ if (!rendition) return { ...NO_FEC };
22667
+ return fecParams(catalog, { name: track.name, type: "video" }, rendition, numericField(rendition.framerate));
22668
+ };
22669
+ return {
22670
+ videoTrackName,
22671
+ audioTrackName,
22672
+ missingVideoRendition: videoTrackName && videoRenditions && !video ? videoTrackName : void 0,
22673
+ missingAudioRendition: audioTrackName && audioRenditions && !audio ? audioTrackName : void 0,
22674
+ videoForKeyframeRepair: video,
22675
+ catalogFps,
22676
+ video: videoParams,
22677
+ audio: audioParams,
22678
+ forTrack
22679
+ };
22680
+ }
22681
+
22682
+ // src/multicast-manager.ts
22683
+ function freezeIngress(tracks) {
22684
+ return Object.freeze({
22685
+ tracks: Object.freeze(
22686
+ [...tracks].map(({ name, packetId }) => Object.freeze({ name, packetId }))
22687
+ )
22688
+ });
22689
+ }
22690
+ function sameIdSet(a, b) {
22691
+ if (a.size !== b.size) return false;
22692
+ for (const id of a) if (!b.has(id)) return false;
22693
+ return true;
22467
22694
  }
22468
22695
  function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
22469
22696
  return {
@@ -22762,9 +22989,8 @@ function repairTrackNamesBySource(tracks) {
22762
22989
  bySource.set(source, names);
22763
22990
  };
22764
22991
  for (const track of tracks) {
22765
- if (track.fec !== void 0 && isFecCatalogConfig(track.fec) && track.fec.repairTrack) {
22766
- add(track.name, track.fec.repairTrack);
22767
- }
22992
+ const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
22993
+ if (typeof repairTrack === "string") add(track.name, repairTrack);
22768
22994
  if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
22769
22995
  add(track.depends[0], track.name);
22770
22996
  }
@@ -22774,6 +23000,7 @@ function repairTrackNamesBySource(tracks) {
22774
23000
  var MulticastManager = class {
22775
23001
  #fec;
22776
23002
  #router;
23003
+ #hostMfuTimeoutMs;
22777
23004
  #transport;
22778
23005
  #reportedFecDiagnosticsFailure = false;
22779
23006
  // ── Stream-global FEC timing reduction ──
@@ -22872,6 +23099,7 @@ var MulticastManager = class {
22872
23099
  this.#emitRepairLifecycle(decision);
22873
23100
  }
22874
23101
  });
23102
+ this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
22875
23103
  this.#transport = new MulticastTransport((packet, ingress) => {
22876
23104
  this.#router.feedPacket(packet, "multicast", void 0, ingress);
22877
23105
  });
@@ -23088,7 +23316,6 @@ var MulticastManager = class {
23088
23316
  ...this.#streamFecBaseline ? [this.#streamFecBaseline] : [],
23089
23317
  ...this.#trackFecTiming.values()
23090
23318
  ];
23091
- if (contributions.length === 0) return;
23092
23319
  let deliveryWindowMs;
23093
23320
  let minimumFailbackWindowMs;
23094
23321
  let mfuTimeoutMs;
@@ -23120,15 +23347,9 @@ var MulticastManager = class {
23120
23347
  }
23121
23348
  }
23122
23349
  const failbackWindowMs = deliveryWindowMs === void 0 ? minimumFailbackWindowMs : Math.max(deliveryWindowMs, minimumFailbackWindowMs ?? 0);
23123
- if (failbackWindowMs !== void 0) {
23124
- this.#transport.setFailbackWindowMs(failbackWindowMs);
23125
- }
23126
- if (mfuTimeoutMs !== void 0) {
23127
- this.#router.setMfuTimeoutMs(mfuTimeoutMs);
23128
- }
23129
- if (reorderWindowFrames !== void 0) {
23130
- this.#router.setReorderWindow(reorderWindowFrames);
23131
- }
23350
+ this.#transport.setFailbackWindowMs(failbackWindowMs ?? null);
23351
+ this.#router.setMfuTimeoutMs(mfuTimeoutMs ?? this.#hostMfuTimeoutMs);
23352
+ this.#router.setReorderWindow(reorderWindowFrames ?? 0);
23132
23353
  }
23133
23354
  /**
23134
23355
  * Configure one track's FEC geometry, block mapper, and block-inactivity
@@ -23155,9 +23376,9 @@ var MulticastManager = class {
23155
23376
  `[MulticastManager] configureTrackFec(${packetId}) catalogTrackName must be non-empty when given`
23156
23377
  );
23157
23378
  }
23158
- if (config.fecK === void 0 || config.fecInterleaveDepthMs === void 0) {
23379
+ if (config.fecK === void 0) {
23159
23380
  throw new Error(
23160
- `[MulticastManager] configureTrackFec(${packetId}) requires fecK and fecInterleaveDepthMs. Derive the per-track FEC geometry from the stream catalog.`
23381
+ `[MulticastManager] configureTrackFec(${packetId}) requires fecK. Derive the per-track FEC geometry from the stream catalog.`
23161
23382
  );
23162
23383
  }
23163
23384
  const blockProfile = parseFecBlockProfile(
@@ -23165,7 +23386,10 @@ var MulticastManager = class {
23165
23386
  `[MulticastManager] configureTrackFec(${packetId}) fecBlockProfile`
23166
23387
  );
23167
23388
  assertPositiveGeometry("fecK", config.fecK);
23168
- assertPositiveGeometry("fecInterleaveDepthMs", config.fecInterleaveDepthMs);
23389
+ const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
23390
+ if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
23391
+ throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
23392
+ }
23169
23393
  assertPositiveGeometry("fecSymbolSize", config.fecSymbolSize);
23170
23394
  assertPositiveGeometry("fps", config.fps);
23171
23395
  assertPositiveGeometry("bandwidth", config.bandwidth);
@@ -23181,8 +23405,8 @@ var MulticastManager = class {
23181
23405
  "fecReorderToleranceMs",
23182
23406
  config.fecReorderToleranceMs
23183
23407
  );
23184
- const deliveryWindowMs = config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
23185
- interleaveDepthMs: config.fecInterleaveDepthMs,
23408
+ const deliveryWindowMs = interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
23409
+ interleaveDepthMs,
23186
23410
  sourceSymbols: config.fecK,
23187
23411
  repairSymbols: config.fecRepairSymbols,
23188
23412
  symbolSize: config.fecSymbolSize,
@@ -23191,10 +23415,10 @@ var MulticastManager = class {
23191
23415
  jitterBufferMs: config.fecReorderToleranceMs
23192
23416
  }) : void 0;
23193
23417
  const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);
23194
- const geometry = trackKind === void 0 ? void 0 : deriveBlockClassifierGeometry({
23418
+ const geometry = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
23195
23419
  trackKind,
23196
23420
  framerate: config.fps,
23197
- interleaveDepthMs: config.fecInterleaveDepthMs,
23421
+ interleaveDepthMs,
23198
23422
  timescale: config.timescale,
23199
23423
  keyframeIntervalTicks: config.keyframeIntervalTicks,
23200
23424
  keyframeIntervalMs: config.keyframeIntervalMs
@@ -23203,7 +23427,7 @@ var MulticastManager = class {
23203
23427
  blockProfile,
23204
23428
  k: config.fecK,
23205
23429
  symbolSize: config.fecSymbolSize,
23206
- interleaveDepthMs: config.fecInterleaveDepthMs,
23430
+ interleaveDepthMs,
23207
23431
  fps: config.fps,
23208
23432
  timeoutMs: deliveryWindowMs,
23209
23433
  ...trackKind === void 0 ? {} : { trackKind },
@@ -23214,7 +23438,7 @@ var MulticastManager = class {
23214
23438
  this.#configureTrackMapper(packetId, fecConfig);
23215
23439
  this.#trackFecTiming.set(packetId, {
23216
23440
  deliveryWindowMs,
23217
- interleaveDepthMs: config.fecInterleaveDepthMs,
23441
+ interleaveDepthMs,
23218
23442
  fps: config.fps,
23219
23443
  k: config.fecK,
23220
23444
  repairSymbols: config.fecRepairSymbols,
@@ -23223,6 +23447,10 @@ var MulticastManager = class {
23223
23447
  this.#applyStreamGlobalFecTiming();
23224
23448
  }
23225
23449
  configureFec(config) {
23450
+ const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
23451
+ if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
23452
+ throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
23453
+ }
23226
23454
  const minimumFailbackWindowMs = config.minimumFailbackWindowMs;
23227
23455
  if (minimumFailbackWindowMs !== void 0 && (!Number.isFinite(minimumFailbackWindowMs) || minimumFailbackWindowMs <= 0)) {
23228
23456
  throw new Error(
@@ -23234,7 +23462,7 @@ var MulticastManager = class {
23234
23462
  config.fecReorderToleranceMs
23235
23463
  );
23236
23464
  const deliveryWindowMs = config.fecK && config.fecK > 0 && config.fecInterleaveDepthMs && config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
23237
- interleaveDepthMs: config.fecInterleaveDepthMs,
23465
+ interleaveDepthMs,
23238
23466
  sourceSymbols: config.fecK,
23239
23467
  repairSymbols: config.fecRepairSymbols,
23240
23468
  symbolSize: config.fecSymbolSize,
@@ -23245,13 +23473,13 @@ var MulticastManager = class {
23245
23473
  this.#fec.configure({
23246
23474
  k: config.fecK,
23247
23475
  symbolSize: config.fecSymbolSize,
23248
- interleaveDepthMs: config.fecInterleaveDepthMs,
23476
+ interleaveDepthMs,
23249
23477
  fps: config.fps,
23250
23478
  timeoutMs: deliveryWindowMs
23251
23479
  });
23252
23480
  this.#streamFecBaseline = {
23253
23481
  deliveryWindowMs,
23254
- interleaveDepthMs: config.fecInterleaveDepthMs,
23482
+ interleaveDepthMs,
23255
23483
  fps: config.fps,
23256
23484
  k: config.fecK,
23257
23485
  repairSymbols: config.fecRepairSymbols,
@@ -23300,6 +23528,7 @@ var MulticastManager = class {
23300
23528
  }
23301
23529
  setMfuTimeoutMs(ms) {
23302
23530
  this.#router.setMfuTimeoutMs(ms);
23531
+ this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
23303
23532
  }
23304
23533
  setTrackCodec(packetId, codec) {
23305
23534
  this.#router.setTrackCodec(packetId, codec);
@@ -23322,6 +23551,7 @@ var MulticastManager = class {
23322
23551
  onTrackPacket(packetId, cb) {
23323
23552
  this.#router.onTrackPacket(packetId, cb);
23324
23553
  }
23554
+ /** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
23325
23555
  onInitSegment(cb) {
23326
23556
  this.#router.onInitSegment(cb);
23327
23557
  }
@@ -23633,7 +23863,10 @@ var MulticastManager = class {
23633
23863
  console.warn("[MulticastManager] rendition switch onCancel failed", error);
23634
23864
  }
23635
23865
  }
23636
- /** Release what the manager and FEC hold for a stream whose packet ID the router retired. */
23866
+ /**
23867
+ * Release what the manager and FEC hold for a stream whose packet ID the
23868
+ * router retired, or whose FEC a catalog reconfiguration no longer configures.
23869
+ */
23637
23870
  #releaseRetiredStream(stream) {
23638
23871
  for (const observer of this.#trackResetObservers) {
23639
23872
  try {
@@ -23652,7 +23885,10 @@ var MulticastManager = class {
23652
23885
  for (const [key, stats] of [...this.#blockPolicyStats]) {
23653
23886
  if (stats.packetId === stream.packetId) this.#blockPolicyStats.delete(key);
23654
23887
  }
23655
- this.#trackFecTiming.delete(stream.packetId);
23888
+ if (this.#trackFecTiming.delete(stream.packetId)) {
23889
+ this.#applyStreamGlobalFecTiming();
23890
+ }
23891
+ this.#multiTrackFecTracks.delete(stream.packetId);
23656
23892
  this.#fec.releasePacket(stream.packetId);
23657
23893
  }
23658
23894
  /** Feed a raw packet from an external source (AMT tunnel, ASM subscription).
@@ -23697,12 +23933,53 @@ var MulticastManager = class {
23697
23933
  * D-17: Builds altGroup → track-name index for getSwitchingSet().
23698
23934
  * D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
23699
23935
  *
23936
+ * FEC availability is per track (draft-ramadan-moq-fec §5.1). A media track
23937
+ * whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is
23938
+ * implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span
23939
+ * it cannot use, is consumed without FEC: it gets no decoder, and none of
23940
+ * its repair tracks (layer 0 by `fec.repairTrack`, further layers and the
23941
+ * overlay by `depends`) is given an owner, so the router drops their packets
23942
+ * before any decoder. Those legs keep the `repair` role and their owner's
23943
+ * switching set. Neither the call nor any other track's FEC fails on that
23944
+ * account; `trackFecUnavailable(name)` reports the reason. FEC a previous
23945
+ * call configured on a packetId this call does not is released
23946
+ * (FECManager.releasePacket); frames its release gate held are delivered in
23947
+ * order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.
23948
+ * Geometry a decoder could not configure (K, P, T, `repairTrack`,
23949
+ * algorithm) still throws.
23950
+ *
23700
23951
  * @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
23701
23952
  * @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
23702
23953
  * a factory returning a distinct decoder per stream.
23703
23954
  */
23704
23955
  configureMultiTrack(catalog, wasmDecoderSource) {
23705
23956
  const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
23957
+ const declaredRepairNames = /* @__PURE__ */ new Set();
23958
+ for (const names of repairNamesBySource.values()) {
23959
+ for (const name of names) declaredRepairNames.add(name);
23960
+ }
23961
+ const fecUnavailableByTrack = /* @__PURE__ */ new Map();
23962
+ for (let i = 0; i < catalog.tracks.length; i++) {
23963
+ const track = catalog.tracks[i];
23964
+ if (track.fec === void 0 || track.packaging === "fec-repair" || declaredRepairNames.has(track.name)) {
23965
+ continue;
23966
+ }
23967
+ const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);
23968
+ if (fecUnavailable === void 0) continue;
23969
+ fecUnavailableByTrack.set(track.name, fecUnavailable);
23970
+ const layerZero = track.fec.repairTrack;
23971
+ if (typeof layerZero === "string" && layerZero.length > 0) {
23972
+ const names = repairNamesBySource.get(track.name) ?? /* @__PURE__ */ new Set();
23973
+ names.add(layerZero);
23974
+ repairNamesBySource.set(track.name, names);
23975
+ }
23976
+ }
23977
+ const unownedRepairNames = /* @__PURE__ */ new Set();
23978
+ for (const sourceName of fecUnavailableByTrack.keys()) {
23979
+ for (const repairName of repairNamesBySource.get(sourceName) ?? []) {
23980
+ unownedRepairNames.add(repairName);
23981
+ }
23982
+ }
23706
23983
  const repairOwnerGroup = /* @__PURE__ */ new Map();
23707
23984
  const repairOwnerAltGroup = /* @__PURE__ */ new Map();
23708
23985
  for (const track of catalog.tracks) {
@@ -23755,11 +24032,13 @@ var MulticastManager = class {
23755
24032
  for (let i = 0; i < catalog.tracks.length; i++) {
23756
24033
  const track = catalog.tracks[i];
23757
24034
  if (resolvedRoles[i] === "repair" || track.fec === void 0) continue;
23758
- if (!isFecCatalogConfig(track.fec)) {
24035
+ const unavailable = fecUnavailableByTrack.has(track.name);
24036
+ if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {
23759
24037
  throw new Error(
23760
24038
  `[MulticastManager] invalid fec config for track '${track.name}'`
23761
24039
  );
23762
24040
  }
24041
+ if (unavailable) continue;
23763
24042
  if (track.packetId !== void 0) fecTrackIndexes.push(i);
23764
24043
  }
23765
24044
  const fecDecoders = /* @__PURE__ */ new Map();
@@ -23788,6 +24067,20 @@ var MulticastManager = class {
23788
24067
  this.#switchingSets.clear();
23789
24068
  this.#repairPacketIds.clear();
23790
24069
  this.#router.clearRepairPacketIdOwners();
24070
+ const fecPacketIds = new Set([...fecDecoders.keys()].map((i) => catalog.tracks[i].packetId));
24071
+ for (const [packetId, catalogTrackName] of this.#multiTrackFecTracks) {
24072
+ if (fecPacketIds.has(packetId)) continue;
24073
+ if (!this.#fec.isTrackConfigured(packetId)) {
24074
+ this.#multiTrackFecTracks.delete(packetId);
24075
+ continue;
24076
+ }
24077
+ this.#router.releaseFecPacket(packetId);
24078
+ this.#releaseRetiredStream({ packetId, catalogTrackName });
24079
+ }
24080
+ this.#fecUnavailableByTrack.clear();
24081
+ for (const [name, reason] of fecUnavailableByTrack) {
24082
+ this.#fecUnavailableByTrack.set(name, reason);
24083
+ }
23791
24084
  const catalogTracksByName = new Map(
23792
24085
  catalog.tracks.map((track) => [track.name, track])
23793
24086
  );
@@ -23809,10 +24102,11 @@ var MulticastManager = class {
23809
24102
  }
23810
24103
  continue;
23811
24104
  }
23812
- if (track.fec !== void 0) {
24105
+ if (track.fec !== void 0 && !fecUnavailableByTrack.has(track.name)) {
23813
24106
  const fecCfg = track.fec;
23814
24107
  if (track.packetId !== void 0) {
23815
24108
  for (const repairName of repairNamesBySource.get(track.name) ?? []) {
24109
+ if (unownedRepairNames.has(repairName)) continue;
23816
24110
  const repairTrack = catalogTracksByName.get(repairName);
23817
24111
  if (repairTrack?.packetId !== void 0) {
23818
24112
  this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
@@ -23833,17 +24127,18 @@ var MulticastManager = class {
23833
24127
  blockProfile,
23834
24128
  k: fecCfg.sourceSymbols,
23835
24129
  symbolSize: fecCfg.symbolSize,
23836
- interleaveDepthMs: fecCfg.interleaveDepthMs,
24130
+ interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
23837
24131
  fps: track.framerate,
23838
24132
  trackKind: "video"
23839
24133
  },
23840
24134
  track.name
23841
24135
  );
24136
+ this.#multiTrackFecTracks.set(track.packetId, track.name);
23842
24137
  this.#configureTrackMapper(track.packetId, {
23843
24138
  blockProfile,
23844
24139
  k: fecCfg.sourceSymbols,
23845
24140
  symbolSize: fecCfg.symbolSize,
23846
- interleaveDepthMs: fecCfg.interleaveDepthMs,
24141
+ interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
23847
24142
  fps: track.framerate
23848
24143
  });
23849
24144
  this.#router.setPacketIdType(track.packetId, "video");
@@ -23851,6 +24146,24 @@ var MulticastManager = class {
23851
24146
  }
23852
24147
  }
23853
24148
  }
24149
+ /**
24150
+ * Why `configureMultiTrack` consumes the named catalog track without FEC
24151
+ * although its `fec` block declares one (draft-ramadan-moq-fec §5.1): no
24152
+ * implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.
24153
+ * the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an
24154
+ * invalid `iso-ssbg1-v1` span (`track_fec`). Absent/zero spans keep in-band
24155
+ * decoding enabled. The same reason the hang
24156
+ * resolver reports (`catalogFecUnavailable`). Undefined for every other
24157
+ * track, and for every track before the first call; each call replaces the
24158
+ * previous call's answers.
24159
+ */
24160
+ trackFecUnavailable(trackName) {
24161
+ return this.#fecUnavailableByTrack.get(trackName);
24162
+ }
24163
+ /** Per-track FEC availability from the last `configureMultiTrack` call. */
24164
+ #fecUnavailableByTrack = /* @__PURE__ */ new Map();
24165
+ /** packetId → catalog track name of the FEC the last `configureMultiTrack` installed. */
24166
+ #multiTrackFecTracks = /* @__PURE__ */ new Map();
23854
24167
  /**
23855
24168
  * Return the track names belonging to the given altGroup switching set.
23856
24169
  * D-17: altGroup groups base+delta+repair into one switching set.
@@ -24061,31 +24374,62 @@ var MulticastManager = class {
24061
24374
 
24062
24375
  // src/fec-worker-setup.ts
24063
24376
  async function setupFecWorkerForTrack(opts) {
24064
- if (!Number.isFinite(opts.fecInterleaveMs) || opts.fecInterleaveMs <= 0) {
24377
+ if (!Number.isFinite(opts.fecInterleaveMs) || !Number.isSafeInteger(opts.fecInterleaveMs) || opts.fecInterleaveMs < 0) {
24065
24378
  throw new Error(
24066
- `[FEC] interleaveDepthMs (opts.fecInterleaveMs) must be finite and > 0 (got ${opts.fecInterleaveMs})`
24379
+ `[FEC] interleaveDepthMs (opts.fecInterleaveMs) must be a finite non-negative integer (got ${opts.fecInterleaveMs})`
24067
24380
  );
24068
24381
  }
24069
- if (!Number.isFinite(opts.mediaUnitDurationMs) || opts.mediaUnitDurationMs <= 0) {
24382
+ if (opts.fecReorderToleranceMs !== void 0 && (!Number.isFinite(opts.fecReorderToleranceMs) || opts.fecReorderToleranceMs < 0)) {
24070
24383
  throw new Error(
24071
- `[FEC] mediaUnitDurationMs must be finite and > 0 (got ${opts.mediaUnitDurationMs})`
24384
+ `[FEC] fecReorderToleranceMs must be finite and >= 0 (got ${opts.fecReorderToleranceMs})`
24385
+ );
24386
+ }
24387
+ const hasCatalogTiming = opts.fecInterleaveMs > 0;
24388
+ const validateWorkerGeometry = () => {
24389
+ if (!Number.isSafeInteger(opts.fecK) || opts.fecK <= 0) {
24390
+ throw new Error(`[FEC] fecK must be a finite positive integer (got ${opts.fecK})`);
24391
+ }
24392
+ if (!Number.isSafeInteger(opts.fecSymbolSize) || opts.fecSymbolSize <= 0 || opts.fecSymbolSize % 8 !== 0) {
24393
+ throw new Error(
24394
+ `[FEC] fecSymbolSize must be a positive 8-byte-aligned integer (got ${opts.fecSymbolSize})`
24395
+ );
24396
+ }
24397
+ if (!Number.isSafeInteger(opts.fecRepairCount) || opts.fecRepairCount <= 0) {
24398
+ throw new Error(
24399
+ `[FEC] fecRepairCount must be a finite positive integer (got ${opts.fecRepairCount})`
24400
+ );
24401
+ }
24402
+ };
24403
+ let deliveryWindowMs;
24404
+ let interleaveDepthBlocks;
24405
+ if (!hasCatalogTiming) {
24406
+ validateWorkerGeometry();
24407
+ deliveryWindowMs = LOCAL_FEC_TIMEOUT_MS;
24408
+ interleaveDepthBlocks = LOCAL_FEC_RETENTION_BLOCKS;
24409
+ } else {
24410
+ const mediaUnitDurationMs = opts.mediaUnitDurationMs;
24411
+ if (typeof mediaUnitDurationMs !== "number" || !Number.isFinite(mediaUnitDurationMs) || mediaUnitDurationMs <= 0) {
24412
+ throw new Error(
24413
+ `[FEC] mediaUnitDurationMs must be finite and > 0 (got ${mediaUnitDurationMs})`
24414
+ );
24415
+ }
24416
+ validateWorkerGeometry();
24417
+ deliveryWindowMs = computeFecDeliveryWindowMs({
24418
+ interleaveDepthMs: opts.fecInterleaveMs,
24419
+ sourceSymbols: opts.fecK,
24420
+ repairSymbols: opts.fecRepairCount,
24421
+ symbolSize: opts.fecSymbolSize,
24422
+ mediaUnitsPerSecond: 1e3 / mediaUnitDurationMs,
24423
+ jitterBufferMs: opts.fecReorderToleranceMs
24424
+ });
24425
+ interleaveDepthBlocks = deriveFecRetentionBlocks(
24426
+ deliveryWindowMs,
24427
+ opts.fecInterleaveMs
24072
24428
  );
24073
24429
  }
24074
24430
  const blockProfile = parseFecBlockProfile(
24075
24431
  opts.fecBlockProfile,
24076
- `[FEC] ${opts.trackName} fec.blockProfile`
24077
- );
24078
- const deliveryWindowMs = computeFecDeliveryWindowMs({
24079
- interleaveDepthMs: opts.fecInterleaveMs,
24080
- sourceSymbols: opts.fecK,
24081
- repairSymbols: opts.fecRepairCount,
24082
- symbolSize: opts.fecSymbolSize,
24083
- mediaUnitsPerSecond: 1e3 / opts.mediaUnitDurationMs,
24084
- jitterBufferMs: opts.fecReorderToleranceMs
24085
- });
24086
- const interleaveDepthBlocks = deriveFecRetentionBlocks(
24087
- deliveryWindowMs,
24088
- opts.fecInterleaveMs
24432
+ `[FEC] ${opts.trackName} fec.blockProfile`
24089
24433
  );
24090
24434
  const origin = typeof location !== "undefined" && location.origin ? location.origin : "http://localhost";
24091
24435
  const workerUrl = new URL(opts.assetUrls.fecWorker, origin);
@@ -24140,9 +24484,10 @@ async function setupFecWorkerForTrack(opts) {
24140
24484
  k: opts.fecK,
24141
24485
  symbolSize: opts.fecSymbolSize,
24142
24486
  interleaveDepthMs: opts.fecInterleaveMs,
24143
- // FEC timing is expressed in media units. For audio this is the access-unit
24144
- // cadence (for example 1024/48kHz), not a video-only framerate field.
24145
- fps: 1e3 / opts.mediaUnitDurationMs,
24487
+ // No-span tracks have no catalog media cadence. The manager still gets
24488
+ // the local cleanup budget, but it must not use a guessed fps for timing
24489
+ // or classification.
24490
+ ...hasCatalogTiming ? { fps: 1e3 / opts.mediaUnitDurationMs } : {},
24146
24491
  timeoutMs: deliveryWindowMs
24147
24492
  }, opts.trackName);
24148
24493
  } catch (e) {
@@ -24182,6 +24527,28 @@ var TrackSourceConflictError = class extends Error {
24182
24527
  };
24183
24528
  var DEFAULT_CROSSFADE_FRAMES = 10;
24184
24529
  var DEFAULT_MAX_OVERLAY_BUFFER = 128;
24530
+ function assertDeltaFallbackConfig(cfg, prefix) {
24531
+ if (!isPositiveFinite2(cfg.framerate)) throw new Error(`${prefix}: framerate required and must be > 0`);
24532
+ if (!isPositiveFinite2(cfg.sourceSymbols))
24533
+ throw new Error(`${prefix}: sourceSymbols (K_delta) required and must be > 0`);
24534
+ if (!isPositiveFinite2(cfg.repairSymbols))
24535
+ throw new Error(`${prefix}: repairSymbols (P_delta) required and must be > 0`);
24536
+ if (cfg.deltaFailWindowBlocks !== void 0 && !(Number.isInteger(cfg.deltaFailWindowBlocks) && cfg.deltaFailWindowBlocks > 0)) {
24537
+ throw new Error(`${prefix}: deltaFailWindowBlocks must be a positive integer when present`);
24538
+ }
24539
+ if (cfg.deltaProbeIntervalMs !== void 0 && !isPositiveFinite2(cfg.deltaProbeIntervalMs))
24540
+ throw new Error(`${prefix}: deltaProbeIntervalMs must be > 0 when present`);
24541
+ if (cfg.interleaveDepthMs !== void 0 && !isPositiveFinite2(cfg.interleaveDepthMs))
24542
+ throw new Error(`${prefix}: interleaveDepthMs must be > 0 when present`);
24543
+ if (cfg.deltaProbeIntervalMs === void 0 && cfg.interleaveDepthMs === void 0) {
24544
+ throw new Error(
24545
+ `${prefix}: either deltaProbeIntervalMs or interleaveDepthMs required for probe interval derivation`
24546
+ );
24547
+ }
24548
+ }
24549
+ function isPositiveFinite2(value) {
24550
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
24551
+ }
24185
24552
  var MediaRouter = class {
24186
24553
  #active = "moq";
24187
24554
  #multicastVideoKeyed = false;
@@ -24341,22 +24708,21 @@ var MediaRouter = class {
24341
24708
  * CLAUDE.md: error on bad init — throws if required inputs are absent.
24342
24709
  *
24343
24710
  * Calling again resets deltaActive=true and clears the window buffer (stream restart).
24711
+ * Pass null on teardown or when a replacement catalog cannot configure this policy.
24344
24712
  */
24345
24713
  configureDelta(cfg) {
24346
- if (typeof cfg.framerate !== "number" || !Number.isFinite(cfg.framerate) || cfg.framerate <= 0) {
24347
- throw new Error("[MediaRouter] configureDelta: framerate required and must be > 0");
24348
- }
24349
- if (typeof cfg.sourceSymbols !== "number" || cfg.sourceSymbols <= 0) {
24350
- throw new Error("[MediaRouter] configureDelta: sourceSymbols (K_delta) required and must be > 0");
24351
- }
24352
- if (typeof cfg.repairSymbols !== "number" || cfg.repairSymbols <= 0) {
24353
- throw new Error("[MediaRouter] configureDelta: repairSymbols (P_delta) required and must be > 0");
24354
- }
24355
- if (cfg.deltaProbeIntervalMs === void 0 && cfg.interleaveDepthMs === void 0) {
24356
- throw new Error(
24357
- "[MediaRouter] configureDelta: either deltaProbeIntervalMs or interleaveDepthMs required for probe interval derivation"
24358
- );
24714
+ if (cfg === null) {
24715
+ this.#deltaConfigured = false;
24716
+ this.#deltaActive = true;
24717
+ this.#deltaWindow = [];
24718
+ this.#deltaWindowSize = 0;
24719
+ this.#deltaFailThresholdRatio = 0;
24720
+ this.#deltaRecoverThresholdRatio = 0;
24721
+ this.#deltaProbeIntervalMs = 0;
24722
+ this.#lastProbeAt = 0;
24723
+ return;
24359
24724
  }
24725
+ assertDeltaFallbackConfig(cfg, "[MediaRouter] configureDelta");
24360
24726
  this.#deltaWindowSize = cfg.deltaFailWindowBlocks ?? Math.ceil(cfg.framerate);
24361
24727
  this.#deltaFailThresholdRatio = cfg.repairSymbols / cfg.sourceSymbols;
24362
24728
  this.#deltaRecoverThresholdRatio = this.#deltaFailThresholdRatio / 2;
@@ -24692,6 +25058,7 @@ var MediaRouter = class {
24692
25058
  /** Dispose timers and per-track sources. Call on teardown to prevent
24693
25059
  * stale timer callbacks firing into garbage-collected closures (WR-03). */
24694
25060
  dispose() {
25061
+ this.configureDelta(null);
24695
25062
  if (this.#audioFallbackTimer) {
24696
25063
  clearTimeout(this.#audioFallbackTimer);
24697
25064
  this.#audioFallbackTimer = null;
@@ -24733,6 +25100,35 @@ var MediaRouter = class {
24733
25100
  }
24734
25101
  };
24735
25102
 
25103
+ // src/delta-fallback-config.ts
25104
+ function resolveDeltaFallback(base, delta) {
25105
+ const repair = base.fec?.repairTrack;
25106
+ if (!repair) throw new Error(`[delta-fallback] base track "${base.name}" missing fec.repairTrack`);
25107
+ const deltaFec = delta.fec;
25108
+ if (!deltaFec?.repairTrack) throw new Error(`[delta-fallback] delta track "${delta.name}" missing fec.repairTrack`);
25109
+ if (deltaFec.repairTrack !== repair)
25110
+ throw new Error(
25111
+ `[delta-fallback] inconsistent repair tracks: ${base.name} references "${repair}" but ${delta.name} references "${deltaFec.repairTrack}"`
25112
+ );
25113
+ if (!(typeof base.framerate === "number" && Number.isFinite(base.framerate) && base.framerate > 0))
25114
+ throw new Error(`[delta-fallback] base track "${base.name}" missing positive framerate`);
25115
+ if (!(isPositive(deltaFec.sourceSymbols) && isPositive(deltaFec.repairSymbols)))
25116
+ throw new Error(`[delta-fallback] delta track "${delta.name}" missing positive fec.sourceSymbols/repairSymbols`);
25117
+ const config = {
25118
+ framerate: base.framerate,
25119
+ sourceSymbols: deltaFec.sourceSymbols,
25120
+ repairSymbols: deltaFec.repairSymbols,
25121
+ deltaFailWindowBlocks: base.fec?.deltaFailWindowBlocks ?? deltaFec.deltaFailWindowBlocks,
25122
+ deltaProbeIntervalMs: base.fec?.deltaProbeIntervalMs ?? deltaFec.deltaProbeIntervalMs,
25123
+ interleaveDepthMs: base.fec?.interleaveDepthMs ?? deltaFec.interleaveDepthMs
25124
+ };
25125
+ assertDeltaFallbackConfig(config, `[delta-fallback] group ${base.name}/${delta.name}`);
25126
+ return { repairTrackName: repair, config };
25127
+ }
25128
+ function isPositive(value) {
25129
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
25130
+ }
25131
+
24736
25132
  // src/align.ts
24737
25133
  function secondsFromTicks(ticks, timescale) {
24738
25134
  if (!Number.isFinite(ticks)) {
@@ -25625,400 +26021,131 @@ var CmcdProducer = class {
25625
26021
  this.#onCmcdParams = options.onCmcdParams;
25626
26022
  this.#onCmcdPost = options.onCmcdPost;
25627
26023
  }
25628
- /** Update the stored transport state. Called by player wiring layer. */
25629
- updateTransport(snapshot) {
25630
- this.#transport = snapshot;
25631
- }
25632
- /**
25633
- * Set the current play-mode. Emitted as `x-bc-play-mode` on subsequent
25634
- * snapshots. Throws on any value outside {'full','base-only','single-track'}
25635
- * (CLAUDE.md: error on bad init).
25636
- */
25637
- updatePlayMode(mode) {
25638
- if (typeof mode !== "string" || !PLAY_MODES.includes(mode)) {
25639
- throw new Error(
25640
- `[CmcdProducer] invalid play-mode "${String(mode)}"; expected one of: ${PLAY_MODES.join(", ")}`
25641
- );
25642
- }
25643
- this.#playMode = mode;
25644
- }
25645
- /**
25646
- * Set the delta-track FEC block-failure rate (0..1 inclusive). Emitted as
25647
- * `x-bc-delta-fail-rate` on snapshots when play-mode is 'full' or 'base-only'.
25648
- * Omitted on single-track streams.
25649
- */
25650
- updateDeltaFailRate(rate) {
25651
- if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1) {
25652
- throw new Error(
25653
- `[CmcdProducer] invalid delta-fail-rate "${String(rate)}"; expected a finite number in [0, 1]`
25654
- );
25655
- }
25656
- this.#deltaFailRate = rate;
25657
- }
25658
- /**
25659
- * Bump the cumulative switching-set misalignment counter. Emitted as
25660
- * `x-bc-switch-miscount` on snapshots when the counter is > 0.
25661
- */
25662
- incrementSwitchMiscount() {
25663
- this.#switchMiscount += 1;
25664
- }
25665
- updateManifestVerification(stats) {
25666
- this.#manifestStats = stats;
25667
- }
25668
- /**
25669
- * One-shot emit of `x-bc-switch-drift-us` for a single misalignment event.
25670
- * Does NOT mutate snapshot state — subsequent onSnapshot() calls will not
25671
- * include the drift key until the next emitSwitchDrift(). Mirrors the
25672
- * existing onTransition() pattern.
25673
- */
25674
- emitSwitchDrift(driftUs) {
25675
- if (typeof driftUs !== "number" || !Number.isFinite(driftUs) || driftUs < 0) {
25676
- throw new Error(
25677
- `[CmcdProducer] invalid drift-us "${String(driftUs)}"; expected a non-negative finite number`
25678
- );
25679
- }
25680
- const params = {};
25681
- if (this.#lastStats) Object.assign(params, buildFecParams(this.#lastStats));
25682
- Object.assign(params, buildTransportParams(this.#transport));
25683
- if (this.#switchMiscount > 0) params["x-bc-switch-miscount"] = this.#switchMiscount;
25684
- params["x-bc-switch-drift-us"] = driftUs;
25685
- this.#emit(params);
25686
- }
25687
- /**
25688
- * Process a FecTrackStats snapshot. Finds the video track, builds
25689
- * x-bc-* vendor keys, and fires both delivery callbacks.
25690
- */
25691
- onSnapshot(data) {
25692
- const videoTrack = data.tracks.find((t) => t.config.trackType === "video");
25693
- if (!videoTrack) return;
25694
- this.#lastStats = videoTrack;
25695
- const params = {
25696
- ...buildFecParams(videoTrack),
25697
- ...buildTransportParams(this.#transport)
25698
- };
25699
- if (this.#playMode !== null) {
25700
- params["x-bc-play-mode"] = this.#playMode;
25701
- }
25702
- if (this.#playMode !== null && this.#playMode !== "single-track" && this.#deltaFailRate !== null) {
25703
- params["x-bc-delta-fail-rate"] = parseFloat(this.#deltaFailRate.toFixed(4));
25704
- }
25705
- if (this.#switchMiscount > 0) {
25706
- params["x-bc-switch-miscount"] = this.#switchMiscount;
25707
- }
25708
- if (this.#manifestStats) {
25709
- params["x-bc-manifest-verified"] = this.#manifestStats.verified > 0;
25710
- params["x-bc-manifest-failures"] = Object.values(this.#manifestStats.failures).reduce((total, count) => total + count, 0);
25711
- }
25712
- this.#emit(params);
25713
- }
25714
- /**
25715
- * Emit transport state without inventing FEC metrics. Cadence is owned by
25716
- * the caller because only the transport integration knows when state changed.
25717
- */
25718
- onTransportSnapshot(data) {
25719
- if (!Number.isFinite(data.timestampMs) || data.timestampMs < 0) {
25720
- throw new Error("[CmcdProducer] transport snapshot timestamp must be a non-negative finite number");
25721
- }
25722
- if (data.playMode !== void 0) this.updatePlayMode(data.playMode);
25723
- if (data.switchMiscount !== void 0 && (!Number.isSafeInteger(data.switchMiscount) || data.switchMiscount < 0)) {
25724
- throw new Error("[CmcdProducer] transport snapshot switch-miscount must be a non-negative safe integer");
25725
- }
25726
- this.#transport = data.transport;
25727
- const params = buildTransportParams(data.transport);
25728
- if (this.#playMode !== null) params["x-bc-play-mode"] = this.#playMode;
25729
- const switchMiscount = data.switchMiscount ?? this.#switchMiscount;
25730
- if (switchMiscount > 0) params["x-bc-switch-miscount"] = switchMiscount;
25731
- this.#emit(params);
25732
- }
25733
- /**
25734
- * Signal a transport transition event. Fires an immediate out-of-band
25735
- * emit with x-bc-ct set. The subsequent onSnapshot() will NOT include
25736
- * x-bc-ct (one-shot).
25737
- */
25738
- onTransition(event) {
25739
- const params = {};
25740
- if (this.#lastStats) {
25741
- Object.assign(params, buildFecParams(this.#lastStats));
25742
- }
25743
- Object.assign(params, buildTransportParams(this.#transport));
25744
- params["x-bc-ct"] = event;
25745
- this.#emit(params);
25746
- }
25747
- /** Fire both delivery callbacks with the given params. */
25748
- #emit(params) {
25749
- this.#onCmcdParams?.(params);
25750
- this.#onCmcdPost?.(formatQueryString(params));
25751
- }
25752
- };
25753
-
25754
- // src/catalog-fec-params.ts
25755
- var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
25756
- function timescaleHz(value) {
25757
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
25758
- }
25759
- function catalogGroupDurationMs(rendition) {
25760
- if (!rendition) return void 0;
25761
- const ticks = numericField(rendition.groupDurationTicks);
25762
- const ms = numericField(rendition.groupDurationMs);
25763
- const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
25764
- if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
25765
- return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
25766
- }
25767
- return ms;
25768
- }
25769
- function resolveCatalogTrackGroupDurationMs(catalog, track) {
25770
- const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
25771
- return catalogGroupDurationMs(renditions?.[track.name]);
25772
- }
25773
- var NO_FEC = {
25774
- fecK: 0,
25775
- fecRepairCount: 0,
25776
- fecTotalRepairCount: 0,
25777
- fecSymbolSize: 0,
25778
- fecInterleaveMs: 0
25779
- };
25780
- function joinedRepairSymbolCount(params, joinedRepairTrackNames) {
25781
- const layers = params.repairLayers;
25782
- if (layers === void 0 || layers.length === 0) return params.fecRepairCount;
25783
- const joined = new Set(joinedRepairTrackNames);
25784
- let total = 0;
25785
- for (const layer of layers) {
25786
- if (joined.has(layer.repairTrackName)) total += layer.repairSymbols;
25787
- }
25788
- return total;
25789
- }
25790
- function recordOf2(value) {
25791
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
25792
- }
25793
- function msfTracks(catalog) {
25794
- const raw = catalog.tracks;
25795
- if (!Array.isArray(raw)) return void 0;
25796
- return { tracks: raw.map((t) => recordOf2(t) ?? {}) };
25797
- }
25798
- function resolveRepairLayers(catalog, source) {
25799
- if (!catalog) return void 0;
25800
- const sourceName = typeof source === "string" ? source : source.name;
25801
- const hang = catalog;
25802
- const roles = typeof source === "string" ? ["video", "audio"] : source.type === "audio" ? ["audio"] : source.type === "video" ? ["video"] : ["video", "audio"];
25803
- const role = roles.find((r) => recordOf2(hang[r]?.renditions?.[sourceName]) !== void 0);
25804
- if (role !== void 0) {
25805
- const renditionPath = `$.${role}.renditions[${JSON.stringify(sourceName)}]`;
25806
- const rendition = recordOf2(hang[role].renditions[sourceName]);
25807
- rejectRepairFieldsOnMediaTrack(renditionPath, rendition, throwRepairContractError);
25808
- const repairRenditions = recordOf2(hang.repair?.renditions) ?? {};
25809
- const descriptors2 = Object.entries(repairRenditions).map(([name, entry]) => {
25810
- const path = `$.repair.renditions[${JSON.stringify(name)}]`;
25811
- const fields = recordOf2(entry);
25812
- if (fields === void 0) {
25813
- throwRepairContractError("track_repair_layer", path, "a repair rendition must be an object");
25814
- }
25815
- rejectRepairTrackGeometry(path, fields, throwRepairContractError);
25816
- return describeRepairTrack(name, path, fields);
25817
- });
25818
- const fecOfRendition = (entry) => {
25819
- const fec2 = recordOf2(recordOf2(entry)?.["fec"]);
25820
- return fec2 !== void 0 && fec2["algorithm"] !== "none" ? fec2 : void 0;
25821
- };
25822
- const mediaRenditions = [
25823
- ...Object.values(recordOf2(hang.video?.renditions) ?? {}),
25824
- ...Object.values(recordOf2(hang.audio?.renditions) ?? {})
25825
- ];
25826
- const reachability2 = repairReachability(mediaRenditions.map((entry) => recordOf2(entry)?.["fec"]));
25827
- for (const d of descriptors2) {
25828
- validateRepairTrackFields(d, throwRepairContractError);
25829
- assertRepairTrackReachable(d, reachability2, throwRepairContractError);
25830
- const target = dependsTarget(d);
25831
- if (target === void 0) continue;
25832
- const targetFec = fecOfRendition(hang.video?.renditions?.[target]) ?? fecOfRendition(hang.audio?.renditions?.[target]);
25833
- if (targetFec === void 0) {
25834
- throwRepairContractError(
25835
- "track_repair_depends",
25836
- `${d.path}.depends`,
25837
- `${JSON.stringify(target)} is not a video or audio rendition with FEC`
25838
- );
25839
- }
25840
- }
25841
- return assembleRepairLayers(
25842
- geometryOf(sourceName, `${renditionPath}.fec`, recordOf2(rendition["fec"])),
25843
- descriptors2,
25844
- throwRepairContractError
25845
- );
25846
- }
25847
- const msf = msfTracks(catalog);
25848
- const msfIndex = msf ? msf.tracks.findIndex((t) => t["name"] === sourceName) : -1;
25849
- if (!msf || msfIndex < 0) return void 0;
25850
- const track = msf.tracks[msfIndex];
25851
- const fec = recordOf2(track["fec"]);
25852
- const packagingOf = (t) => t["packaging"];
25853
- const fecOfTrack = (t) => {
25854
- const trackFec = recordOf2(t?.["fec"]);
25855
- return trackFec !== void 0 && trackFec["algorithm"] !== "none" ? trackFec : void 0;
25856
- };
25857
- const descriptors = [];
25858
- const mediaTracks = [];
25859
- msf.tracks.forEach((t, i) => {
25860
- const path = `$.tracks[${i}]`;
25861
- if (packagingOf(t) === "fec-repair") {
25862
- rejectRepairTrackGeometry(path, t, throwRepairContractError);
25863
- if (typeof t["name"] === "string") descriptors.push(describeRepairTrack(t["name"], path, t));
25864
- return;
26024
+ /** Update the stored transport state. Called by player wiring layer. */
26025
+ updateTransport(snapshot) {
26026
+ this.#transport = snapshot;
26027
+ }
26028
+ /**
26029
+ * Set the current play-mode. Emitted as `x-bc-play-mode` on subsequent
26030
+ * snapshots. Throws on any value outside {'full','base-only','single-track'}
26031
+ * (CLAUDE.md: error on bad init).
26032
+ */
26033
+ updatePlayMode(mode) {
26034
+ if (typeof mode !== "string" || !PLAY_MODES.includes(mode)) {
26035
+ throw new Error(
26036
+ `[CmcdProducer] invalid play-mode "${String(mode)}"; expected one of: ${PLAY_MODES.join(", ")}`
26037
+ );
25865
26038
  }
25866
- rejectRepairFieldsOnMediaTrack(path, t, throwRepairContractError);
25867
- mediaTracks.push(t);
25868
- });
25869
- const reachability = repairReachability(mediaTracks.map((t) => t["fec"]));
25870
- for (const d of descriptors) {
25871
- validateRepairTrackFields(d, throwRepairContractError);
25872
- assertRepairTrackReachable(d, reachability, throwRepairContractError);
25873
- const target = dependsTarget(d);
25874
- if (target === void 0) continue;
25875
- const targetTrack = msf.tracks.find((t) => t["name"] === target);
25876
- if (targetTrack === void 0 || packagingOf(targetTrack) !== "mmtp" || fecOfTrack(targetTrack) === void 0) {
25877
- throwRepairContractError(
25878
- "track_repair_depends",
25879
- `${d.path}.depends`,
25880
- `${JSON.stringify(target)} is not an mmtp track with FEC`
26039
+ this.#playMode = mode;
26040
+ }
26041
+ /**
26042
+ * Set the delta-track FEC block-failure rate (0..1 inclusive). Emitted as
26043
+ * `x-bc-delta-fail-rate` on snapshots when play-mode is 'full' or 'base-only'.
26044
+ * Omitted on single-track streams.
26045
+ */
26046
+ updateDeltaFailRate(rate) {
26047
+ if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0 || rate > 1) {
26048
+ throw new Error(
26049
+ `[CmcdProducer] invalid delta-fail-rate "${String(rate)}"; expected a finite number in [0, 1]`
25881
26050
  );
25882
26051
  }
26052
+ this.#deltaFailRate = rate;
25883
26053
  }
25884
- return assembleRepairLayers(geometryOf(sourceName, `$.tracks[${msfIndex}].fec`, fec), descriptors, throwRepairContractError, {
25885
- layerZeroMustBeDeclared: true
25886
- });
25887
- }
25888
- function moqtPriority2(value) {
25889
- return Number.isSafeInteger(value) && value >= 0 && value <= 255 ? value : null;
25890
- }
25891
- function catalogRepairTarget(catalog, source) {
25892
- const repair = resolveRepairLayers(catalog, source);
25893
- const layerZero = repair?.baseLayers[0];
25894
- if (!catalog || !layerZero) return null;
25895
- const sourceName = typeof source === "string" ? source : source.name;
25896
- const msf = msfTracks(catalog);
25897
- const entry = msf?.tracks.find((t) => t["name"] === sourceName);
25898
- const sourcePriority = entry === void 0 ? null : moqtPriority2(entry["priority"]);
25899
- return { repairTrack: layerZero.repairTrackName, sourcePriority, repairPriority: layerZero.priority };
25900
- }
25901
- function geometryOf(name, path, fec) {
25902
- return {
25903
- name,
25904
- path,
25905
- algorithm: fec?.["algorithm"],
25906
- sourceSymbols: fec?.["sourceSymbols"],
25907
- repairSymbols: fec?.["repairSymbols"],
25908
- repairTrack: fec?.["repairTrack"]
25909
- };
25910
- }
25911
- function numericField(value) {
25912
- const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
25913
- return Number.isFinite(n) && n > 0 ? n : void 0;
25914
- }
25915
- function nonNegativeField(value) {
25916
- const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
25917
- return Number.isFinite(n) && n >= 0 ? n : void 0;
25918
- }
25919
- function blockProfileUnavailable(value, path, declaresFecInstance) {
25920
- try {
25921
- readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
25922
- return void 0;
25923
- } catch (error) {
25924
- if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile") throw error;
25925
- return Object.freeze({ code: "track_fec_block_profile", path: error.path, message: error.message });
26054
+ /**
26055
+ * Bump the cumulative switching-set misalignment counter. Emitted as
26056
+ * `x-bc-switch-miscount` on snapshots when the counter is > 0.
26057
+ */
26058
+ incrementSwitchMiscount() {
26059
+ this.#switchMiscount += 1;
25926
26060
  }
25927
- }
25928
- function isoSpanUnavailable(fec, path, declaresFecInstance) {
25929
- if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
25930
- return Object.freeze({
25931
- code: "track_fec",
25932
- path,
25933
- message: `[repair-layers:track_fec] ${path}: iso-ssbg1-v1 requires a block span of at least ${MIN_INTERLEAVE_DEPTH_MS} ms, which bounds its block location`
25934
- });
25935
- }
25936
- function fecParams(catalog, track, rendition, fps, sampleRate) {
25937
- const mediaRate = sampleRate === void 0 ? { fps } : { fps, sampleRate };
25938
- const fec = rendition?.fec;
25939
- if (!fec || !track) return { ...NO_FEC, ...mediaRate };
25940
- const repair = resolveRepairLayers(catalog, track);
25941
- const fecPath = `$.${track.type}.renditions[${JSON.stringify(track.name)}].fec`;
25942
- if (!catalogDeclaresFec(fec)) {
25943
- const declaresFecInstance = fec.algorithm !== "none";
25944
- const kValid = Number.isSafeInteger(fec.sourceSymbols) && (fec.sourceSymbols ?? 0) > 0;
25945
- const tValid = Number.isSafeInteger(fec.symbolSize) && (fec.symbolSize ?? 0) > 0;
25946
- if (declaresFecInstance && !(kValid && tValid)) {
25947
- throwRepairContractError(
25948
- "track_fec",
25949
- `${fecPath}.${kValid ? "symbolSize" : "sourceSymbols"}`,
25950
- "must be a positive integer"
26061
+ updateManifestVerification(stats) {
26062
+ this.#manifestStats = stats;
26063
+ }
26064
+ /**
26065
+ * One-shot emit of `x-bc-switch-drift-us` for a single misalignment event.
26066
+ * Does NOT mutate snapshot state — subsequent onSnapshot() calls will not
26067
+ * include the drift key until the next emitSwitchDrift(). Mirrors the
26068
+ * existing onTransition() pattern.
26069
+ */
26070
+ emitSwitchDrift(driftUs) {
26071
+ if (typeof driftUs !== "number" || !Number.isFinite(driftUs) || driftUs < 0) {
26072
+ throw new Error(
26073
+ `[CmcdProducer] invalid drift-us "${String(driftUs)}"; expected a non-negative finite number`
25951
26074
  );
25952
26075
  }
25953
- const fecUnavailable = blockProfileUnavailable(fec.blockProfile, `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(fec, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
25954
- return fecUnavailable === void 0 ? { ...NO_FEC, ...mediaRate } : { ...NO_FEC, ...mediaRate, fecUnavailable };
26076
+ const params = {};
26077
+ if (this.#lastStats) Object.assign(params, buildFecParams(this.#lastStats));
26078
+ Object.assign(params, buildTransportParams(this.#transport));
26079
+ if (this.#switchMiscount > 0) params["x-bc-switch-miscount"] = this.#switchMiscount;
26080
+ params["x-bc-switch-drift-us"] = driftUs;
26081
+ this.#emit(params);
25955
26082
  }
25956
- const fecBlockProfile = readFecBlockProfile(fec.blockProfile, `${fecPath}.blockProfile`, true, throwRepairContractError);
25957
- return {
25958
- fecK: fec.sourceSymbols ?? 0,
25959
- fecBlockProfile,
25960
- fecRepairCount: fec.repairSymbols ?? 0,
25961
- // The wire RSB_length: every base layer's packets carry the base total (§7.1).
25962
- fecTotalRepairCount: repair?.totalBaseRepairSymbols ?? fec.repairSymbols ?? 0,
25963
- fecSymbolSize: fec.symbolSize ?? 0,
25964
- // interleaveDepthMs is the FEC block span in MILLISECONDS (§5.1) — the
25965
- // only interleave key the catalog carries post-BLO-14526. Receivers
25966
- // derive D by positive round-half-up of span / GOP duration where needed.
25967
- fecInterleaveMs: catalogFecInterleaveMs(fec) ?? 0,
25968
- // Additive, never a multiplier on the span: jitter belongs to the network
25969
- // path and the span belongs to the encoder, so scaling one by the other
25970
- // hands one network different slack per track. `numericField` rejects 0,
25971
- // so it cannot be reused here — a declared 0 is a legitimate value.
25972
- fecReorderToleranceMs: nonNegativeField(fec.reorderToleranceMs),
25973
- repairTrackName: fec.repairTrack,
25974
- repairLayers: repair?.baseLayers,
25975
- repairOverlays: repair?.overlays,
25976
- ...mediaRate
25977
- };
25978
- }
25979
- function resolveCatalogTrackFec(catalog, tracks) {
25980
- const videoTrackName = tracks.find((t) => t.type === "video")?.name;
25981
- const audioTrackName = tracks.find((t) => t.type === "audio")?.name;
25982
- const videoRenditions = catalog?.video?.renditions;
25983
- const audioRenditions = catalog?.audio?.renditions;
25984
- const video = videoTrackName ? videoRenditions?.[videoTrackName] : void 0;
25985
- const audio = audioTrackName ? audioRenditions?.[audioTrackName] : void 0;
25986
- const catalogFps = numericField(video?.framerate);
25987
- const sampleRate = numericField(audio?.sampleRate ?? audio?.samplerate);
25988
- const audioFps = sampleRate ? sampleRate / 1024 : void 0;
25989
- const videoParams = fecParams(catalog, videoTrackName ? { name: videoTrackName, type: "video" } : void 0, video, catalogFps);
25990
- const audioParams = fecParams(
25991
- catalog,
25992
- audioTrackName ? { name: audioTrackName, type: "audio" } : void 0,
25993
- audio,
25994
- audioFps,
25995
- sampleRate
25996
- );
25997
- const forTrack = (track) => {
25998
- if (track.type === "audio") {
25999
- if (track.name === audioTrackName) return audioParams;
26000
- const rendition2 = audioRenditions?.[track.name];
26001
- if (!rendition2) return { ...NO_FEC };
26002
- const rate = numericField(rendition2.sampleRate ?? rendition2.samplerate);
26003
- return fecParams(catalog, { name: track.name, type: "audio" }, rendition2, rate ? rate / 1024 : void 0, rate);
26083
+ /**
26084
+ * Process a FecTrackStats snapshot. Finds the video track, builds
26085
+ * x-bc-* vendor keys, and fires both delivery callbacks.
26086
+ */
26087
+ onSnapshot(data) {
26088
+ const videoTrack = data.tracks.find((t) => t.config.trackType === "video");
26089
+ if (!videoTrack) return;
26090
+ this.#lastStats = videoTrack;
26091
+ const params = {
26092
+ ...buildFecParams(videoTrack),
26093
+ ...buildTransportParams(this.#transport)
26094
+ };
26095
+ if (this.#playMode !== null) {
26096
+ params["x-bc-play-mode"] = this.#playMode;
26004
26097
  }
26005
- if (track.name === videoTrackName) return videoParams;
26006
- const rendition = videoRenditions?.[track.name];
26007
- if (!rendition) return { ...NO_FEC };
26008
- return fecParams(catalog, { name: track.name, type: "video" }, rendition, numericField(rendition.framerate));
26009
- };
26010
- return {
26011
- videoTrackName,
26012
- audioTrackName,
26013
- missingVideoRendition: videoTrackName && videoRenditions && !video ? videoTrackName : void 0,
26014
- missingAudioRendition: audioTrackName && audioRenditions && !audio ? audioTrackName : void 0,
26015
- videoForKeyframeRepair: video,
26016
- catalogFps,
26017
- video: videoParams,
26018
- audio: audioParams,
26019
- forTrack
26020
- };
26021
- }
26098
+ if (this.#playMode !== null && this.#playMode !== "single-track" && this.#deltaFailRate !== null) {
26099
+ params["x-bc-delta-fail-rate"] = parseFloat(this.#deltaFailRate.toFixed(4));
26100
+ }
26101
+ if (this.#switchMiscount > 0) {
26102
+ params["x-bc-switch-miscount"] = this.#switchMiscount;
26103
+ }
26104
+ if (this.#manifestStats) {
26105
+ params["x-bc-manifest-verified"] = this.#manifestStats.verified > 0;
26106
+ params["x-bc-manifest-failures"] = Object.values(this.#manifestStats.failures).reduce((total, count) => total + count, 0);
26107
+ }
26108
+ this.#emit(params);
26109
+ }
26110
+ /**
26111
+ * Emit transport state without inventing FEC metrics. Cadence is owned by
26112
+ * the caller because only the transport integration knows when state changed.
26113
+ */
26114
+ onTransportSnapshot(data) {
26115
+ if (!Number.isFinite(data.timestampMs) || data.timestampMs < 0) {
26116
+ throw new Error("[CmcdProducer] transport snapshot timestamp must be a non-negative finite number");
26117
+ }
26118
+ if (data.playMode !== void 0) this.updatePlayMode(data.playMode);
26119
+ if (data.switchMiscount !== void 0 && (!Number.isSafeInteger(data.switchMiscount) || data.switchMiscount < 0)) {
26120
+ throw new Error("[CmcdProducer] transport snapshot switch-miscount must be a non-negative safe integer");
26121
+ }
26122
+ this.#transport = data.transport;
26123
+ const params = buildTransportParams(data.transport);
26124
+ if (this.#playMode !== null) params["x-bc-play-mode"] = this.#playMode;
26125
+ const switchMiscount = data.switchMiscount ?? this.#switchMiscount;
26126
+ if (switchMiscount > 0) params["x-bc-switch-miscount"] = switchMiscount;
26127
+ this.#emit(params);
26128
+ }
26129
+ /**
26130
+ * Signal a transport transition event. Fires an immediate out-of-band
26131
+ * emit with x-bc-ct set. The subsequent onSnapshot() will NOT include
26132
+ * x-bc-ct (one-shot).
26133
+ */
26134
+ onTransition(event) {
26135
+ const params = {};
26136
+ if (this.#lastStats) {
26137
+ Object.assign(params, buildFecParams(this.#lastStats));
26138
+ }
26139
+ Object.assign(params, buildTransportParams(this.#transport));
26140
+ params["x-bc-ct"] = event;
26141
+ this.#emit(params);
26142
+ }
26143
+ /** Fire both delivery callbacks with the given params. */
26144
+ #emit(params) {
26145
+ this.#onCmcdParams?.(params);
26146
+ this.#onCmcdPost?.(formatQueryString(params));
26147
+ }
26148
+ };
26022
26149
 
26023
26150
  // src/repair-fetch.ts
26024
26151
  var MOQT_PRIORITY_MAX = 255;
@@ -26201,7 +26328,7 @@ function wireRepairFetch(mc, opts) {
26201
26328
  if (profile === void 0) {
26202
26329
  throw new Error(`[RepairFetch] packetId=${packetId} has no configured fec.blockProfile`);
26203
26330
  }
26204
- return profile;
26331
+ return parseFecBlockProfile(profile, `[RepairFetch] packetId=${packetId} fec.blockProfile`);
26205
26332
  };
26206
26333
  const execute = async (entry, run) => {
26207
26334
  let fed = 0;
@@ -26268,7 +26395,9 @@ function wireRepairFetch(mc, opts) {
26268
26395
  dispatch();
26269
26396
  });
26270
26397
  const presentationDeadlineAt = (sbn, packetId, catalogTrackName, now) => {
26271
- const waitDeadlineAt = now + mc.fecManager.repairFetchTimeoutMsForBlock(sbn, packetId, catalogTrackName);
26398
+ const waitMs = mc.fecManager.repairFetchTimeoutMsForBlock(sbn, packetId, catalogTrackName);
26399
+ if (waitMs === void 0) return null;
26400
+ const waitDeadlineAt = now + waitMs;
26272
26401
  const anchor = mc.fecManager.blockAnchorAtMs(sbn, packetId, catalogTrackName);
26273
26402
  if (anchor === void 0) return waitDeadlineAt;
26274
26403
  let delayMs;
@@ -26278,7 +26407,9 @@ function wireRepairFetch(mc, opts) {
26278
26407
  return null;
26279
26408
  }
26280
26409
  if (!(Number.isFinite(delayMs) && delayMs > 0)) return null;
26281
- const ceilingAt = anchor + mc.fecManager.terminalRetentionMsForPacket(packetId);
26410
+ const retentionMs = mc.fecManager.terminalRetentionMsForPacket(packetId);
26411
+ if (retentionMs === void 0) return null;
26412
+ const ceilingAt = anchor + retentionMs;
26282
26413
  return Math.max(waitDeadlineAt, Math.min(anchor + delayMs, ceilingAt));
26283
26414
  };
26284
26415
  mc.onRepairSubscribe((sbn, packetId, catalogTrackName, blockClass) => {
@@ -26427,7 +26558,10 @@ export {
26427
26558
  catalogAmtNetworkSource,
26428
26559
  catalogDeclaresFec,
26429
26560
  catalogFecInterleaveMs,
26561
+ catalogFecMediaUnitsPerSecond,
26562
+ catalogFecUnavailable,
26430
26563
  catalogGroupDurationMs,
26564
+ catalogKeyframeCadenceMs,
26431
26565
  catalogRepairTarget,
26432
26566
  classifyH264Keyframe,
26433
26567
  classifyHevcKeyframe,
@@ -26483,6 +26617,7 @@ export {
26483
26617
  isCompleteFmp4DataUnit,
26484
26618
  isFecBlockProfile,
26485
26619
  isFecCatalogConfig,
26620
+ isFecCatalogGeometry,
26486
26621
  isFmp4InitSegment,
26487
26622
  isHevcCodec,
26488
26623
  isHevcIrapNalUnitType,
@@ -26524,6 +26659,7 @@ export {
26524
26659
  repairReachability,
26525
26660
  resolveCatalogTrackFec,
26526
26661
  resolveCatalogTrackGroupDurationMs,
26662
+ resolveDeltaFallback,
26527
26663
  resolveFecState,
26528
26664
  resolveGlobalMmtWasmBindings,
26529
26665
  resolveGroupDurationMs,