@blockcast/mmt-container 0.1.0 → 0.3.0-main.0bde85f7082f

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 (799) hide show
  1. package/README.md +524 -0
  2. package/dist/.build-stamp +0 -0
  3. package/dist/__tests__/test-utils.d.ts +71 -0
  4. package/dist/__tests__/test-utils.d.ts.map +1 -0
  5. package/dist/align.d.ts +152 -0
  6. package/dist/align.d.ts.map +1 -0
  7. package/dist/align.js +88 -0
  8. package/dist/align.js.map +7 -0
  9. package/dist/audio-init-parser.d.ts +66 -0
  10. package/dist/audio-init-parser.d.ts.map +1 -0
  11. package/dist/audio-init-parser.js +118 -0
  12. package/dist/audio-init-parser.js.map +7 -0
  13. package/dist/audio-ring-buffer.d.ts +50 -0
  14. package/dist/audio-ring-buffer.d.ts.map +1 -0
  15. package/dist/audio-ring-buffer.js +80 -0
  16. package/dist/audio-ring-buffer.js.map +7 -0
  17. package/dist/authored-timing.d.ts +32 -0
  18. package/dist/authored-timing.d.ts.map +1 -0
  19. package/dist/authored-timing.js +32 -0
  20. package/dist/authored-timing.js.map +7 -0
  21. package/dist/block-policy.d.ts +314 -0
  22. package/dist/block-policy.d.ts.map +1 -0
  23. package/dist/block-policy.js +466 -0
  24. package/dist/block-policy.js.map +7 -0
  25. package/dist/browser-runtime.js +26549 -0
  26. package/dist/browser-runtime.js.map +7 -0
  27. package/dist/carp-catalog.d.ts +429 -0
  28. package/dist/carp-catalog.d.ts.map +1 -0
  29. package/dist/carp-catalog.js +985 -0
  30. package/dist/carp-catalog.js.map +7 -0
  31. package/dist/catalog-fec-params.d.ts +275 -0
  32. package/dist/catalog-fec-params.d.ts.map +1 -0
  33. package/dist/catalog-fec-params.js +702 -0
  34. package/dist/catalog-fec-params.js.map +7 -0
  35. package/dist/catalog-network-source.d.ts +42 -0
  36. package/dist/catalog-network-source.d.ts.map +1 -0
  37. package/dist/catalog-network-source.js +47 -0
  38. package/dist/catalog-network-source.js.map +7 -0
  39. package/dist/catalog-validator.d.ts +18 -0
  40. package/dist/catalog-validator.d.ts.map +1 -0
  41. package/dist/catalog-validator.js +7747 -0
  42. package/dist/catalog-validator.js.map +7 -0
  43. package/dist/cmaf-block-assembler.d.ts +285 -0
  44. package/dist/cmaf-block-assembler.d.ts.map +1 -0
  45. package/dist/cmaf-block-assembler.js +1966 -0
  46. package/dist/cmaf-block-assembler.js.map +7 -0
  47. package/dist/cmaf-group-batcher.d.ts +38 -0
  48. package/dist/cmaf-group-batcher.d.ts.map +1 -0
  49. package/dist/cmaf-group-batcher.js +176 -0
  50. package/dist/cmaf-group-batcher.js.map +7 -0
  51. package/dist/cmcd-producer.d.ts +104 -0
  52. package/dist/cmcd-producer.d.ts.map +1 -0
  53. package/dist/cmcd-producer.js +183 -0
  54. package/dist/cmcd-producer.js.map +7 -0
  55. package/dist/codec-utils.d.ts +384 -0
  56. package/dist/codec-utils.d.ts.map +1 -0
  57. package/dist/codec-utils.js +1546 -0
  58. package/dist/codec-utils.js.map +7 -0
  59. package/dist/conceal-timeline.d.ts +146 -0
  60. package/dist/conceal-timeline.d.ts.map +1 -0
  61. package/dist/conceal-timeline.js +137 -0
  62. package/dist/conceal-timeline.js.map +7 -0
  63. package/dist/delivery-gap.d.ts +75 -0
  64. package/dist/delivery-gap.d.ts.map +1 -0
  65. package/dist/delivery-gap.js +88 -0
  66. package/dist/delivery-gap.js.map +7 -0
  67. package/dist/duplicate-ledger.d.ts +197 -0
  68. package/dist/duplicate-ledger.d.ts.map +1 -0
  69. package/dist/duplicate-ledger.js +226 -0
  70. package/dist/duplicate-ledger.js.map +7 -0
  71. package/dist/fec-codec.d.ts +32 -0
  72. package/dist/fec-codec.d.ts.map +1 -0
  73. package/dist/fec-codec.js +47 -0
  74. package/dist/fec-codec.js.map +7 -0
  75. package/dist/fec-manager.d.ts +583 -0
  76. package/dist/fec-manager.d.ts.map +1 -0
  77. package/dist/fec-manager.js +3899 -0
  78. package/dist/fec-manager.js.map +7 -0
  79. package/dist/fec-starvation-detector.d.ts +59 -0
  80. package/dist/fec-starvation-detector.d.ts.map +1 -0
  81. package/dist/fec-starvation-detector.js +60 -0
  82. package/dist/fec-starvation-detector.js.map +7 -0
  83. package/dist/fec-state.d.ts +131 -0
  84. package/dist/fec-state.d.ts.map +1 -0
  85. package/dist/fec-state.js +87 -0
  86. package/dist/fec-state.js.map +7 -0
  87. package/dist/fec-worker-setup.d.ts +94 -0
  88. package/dist/fec-worker-setup.d.ts.map +1 -0
  89. package/dist/fec-worker-setup.js +673 -0
  90. package/dist/fec-worker-setup.js.map +7 -0
  91. package/dist/fmp4-completion.d.ts +30 -0
  92. package/dist/fmp4-completion.d.ts.map +1 -0
  93. package/dist/fmp4-completion.js +21 -0
  94. package/dist/fmp4-completion.js.map +7 -0
  95. package/dist/fmp4-reader.d.ts +37 -0
  96. package/dist/fmp4-reader.d.ts.map +1 -0
  97. package/dist/fmp4-reader.js +100 -0
  98. package/dist/fmp4-reader.js.map +7 -0
  99. package/dist/index.d.ts +44 -3
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +460 -14
  102. package/dist/index.js.map +7 -1
  103. package/dist/iso-block-locator.d.ts +118 -0
  104. package/dist/iso-block-locator.d.ts.map +1 -0
  105. package/dist/iso-block-locator.js +289 -0
  106. package/dist/iso-block-locator.js.map +7 -0
  107. package/dist/iso-bmff-timing.d.ts +78 -0
  108. package/dist/iso-bmff-timing.d.ts.map +1 -0
  109. package/dist/iso-bmff-timing.js +593 -0
  110. package/dist/iso-bmff-timing.js.map +7 -0
  111. package/dist/loc.d.ts +70 -75
  112. package/dist/loc.d.ts.map +1 -1
  113. package/dist/loc.js +106 -192
  114. package/dist/loc.js.map +7 -1
  115. package/dist/manifest-auth-plan.d.ts +30 -0
  116. package/dist/manifest-auth-plan.d.ts.map +1 -0
  117. package/dist/manifest-auth-plan.js +1266 -0
  118. package/dist/manifest-auth-plan.js.map +7 -0
  119. package/dist/media-router.d.ts +262 -0
  120. package/dist/media-router.d.ts.map +1 -0
  121. package/dist/media-router.js +584 -0
  122. package/dist/media-router.js.map +7 -0
  123. package/dist/mfu-reassembler.d.ts +313 -9
  124. package/dist/mfu-reassembler.d.ts.map +1 -1
  125. package/dist/mfu-reassembler.js +3051 -285
  126. package/dist/mfu-reassembler.js.map +7 -1
  127. package/dist/mfu-reassembly-policy.d.ts +24 -0
  128. package/dist/mfu-reassembly-policy.d.ts.map +1 -0
  129. package/dist/mfu-reassembly-policy.js +99 -0
  130. package/dist/mfu-reassembly-policy.js.map +7 -0
  131. package/dist/mmtp-container-parser.d.ts +568 -0
  132. package/dist/mmtp-container-parser.d.ts.map +1 -0
  133. package/dist/mmtp-container-parser.js +6580 -0
  134. package/dist/mmtp-container-parser.js.map +7 -0
  135. package/dist/mmtp-container-parser.no-modules.js +9616 -0
  136. package/dist/mmtp-container-parser.no-modules.js.map +7 -0
  137. package/dist/mmtp-decode-pipeline.d.ts +138 -0
  138. package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
  139. package/dist/mmtp-decode-pipeline.js +6814 -0
  140. package/dist/mmtp-decode-pipeline.js.map +7 -0
  141. package/dist/mmtp-router.d.ts +679 -0
  142. package/dist/mmtp-router.d.ts.map +1 -0
  143. package/dist/mmtp-router.js +11278 -0
  144. package/dist/mmtp-router.js.map +7 -0
  145. package/dist/mmtp-to-cmaf.d.ts +187 -0
  146. package/dist/mmtp-to-cmaf.d.ts.map +1 -0
  147. package/dist/mmtp-to-cmaf.js +716 -0
  148. package/dist/mmtp-to-cmaf.js.map +7 -0
  149. package/dist/multicast-auth-catalog.d.ts +115 -0
  150. package/dist/multicast-auth-catalog.d.ts.map +1 -0
  151. package/dist/multicast-auth-catalog.js +202 -0
  152. package/dist/multicast-auth-catalog.js.map +7 -0
  153. package/dist/multicast-input.d.ts +141 -0
  154. package/dist/multicast-input.d.ts.map +1 -0
  155. package/dist/multicast-input.js +390 -0
  156. package/dist/multicast-input.js.map +7 -0
  157. package/dist/multicast-manager.d.ts +669 -0
  158. package/dist/multicast-manager.d.ts.map +1 -0
  159. package/dist/multicast-manager.js +19101 -0
  160. package/dist/multicast-manager.js.map +7 -0
  161. package/dist/multicast-transport.d.ts +271 -0
  162. package/dist/multicast-transport.d.ts.map +1 -0
  163. package/dist/multicast-transport.js +2264 -0
  164. package/dist/multicast-transport.js.map +7 -0
  165. package/dist/multicast-unwrap.d.ts +57 -0
  166. package/dist/multicast-unwrap.d.ts.map +1 -0
  167. package/dist/multicast-unwrap.js +418 -0
  168. package/dist/multicast-unwrap.js.map +7 -0
  169. package/dist/no-modules.d.ts +10 -0
  170. package/dist/no-modules.d.ts.map +1 -0
  171. package/dist/no-modules.js +9491 -0
  172. package/dist/no-modules.js.map +7 -0
  173. package/dist/phase14-catalog-override.d.ts +126 -0
  174. package/dist/phase14-catalog-override.d.ts.map +1 -0
  175. package/dist/phase14-catalog-override.js +155 -0
  176. package/dist/phase14-catalog-override.js.map +7 -0
  177. package/dist/repair-fetch.d.ts +270 -0
  178. package/dist/repair-fetch.d.ts.map +1 -0
  179. package/dist/repair-fetch.js +381 -0
  180. package/dist/repair-fetch.js.map +7 -0
  181. package/dist/repair-layers.d.ts +158 -0
  182. package/dist/repair-layers.d.ts.map +1 -0
  183. package/dist/repair-layers.js +344 -0
  184. package/dist/repair-layers.js.map +7 -0
  185. package/dist/sample-combiner.d.ts +212 -0
  186. package/dist/sample-combiner.d.ts.map +1 -0
  187. package/dist/sample-combiner.js +595 -0
  188. package/dist/sample-combiner.js.map +7 -0
  189. package/dist/signaling.d.ts +29 -0
  190. package/dist/signaling.d.ts.map +1 -0
  191. package/dist/signaling.js +572 -0
  192. package/dist/signaling.js.map +7 -0
  193. package/dist/source-auth.d.ts +2 -0
  194. package/dist/source-auth.d.ts.map +1 -0
  195. package/dist/source-auth.js +1209 -0
  196. package/dist/source-auth.js.map +7 -0
  197. package/dist/switching-set-alignment.d.ts +61 -0
  198. package/dist/switching-set-alignment.d.ts.map +1 -0
  199. package/dist/switching-set-alignment.js +70 -0
  200. package/dist/switching-set-alignment.js.map +7 -0
  201. package/dist/timestamp-codec.d.ts +34 -0
  202. package/dist/timestamp-codec.d.ts.map +1 -0
  203. package/dist/timestamp-codec.js +473 -0
  204. package/dist/timestamp-codec.js.map +7 -0
  205. package/dist/track-pipeline-setup.d.ts +83 -0
  206. package/dist/track-pipeline-setup.d.ts.map +1 -0
  207. package/dist/track-pipeline-setup.js +291 -0
  208. package/dist/track-pipeline-setup.js.map +7 -0
  209. package/dist/track-pipeline.d.ts +121 -0
  210. package/dist/track-pipeline.d.ts.map +1 -0
  211. package/dist/track-pipeline.js +209 -0
  212. package/dist/track-pipeline.js.map +7 -0
  213. package/dist/track-session.d.ts +414 -0
  214. package/dist/track-session.d.ts.map +1 -0
  215. package/dist/track-session.js +754 -0
  216. package/dist/track-session.js.map +7 -0
  217. package/dist/transport-pool.d.ts +69 -0
  218. package/dist/transport-pool.d.ts.map +1 -0
  219. package/dist/transport-pool.js +74 -0
  220. package/dist/transport-pool.js.map +7 -0
  221. package/dist/wasm-mfu-reassembler.d.ts +97 -0
  222. package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
  223. package/dist/wasm-mfu-reassembler.js +2133 -0
  224. package/dist/wasm-mfu-reassembler.js.map +7 -0
  225. package/dist/wasm-mmtp-container-parser.d.ts +10 -0
  226. package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
  227. package/dist/wasm-mmtp-container-parser.js +6834 -0
  228. package/dist/wasm-mmtp-container-parser.js.map +7 -0
  229. package/node_modules/@blockcast/fec-worker/README.md +223 -0
  230. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  231. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +38 -0
  232. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +1 -0
  233. package/node_modules/@blockcast/fec-worker/dist/block-order.js +78 -0
  234. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +1 -0
  235. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +93 -0
  236. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
  237. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +346 -0
  238. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
  239. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +380 -0
  240. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
  241. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
  242. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
  243. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
  244. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
  245. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +859 -0
  246. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
  247. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
  248. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
  249. package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
  250. package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
  251. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
  252. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
  253. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +370 -0
  254. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
  255. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
  256. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
  257. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
  258. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
  259. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +194 -0
  260. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
  261. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +62 -0
  262. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
  263. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
  264. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
  265. package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
  266. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
  267. package/node_modules/@blockcast/fec-worker/package.json +59 -0
  268. package/node_modules/@blockcast/fec-worker/src/block-order.ts +93 -0
  269. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +392 -0
  270. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +399 -0
  271. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1502 -0
  272. package/node_modules/@blockcast/fec-worker/src/index.ts +35 -0
  273. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +589 -0
  274. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
  275. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +209 -0
  276. package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
  277. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  278. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  279. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  280. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  281. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  282. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  283. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  284. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  285. package/node_modules/@blockcast/mmt-base/README.md +134 -0
  286. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  287. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
  288. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
  289. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +128 -0
  290. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
  291. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
  292. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  293. package/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
  294. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  295. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
  296. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  297. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
  298. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  299. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
  300. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
  301. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
  302. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
  303. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
  304. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
  305. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
  306. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
  307. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +57 -0
  308. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
  309. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +98 -0
  310. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
  311. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +312 -0
  312. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
  313. package/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
  314. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
  315. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  316. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
  317. package/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
  318. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
  319. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
  320. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
  321. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +107 -0
  322. package/node_modules/@blockcast/mmt-base/src/signalling.ts +862 -0
  323. package/node_modules/@blockcast/mmt-fec/README.md +271 -0
  324. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  325. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +82 -0
  326. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
  327. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +296 -0
  328. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
  329. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +106 -0
  330. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
  331. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +288 -0
  332. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
  333. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
  334. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
  335. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
  336. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
  337. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
  338. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
  339. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
  340. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
  341. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +205 -0
  342. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
  343. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +240 -0
  344. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
  345. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +28 -0
  346. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
  347. package/node_modules/@blockcast/mmt-fec/dist/index.js +28 -0
  348. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
  349. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
  350. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
  351. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
  352. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
  353. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +258 -0
  354. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
  355. package/node_modules/@blockcast/mmt-fec/dist/oti.js +382 -0
  356. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
  357. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
  358. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
  359. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
  360. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
  361. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +82 -0
  362. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +1 -0
  363. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +170 -0
  364. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +1 -0
  365. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  366. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  367. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  368. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  369. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  370. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  371. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  372. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  373. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +134 -0
  374. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  375. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
  376. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
  377. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +128 -0
  378. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
  379. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
  380. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  381. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
  382. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  383. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
  384. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  385. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
  386. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  387. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
  388. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
  389. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
  390. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
  391. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
  392. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
  393. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
  394. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
  395. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +57 -0
  396. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
  397. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +98 -0
  398. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
  399. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +312 -0
  400. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
  401. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
  402. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
  403. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
  404. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
  405. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
  406. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
  407. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
  408. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
  409. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +107 -0
  410. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +862 -0
  411. package/node_modules/@blockcast/mmt-fec/package.json +79 -0
  412. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +371 -0
  413. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +439 -0
  414. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
  415. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
  416. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +310 -0
  417. package/node_modules/@blockcast/mmt-fec/src/index.ts +94 -0
  418. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
  419. package/node_modules/@blockcast/mmt-fec/src/oti.ts +463 -0
  420. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
  421. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +195 -0
  422. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  423. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
  424. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
  425. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
  426. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
  427. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
  428. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
  429. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
  430. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
  431. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
  432. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
  433. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
  434. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
  435. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +99 -0
  436. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
  437. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +492 -0
  438. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
  439. package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
  440. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
  441. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
  442. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
  443. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +669 -0
  444. package/node_modules/@noble/hashes/LICENSE +21 -0
  445. package/node_modules/@noble/hashes/README.md +521 -0
  446. package/node_modules/@noble/hashes/_assert.d.ts +17 -0
  447. package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
  448. package/node_modules/@noble/hashes/_assert.js +18 -0
  449. package/node_modules/@noble/hashes/_assert.js.map +1 -0
  450. package/node_modules/@noble/hashes/_blake.d.ts +14 -0
  451. package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
  452. package/node_modules/@noble/hashes/_blake.js +50 -0
  453. package/node_modules/@noble/hashes/_blake.js.map +1 -0
  454. package/node_modules/@noble/hashes/_md.d.ts +51 -0
  455. package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
  456. package/node_modules/@noble/hashes/_md.js +162 -0
  457. package/node_modules/@noble/hashes/_md.js.map +1 -0
  458. package/node_modules/@noble/hashes/_u64.d.ts +55 -0
  459. package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
  460. package/node_modules/@noble/hashes/_u64.js +90 -0
  461. package/node_modules/@noble/hashes/_u64.js.map +1 -0
  462. package/node_modules/@noble/hashes/argon2.d.ts +32 -0
  463. package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
  464. package/node_modules/@noble/hashes/argon2.js +401 -0
  465. package/node_modules/@noble/hashes/argon2.js.map +1 -0
  466. package/node_modules/@noble/hashes/blake1.d.ts +106 -0
  467. package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
  468. package/node_modules/@noble/hashes/blake1.js +459 -0
  469. package/node_modules/@noble/hashes/blake1.js.map +1 -0
  470. package/node_modules/@noble/hashes/blake2.d.ts +116 -0
  471. package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
  472. package/node_modules/@noble/hashes/blake2.js +420 -0
  473. package/node_modules/@noble/hashes/blake2.js.map +1 -0
  474. package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
  475. package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
  476. package/node_modules/@noble/hashes/blake2b.js +14 -0
  477. package/node_modules/@noble/hashes/blake2b.js.map +1 -0
  478. package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
  479. package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
  480. package/node_modules/@noble/hashes/blake2s.js +24 -0
  481. package/node_modules/@noble/hashes/blake2s.js.map +1 -0
  482. package/node_modules/@noble/hashes/blake3.d.ts +54 -0
  483. package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
  484. package/node_modules/@noble/hashes/blake3.js +255 -0
  485. package/node_modules/@noble/hashes/blake3.js.map +1 -0
  486. package/node_modules/@noble/hashes/crypto.d.ts +2 -0
  487. package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
  488. package/node_modules/@noble/hashes/crypto.js +5 -0
  489. package/node_modules/@noble/hashes/crypto.js.map +1 -0
  490. package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
  491. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
  492. package/node_modules/@noble/hashes/cryptoNode.js +18 -0
  493. package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
  494. package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
  495. package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
  496. package/node_modules/@noble/hashes/eskdf.js +166 -0
  497. package/node_modules/@noble/hashes/eskdf.js.map +1 -0
  498. package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
  499. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
  500. package/node_modules/@noble/hashes/esm/_assert.js +15 -0
  501. package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
  502. package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
  503. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
  504. package/node_modules/@noble/hashes/esm/_blake.js +45 -0
  505. package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
  506. package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
  507. package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
  508. package/node_modules/@noble/hashes/esm/_md.js +155 -0
  509. package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
  510. package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
  511. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
  512. package/node_modules/@noble/hashes/esm/_u64.js +67 -0
  513. package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
  514. package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
  515. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
  516. package/node_modules/@noble/hashes/esm/argon2.js +392 -0
  517. package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
  518. package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
  519. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
  520. package/node_modules/@noble/hashes/esm/blake1.js +452 -0
  521. package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
  522. package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
  523. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
  524. package/node_modules/@noble/hashes/esm/blake2.js +413 -0
  525. package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
  526. package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
  527. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
  528. package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
  529. package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
  530. package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
  531. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
  532. package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
  533. package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
  534. package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
  535. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
  536. package/node_modules/@noble/hashes/esm/blake3.js +251 -0
  537. package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
  538. package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
  539. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
  540. package/node_modules/@noble/hashes/esm/crypto.js +2 -0
  541. package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
  542. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
  543. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
  544. package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
  545. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
  546. package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
  547. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
  548. package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
  549. package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
  550. package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
  551. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
  552. package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
  553. package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
  554. package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
  555. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
  556. package/node_modules/@noble/hashes/esm/hmac.js +86 -0
  557. package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
  558. package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
  559. package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
  560. package/node_modules/@noble/hashes/esm/index.js +33 -0
  561. package/node_modules/@noble/hashes/esm/index.js.map +1 -0
  562. package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
  563. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
  564. package/node_modules/@noble/hashes/esm/legacy.js +281 -0
  565. package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
  566. package/node_modules/@noble/hashes/esm/package.json +10 -0
  567. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
  568. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
  569. package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
  570. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
  571. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
  572. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
  573. package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
  574. package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
  575. package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
  576. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
  577. package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
  578. package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
  579. package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
  580. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
  581. package/node_modules/@noble/hashes/esm/sha1.js +11 -0
  582. package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
  583. package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
  584. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
  585. package/node_modules/@noble/hashes/esm/sha2.js +375 -0
  586. package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
  587. package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
  588. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
  589. package/node_modules/@noble/hashes/esm/sha256.js +20 -0
  590. package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
  591. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
  592. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
  593. package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
  594. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
  595. package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
  596. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
  597. package/node_modules/@noble/hashes/esm/sha3.js +234 -0
  598. package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
  599. package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
  600. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
  601. package/node_modules/@noble/hashes/esm/sha512.js +26 -0
  602. package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
  603. package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
  604. package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
  605. package/node_modules/@noble/hashes/esm/utils.js +281 -0
  606. package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
  607. package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
  608. package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
  609. package/node_modules/@noble/hashes/hkdf.js +88 -0
  610. package/node_modules/@noble/hashes/hkdf.js.map +1 -0
  611. package/node_modules/@noble/hashes/hmac.d.ts +35 -0
  612. package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
  613. package/node_modules/@noble/hashes/hmac.js +91 -0
  614. package/node_modules/@noble/hashes/hmac.js.map +1 -0
  615. package/node_modules/@noble/hashes/index.d.ts +1 -0
  616. package/node_modules/@noble/hashes/index.d.ts.map +1 -0
  617. package/node_modules/@noble/hashes/index.js +33 -0
  618. package/node_modules/@noble/hashes/index.js.map +1 -0
  619. package/node_modules/@noble/hashes/legacy.d.ts +71 -0
  620. package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
  621. package/node_modules/@noble/hashes/legacy.js +287 -0
  622. package/node_modules/@noble/hashes/legacy.js.map +1 -0
  623. package/node_modules/@noble/hashes/package.json +266 -0
  624. package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
  625. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
  626. package/node_modules/@noble/hashes/pbkdf2.js +101 -0
  627. package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
  628. package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
  629. package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
  630. package/node_modules/@noble/hashes/ripemd160.js +16 -0
  631. package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
  632. package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
  633. package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
  634. package/node_modules/@noble/hashes/scrypt.js +232 -0
  635. package/node_modules/@noble/hashes/scrypt.js.map +1 -0
  636. package/node_modules/@noble/hashes/sha1.d.ts +11 -0
  637. package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
  638. package/node_modules/@noble/hashes/sha1.js +14 -0
  639. package/node_modules/@noble/hashes/sha1.js.map +1 -0
  640. package/node_modules/@noble/hashes/sha2.d.ts +159 -0
  641. package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
  642. package/node_modules/@noble/hashes/sha2.js +384 -0
  643. package/node_modules/@noble/hashes/sha2.js.map +1 -0
  644. package/node_modules/@noble/hashes/sha256.d.ts +20 -0
  645. package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
  646. package/node_modules/@noble/hashes/sha256.js +23 -0
  647. package/node_modules/@noble/hashes/sha256.js.map +1 -0
  648. package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
  649. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
  650. package/node_modules/@noble/hashes/sha3-addons.js +402 -0
  651. package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
  652. package/node_modules/@noble/hashes/sha3.d.ts +53 -0
  653. package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
  654. package/node_modules/@noble/hashes/sha3.js +239 -0
  655. package/node_modules/@noble/hashes/sha3.js.map +1 -0
  656. package/node_modules/@noble/hashes/sha512.d.ts +26 -0
  657. package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
  658. package/node_modules/@noble/hashes/sha512.js +29 -0
  659. package/node_modules/@noble/hashes/sha512.js.map +1 -0
  660. package/node_modules/@noble/hashes/src/_assert.ts +22 -0
  661. package/node_modules/@noble/hashes/src/_blake.ts +50 -0
  662. package/node_modules/@noble/hashes/src/_md.ts +176 -0
  663. package/node_modules/@noble/hashes/src/_u64.ts +91 -0
  664. package/node_modules/@noble/hashes/src/argon2.ts +497 -0
  665. package/node_modules/@noble/hashes/src/blake1.ts +534 -0
  666. package/node_modules/@noble/hashes/src/blake2.ts +486 -0
  667. package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
  668. package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
  669. package/node_modules/@noble/hashes/src/blake3.ts +272 -0
  670. package/node_modules/@noble/hashes/src/crypto.ts +9 -0
  671. package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
  672. package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
  673. package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
  674. package/node_modules/@noble/hashes/src/hmac.ts +94 -0
  675. package/node_modules/@noble/hashes/src/index.ts +31 -0
  676. package/node_modules/@noble/hashes/src/legacy.ts +293 -0
  677. package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
  678. package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
  679. package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
  680. package/node_modules/@noble/hashes/src/sha1.ts +10 -0
  681. package/node_modules/@noble/hashes/src/sha2.ts +402 -0
  682. package/node_modules/@noble/hashes/src/sha256.ts +24 -0
  683. package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
  684. package/node_modules/@noble/hashes/src/sha3.ts +258 -0
  685. package/node_modules/@noble/hashes/src/sha512.ts +34 -0
  686. package/node_modules/@noble/hashes/src/utils.ts +395 -0
  687. package/node_modules/@noble/hashes/utils.d.ts +161 -0
  688. package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
  689. package/node_modules/@noble/hashes/utils.js +313 -0
  690. package/node_modules/@noble/hashes/utils.js.map +1 -0
  691. package/package.json +179 -8
  692. package/schemas/catalog.schema.json +357 -0
  693. package/src/align.ts +239 -0
  694. package/src/audio-init-parser.ts +189 -0
  695. package/src/audio-ring-buffer.ts +120 -0
  696. package/src/authored-timing.ts +63 -0
  697. package/src/block-policy.ts +903 -0
  698. package/src/carp-catalog.ts +1065 -0
  699. package/src/catalog-fec-params.ts +729 -0
  700. package/src/catalog-network-source.ts +74 -0
  701. package/src/catalog-validator.ts +34 -0
  702. package/src/cmaf-block-assembler.ts +1255 -0
  703. package/src/cmaf-group-batcher.ts +104 -0
  704. package/src/cmcd-producer.ts +329 -0
  705. package/src/codec-utils.ts +1984 -0
  706. package/src/conceal-timeline.ts +293 -0
  707. package/src/delivery-gap.ts +147 -0
  708. package/src/duplicate-ledger.ts +372 -0
  709. package/src/fec-codec.ts +61 -0
  710. package/src/fec-manager.ts +4298 -0
  711. package/src/fec-starvation-detector.ts +128 -0
  712. package/src/fec-state.ts +219 -0
  713. package/src/fec-worker-setup.ts +283 -0
  714. package/src/fmp4-completion.ts +47 -0
  715. package/src/fmp4-reader.ts +112 -0
  716. package/src/index.ts +590 -21
  717. package/src/iso-block-locator.ts +329 -0
  718. package/src/iso-bmff-timing.ts +813 -0
  719. package/src/loc.ts +128 -154
  720. package/src/manifest-auth-plan.ts +107 -0
  721. package/src/media-router.ts +841 -0
  722. package/src/mfu-reassembler.ts +3480 -155
  723. package/src/mfu-reassembly-policy.ts +134 -0
  724. package/src/mmtp-container-parser.ts +2380 -0
  725. package/src/mmtp-decode-pipeline.ts +406 -0
  726. package/src/mmtp-router.ts +4860 -0
  727. package/src/mmtp-to-cmaf.ts +977 -0
  728. package/src/multicast-auth-catalog.ts +340 -0
  729. package/src/multicast-input.ts +793 -0
  730. package/src/multicast-manager.ts +2783 -0
  731. package/src/multicast-transport.ts +1058 -0
  732. package/src/multicast-unwrap.ts +132 -0
  733. package/src/no-modules.ts +21 -0
  734. package/src/phase14-catalog-override.ts +336 -0
  735. package/src/repair-fetch.ts +772 -0
  736. package/src/repair-layers.ts +533 -0
  737. package/src/sample-combiner.ts +938 -0
  738. package/src/signaling.ts +78 -0
  739. package/src/source-auth.ts +1 -0
  740. package/src/switching-set-alignment.ts +109 -0
  741. package/src/timestamp-codec.ts +265 -0
  742. package/src/track-pipeline-setup.ts +205 -0
  743. package/src/track-pipeline.ts +332 -0
  744. package/src/track-session.ts +1010 -0
  745. package/src/transport-pool.ts +128 -0
  746. package/src/wasm-mfu-reassembler.ts +405 -0
  747. package/src/wasm-mmtp-container-parser.ts +23 -0
  748. package/test-vectors/align.json +199 -0
  749. package/test-vectors/catalog/README.md +106 -0
  750. package/test-vectors/catalog/negative/archive-origin-with-path.json +33 -0
  751. package/test-vectors/catalog/negative/archive-unknown-field.json +34 -0
  752. package/test-vectors/catalog/negative/fec-block-profile-missing.json +51 -0
  753. package/test-vectors/catalog/negative/fec-block-profile-unknown.json +52 -0
  754. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +28 -0
  755. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +33 -0
  756. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +31 -0
  757. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +32 -0
  758. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +34 -0
  759. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +24 -0
  760. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +33 -0
  761. package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
  762. package/test-vectors/catalog/negative/media-track-repair-layer.json +16 -0
  763. package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
  764. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
  765. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
  766. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
  767. package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
  768. package/test-vectors/catalog/negative/multicast-network-source-empty.json +44 -0
  769. package/test-vectors/catalog/negative/multicast-packet-id-signaling.json +26 -0
  770. package/test-vectors/catalog/negative/network-source-object.json +25 -0
  771. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +24 -0
  772. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +27 -0
  773. package/test-vectors/catalog/negative/repair-track-legacy-shape.json +19 -0
  774. package/test-vectors/catalog/negative/track-role-empty.json +16 -0
  775. package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
  776. package/test-vectors/catalog/positive/archive-origin.json +34 -0
  777. package/test-vectors/catalog/positive/fec-iso-ssbg1.json +52 -0
  778. package/test-vectors/catalog/positive/fec-layered-repair.json +106 -0
  779. package/test-vectors/catalog/positive/fec-multicast.json +52 -0
  780. package/test-vectors/catalog/positive/flat-av.json +30 -0
  781. package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
  782. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +43 -0
  783. package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
  784. package/test-vectors/catalog/positive/track-role.json +49 -0
  785. package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
  786. package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
  787. package/test-vectors/catalog-multicast-auth-v1.json +26 -0
  788. package/test-vectors/catalog-validation.json +732 -0
  789. package/test-vectors/catalog-validator-public-api.mjs +25 -0
  790. package/test-vectors/generate-source-auth-fixture.mjs +114 -0
  791. package/test-vectors/hevc-x265-repeat-headers-v1.json +40 -0
  792. package/test-vectors/source-auth-v1.json +61 -0
  793. package/test-vectors/source-contiguity-v1.json +131 -0
  794. package/test-vectors/subsample-placement-v1.json +230 -0
  795. package/dist/mmtp.d.ts +0 -73
  796. package/dist/mmtp.d.ts.map +0 -1
  797. package/dist/mmtp.js +0 -210
  798. package/dist/mmtp.js.map +0 -1
  799. package/src/mmtp.ts +0 -277
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/track-session.ts", "../src/delivery-gap.ts", "../src/fec-manager.ts", "../../mmt-alta-parse/src/index.ts", "../../mmt-base/src/mmtp.ts", "../../mmt-base/src/ntp-short.ts", "../../mmt-base/src/serial-number.ts", "../../mmt-base/src/fec-block-profile.ts", "../src/iso-block-locator.ts", "../src/fec-state.ts", "../src/block-policy.ts"],
4
+ "sourcesContent": ["/**\n * TrackSession \u2014 per-track transport state machine.\n * SessionManager \u2014 orchestrates all sessions + TransportPool.\n *\n * Implements Patent Claim 9 (draft-ramadan-moq-fec APPLICATION-1):\n * (a) subscribe unicast\n * (b) simultaneously join multicast\n * (c) render from unicast during join\n * (d) activate multicast on RAP, suppress unicast\n * (e) cross-transport FEC repair during transition\n * (f) hysteresis before switching away from multicast\n * (g) re-subscribe unicast on sustained failure\n *\n * PIM: manages transport state without inspecting packet contents.\n */\n\nimport {\n TransportPool,\n type Subscription,\n type SubscriptionMode,\n type AcquireOptions,\n type DirectSocketHealth,\n type TransportPoolOptions,\n} from './transport-pool.js'\nimport { fecBlockBudgetMs } from '@blockcast/mmt-fec'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TrackState =\n | 'idle'\n | 'unicast'\n | 'unicast-fallback'\n | 'joining'\n | 'multicast'\n | 'amt'\n | 'failing'\n\nexport interface TrackSessionConfig {\n trackName: string\n trackType: 'video' | 'audio'\n packetId: number\n /** FEC interleave depth from catalog (ms). Used for hysteresis timer. */\n fecInterleaveMs: number\n /** Explicit catalog jitter/reorder allowance in ms. */\n fecReorderToleranceMs?: number\n /** FEC K from catalog. Used for audio worklet latency calculation. */\n fecK: number\n /** FEC repair symbols per block (P). NOT symbol size. */\n fecRepairCount: number\n /**\n * Repair symbols per block this receiver actually joined: the sum of\n * `repairSymbols` over the joined BASE layers, not a count of layers\n * (draft-ramadan-moq-fec 6.3). A keyframe overlay is a separate FEC instance\n * and is not included.\n *\n * This is runtime join state, not catalog geometry: a receiver on a clean\n * path joins layer 0 only while a lossy one adds layers, so the catalog's\n * total P is not what this receiver is receiving. Diagnostics that report P\n * (the worker snapshot, CMCD `x-bc-fec-p`) must read this.\n *\n * Absent when the caller joins no layers explicitly, in which case P_0 is\n * what was joined. That is an identity on a single-layer track, not a\n * catalog default: with one layer, P_0 is the total.\n */\n joinedRepairCount?: number\n /** FEC symbol size in bytes (T). Required for WASM decoder OTI. */\n fecSymbolSize: number\n /** MoQ repair track name from catalog FEC config. */\n repairTrackName?: string\n /** Frame samples from initData parsing (audio-init-parser.ts). */\n frameSamples?: number\n /** Audio sample rate from catalog. */\n sampleRate?: number\n /** Ordered transport preference: best first. */\n preferredTransports: SubscriptionMode[]\n /** Subscription key for multicast/AMT: \"(S,G,port)\". */\n multicastKey?: string\n /** MoQ track name for unicast subscription. */\n moqTrackName?: string\n /** AMT relay address (if AMT is in preference chain). */\n amtRelay?: string\n /** MoQ relay URL (for WebTransport subscription). */\n moqRelayUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// PIM callbacks (protocol-independent \u2014 transport state only)\n// ---------------------------------------------------------------------------\n\nexport interface SessionManagerCallbacks {\n /** Fired on every transport state transition. */\n onStateChange?(trackName: string, from: TrackState, to: TrackState): void\n /** Fired to suppress/unsuppress unicast for this track (transport arbitration). */\n onSuppress?(trackName: string, suppressed: boolean): void\n /**\n * Fired when a track sat in `joining` for SessionManager.takeoverDeadlineMs\n * without a RAP (BLO-9413/D9). The session has already returned to `unicast`\n * (nothing was suppressed, so nothing to restore) and it keeps its FEC\n * decoder armed, because the multicast socket is still joined and the router\n * still admits the stream's symbols. The host should abandon the router-side\n * attempt (e.g. MediaRouter.failbackToMoq()) so the router stops waiting for\n * a keyframe that is never coming; it must not dispose the decoder here. The\n * decoder is released on the next transport edge (unsubscribe, failbackAll,\n * or transportFailed falling back to unicast) through fec-teardown.\n */\n onTakeoverTimeout?(trackName: string): void\n}\n\n// ---------------------------------------------------------------------------\n// MMTP application-layer extension (FEC lifecycle, media resets)\n// ---------------------------------------------------------------------------\n\n/**\n * MMTP track events (discriminated union).\n * Each protocol layer defines its own event union \u2014 MMTP events cover\n * media resets, latency, and FEC lifecycle.\n */\nexport type MmtpTrackEvent =\n | { type: 'reset' }\n | { type: 'latency'; latencyMs: number }\n | { type: 'fec-setup' }\n | { type: 'fec-teardown' }\n\n/**\n * MMTP application-layer extension of SessionManagerCallbacks.\n * Single onTrackEvent dispatches all MMTP-specific events.\n * PIM consumers use the base SessionManagerCallbacks (no onTrackEvent).\n *\n * IWA can define its own subinterface with its own TrackEvent union\n * (e.g., IwaTrackEvent for socket health, AMT state) using the same pattern.\n */\nexport interface MmtpSessionCallbacks extends SessionManagerCallbacks {\n onTrackEvent?(trackName: string, event: MmtpTrackEvent, config: TrackSessionConfig): void\n}\n\n/**\n * Loss-hysteresis window covering the signaled FEC block span plus jitter.\n *\n * `fecK` no longer feeds the math; it is still validated as a catalog\n * well-formedness guard and kept in the signature for positional API compat\n * (removal is a follow-up with the next cross-repo pin bump).\n */\nexport function computeFecLossWindowMs(fecInterleaveMs: number, fecK: number, jitterBufferMs?: number): number {\n return computeFecDeliveryWindowMs({\n interleaveDepthMs: fecInterleaveMs,\n sourceSymbols: fecK,\n jitterBufferMs,\n })\n}\n\nexport interface FecDeliveryWindowConfig {\n interleaveDepthMs: number\n sourceSymbols: number\n repairSymbols?: number\n symbolSize?: number\n mediaUnitsPerSecond?: number\n bandwidthBps?: number\n /**\n * Explicit jitter/reorder allowance in ms, added once to the complete span.\n * Callers pass their signaled catalog allowance, never a local default.\n */\n jitterBufferMs?: number\n}\n\n/**\n * The catalog interleaveDepthMs is the complete FEC block span, including the\n * repair tail. K, P, cadence, and bandwidth must not expand it again. Geometry\n * fields remain accepted for existing callers, but do not feed deadline math.\n */\nexport function computeFecDeliveryWindowMs(\n config: FecDeliveryWindowConfig,\n): number {\n const fecInterleaveMs = config.interleaveDepthMs\n const fecK = config.sourceSymbols\n if (!Number.isFinite(fecInterleaveMs) || fecInterleaveMs <= 0) {\n throw new Error(\n `[TrackSession] fecInterleaveMs=${fecInterleaveMs}. ` +\n `Derive a positive FEC interleave window from the stream catalog.`\n )\n }\n if (!Number.isFinite(fecK) || fecK <= 0) {\n throw new Error(\n `[TrackSession] fecK=${fecK}. ` +\n `Derive a positive FEC source-symbol count from the stream catalog.`\n )\n }\n\n // Absence stays absent; it does not authorize a synthesized reorder budget.\n if (config.jitterBufferMs === undefined) return Math.ceil(fecInterleaveMs)\n return fecBlockBudgetMs(fecInterleaveMs, config.jitterBufferMs)\n}\n\n/** Convert the shared FEC delivery deadline into an integer media timescale. */\nexport function computeFecDeliveryWindowMediaUnits(\n config: FecDeliveryWindowConfig,\n timestampTimescale: number,\n): number {\n if (!Number.isFinite(timestampTimescale) || timestampTimescale <= 0) {\n throw new Error(\n `[TrackSession] timestampTimescale=${timestampTimescale}. ` +\n 'Derive a positive media timescale from the container.',\n )\n }\n const deliveryWindow = Math.ceil(\n computeFecDeliveryWindowMs(config) * timestampTimescale / 1000,\n )\n if (!Number.isSafeInteger(deliveryWindow)) {\n throw new Error('[TrackSession] FEC delivery window exceeds the media timestamp range.')\n }\n return deliveryWindow\n}\n\n// ---------------------------------------------------------------------------\n// TrackSession\n// ---------------------------------------------------------------------------\n\nexport class TrackSession {\n readonly config: TrackSessionConfig\n #state: TrackState = 'idle'\n #subscription: Subscription | null = null\n #hysteresisTimer: ReturnType<typeof setTimeout> | null = null\n #joinDeadlineTimer: ReturnType<typeof setTimeout> | null = null\n #previousPrimary: SubscriptionMode | null = null\n #manager: SessionManager\n // Single suppression authority flag (BLO-9413 failback coherence): every\n // suppress this session fires is recorded here, and every path that leaves\n // a suppressed state unwinds through #unsuppress(). Before this flag,\n // unsubscribe() only un-suppressed from multicast/amt \u2014 a session torn down\n // from `failing` (exactly where a starving track sits) was re-subscribed to\n // unicast with the host's media still muted: MoQ playing into a dead sink.\n #suppressFired = false\n // Single FEC authority flag: set by the fec-setup edges (joinStarted,\n // enterUnicastFallback), cleared by the fec-teardown edge in #transition.\n // Setup and teardown are paired by construction, so a takeover timeout that\n // parks the session in `unicast` keeps the decoder the still-joined\n // transport is feeding, and the later unsubscribe still tears it down.\n #fecArmed = false\n\n constructor(config: TrackSessionConfig, manager: SessionManager) {\n this.config = config\n this.#manager = manager\n }\n\n // -----------------------------------------------------------------------\n // State\n // -----------------------------------------------------------------------\n\n get state(): TrackState { return this.#state }\n get subscription(): Subscription | null { return this.#subscription }\n\n /** True while this session's onSuppress(true) is outstanding (no matching\n * onSuppress(false) yet). Test/assertion surface for failback coherence. */\n get isSuppressed(): boolean { return this.#suppressFired }\n\n /** Current active transport mode, or null if idle. */\n get transport(): SubscriptionMode | null {\n switch (this.#state) {\n case 'multicast': return 'multicast'\n case 'amt': return 'amt'\n case 'unicast': return 'moq'\n case 'unicast-fallback': return 'moq' // SSM bridge absent \u2014 MoQ unicast is the only source\n case 'joining': return 'moq' // MoQ still primary during join\n case 'failing': return this.#previousPrimary\n default: return null\n }\n }\n\n // -----------------------------------------------------------------------\n // Computed (no magic numbers)\n // -----------------------------------------------------------------------\n\n /** Audio worklet latency: max(fecInterleaveMs, frameDurationMs * fecK).\n * Returns undefined if frame duration unknown (no initData). */\n get audioLatencyMs(): number | undefined {\n const fd = this.frameDurationMs\n if (fd === undefined) {\n return this.config.fecInterleaveMs > 0 ? this.config.fecInterleaveMs : undefined\n }\n return Math.max(this.config.fecInterleaveMs, fd * this.config.fecK)\n }\n\n /** Frame duration in ms, derived from frameSamples + sampleRate. */\n get frameDurationMs(): number | undefined {\n const { frameSamples, sampleRate } = this.config\n if (!frameSamples || !sampleRate || sampleRate <= 0) return undefined\n return (frameSamples / sampleRate) * 1000\n }\n\n // -----------------------------------------------------------------------\n // Transitions (Patent Claim 9)\n // -----------------------------------------------------------------------\n\n /** idle \u2192 unicast: player subscribes to this track. */\n subscribe(): void {\n if (this.#state !== 'idle') return\n this.#transition('unicast')\n\n // Emit audio latency on first subscribe\n if (this.config.trackType === 'audio') {\n const latency = this.audioLatencyMs\n if (latency !== undefined) {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'latency', latencyMs: latency }, this.config)\n }\n }\n }\n\n /** unicast \u2192 joining: multicast/AMT capability detected, join in progress. */\n joinStarted(): void {\n if (this.#state !== 'unicast') return\n this.#transition('joining')\n // FEC decoders must be ready before multicast data arrives (Claim 9e:\n // cross-transport repair). A session re-joining after a takeover timeout\n // still owns its decoder, so fec-setup fires once per FEC epoch.\n this.#armFec()\n }\n\n /**\n * any non-fallback state \u2192 unicast-fallback. Used when the SSM bridge is\n * absent (vanilla Chromium / no extension) and the host falls back to a\n * MoQ unicast subscription per track. Terminal-ish: activateMulticast,\n * failbackAll, and transportFailed are no-ops while in this state.\n *\n * Fires fec-setup so the per-track repair worker spins up \u2014 repair packets\n * still flow over the MoQ repair-track subscription, so FEC source+repair\n * can converge as in the multicast path.\n *\n * Audio latency is NOT re-emitted here: subscribe() already fires latency\n * once on the idle \u2192 unicast edge, and enterUnicastFallback is only ever\n * reached from a non-idle state (SessionManager skips idle sessions).\n */\n enterUnicastFallback(): void {\n if (this.#state === 'unicast-fallback') return\n this.#clearHysteresis()\n this.#transition('unicast-fallback')\n // Suppress the redundant unicast media decoder. On the no-bridge path the\n // host feeds MoQ-unicast source packets into MulticastManager (the render\n // consumer); a second base-media subscribe (the @moq/watch decoder) would\n // split the single connection's QUIC flow-control budget and stall delivery\n // (BLO-8969). Fire suppress here, on the fallback edge, so the decoder is\n // gated off before it subscribes rather than after the relay forward task\n // has already wedged.\n this.#suppress()\n this.#armFec()\n }\n\n /**\n * unicast-fallback \u2192 unicast. Called when the SSM bridge appears mid-session\n * (extension installed after fallback engaged). Host first aborts the fallback\n * drainers, then transitions sessions back to unicast so the regular\n * multicast/AMT preference chain can take over via joinStarted().\n */\n exitUnicastFallback(): void {\n if (this.#state !== 'unicast-fallback') return\n this.#transition('unicast')\n // Un-suppress the unicast decoder: the bridge appeared mid-session, so the\n // host tears down the fallback drainers and the regular preference chain\n // (joinStarted -> joining -> rapReceived) will re-suppress on multicast RAP.\n this.#unsuppress()\n }\n\n /**\n * joining \u2192 multicast or amt: RAP received on the joined transport (Claim 9d).\n * Resets audio worklet, then suppresses MoQ.\n */\n rapReceived(mode: 'multicast' | 'amt'): void {\n if (this.#state !== 'joining') return\n this.#previousPrimary = mode\n this.#transition(mode)\n if (this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n this.#suppress()\n }\n\n /** multicast/amt \u2192 failing: loss detected, start hysteresis timer (Claim 9f). */\n lossDetected(): void {\n if (this.#state !== 'multicast' && this.#state !== 'amt') return\n this.#previousPrimary = this.#state as SubscriptionMode\n this.#transition('failing')\n\n // Hysteresis timer: sustained loss before failback (Claim 9f). Use the\n // same complete span plus explicit allowance as FECManager and workers.\n const hysteresisMs = computeFecLossWindowMs(\n this.config.fecInterleaveMs,\n this.config.fecK,\n this.config.fecReorderToleranceMs,\n )\n this.#hysteresisTimer = setTimeout(() => {\n this.hysteresisExpired()\n }, hysteresisMs)\n }\n\n /** failing \u2192 previous primary: loss recovered within hysteresis window. */\n lossRecovered(): void {\n if (this.#state !== 'failing') return\n this.#clearHysteresis()\n if (this.#previousPrimary) {\n this.#transition(this.#previousPrimary as TrackState)\n }\n }\n\n /**\n * Transport failed (AMT relay down, multicast interface gone).\n * Try next transport in preference chain.\n */\n transportFailed(): void {\n this.#clearHysteresis()\n // Unicast-fallback is terminal: the host owns the subscription and\n // SessionManager transport machinery does not arbitrate it.\n if (this.#state === 'unicast-fallback') return\n const current = this.#previousPrimary ?? this.transport\n const prefs = this.config.preferredTransports\n const currentIdx = current ? prefs.indexOf(current) : -1\n const nextMode = prefs[currentIdx + 1]\n\n if (nextMode !== undefined && nextMode !== 'moq') {\n // Next is multicast or AMT: start joining. `joining` means MoQ is\n // primary again (see transport getter), so if the failed transport had\n // suppressed unicast, restore it for the join window or the viewer\n // watches black video until the next RAP (which may never come).\n // The chain position moves with the attempt: a join that times out\n // parks the session in `unicast` with this transport recorded, so the\n // next failure continues past it rather than re-trying the transport\n // that already failed and re-firing onTakeoverTimeout on every lap.\n this.#previousPrimary = nextMode\n this.#transition('joining')\n this.#unsuppress()\n return\n }\n // MoQ is next, or the chain is exhausted: unicast either way (Claim 9g).\n // For a session parked in `unicast` with its decoder retained after a\n // takeover timeout, this transition is a self-edge and fires no teardown,\n // so the release is explicit. It is the \"transportFailed falling back to\n // unicast\" release path the onTakeoverTimeout doc names, and #disarmFec is\n // idempotent, so it is a no-op when the transition already fired the\n // teardown. The audio reset is not flag-guarded, so it is tied to the edge\n // itself: a session already resting in unicast changed no transport and\n // owes the host no reset, however many pool subscriptions fail for it.\n const from = this.#state\n this.#previousPrimary = null\n this.#transition('unicast')\n this.#disarmFec()\n this.#unsuppress()\n if (from !== 'unicast' && this.config.trackType === 'audio') {\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'reset' }, this.config)\n }\n }\n\n /** Hysteresis expired \u2192 failback to next transport (Claim 9g). */\n hysteresisExpired(): void {\n if (this.#state !== 'failing') return\n this.#clearHysteresis()\n this.transportFailed()\n }\n\n /** \u2192 idle: player unsubscribes / tab close / cleanup. */\n unsubscribe(): void {\n this.#clearHysteresis()\n if (this.#state !== 'idle') {\n this.#transition('idle')\n // Unwind any outstanding suppression regardless of which state we left.\n // The old `wasActive` check (multicast/amt only) leaked suppression when\n // tearing down from `failing` or `unicast-fallback` \u2014 failbackAll()'s\n // unsubscribe\u2192subscribe cycle then re-entered unicast with the host's\n // media still muted (BLO-9413 freeze: \"failing back\" logged, video dead).\n this.#unsuppress()\n }\n }\n\n /** True when SessionManager.enterUnicastFallback put this track into the\n * host-driven unicast subscription mode. Host can iterate sessions to\n * decide whether to keep its fallback drainer alive across MediaRouter\n * arbitration events. */\n get isUnicastFallback(): boolean {\n return this.#state === 'unicast-fallback'\n }\n\n /**\n * True while this track owns a FEC decoder (fec-setup fired, fec-teardown\n * not yet). Stays true across a takeover timeout that parked the session in\n * `unicast`; SessionManager.failbackAll uses it to release such sessions.\n */\n get isFecArmed(): boolean {\n return this.#fecArmed\n }\n\n /**\n * Start the deadline a `joining` entry deferred while the join was inactive\n * (SessionManager.joinActive). The track gets the FULL deadline from here,\n * because that is when media can first arrive. No-op outside `joining` and\n * when a deadline is already running, so it never shortens or restarts one.\n *\n * The `joinActive` guard is what makes this safe to call from anywhere: a\n * track parked in `joining` with a deferred (null) timer satisfies the other\n * two, so without it a host calling this while the join is still inactive\n * would arm the exact deadline the gate exists to defer -- silently\n * reinstating BLO-34289. It is a no-op for the internal caller, which writes\n * `#joinActive = true` before iterating the sessions.\n */\n armDeferredJoinDeadline(): void {\n if (this.#state !== 'joining' || this.#joinDeadlineTimer !== null) return\n if (!this.#manager.joinActive) return\n this.#armJoinDeadline(this.#manager.takeoverDeadlineMs)\n }\n\n dispose(): void {\n this.#clearHysteresis()\n this.#clearJoinDeadline()\n this.#state = 'idle'\n this.#subscription = null\n // Release through the teardown edge, not a bare assignment: the\n // retained-FEC park makes `unicast` + isFecArmed a resting state, and\n // SessionManager.dispose() calls this directly (removeTrack() reaches it\n // via unsubscribe(), which has already disarmed). A bare assignment there\n // drops the host's decoder without a fec-teardown -- a leaked Worker.\n this.#disarmFec()\n }\n\n // -----------------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------------\n\n #transition(to: TrackState, opts?: { retainFec?: boolean }): void {\n const from = this.#state\n if (from === to) return\n // Takeover deadline (BLO-9413/D9): every entry into `joining` arms it\n // (joinStarted AND transportFailed-failover both route through here);\n // every exit cancels it. Without this, a transport that accepts the\n // subscribe but never delivers a RAP parks the session in `joining`\n // forever (observed live in the Phase-0 headless repro). The value is\n // resolved before any state write, so the one residual failure -- a\n // manager whose deadline was never set at all, which addTrack() refuses --\n // leaves the session in its previous state rather than half-transitioned.\n // Invalid values never reach here: SessionManager's setter refuses them.\n // The read is unconditional even when `joinActive` defers the arm, so a\n // manager without a deadline still fails closed on every joining edge.\n const joinDeadlineMs = to === 'joining' ? this.#manager.takeoverDeadlineMs : undefined\n this.#state = to\n if (from === 'joining') this.#clearJoinDeadline()\n if (joinDeadlineMs !== undefined && this.#manager.joinActive) {\n this.#armJoinDeadline(joinDeadlineMs)\n }\n // FEC teardown: entering a state without a FEC decoder releases the one\n // this track armed, unless the caller keeps it (takeover timeout: the\n // transport is still joined and still feeding the decoder).\n const fecActive = (s: TrackState) =>\n s === 'joining' ||\n s === 'multicast' ||\n s === 'amt' ||\n s === 'failing' ||\n s === 'unicast-fallback'\n if (!fecActive(to) && !opts?.retainFec) this.#disarmFec()\n this.#manager.callbacks?.onStateChange?.(this.config.trackName, from, to)\n }\n\n #clearHysteresis(): void {\n if (this.#hysteresisTimer !== null) {\n clearTimeout(this.#hysteresisTimer)\n this.#hysteresisTimer = null\n }\n }\n\n #armJoinDeadline(deadlineMs: number): void {\n this.#clearJoinDeadline()\n if (deadlineMs === 0) return // a declared 0 disables the deadline\n this.#joinDeadlineTimer = setTimeout(() => {\n this.#joinDeadlineTimer = null\n if (this.#state !== 'joining') return\n // No RAP within the deadline: return to unicast for arbitration only.\n // MoQ was never suppressed in `joining`, so there is nothing to restore\n // and this edge stays silent on onSuppress (asserted in tests). The\n // multicast socket is still joined and the router still admits this\n // stream's symbols, so the FEC decoder stays armed; the next transport\n // edge releases it.\n this.#transition('unicast', { retainFec: true })\n this.#manager.callbacks?.onTakeoverTimeout?.(this.config.trackName)\n }, deadlineMs)\n }\n\n #clearJoinDeadline(): void {\n if (this.#joinDeadlineTimer !== null) {\n clearTimeout(this.#joinDeadlineTimer)\n this.#joinDeadlineTimer = null\n }\n }\n\n /** Fire fec-setup exactly once per FEC epoch. */\n #armFec(): void {\n if (this.#fecArmed) return\n this.#fecArmed = true\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-setup' }, this.config)\n }\n\n /** Fire fec-teardown for an armed decoder; no-op when none is armed. */\n #disarmFec(): void {\n if (!this.#fecArmed) return\n this.#fecArmed = false\n this.#manager.mmtp?.onTrackEvent?.(this.config.trackName, { type: 'fec-teardown' }, this.config)\n }\n\n /** Fire onSuppress(true) exactly once per suppression epoch. */\n #suppress(): void {\n if (this.#suppressFired) return\n this.#suppressFired = true\n this.#manager.callbacks?.onSuppress?.(this.config.trackName, true)\n }\n\n /** Unwind an outstanding suppression; no-op when none is outstanding. */\n #unsuppress(): void {\n if (!this.#suppressFired) return\n this.#suppressFired = false\n this.#manager.callbacks?.onSuppress?.(this.config.trackName, false)\n }\n}\n\n// ---------------------------------------------------------------------------\n// SessionManager\n// ---------------------------------------------------------------------------\n\n/**\n * Largest takeover deadline setTimeout represents faithfully. setTimeout\n * coerces its delay to a signed 32-bit int, so a larger value fires on the\n * next tick (Node also warns TimeoutOverflowWarning) instead of after the\n * window the host asked for -- silently inverting the feature. Rejecting it\n * keeps the classic units bug (seconds-as-ms, a wrong timescale) loud.\n */\nexport const MAX_TAKEOVER_DEADLINE_MS = 2_147_483_647\n\n/**\n * How long `joinActive` may stay false before the library says so. The deferral\n * is unbounded by construction -- that is the feature -- so a host that never\n * flips it back parks every track in `joining` with no timer at all: exactly the\n * forever-join the takeover deadline exists to prevent, only silent. MoQ stays\n * unsuppressed so the viewer sees video, but onTakeoverTimeout never fires, the\n * FEC decoder stays armed, and the preference chain never advances past the\n * transport that failed. It looks like a slow join. A host is one un-awaited\n * rejection between `false` and `true` away from it, so the library complains\n * rather than trusting every future host to get all of its exit paths right.\n *\n * Generous against a genuinely slow subscribe -- a bridge reconnect or AMT relay\n * handshake is seconds -- so anything still unsettled here is broken, not slow.\n */\nexport const JOIN_DEFER_WARN_MS = 30_000\n\nexport class SessionManager {\n readonly pool: TransportPool\n readonly sessions = new Map<string, TrackSession>()\n callbacks: SessionManagerCallbacks | null = null\n #takeoverDeadlineMs: number | undefined = undefined\n #joinActive = true\n #joinDeferWarnTimer: ReturnType<typeof setTimeout> | null = null\n\n /**\n * Takeover deadline (BLO-9413/D9): the time a session may sit in `joining`\n * without a RAP before returning to `unicast` and firing onTakeoverTimeout.\n * MoQ is never suppressed during `joining`, so expiry abandons the takeover\n * without a playback glitch. The host derives it from the catalog (RAP\n * cadence from groupDurationTicks/timescale plus the signaled FEC delivery\n * window or reorder tolerance) and sets it before addTrack(). There is no\n * default: an unset value fails addTrack() closed, and reading the getter\n * before assignment throws the same way, so no host ever holds a\n * `number | undefined` it could coalesce back into a default.\n * `hasTakeoverDeadline` answers whether one was admitted. A declared 0\n * disables the deadline. The value is an integer number of milliseconds:\n * setTimeout truncates fractions, so a host that divides ticks by a\n * timescale must round before assigning.\n *\n * Assignment validates, so the diagnostic lands at the host's own call site.\n * That is what bounds the blast radius: `joining` is entered from\n * joinStarted(), from the transportFailed() failover branch -- itself\n * reachable from a setTimeout via hysteresisExpired(), where no caller could\n * catch a throw -- and from the activateMulticast()/activateAmt()/pool-failure\n * loops, where a throw on one session would abandon its siblings mid-loop\n * (the split-brain those loops exist to prevent). Refusing a bad value at\n * assignment means no host mutation can make an already-admitted session\n * throw out of a transport edge.\n */\n get takeoverDeadlineMs(): number {\n return this.#requireTakeoverDeadlineMs()\n }\n\n set takeoverDeadlineMs(value: number) {\n if (!Number.isInteger(value) || value < 0 || value > MAX_TAKEOVER_DEADLINE_MS) {\n throw new Error(\n `[SessionManager] takeoverDeadlineMs must be an integer from 0 to ${MAX_TAKEOVER_DEADLINE_MS} (0 disables it, larger values overflow setTimeout); got ${String(value)}`,\n )\n }\n this.#takeoverDeadlineMs = value\n }\n\n /** True once the host has admitted a deadline; a declared 0 counts. */\n get hasTakeoverDeadline(): boolean {\n return this.#takeoverDeadlineMs !== undefined\n }\n\n /**\n * Whether the transport the joining tracks are waiting on is actually\n * subscribed. The takeover deadline is a media-arrival budget and media\n * cannot arrive before the join exists, so while this is false, entering\n * `joining` arms no deadline; flipping it true starts the FULL deadline for\n * every track already parked there.\n *\n * Default true, which is the arm-on-`joining` behaviour every host had\n * before: a host that only calls joinStarted() after its own join resolves\n * (moqtail-private's player, post-BLO-33339) is already at the right edge and\n * needs no gate. A host that arms joinStarted() up front \u2014 to have FEC\n * decoders ready before the first datagram \u2014 sets this false before addTrack()\n * and true when its subscribe settles. Charging a slow subscribe (bridge\n * reconnect, AMT relay handshake) against the media budget expired the\n * deadline before the subscription existed and abandoned the join on the\n * happy path. BLO-34289.\n *\n * The deadline is a media budget (group cadence plus FEC delivery window) and\n * budgets nothing for reaching the stream, so ideally it starts at\n * MulticastTransport.onDataPlaneLive, the join's first accepted packet. Over\n * AMT that is measurably later than a settled subscribe: the subscribe\n * settles once the gateway has sent its Membership Update, and the relay's\n * upstream join plus a path RTT (0.4-0.8 s measured to the prod relay) still\n * precede the first packet. But RFC 7450 gives a gateway no signal that the\n * relay has traffic for its (S,G), so a join whose source never sends never\n * reaches that edge. Starting the deadline there is therefore sound only when\n * the provider bounds the join phase itself, with a typed subscription error\n * for a join that delivers nothing (MulticastTransport forwards it to\n * onFailback). A host whose provider makes no such guarantee flips this true\n * when the subscribe settles and accepts that join latency is charged to the\n * media budget; holding it until the first packet would park a silent join\n * in `joining` forever.\n *\n * The flag tracks the host's primary join, not per-track transport state. A\n * track that fails over to the next transport in its own chain while this is\n * false (transportFailed's failover branch also enters `joining`) has that\n * deadline gated on the primary join activating, not on its own transport.\n * Under the documented usage -- false once at setup, true once the subscribe\n * settles -- the two coincide.\n *\n * The gate is on the ENTRY into `joining`, and false cannot take back a\n * deadline already running: the `true -> false` edge arms the warn below and\n * clears nothing. So set it false BEFORE the `joining` entry it is meant to\n * gate. A host that instead flipped it false after a `transportFailed()`\n * failover had already entered `joining` would leave the full deadline\n * counting against a subscribe that had not begun -- BLO-34289 again, on the\n * failover edge. Clearing running deadlines on that edge is deliberately NOT\n * done: this flag is manager-wide, so one transport's brief false would\n * cancel an unrelated track's live deadline and then re-arm it at FULL length\n * on the next activation (armDeferredJoinDeadline sees `joining` + a null\n * timer and cannot tell a cleared deadline from a deferred one). Every\n * unrelated transport blip would restart the whole budget, so under repeated\n * failover a track that should have timed out is extended indefinitely and\n * never advances. Only a false that never returns true loses the timer\n * outright, and JOIN_DEFER_WARN_MS already covers that.\n *\n * Leaving this false is not free: see JOIN_DEFER_WARN_MS.\n */\n get joinActive(): boolean {\n return this.#joinActive\n }\n\n set joinActive(active: boolean) {\n const wasActive = this.#joinActive\n this.#joinActive = active\n if (!active) {\n // Edge-triggered: a host re-declaring false has not restarted its\n // subscribe, and total deferral is what the warning is about.\n if (wasActive) this.#armJoinDeferWarn()\n return\n }\n this.#clearJoinDeferWarn()\n if (wasActive) return\n for (const [, session] of this.sessions) session.armDeferredJoinDeadline()\n }\n\n #armJoinDeferWarn(): void {\n this.#joinDeferWarnTimer = setTimeout(() => {\n this.#joinDeferWarnTimer = null\n const deferred = [...this.sessions.values()].filter(s => s.state === 'joining').length\n console.warn(\n `[SessionManager] joinActive has been false for ${JOIN_DEFER_WARN_MS} ms and ${deferred} track(s) sit in 'joining' with no takeover deadline. Set joinActive = true when the subscribe settles, on every exit path including failures, or these tracks never time out, never release their FEC decoders, and never advance to the next transport.`,\n )\n }, JOIN_DEFER_WARN_MS)\n }\n\n #clearJoinDeferWarn(): void {\n if (this.#joinDeferWarnTimer === null) return\n clearTimeout(this.#joinDeferWarnTimer)\n this.#joinDeferWarnTimer = null\n }\n\n /** Narrow to MMTP callbacks (onTrackEvent). Safe \u2014 optional chaining handles PIM-only consumers. */\n get mmtp(): MmtpSessionCallbacks | null {\n return this.callbacks as MmtpSessionCallbacks | null\n }\n\n constructor(poolOptions?: TransportPoolOptions) {\n this.pool = new TransportPool(poolOptions)\n\n // Wire pool failure \u2192 per-track failover\n this.pool.onSubscriptionFailed(sub => {\n for (const trackName of sub.consumers) {\n const session = this.sessions.get(trackName)\n if (session) session.transportFailed()\n }\n })\n }\n\n // -----------------------------------------------------------------------\n // Track management\n // -----------------------------------------------------------------------\n\n /**\n * The admitted takeover deadline. Throws only when the host never set one,\n * every other invalid value being refused by the setter, and returns a\n * declared 0 as 0. The getter and addTrack() both route through it, so a\n * session cannot exist without an admitted deadline, which is what makes\n * the #transition() read unable to throw on an already-admitted session.\n */\n #requireTakeoverDeadlineMs(): number {\n const value = this.#takeoverDeadlineMs\n if (value === undefined) {\n throw new Error(\n '[SessionManager] takeoverDeadlineMs is unset; set a catalog-derived takeover deadline (0 disables it) before adding tracks',\n )\n }\n return value\n }\n\n /** Add a track session from catalog config. */\n addTrack(config: TrackSessionConfig): TrackSession {\n if (this.sessions.has(config.trackName)) {\n throw new Error(`[SessionManager] Track \"${config.trackName}\" already exists`)\n }\n this.#requireTakeoverDeadlineMs()\n const session = new TrackSession(config, this)\n this.sessions.set(config.trackName, session)\n return session\n }\n\n /** Remove a track session. Releases subscription with grace period. */\n removeTrack(trackName: string): void {\n const session = this.sessions.get(trackName)\n if (!session) return\n session.unsubscribe()\n\n // Release pool subscription\n const subKey = session.config.multicastKey ?? session.config.moqTrackName\n if (subKey) this.pool.releaseSubscription(subKey, trackName)\n\n session.dispose()\n this.sessions.delete(trackName)\n }\n\n // -----------------------------------------------------------------------\n // Atomic multi-track operations\n // -----------------------------------------------------------------------\n\n /**\n * All tracks with multicast RAP \u2192 multicast state (atomic).\n * Called when MediaRouter fires onSourceSwitch('multicast').\n * Prevents split-brain between video and audio.\n *\n * A takeover deadline can return a track from `joining` to `unicast` while\n * the multicast socket keeps delivering packets and the track keeps its FEC\n * decoder armed. If a valid RAP arrives later, that late-RAP edge suppresses\n * unicast and promotes multicast without a second fec-setup.\n */\n activateMulticast(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'unicast') {\n session.joinStarted()\n }\n if (session.state === 'joining') {\n session.rapReceived('multicast')\n }\n }\n }\n\n /**\n * All tracks with AMT RAP \u2192 amt state (atomic).\n */\n activateAmt(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'unicast') {\n session.joinStarted()\n }\n if (session.state === 'joining') {\n session.rapReceived('amt')\n }\n }\n }\n\n /**\n * All tracks \u2192 unicast (failback, atomic).\n * Called when MulticastManager fires onFailback().\n * unicast-fallback sessions are skipped \u2014 the host owns those subscriptions\n * and they must drain uninterrupted across transport-state events.\n *\n * Coherence invariant (BLO-9413): after this returns, every non-fallback\n * session is `unicast` (or `idle`), unsuppressed, and without a FEC decoder.\n * unsubscribe() unwinds any outstanding suppression regardless of the state\n * it tears down from (`failing` included; see TrackSession.#suppressFired),\n * and a session that a takeover timeout parked in `unicast` with its decoder\n * still armed is cycled too, because this transport edge is what releases\n * that decoder (unsubscribe fires fec-teardown).\n */\n failbackAll(): void {\n for (const [, session] of this.sessions) {\n const parkedWithFec = session.state === 'unicast' && session.isFecArmed\n if (\n parkedWithFec ||\n (session.state !== 'idle' &&\n session.state !== 'unicast' &&\n session.state !== 'unicast-fallback')\n ) {\n session.unsubscribe()\n session.subscribe() // re-enter unicast\n }\n }\n }\n\n /**\n * All tracks \u2192 unicast-fallback (atomic).\n * Called by the host when the multicast gateway is absent and\n * the player will subscribe to source tracks over MoQ unicast and feed\n * packets via MulticastManager.feedUnicastSource() (or manager.router\n * directly). Sessions in this state stay terminal until the host calls\n * unsubscribe() / disposes the SessionManager.\n */\n enterUnicastFallback(): void {\n for (const [, session] of this.sessions) {\n if (session.state === 'idle') continue\n session.enterUnicastFallback()\n }\n }\n\n /**\n * True if any session is currently in unicast-fallback. Hosts use this to\n * gate `multicastTrack.close()` on failback events \u2014 closing the shared\n * broadcast would kill the host's fallback drainers.\n */\n hasActiveFallback(): boolean {\n for (const [, session] of this.sessions) {\n if (session.isUnicastFallback) return true\n }\n return false\n }\n\n /**\n * All fallback tracks \u2192 unicast (atomic).\n * Called when the SSM bridge appears mid-session and the host has aborted\n * the fallback drainers. The session re-enters the regular preference chain\n * via joinStarted() (issued by the host immediately after).\n */\n exitUnicastFallback(): void {\n for (const [, session] of this.sessions) {\n if (session.isUnicastFallback) session.exitUnicastFallback()\n }\n }\n\n // -----------------------------------------------------------------------\n // Cleanup lifecycle\n // -----------------------------------------------------------------------\n\n /** Tab closed \u2014 release all subscriptions for this tab (grace period). */\n tabClosed(tabId: string): void {\n // All tracks owned by this tab \u2192 idle\n for (const [trackName, session] of this.sessions) {\n session.unsubscribe()\n this.pool.releaseAllForConsumer(trackName)\n }\n }\n\n /** Bridge WebTransport connection dropped \u2014 same as tab close. */\n bridgeDisconnected(): void {\n this.tabClosed('bridge')\n }\n\n /**\n * Track selection changed (user or ABR).\n * Old track released (grace period), new track subscribed.\n */\n trackSelectionChanged(oldTrackName: string, newConfig: TrackSessionConfig): void {\n this.removeTrack(oldTrackName)\n const session = this.addTrack(newConfig)\n session.subscribe()\n }\n\n // -----------------------------------------------------------------------\n // Query\n // -----------------------------------------------------------------------\n\n /** Get a track session by name. */\n getTrack(trackName: string): TrackSession | undefined {\n return this.sessions.get(trackName)\n }\n\n // -----------------------------------------------------------------------\n // Cleanup\n // -----------------------------------------------------------------------\n\n dispose(): void {\n this.#clearJoinDeferWarn()\n for (const [, session] of this.sessions) {\n session.dispose()\n }\n this.sessions.clear()\n this.pool.dispose()\n this.callbacks = null\n }\n}\n", "/**\n * Receiver-measured multicast delivery gap (BLO-35597).\n *\n * The FEC layer gives a block's multicast symbols a late-symbol wait before it\n * declares the block over: the catalog block span plus an allowance for the\n * path's arrival jitter. The catalog can only signal a tolerance it knows in\n * advance (`fec.reorderToleranceMs`); the path the receiver actually sits on\n * is measured here, in-band, from the arrival times of the multicast\n * datagrams themselves.\n *\n * Which statistic, and why not RFC 3550 interarrival jitter\n * ---------------------------------------------------------\n * The wait is an inactivity bound: a block fails when no symbol reaches it for\n * the wait. What it has to outlast is therefore the longest silence the\n * delivery path produces, not the path's average variation.\n *\n * RFC 3550 \u00A76.4.1 `J` is a mean absolute transit-time difference smoothed with\n * gain 1/16. It is the right number for sizing a playout buffer against steady\n * jitter, and the wrong one for a bimodal path. On the path this was measured\n * on (iPhone Safari over Wi-Fi, prod AMT, 2026-09-28, 36,657 datagrams in 57 s)\n * arrivals were either back-to-back (p90 inter-arrival 2 ms) or stalled for\n * one 802.11 power-save beacon interval (102.4 ms; 135 stalls of 50-110 ms,\n * recurring every ~511 ms at the median). Applied to that trace, `J` has a p99\n * of 6 ms: a wait of span + J expires inside every stall, and the stall's\n * symbols then arrive at a block that has already been failed.\n *\n * So the estimator is the largest delivery gap observed over a trailing\n * window: the time between consecutive multicast datagram arrivals, measured\n * on the page thread where the FEC deadlines themselves run (a main-thread or\n * bridge-batching delay is as real to those timers as a radio stall). It is\n * receiver-wide rather than per stream because the stall is a property of the\n * link every stream shares; a quiet stream learns the gap from a busy one.\n *\n * Only a gap that ended in delivery counts. A silence is closed by the first\n * packet after it; if that packet is the in-sequence successor (MMTP\n * packet_sequence_number + 1) of the last one its packet_id delivered, nothing\n * of that flow was lost across the silence: its packets were held and then\n * delivered, which is exactly the lateness a longer wait turns from a failed\n * block into a completed one. If the sequence jumped, the silence was (at\n * least partly) loss -- a tunnel outage, a publisher pause -- and waiting\n * longer would not have delivered anything, so the gap is not taken as\n * jitter.\n *\n * The trailing window is supplied by the caller from the catalog. It must be\n * long enough to contain one recurrence of the stall it has to remember, and\n * nothing here can know that recurrence in advance. The window is also the\n * largest gap reported: a silence longer than the catalog horizon is outside\n * what the estimator models (a backgrounded tab, a long GC), and every bound\n * derived from the wait (block lifetime, terminal retention, the release\n * gate's hold ceiling) must stay a catalog quantity rather than a measurement.\n * `undefined` means no catalog horizon is known: nothing bounds the gap, so\n * none is reported and the catalog tolerance alone applies.\n */\nexport class DeliveryGapEstimator {\n /** Most recent arrival, or undefined before the first one since reset. */\n #lastArrivalMs: number | undefined\n /** Last MMTP packet_sequence_number seen per packet_id. */\n #lastSequence = new Map<number, number>()\n /**\n * Monotonic deque of (arrivalMs, gapMs): gaps strictly decrease from head to\n * tail, so the head is the window maximum. Amortized O(1) per arrival.\n */\n #times: number[] = []\n #gaps: number[] = []\n #head = 0\n #windowMs: number | undefined\n\n /**\n * @param windowMs trailing window in ms, which also caps the reported gap,\n * or undefined when the catalog gives none (no gap is reported).\n */\n setWindowMs(windowMs: number | undefined): void {\n if (windowMs !== undefined && !(Number.isFinite(windowMs) && windowMs > 0)) {\n throw new Error(\n `[DeliveryGap] window must be a finite positive ms value or undefined (got ${windowMs})`,\n )\n }\n this.#windowMs = windowMs\n }\n\n get windowMs(): number | undefined {\n return this.#windowMs\n }\n\n /**\n * Record one multicast datagram arrival at receiver time `nowMs`, carrying\n * MMTP `packetSequenceNumber` (32-bit, per packet_id) on `packetId`.\n */\n noteArrival(nowMs: number, packetId: number, packetSequenceNumber: number): void {\n const last = this.#lastArrivalMs\n const previousSequence = this.#lastSequence.get(packetId)\n this.#lastArrivalMs = nowMs\n this.#lastSequence.set(packetId, packetSequenceNumber)\n if (last === undefined || previousSequence === undefined) return\n if (this.#windowMs === undefined) return\n // MMTP packet_sequence_number is a 32-bit counter that wraps.\n if (((previousSequence + 1) >>> 0) !== (packetSequenceNumber >>> 0)) return\n const gap = nowMs - last\n this.#evict(nowMs)\n // A gap no larger than the current tail can never become the maximum\n // while the tail is still in the window, except as the newest entry.\n while (this.#gaps.length > this.#head && this.#gaps[this.#gaps.length - 1] <= gap) {\n this.#gaps.pop()\n this.#times.pop()\n }\n this.#gaps.push(gap)\n this.#times.push(nowMs)\n this.#compact()\n }\n\n /**\n * Largest gap observed within the window ending at `nowMs`, at most the\n * window itself; 0 if none or when no window is set.\n */\n peakGapMs(nowMs: number): number {\n const windowMs = this.#windowMs\n if (windowMs === undefined) return 0\n this.#evict(nowMs)\n return this.#gaps.length > this.#head ? Math.min(this.#gaps[this.#head], windowMs) : 0\n }\n\n /** Forget everything: a new delivery path starts with no measurement. */\n reset(): void {\n this.#lastArrivalMs = undefined\n this.#lastSequence.clear()\n this.#times = []\n this.#gaps = []\n this.#head = 0\n }\n\n #evict(nowMs: number): void {\n const windowMs = this.#windowMs\n if (windowMs === undefined) return\n while (this.#times.length > this.#head && this.#times[this.#head] < nowMs - windowMs) {\n this.#head++\n }\n }\n\n #compact(): void {\n // Drop consumed head slots once they outnumber live ones.\n if (this.#head > 0 && this.#head * 2 >= this.#times.length) {\n this.#times = this.#times.slice(this.#head)\n this.#gaps = this.#gaps.slice(this.#head)\n this.#head = 0\n }\n }\n}\n", "/**\n * FEC Manager \u2014 codec-agnostic FEC decode service.\n *\n * Receives (packetId, sbn, esi, symbolData) tuples from any transport\n * (MMTP, LOC, etc.) and manages FEC block lifecycle: source counting,\n * WASM RaptorQ decode, block timeout, and recovery/failure notification.\n *\n * Extracted from MulticastManager to decouple FEC block logic from\n * MMTP packet parsing and frame routing.\n */\n\nimport { computeFecDeliveryWindowMs } from './track-session.js'\nimport { DeliveryGapEstimator } from './delivery-gap.js'\nimport {\n deriveFecInterleaveDepth,\n deriveFecRetentionBlocks,\n tryDeriveFecRetentionBlocks,\n} from '@blockcast/mmt-fec'\nimport {\n isKStrideBlock,\n isoBlockContains,\n isoBlocksBehindLowerBound,\n kStrideBlockPrecedes,\n kStrideBlocksBehind,\n kStrideNextBlock,\n parseFecBlockProfile,\n serial32Distance,\n serial32Precedes,\n ssbg1FitsSymbol,\n type FecBlockProfile,\n} from '@blockcast/mmt-base'\nimport { IsoBlockLocator, type IsoRepairPlacement } from './iso-block-locator.js'\nimport { MIN_INTERLEAVE_DEPTH_MS } from './fec-state.js'\nimport {\n BlockClassifier,\n type BlockClass,\n type BlockClassifierGeometry,\n type ClassifierChange,\n type ClassifierMode,\n type ClassifierReason,\n type ReclassificationEvidence,\n type SourceSymbolEvidence,\n type TrackKind,\n} from './block-policy.js'\n\nimport type { MmtpDeliveryProvenance } from './mfu-reassembler.js'\nimport type {\n FecWorkerClient,\n FecTrackStats,\n FecBlockSnapshot,\n FrameMeta,\n FecWorkerErrorCode,\n} from '@blockcast/fec-worker'\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n// MIN_INTERLEAVE_DEPTH_MS lives with the catalog FEC predicate that also\n// needs it (fec-state.ts); re-exported here for existing importers.\nexport { MIN_INTERLEAVE_DEPTH_MS }\nconst WORKER_ERROR_LOG_CAP = 5\nconst WORKER_ERROR_KIND_CAP = 32\nconst WORKER_ERROR_OVERFLOW_KIND = 'other'\nconst STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES = 2\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Algorithm-agnostic FEC decoder backend interface.\n *\n * Implementations:\n * - RaptorQ WASM (mmt_wasm.js) \u2014 RFC 6330\n * - Reed-Solomon WASM \u2014 future\n * - XOR \u2014 future\n *\n * Source/repair symbol IDs follow ISO 23008-1 \u00A7C.5.2/C.5.3:\n * source: SS_ID (flat 32-bit symbol identifier)\n * repair: SS_Start + SSB_length + RS_ID (block-scoped repair coords)\n */\nexport type WasmFecDecoder = {\n add_source(ss_id: number, data: Uint8Array, ts: number): Uint8Array | null\n add_repair(ss_start: number, ssb_length: number, rs_id: number, data: Uint8Array, ts: number): Uint8Array | null\n configure(oti: Uint8Array): void\n cleanup(current_time: number): Uint8Array\n /** Retire the named blocks from the decoder (last-chance decode + stats). */\n cleanup_blocks_detailed(sbns: Uint32Array): unknown\n /**\n * iso-ssbg1-v1 only: retire the source symbols the decoder holds before a\n * repair states their block, once no repair can place them any more, by the\n * decoder's own horizon behind `current`, the newest SS_ID seen. Retires no\n * block: the manager retires blocks itself, by their deadlines. Returns how\n * many it retired. The manager calls it after every feed that grows the\n * decoder and on block retirement. (`MmtFecDecoder.retire_unplaced`; the\n * worker path uses `cleanup_by_ss_start`, which applies the same rule.)\n */\n retire_unplaced(current: number): number\n flush?(): void\n reset_stats?(): void\n}\n\n/** Algorithm-agnostic FEC stats snapshot. */\nexport interface FecStats {\n sourcePackets: number\n repairPackets: number\n duplicateSymbols: number\n blocksClean: number\n blocksRecovered: number\n blocksFailed: number\n blocksComplete: number\n mixedPathBlocks: number\n recentBlockPathCounts: FecBlockPathCount[]\n k: number\n /** Of blocks with loss, what percentage were recovered (0-100). */\n recoveryRate: number\n /** What percentage of blocks had any loss (0-100). */\n packetLossRate: number\n /** Per-MMTP-packet-ID counters for the synchronous WASM decoder path. */\n tracks: FecPacketStats[]\n /** Latest Worker/WASM snapshot for each packet ID. */\n workers: Array<FecTrackStats & { packetId: number }>\n /** Main-thread repair symbols rejected before dispatch because their size did not match T. */\n repairSymbolSizeMismatches: number\n /** Bounded Worker error counts keyed by stable code, or message text for legacy errors. */\n workerErrors: Record<string, number>\n}\n\nexport interface FecPacketStats {\n packetId: number\n sourcePackets: number\n repairPackets: number\n duplicateSymbols: number\n blocksClean: number\n blocksRecovered: number\n blocksFailed: number\n blocksComplete: number\n k: number\n recoveryRate: number\n packetLossRate: number\n}\n\nexport type FecSymbolSource = 'multicast' | 'moq'\n\n/**\n * One iso-ssbg1-v1 first-arrival source symbol MmtpRouter holds until a repair\n * symbol states its block: the exact protected packet (no SS_ID, unpadded),\n * the transport that delivered it and its classification evidence.\n */\nexport interface IsoHeldSource {\n readonly symbolData: Uint8Array\n readonly source: FecSymbolSource\n readonly evidence: SourceSymbolEvidence\n /** Larger than one ssbg_mode1 symbol: counted, never decoded. */\n readonly oversized: boolean\n}\n\n/**\n * How the multicast leg hands its symbols to this manager.\n *\n * - `'datagram'`: every symbol is an independent datagram from the receiving\n * socket. A pause in arrivals is the only loss signal the receiver has, so a\n * block that stays idle past the catalog-derived inactivity deadline fails.\n * - `'ordered-relay'`: the provider relays one receiving socket over a\n * reliable, ordered stream (the BCDS bridge's WebTransport uni stream). The\n * delivered sequence is a subsequence of the socket's receive order, so a\n * pause carries no loss information at all: the relay's own scheduling\n * decides when bytes arrive. What does carry it is order. Once the stream\n * has advanced more than the decoder's interleave horizon past a block, no\n * symbol of that block can still be in flight, and the block's fate follows\n * from the symbols it holds (in-band SS_ID/SS_Start and SSB_length), not\n * from a wall clock tuned for raw datagrams.\n *\n * Only the provider can know which of the two it is delivering; an undeclared\n * provider promises no ordering and keeps datagram semantics.\n */\nexport type MulticastSymbolDelivery = 'datagram' | 'ordered-relay'\n\nfunction isMulticastSymbolDelivery(value: unknown): value is MulticastSymbolDelivery {\n return value === 'datagram' || value === 'ordered-relay'\n}\n\nexport interface FecBlockPathCount {\n packetId: number\n sbn: number\n catalogTrackName?: string\n multicastSymbols: number\n moqSymbols: number\n complete: boolean\n}\n\ntype FecLifecycleTrace = {\n atMs: number\n event:\n | 'repair-requested'\n | 'repair-arrived'\n | 'timeout-queued'\n | 'horizon-settled'\n | 'cleanup-ack'\n | 'cleanup-ack-timeout'\n | 'recovered'\n | 'failed'\n | 'decoder-unavailable'\n packetId: number\n sbn: number\n catalogTrackName?: string\n sourceCount: number\n sourceEsis: number[]\n repairRsIds: number[]\n decoderPath: 'worker' | 'sync' | 'none'\n deadlineRemainingMs?: number\n pendingCleanup: boolean\n rsId?: number\n}\n\nexport interface FECManagerConfig {\n /**\n * The catalog `fec.blockProfile` of the track (BLO-35597). Required for every\n * configured track: there is no default, and nothing infers it.\n * `k-padded-v1` keeps the legacy K-stride geometry; `iso-ssbg1-v1` is\n * ISO/IEC 23008-1 ssbg_mode1 with K' = SSB_length blocks located by\n * `(SS_Start, SSB_length)` range membership, `k` being only their maximum.\n */\n blockProfile?: FecBlockProfile\n k?: number\n /** Symbol size in bytes (T). NOT repair count. */\n symbolSize?: number\n interleaveDepthMs?: number\n fps?: number\n /** Guarded block inactivity deadline derived from the full delivery geometry. */\n timeoutMs?: number\n /** Media kind of the track (block class `audio` vs video classification). */\n trackKind?: TrackKind\n /** Catalog codec string. */\n codec?: string\n /**\n * Block classifier geometry derived from raw catalog timing\n * (`deriveBlockClassifierGeometry`). Absent geometry classifies every video\n * block `unknown` (fail-closed: protected).\n */\n geometry?: BlockClassifierGeometry\n}\n\ninterface ResolvedFECTrackConfig {\n blockProfile?: FecBlockProfile\n k: number\n symbolSize: number\n interleaveDepthMs: number\n timeoutMs: number\n fps?: number\n trackKind?: TrackKind\n codec?: string\n geometry?: BlockClassifierGeometry\n}\n\n/** Which symbol path completed a short block. */\nexport type RepairVia = 'inband' | 'quic'\n\n/** Lifecycle resolution of one FEC block, for in-order frame release. */\nexport type FecBlockResolution = 'pending' | 'complete' | 'failed' | 'untracked'\n\ninterface MutableFecCounters {\n sourcePackets: number\n repairPackets: number\n duplicateSymbols: number\n blocksClean: number\n blocksRecovered: number\n blocksFailed: number\n}\n\nfunction newFecCounters(): MutableFecCounters {\n return {\n sourcePackets: 0,\n repairPackets: 0,\n duplicateSymbols: 0,\n blocksClean: 0,\n blocksRecovered: 0,\n blocksFailed: 0,\n }\n}\n\n/**\n * The one timer armed for a block deadline (BLO-36599).\n *\n * Symbols move a block's deadline later many times per block. Re-arming a\n * browser timer for each of them cost ~41 us per clear+set pair on a Kirin 659,\n * so the deadline itself is stored as a performance.now() timestamp and this\n * timer only guarantees a wake-up no later than it. When the timer fires it\n * re-checks the stored deadline and re-arms for the remainder if activity\n * moved it. `fireAtMs` is what the armed timer was scheduled for; `handle` is\n * null once it fired without re-arming.\n */\ninterface DeadlineTimer {\n handle: ReturnType<typeof setTimeout> | null\n fireAtMs: number\n}\n\n/**\n * Delete the `count` oldest (first-inserted) keys of `collection` in place\n * and return them.\n */\nfunction dropOldest(\n collection: Set<string> | Map<string, unknown>,\n count: number,\n): string[] {\n const dropped: string[] = []\n for (const key of collection.keys()) {\n if (dropped.length >= count) break\n dropped.push(key)\n }\n for (const key of dropped) collection.delete(key)\n return dropped\n}\n\n/** A block key whose terminal state expires at `expiresAtMs`. */\ninterface TerminalRecord {\n readonly key: string\n readonly expiresAtMs: number\n}\n\n/** FIFO of terminal records in the order blocks became terminal. */\nclass TerminalQueue {\n #records: TerminalRecord[] = []\n #head = 0\n\n push(record: TerminalRecord): void {\n this.#records.push(record)\n }\n\n peek(): TerminalRecord | undefined {\n return this.#records[this.#head]\n }\n\n shift(): void {\n this.#head++\n // Compact once the consumed prefix outweighs the live tail, so the array\n // never holds more than twice the records still in the window.\n if (this.#head * 2 >= this.#records.length) {\n this.#records = this.#records.slice(this.#head)\n this.#head = 0\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// FECManager\n// ---------------------------------------------------------------------------\n\n/** Why a stream's Worker decoder stopped being routable. */\nexport type FecDecoderUnavailableReason = 'cleared' | 'disposed'\n\n/**\n * What still decodes a stream after its Worker left routing: the synchronous\n * WASM decoder registered for the same stream (the Worker path took precedence\n * over it while both were live), or nothing.\n */\nexport type FecFallbackDecoder = 'sync' | 'none'\n\nexport interface FecDecoderUnavailableEvent {\n readonly packetId: number\n readonly catalogTrackName?: string\n readonly reason: FecDecoderUnavailableReason\n /**\n * What decodes the stream at the moment the Worker left. A synchronous\n * decoder discarded later (a resetTrack flush failure drops it without an\n * event) is not reported here; a consumer that keeps a stream alive on\n * 'sync' owns that decoder's lifecycle itself.\n */\n readonly fallbackDecoder: FecFallbackDecoder\n}\n\nexport class FECManager {\n // Per-track WASM RaptorQ decoders, keyed by physical stream identity.\n #fecDecoders = new Map<string, { client: WasmFecDecoder; k: number }>()\n /** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */\n\n // Per-track Worker-backed FEC decoders, keyed by physical stream identity.\n // Worker-backed tracks use async decode \u2014 recovered frames arrive via onFrame callback,\n // not synchronous return from feedSourceSymbol/feedRepairSymbol.\n #workerClients = new Map<string, FecWorkerClient>()\n // Worker clients whose streams releasePacket ended. A Worker posts\n // asynchronously, so a message it sent before its reset can still arrive;\n // the stream it describes is gone, and letting it in would rebuild state\n // (block ledgers, statistics, lifecycle events) for a retired packet ID.\n #releasedWorkerClients = new WeakSet<FecWorkerClient>()\n // Worker setup can finish while an interleaved block is already collecting\n // on the sync decoder. Keep that block on its original decoder so later\n // source/repair symbols do not split across two RaptorQ states.\n #syncDecoderBlockOwners = new Map<string, WasmFecDecoder>()\n #decoderBlockOwners = new WeakMap<object, Map<number, {\n catalogTrackName?: string\n conflicted: boolean\n }>>()\n\n #fecK = 4\n #fecSymbolSize = 0\n #fecTimeoutMs = 500 // default 500ms; refined by setFramerate() when fps known\n #trackConfigs = new Map<number, ResolvedFECTrackConfig>()\n #fecStats = newFecCounters()\n #fecTrackStats = new Map<number, MutableFecCounters>()\n #fecWorkerStats = new Map<number, FecTrackStats>()\n #fecStatsObservers = new Set<(packetId: number, stats: FecTrackStats) => void>()\n\n // Block inactivity tracking: (packetId, SBN) -> the one armed timer. The\n // deadline is derived from catalog geometry and refreshed on progress; only\n // an idle block fails. #activeBlockDeadlinesMs is the authoritative\n // deadline; the timer re-checks it when it fires (see DeadlineTimer).\n #activeBlocks = new Map<string, DeadlineTimer>()\n #activeBlockDeadlinesMs = new Map<string, number>()\n // The two terms #activeBlockDeadlinesMs is the later of (BLO-35597):\n // - the multicast late-symbol wait, refreshed by every accepted symbol;\n // - a D-28 repair hold, set by the repair scheduler only while a repair\n // FETCH it admitted can still land before the block's presentation\n // deadline. The multicast span elapsing never ends a fetch the scheduler\n // judged able to arrive in time.\n #blockWaitDeadlinesMs = new Map<string, number>()\n #repairHoldUntilMs = new Map<string, number>()\n // Receiver-measured multicast delivery gap: the late-symbol wait's jitter\n // term (see DeliveryGapEstimator for the statistic and why).\n #deliveryGaps = new DeliveryGapEstimator()\n #lifecycleTrace: FecLifecycleTrace[] = []\n // Worker-backed timeouts enqueue cleanup behind all prior symbol messages.\n // Failure remains pending until cleanupComplete acknowledges that queue drain.\n #pendingWorkerCleanups = new Set<string>()\n #pendingWorkerCleanupClients = new Map<string, FecWorkerClient>()\n // One bound per owed cleanup: the acknowledgement is awaited for at most one\n // catalog-derived block lifetime (fail bound plus repair grace).\n // A Worker that hangs or drops one message must not leave the block\n // 'pending' forever, which would park every later frame of its stream in\n // the in-order release gate. Each entry owns its handle; the callback\n // checks it still owns the key, so a settled or reset key is never touched.\n #pendingWorkerCleanupBounds = new Map<string, { handle: ReturnType<typeof setTimeout> }>()\n // Source symbol count per track-scoped block \u2014 marks complete when K symbols arrive.\n #blockSourceCounts = new Map<string, number>()\n #blockSourceEsis = new Map<string, Set<number>>()\n // Authored SSB_length per block (ISO/IEC 23008-1 \u00A7C.5.3), learned from the\n // first repair symbol. The FFmpeg moq_mmt publisher closes a block every D\n // media units, usually before it holds K symbols, and fec-raptorq\n // force_complete() encodes it as a K-symbol RaptorQ block whose ESIs\n // SSB_length..K-1 are zero padding that is never sent. Such a block is clean\n // at SSB_length source arrivals and recoverable from SSB_length received\n // symbols; waiting for K fails every one of them.\n #blockAuthoredLengths = new Map<string, number>()\n #ssbLengthConflictCount = 0\n #blockRepairRsIds = new Map<string, Set<number>>()\n // Blocks already recovered \u2014 prevents late source symbols from restarting timeouts.\n // WASM removes blocks after recovery; late symbols create empty blocks returning null,\n // which would falsely start new timeouts without this guard.\n //\n // Every insertion goes through #markTerminal, which retires the key once its\n // catalog-derived retention window (terminalRetentionMsForPacket) passes. The\n // previous bound (trim to 500 at 1000) lived in cancelBlockTimeout alone, so\n // a stream whose blocks only failed grew this set without limit (BLO-36599).\n #completedBlocks = new Set<string>()\n #terminalExpiryMs = new Map<string, number>()\n #terminalQueues = new Map<number, TerminalQueue>()\n // sbn -> (block key -> packetId) for every key held by #activeBlocks,\n // #blockSourceCounts, #blockCatalogTrackNames or #completedBlocks. A lookup\n // without a catalog name used to string-parse every key of all of those on\n // each source packet, which is O(retained blocks) per packet (BLO-36599).\n #blockKeysBySbn = new Map<number, Map<string, number>>()\n // Stable per-name encodings for stream and block keys, bounded by the\n // catalog's track names; #blockKey is built several times per packet.\n #encodedTrackNames = new Map<string, string>()\n // The last block and stream key built. One packet asks for the same block's\n // key from every FECManager entry point it passes through (~6 per source\n // symbol), so each key is built once per packet rather than once per call\n // (BLO-36599). Both keys are pure functions of their arguments.\n #lastBlockKeySbn = -1\n #lastBlockKeyPacketId = -1\n #lastBlockKeyTrackName: string | undefined = undefined\n #lastBlockKey = ''\n #lastStreamKeyPacketId = -1\n #lastStreamKeyTrackName: string | undefined = undefined\n #lastStreamKey = ''\n #seenSymbols = new Set<string>()\n #blockPathCounts = new Map<string, FecBlockPathCount>()\n // Receiver-local first-accepted-symbol time per block, bounded like\n // #blockPathCounts (see blockAnchorAtMs).\n #blockAnchorsMs = new Map<string, number>()\n // Exact catalog identity observed on accepted symbols for each FEC block.\n // Conflicting identities collapse to null so recovery fails closed.\n #blockCatalogTrackNames = new Map<string, string | null>()\n // A receiver joins an already-running live stream in the middle of D\n // interleaved source blocks. This applies to both multicast and reliable MoQ:\n // a MoQ group boundary is not an FEC ESI=0 boundary. Those blocks are not\n // packet loss because their earlier ESIs predate the subscription. Ignore\n // each such block until ESI=0 establishes a complete boundary for the track.\n // After that boundary, a first observed ESI>0 is real delivery loss and must\n // continue through normal FEC recovery/failure accounting.\n #startupAlignedPackets = new Set<string>()\n #startupPartialBlocks = new Set<string>()\n // A live multicast join can observe an ESI=0 source boundary before the\n // repair lane catches up. Returning false here also drops that media packet\n // at MmtpRouter, so the readiness probe is multicast-only and bounded: an\n // actual multicast repair unlocks the next boundary, while a second source\n // boundary fails open. Reliable MoQ media continues through normal startup\n // alignment and retained MoQ repair cannot prove multicast readiness.\n #startupRequireRepairReady = false\n #startupRepairReadyPackets = new Set<string>()\n #startupRepairBoundaryCounts = new Map<string, number>()\n #startupRepairFailOpenPackets = new Set<string>()\n // SBNs that reached K source-symbol arrivals via trackBlock. Used by the\n // worker recovery suppression check (see onFrame handler below) so we only\n // suppress `recovered=true` emits for blocks that ALSO completed cleanly\n // via source symbols. Separating this from #completedBlocks (which is also\n // populated by every worker frame emit, by failed timeouts, and by external\n // callers) was the BLO-3323 L15 fix: the prior predicate caused blocksRecovered\n // to stay at 0 because the SBN was always in #completedBlocks by the time the\n // worker fired recovered=true.\n #cleanlyCompletedBlocks = new Set<string>()\n // D-28: blocks whose repair deadline already fired onRepairNeeded. At most\n // one repair request per block; entries are removed when the block\n // completes, recovers, or fails.\n #repairRequestedBlocks = new Set<string>()\n // D-28: repair deadlines refreshed by accepted source progress until the\n // first repair request. A `null` value marks a deadline that already fired\n // and requested repair (never re-armed). At most K accepted source symbols\n // can refresh the horizon, so browser scheduling delay cannot create an\n // early loss signal and repair traffic cannot keep a block alive\n // indefinitely. #repairDeadlineAtMs is the authoritative deadline; the one\n // armed timer re-checks it when it fires (see DeadlineTimer).\n #repairDeadlines = new Map<string, DeadlineTimer | null>()\n #repairDeadlineAtMs = new Map<string, number>()\n // Worker recovery emits all K source symbols. Track recovery once per block\n // while routing only ESI slots that were absent from the direct source path.\n #workerRecoveredBlocks = new Set<string>()\n\n // How the multicast leg delivers symbols (see MulticastSymbolDelivery). The\n // provider declares it per transport join; absence keeps datagram semantics.\n #multicastSymbolDelivery: MulticastSymbolDelivery = 'datagram'\n // ordered-relay: per stream, the highest SBN any multicast symbol carried.\n // The relay preserves the receiving socket's order, so every block more than\n // the decoder's interleave horizon behind this frontier has received every\n // symbol it will ever receive over multicast.\n #orderedFrontiers = new Map<string, number>()\n // ordered-relay: blocks with multicast symbols that are neither terminal nor\n // handed to a decoder cleanup, per stream, keyed by SBN. They carry no\n // timer; the frontier settles them. At most one interleave horizon of blocks\n // per stream is ever here.\n #orderedPendingBlocks = new Map<string, Map<number, string>>()\n\n // \u2500\u2500 Keyframe-protected repair policy (block-policy.ts) \u2500\u2500\n // One classifier per physical stream, built from that stream's geometry.\n #classifiers = new Map<string, BlockClassifier>()\n // Geometry a stream-scoped configuration gave one stream (streamKey). ABR\n // rungs can share a packetId with a different framerate or GOP, so the\n // packet-level geometry is only the geometry of a packet whose streams\n // were never configured apart (see #geometryFor).\n #streamGeometries = new Map<string, BlockClassifierGeometry>()\n // Terminal outcomes retained for in-order frame release: a failed block is\n // resolved-with-loss, a completed one resolved-clean.\n #failedBlocks = new Set<string>()\n // A block's class at resolution, so events emitted after the classifier\n // forgot the block (the router counts a recovery after cancelling the\n // block) still carry it.\n #resolvedBlockClasses = new Map<string, BlockClass>()\n // Blocks already reported through onBlockRepaired (once per block).\n #repairedBlocks = new Set<string>()\n // Blocks created without any symbol because every symbol vanished.\n #materializedBlocks = new Set<string>()\n // packetIds whose ordered-relay horizon became underivable and fell back to\n // the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once\n // per stream. Not only throws: the ordinary out-of-range case is detected by\n // arithmetic and never throws at all.\n #orderedHorizonWarnedFor = new Set<number>()\n // Highest SBN seen per stream and when, for vanished-block detection.\n #highestSbn = new Map<string, { sbn: number; atMs: number }>()\n // iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a\n // block only once a repair symbol stated the block's (SS_Start, SSB_length).\n #isoLocators = new Map<string, IsoBlockLocator<IsoHeldSource>>()\n #isoRepairRefusedCount = 0\n #isoSourceOversizeCount = 0\n // Held sources a locator's capacity cap evicted (IsoBlockLocator.heldCapacity):\n // only a stream whose SS_IDs are not consecutive reaches it. Cumulative,\n // unlike the locators, which a reset discards.\n #isoHeldEvictedCount = 0\n // Sources a synchronous decoder held unplaced and retired at its horizon\n // (WasmFecDecoder.retire_unplaced): placed sources whose block's repair\n // never reached that decoder. Cumulative.\n #isoDecoderUnplacedRetired = 0\n // Failed retire_unplaced calls. The sweep runs on every feed, so its warn is\n // rate-limited like the other per-symbol warns here. Cumulative.\n #isoDecoderUnplacedRetireFailed = 0\n // The latest source symbol per stream that carried its MMTP\n // packet_sequence_number: the anchor that bounds how many blocks the\n // producer can have started before a later source symbol.\n #sourceAnchors = new Map<string, { sbn: number; psn: number; atMs: number }>()\n // Catalog/reset epoch per packetId. Gap decisions carry it for fencing.\n #packetEpochs = new Map<number, number>()\n\n // Framerate detection state\n #fpsDetected = false\n #fecFps?: number\n #fecInterleaveDepthMs = 0\n\n // Worker error log rate-limiter \u2014 bounds console.warn output and exported\n // error cardinality when the FEC Worker emits the same failure repeatedly\n // with volatile packet details (e.g. ssStart) in the message text.\n // Stable Worker codes aggregate those messages; uncoded/legacy messages are\n // capped into an `other` bucket. This also covers wasm-bindgen\n // \"recursive use of an object\" bubbled from add_source/add_repair during\n // overlapping message processing). Without this, Point 5 of the 7-point\n // validation harness \u2014 which caps headless warnings at 500 \u2014 fails even\n // though the errors are duplicates of a single underlying event.\n #workerErrorCounts = new Map<string, number>()\n #decoderUnavailableEvents = 0\n #repairSymbolSizeMismatchCount = 0\n\n // Event callbacks \u2014 block lifecycle notifications for MmtpRouter wiring.\n // NOTE: For sync WASM path, recovery routing is handled by MmtpRouter from\n // feedSourceSymbol/feedRepairSymbol return values. For Worker path, recovery\n // arrives via onBlockRecovered callback.\n onBlockStarted:\n | ((sbn: number, packetId: number, catalogTrackName?: string) => void)\n | null = null\n onBlockComplete:\n | ((sbn: number, packetId: number, catalogTrackName?: string) => void)\n | null = null\n /** A block reached its fail bound. `blockClass` is its class at failure. */\n onBlockFailed:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | null = null\n /**\n * Fired once per stream when its Worker decoder stops being routable.\n * `reason` is 'cleared' when clearFecWorker() removed it and 'disposed' when\n * a symbol or reset found the registered client already disposed and\n * evicted it. `fallbackDecoder` is 'sync' when a synchronous WASM decoder\n * registered for the same stream keeps decoding its symbols, and 'none'\n * when nothing does. Only 'none' leaves the stream unprotected: the host\n * must then register a decoder again or leave the stream, because counting\n * its symbols as handled would hide the loss.\n */\n onDecoderUnavailable: ((event: FecDecoderUnavailableEvent) => void) | null = null\n\n // D-28 on-demand repair callbacks. When onRepairNeeded is registered, each\n // block arms a one-shot repair deadline after the derived K-symbol source\n // horizon; a block still short of K when it fires requests a per-SBN MoQ\n // repair fetch upstream. The inactivity fail bound is unchanged either way \u2014\n // repair and failure are orthogonal timers, so wiring D-28 never delays\n // failure accounting. onRepairResolved fires when a repair-requested block\n // leaves the active set, with the outcome (\"completed\" after clean completion\n // or recovery, \"failed\" at the fail bound) so the upstream in-flight slot\n // (MulticastManager.clearRepairForBlock) can be released and logged.\n onRepairNeeded:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n ) => void)\n | null = null\n onRepairResolved:\n | ((\n sbn: number,\n packetId: number,\n outcome: 'completed' | 'failed',\n catalogTrackName: string | undefined,\n via: RepairVia | null,\n ) => void)\n | null = null\n\n /**\n * A block that could not complete from its first-arrival source symbols\n * alone was completed anyway: by FEC decode (in-band or QUIC repair\n * symbols) or, after a D-28 request, by late symbols. Fired once per block.\n */\n onBlockRepaired:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n blockClass: BlockClass,\n via: RepairVia,\n ) => void)\n | null = null\n /** A block's class changed on new evidence or when classification was disabled. */\n onBlockReclassified:\n | ((\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n from: BlockClass,\n to: BlockClass,\n evidence: ReclassificationEvidence,\n ) => void)\n | null = null\n /** A video stream's classifier mode was established or changed. */\n onClassifierMode:\n | ((\n packetId: number,\n catalogTrackName: string | undefined,\n mode: ClassifierMode,\n reason: ClassifierReason | null,\n ) => void)\n | null = null\n /** A block every symbol of which vanished was created by SBN evidence. */\n onBlockVanished:\n | ((sbn: number, packetId: number, catalogTrackName: string | undefined) => void)\n | null = null\n /** A block reached a terminal state (completed, recovered, or failed). */\n onBlockResolved:\n | ((sbn: number, packetId: number, catalogTrackName: string | undefined) => void)\n | null = null\n /** A stream's SBN went backwards (or jumped implausibly): the source restarted. */\n onSourceDiscontinuity:\n | ((packetId: number, catalogTrackName: string | undefined) => void)\n | null = null\n\n // Worker-path callbacks \u2014 async recovery and stats from FecWorkerClient.\n onBlockRecovered: ((sbn: number, packetId: number, data: ArrayBuffer, meta: FrameMeta, catalogTrackName?: string) => void) | null = null\n onFecStatsUpdate: ((packetId: number, stats: FecTrackStats) => void) | null = null\n\n #encodeTrackName(catalogTrackName: string): string {\n let encoded = this.#encodedTrackNames.get(catalogTrackName)\n if (encoded === undefined) {\n encoded = encodeURIComponent(catalogTrackName)\n this.#encodedTrackNames.set(catalogTrackName, encoded)\n }\n return encoded\n }\n\n #streamKey(packetId: number, catalogTrackName?: string): string {\n if (\n packetId === this.#lastStreamKeyPacketId &&\n catalogTrackName === this.#lastStreamKeyTrackName\n ) {\n return this.#lastStreamKey\n }\n const key = catalogTrackName === undefined\n ? `${packetId}`\n : `${packetId}:${this.#encodeTrackName(catalogTrackName)}`\n this.#lastStreamKeyPacketId = packetId\n this.#lastStreamKeyTrackName = catalogTrackName\n this.#lastStreamKey = key\n return key\n }\n\n #belongsToPacket(key: string, packetId: number): boolean {\n return Number(key.split(':', 1)[0]) === packetId\n }\n\n #belongsToStream(key: string, packetId: number, catalogTrackName: string): boolean {\n if (!this.#belongsToPacket(key, packetId)) return false\n const parts = key.split(':')\n const encodedTrackName = this.#encodeTrackName(catalogTrackName)\n if (parts.length === 2) return parts[1] === encodedTrackName\n if (parts.length >= 3) return parts[2] === encodedTrackName\n return false\n }\n\n #packetIdsForStreamKeys(keys: Set<string>): number[] {\n return [...new Set([...keys].map((key) => Number(key.split(':', 1)[0])))]\n }\n\n #parseStreamKey(key: string): { packetId: number; catalogTrackName?: string } {\n const separator = key.indexOf(':')\n if (separator < 0) return { packetId: Number(key) || 0 }\n return {\n packetId: Number(key.slice(0, separator)) || 0,\n catalogTrackName: decodeURIComponent(key.slice(separator + 1)),\n }\n }\n\n #blockKey(sbn: number, packetId: number, catalogTrackName?: string): string {\n if (\n sbn === this.#lastBlockKeySbn &&\n packetId === this.#lastBlockKeyPacketId &&\n catalogTrackName === this.#lastBlockKeyTrackName\n ) {\n return this.#lastBlockKey\n }\n const base = `${packetId}:${sbn}`\n const key = catalogTrackName === undefined\n ? base\n : `${base}:${this.#encodeTrackName(catalogTrackName)}`\n this.#lastBlockKeySbn = sbn\n this.#lastBlockKeyPacketId = packetId\n this.#lastBlockKeyTrackName = catalogTrackName\n this.#lastBlockKey = key\n return key\n }\n\n /** Record that `key` (block `sbn` on `packetId`) is held by a lookup set. */\n #indexBlockKey(key: string, sbn: number, packetId: number): void {\n let keys = this.#blockKeysBySbn.get(sbn)\n if (!keys) {\n keys = new Map()\n this.#blockKeysBySbn.set(sbn, keys)\n }\n keys.set(key, packetId)\n }\n\n /** True while any set a catalog-name-less lookup consults still holds `key`. */\n #isBlockKeyHeld(key: string): boolean {\n return this.#activeBlocks.has(key) ||\n this.#blockSourceCounts.has(key) ||\n this.#blockCatalogTrackNames.has(key) ||\n this.#completedBlocks.has(key)\n }\n\n /** Drop `key` from the SBN index once no lookup set holds it any more. */\n #unindexBlockKey(key: string): void {\n if (this.#isBlockKeyHeld(key)) return\n const sbn = this.#parseBlockKey(key).sbn\n const keys = this.#blockKeysBySbn.get(sbn)\n if (!keys) return\n keys.delete(key)\n if (keys.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n\n /**\n * Make `key` terminal: late symbols for it are duplicates from now on. The\n * key is retired terminalRetentionMsForPacket later, when no copy of any of\n * its symbols can still be in flight on either transport and no frame can\n * still be held behind it. Retirement is swept lazily here, on the stream's\n * own terminal events, so it needs no timer.\n */\n #markTerminal(key: string, sbn: number, packetId: number, clean: boolean): void {\n const nowMs = performance.now()\n this.#retireTerminalBlocks(packetId, nowMs)\n this.#forgetOrderedPending(key)\n this.#completedBlocks.add(key)\n if (clean) this.#cleanlyCompletedBlocks.add(key)\n this.#indexBlockKey(key, sbn, packetId)\n const expiresAtMs = nowMs + this.terminalRetentionMsForPacket(packetId)\n this.#terminalExpiryMs.set(key, expiresAtMs)\n let queue = this.#terminalQueues.get(packetId)\n if (!queue) {\n queue = new TerminalQueue()\n this.#terminalQueues.set(packetId, queue)\n }\n queue.push({ key, expiresAtMs })\n }\n\n #retireTerminalBlocks(packetId: number, nowMs: number): void {\n const queue = this.#terminalQueues.get(packetId)\n if (!queue) return\n for (let record = queue.peek(); record !== undefined; record = queue.peek()) {\n if (record.expiresAtMs > nowMs) return\n // A key made terminal again later (or reset and reused) owns a newer\n // record; this one no longer describes it.\n const current = this.#terminalExpiryMs.get(record.key) === record.expiresAtMs\n // An ordered relay can still hand over the rest of a terminal block's\n // symbols (a clean block's remaining repair symbols) for as long as the\n // relay pauses. Until the stream passes the block's interleave horizon\n // those symbols are late copies, not a new block, so the key stays.\n if (current && this.#withinOrderedHorizon(record.key)) return\n queue.shift()\n if (!current) continue\n this.#terminalExpiryMs.delete(record.key)\n this.#completedBlocks.delete(record.key)\n this.#cleanlyCompletedBlocks.delete(record.key)\n this.#workerRecoveredBlocks.delete(record.key)\n // The policy's terminal ledgers describe the same terminal state and\n // retire with it: a failed block must never outlive its failure record\n // as 'complete' (that would hide a loss from the release gate).\n this.#failedBlocks.delete(record.key)\n this.#resolvedBlockClasses.delete(record.key)\n this.#repairedBlocks.delete(record.key)\n this.#unindexBlockKey(record.key)\n }\n }\n\n // -----------------------------------------------------------------------\n // Ordered-relay settlement (MulticastSymbolDelivery 'ordered-relay')\n // -----------------------------------------------------------------------\n\n /**\n * The decoder's interleave horizon for one stream, in blocks: the same\n * retention the Worker and the WASM decoder are configured with, because it\n * comes from the same function they do (`deriveFecRetentionBlocks`, over the\n * block's delivery window and its span). The decoder retires every block\n * more than this many SBNs behind the newest one it saw (`cleanup_by_sbn`),\n * so settling earlier than this would decide a block the decoder would still\n * have recovered.\n *\n * This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA\n * UNITS per block, not a block count -- and a comment here asserted the two\n * matched. They did, only while the decoder's retention was also (wrongly)\n * sized in D. Once BLO-35597 sized retention as a real block count this\n * settled 2-5 blocks early on the live ISS catalog at reorderToleranceMs=100\n * (720p 1 vs 6, audio 2 vs 5), making the manager the binding constraint and\n * the decoder fix inert on ordered relays. Both sides now consume one\n * derivation; do not re-derive a horizon at either call site.\n */\n #orderedHorizonBlocksFor(packetId: number, config: ResolvedFECTrackConfig): number {\n if (!FECManager.#hasOrderedGeometry(config)) {\n throw new Error(\n `[FECManager] packetId=${packetId}: ordered-relay settlement derives the ` +\n `decoder's interleave horizon from the catalog interleaveDepthMs ` +\n `(>= ${MIN_INTERLEAVE_DEPTH_MS}ms), the media-unit rate and the block ` +\n `delivery window; got interleaveDepthMs=${config.interleaveDepthMs} ` +\n `fps=${String(config.fps)} timeoutMs=${config.timeoutMs}.`,\n )\n }\n // Config time is where failing closed is right, so a > 255 geometry throws\n // from here with the message oti.ts wrote: it names interleaveDepthMs and\n // the catalog fix. #derivableOrderedHorizon deliberately does not.\n return deriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs)\n }\n\n /**\n * Whether a stream's geometry can define an ordered-relay horizon at all.\n * One definition, shared by the config-time and packet-feed callers, so the\n * two can never disagree about which streams settle by stream order.\n */\n static #hasOrderedGeometry(config: ResolvedFECTrackConfig): boolean {\n return (\n config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS &&\n // `fps` is no longer an input to the horizon, but it still gates WHICH\n // streams settle by stream order at all: a track with no usable\n // media-unit rate fell back to datagram semantics before this and must\n // keep doing so. Removing this widens ordered-relay settlement to streams\n // that never had it. Load-bearing \u2014 do not drop it as unused.\n config.fps !== undefined &&\n Number.isFinite(config.fps) &&\n config.fps > 0 &&\n Number.isFinite(config.timeoutMs) &&\n config.timeoutMs > 0\n )\n }\n\n /**\n * The horizon for one stream, or undefined when its geometry cannot define\n * one.\n *\n * `config.timeoutMs` is the block's delivery window. On the live path\n * multicast-manager computes it with `jitterBufferMs: fec.reorderToleranceMs`\n * and passes it to configure(), which is the same `deliveryWindowMs`\n * fec-worker-setup sizes decoder retention from, so the two horizons are\n * equal there.\n *\n * They are NOT equal on the fallback path, and the skew runs in the\n * direction this file's invariant calls unsafe. `jitterBufferMs` reaches\n * fec-manager only inside an explicit configure({timeoutMs}); all four of\n * its own computeFecDeliveryWindowMs calls (one in #computeTimeout, one in\n * configure(), two in setFramerate) omit it, so a manager-derived\n * timeoutMs is tolerance-free and yields a\n * horizon NARROWER than the decoder's retention \u2014 the direction that settles\n * a block the decoder would still have recovered. At 720p/33ms the decoder\n * retains ceil(133/33)+1 = 6 while a tolerance-free 33ms window gives\n * ceil(33/33)+1 = 2. Two writes can also raise a validated timeoutMs\n * afterwards without re-validating (configure()'s widening loop and\n * setFramerate).\n *\n * Not fixed here: the real fix is to thread the tolerance into those four\n * calls so both sides derive from the same quantity, which is a change to\n * inputs fec-manager does not currently receive. Tracked in BLO-38250. The\n * live ISS path does not hit it \u2014 multicast-manager always supplies both\n * `timeoutMs` and `fps`, and setFramerate skips tracks that already carry\n * `fps` \u2014 so this is a latent skew on the fallback, not a live defect. Do\n * not read the old \"can only skew narrower, never wider\" note as a safety\n * argument: narrower IS the failing direction.\n */\n #derivableOrderedHorizon(config: ResolvedFECTrackConfig, packetId: number): number | undefined {\n if (!FECManager.#hasOrderedGeometry(config)) return undefined\n try {\n // `tryDeriveFecRetentionBlocks`, not the throwing variant: this is the\n // per-symbol path (#settlesByOrder -> #orderedHorizonBlocks, from\n // noteBlockActivity on every source and repair symbol), and a geometry\n // past the 255-block ceiling is STEADY STATE, not a transient --\n // `timeoutMs` does not narrow back on its own. Throwing here would\n // construct an Error and capture a stack in oti.ts, then unwind it,\n // for every symbol on the stream, and #advanceOrderedFrontier's\n // `blocks === undefined` branch re-enters noteBlockActivity per pending\n // SBN and would multiply it again. The ceiling itself stays owned by\n // oti.ts -- open-coding the comparison here would re-create the second\n // derivation this change exists to remove.\n const blocks = tryDeriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs)\n if (blocks === undefined) {\n // Falls back to datagram semantics through the `blocks === undefined`\n // branch already written for it in #advanceOrderedFrontier. Two writes\n // can raise a track's timeoutMs after #orderedHorizonBlocksFor\n // validated it (configure()'s widening loop and setFramerate's), and\n // neither re-validates, so this is reachable without a second\n // config-time check.\n this.#noteLeftOrderedSettlement(\n packetId,\n `geometry cannot define a retention horizon within the 255-block ceiling ` +\n `(timeoutMs=${config.timeoutMs}ms over interleaveDepthMs=${config.interleaveDepthMs}ms)`,\n )\n }\n return blocks\n } catch (error) {\n // Backstop only -- nothing above throws today. Deliberately unnarrowed:\n // if the callee grows a new throw, this is what makes it visible rather\n // than silently swallowed. The contract of this path\n // (#orderedHorizonBlocks) is that it must not throw.\n this.#noteLeftOrderedSettlement(packetId, String(error))\n return undefined\n }\n }\n\n /**\n * Warn once per stream that it dropped from ordered-relay settlement to the\n * datagram deadline. Swallowing is right for the packet-feed contract, but a\n * configured stream changing settlement mid-flight is a behavioural change,\n * and it is otherwise indistinguishable from a stream that never had\n * geometry.\n */\n #noteLeftOrderedSettlement(packetId: number, reason: string): void {\n if (this.#orderedHorizonWarnedFor.has(packetId)) return\n this.#orderedHorizonWarnedFor.add(packetId)\n console.warn(\n `[FECManager] packetId=${packetId} left ordered-relay settlement for the datagram deadline: ${reason}`,\n )\n }\n\n /**\n * The stream's interleave horizon, or undefined when its geometry cannot\n * define one. Two different streams reach that case, with different causes:\n * a packetId that was never configured, and so resolves to the manager\n * defaults (explicitly configured tracks are refused an underivable geometry\n * at config time \u2014 #upsertTrackConfig, setMulticastSymbolDelivery); or a\n * configured track whose timeoutMs was later widened past the 255-block\n * ceiling without re-validating, which #derivableOrderedHorizon catches\n * rather than throwing from the packet-feed path. Either way the stream\n * keeps datagram semantics instead of throwing. The manager defaults can\n * also change under a stream that already holds blocks, so a caller cannot\n * assume a horizon that was defined when a block was held is still defined\n * (see #advanceOrderedFrontier).\n */\n #orderedHorizonBlocks(packetId: number): number | undefined {\n return this.#derivableOrderedHorizon(this.#resolveTrackConfig(packetId), packetId)\n }\n\n /** True while a block of an ordered-relay stream can still receive symbols. */\n #withinOrderedHorizon(key: string): boolean {\n if (this.#multicastSymbolDelivery !== 'ordered-relay') return false\n const block = this.#parseBlockKey(key)\n const frontier = this.#orderedFrontiers.get(\n this.#streamKey(block.packetId, block.catalogTrackName),\n )\n if (frontier === undefined) return false\n const horizon = this.#orderedHorizonBlocks(block.packetId)\n if (horizon === undefined) return false\n return this.#blocksBehind(block.sbn, frontier, block.packetId) <= horizon\n }\n\n /** Whether a block's fate is decided by stream order rather than a timer. */\n #settlesByOrder(key: string): boolean {\n return this.#multicastSymbolDelivery === 'ordered-relay' &&\n (this.#blockPathCounts.get(key)?.multicastSymbols ?? 0) > 0 &&\n this.#orderedHorizonBlocks(this.#parseBlockKey(key).packetId) !== undefined\n }\n\n #forgetOrderedPending(key: string): void {\n if (this.#orderedPendingBlocks.size === 0) return\n const block = this.#parseBlockKey(key)\n const streamKey = this.#streamKey(block.packetId, block.catalogTrackName)\n const pending = this.#orderedPendingBlocks.get(streamKey)\n if (pending?.get(block.sbn) !== key) return\n pending.delete(block.sbn)\n if (pending.size === 0) this.#orderedPendingBlocks.delete(streamKey)\n }\n\n /**\n * Record the stream position of a multicast symbol and settle every pending\n * block it proves final: a block more than the interleave horizon behind the\n * frontier has already been handed every symbol the relay will deliver.\n */\n #advanceOrderedFrontier(\n streamKey: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n const previous = this.#orderedFrontiers.get(streamKey)\n const frontier = previous === undefined || this.#blockPrecedes(previous, sbn, packetId)\n ? sbn\n : previous\n if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier)\n const pending = this.#orderedPendingBlocks.get(streamKey)\n if (!pending) return\n const blocks = this.#orderedHorizonBlocks(packetId)\n if (blocks === undefined) {\n // Either an unconfigured stream (it resolves to the manager defaults,\n // and configure() accepts a sub-floor interleaveDepthMs) or a configured\n // one whose timeoutMs was widened past the 255-block ceiling after\n // validation (#derivableOrderedHorizon). Stream\n // order can no longer settle its held blocks, so hand them to the\n // datagram deadline -- as setMulticastSymbolDelivery('datagram') does --\n // rather than settle them a horizon early or hold them with no timer.\n this.#orderedPendingBlocks.delete(streamKey)\n for (const pendingSbn of pending.keys()) {\n this.noteBlockActivity(pendingSbn, packetId, catalogTrackName)\n }\n return\n }\n for (const [pendingSbn, key] of pending) {\n if (this.#blocksBehind(pendingSbn, frontier, packetId) <= blocks) continue\n pending.delete(pendingSbn)\n this.#settleOrderedBlock(key, pendingSbn, packetId, catalogTrackName)\n }\n if (pending.size === 0 && this.#orderedPendingBlocks.get(streamKey) === pending) {\n this.#orderedPendingBlocks.delete(streamKey)\n }\n }\n\n /**\n * Decide a block the ordered relay has finished delivering. Its accounting\n * is final: SSB_length (from any repair symbol) against the source and\n * repair symbols it holds says whether the decoder can still recover it.\n * A block short of that asks for repair when D-28 is wired, under the same\n * post-request fetch budget the datagram path grants; otherwise, and for a\n * block the decoder holds enough symbols for, the decoder finalizes it\n * exactly as a datagram deadline would (a Worker acknowledges the cleanup\n * only after every earlier symbol, so an in-flight recovery still wins).\n */\n #settleOrderedBlock(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n if (\n this.#completedBlocks.has(key) ||\n this.#pendingWorkerCleanups.has(key) ||\n this.#repairRequestedBlocks.has(key)\n ) return\n const authoredLength = this.#blockAuthoredLengths.get(key)\n const received =\n (this.#blockSourceCounts.get(key) ?? 0) +\n (this.#blockRepairRsIds.get(key)?.size ?? 0)\n if (\n this.onRepairNeeded &&\n (authoredLength === undefined || received < authoredLength)\n ) {\n this.#requestRepair(sbn, packetId, catalogTrackName, true)\n return\n }\n this.#traceLifecycle('horizon-settled', sbn, packetId, catalogTrackName)\n this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName)\n }\n\n /** Register a block whose multicast symbols arrive over an ordered relay. */\n #holdForOrderedSettlement(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n // A block that started on the other transport may already hold a timer.\n // Its multicast symbols now decide it by order; a wall clock cannot.\n this.#clearRepairDeadline(key)\n if (this.#activeBlocks.has(key)) {\n this.#clearBlockTimer(key)\n this.#unindexBlockKey(key)\n }\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let pending = this.#orderedPendingBlocks.get(streamKey)\n if (!pending) {\n pending = new Map()\n this.#orderedPendingBlocks.set(streamKey, pending)\n }\n pending.set(sbn, key)\n }\n\n /** Cancel and forget a block's inactivity timer and deadline. */\n #clearBlockTimer(key: string): void {\n const timer = this.#activeBlocks.get(key)\n if (timer?.handle != null) clearTimeout(timer.handle)\n this.#activeBlocks.delete(key)\n this.#activeBlockDeadlinesMs.delete(key)\n this.#blockWaitDeadlinesMs.delete(key)\n this.#repairHoldUntilMs.delete(key)\n }\n\n #parseBlockKey(key: string): { packetId: number; sbn: number; catalogTrackName?: string } {\n const [packetIdText, sbnText, ...catalogParts] = key.split(':')\n const catalogTrackName = catalogParts.length > 0\n ? decodeURIComponent(catalogParts.join(':'))\n : undefined\n return {\n packetId: Number(packetIdText) || 0,\n sbn: Number(sbnText) || 0,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n }\n }\n\n #decoderForPacket(packetId: number, catalogTrackName?: string) {\n return this.#fecDecoders.get(this.#streamKey(packetId, catalogTrackName))\n ?? this.#fecDecoders.get(this.#streamKey(packetId))\n }\n\n #workerForPacket(packetId: number, catalogTrackName?: string) {\n return this.#workerClients.get(this.#streamKey(packetId, catalogTrackName))\n ?? this.#workerClients.get(this.#streamKey(packetId))\n }\n\n /**\n * The stream's Worker client for routing, or undefined after evicting a\n * registered client that was already disposed. A disposed client throws on\n * every command, so it leaves routing here before a symbol reaches it and\n * the stream reports its decoder unavailable instead.\n */\n #routableWorkerForPacket(packetId: number, catalogTrackName?: string): FecWorkerClient | undefined {\n const client = this.#workerForPacket(packetId, catalogTrackName)\n if (client === undefined || client.disposed !== true) return client\n this.#evictDisposedWorker(client)\n return undefined\n }\n\n #claimDecoderBlock(\n client: object,\n sbn: number,\n catalogTrackName?: string,\n ): boolean {\n let blocks = this.#decoderBlockOwners.get(client)\n if (!blocks) {\n blocks = new Map()\n this.#decoderBlockOwners.set(client, blocks)\n }\n const owner = blocks.get(sbn)\n if (!owner) {\n blocks.set(sbn, { catalogTrackName, conflicted: false })\n if (blocks.size > 512) {\n const stale = [...blocks.keys()].slice(0, 256)\n for (const key of stale) blocks.delete(key)\n }\n return true\n }\n if (owner.conflicted) return false\n if (owner.catalogTrackName === catalogTrackName) return true\n owner.conflicted = true\n return false\n }\n\n #decoderBlockCatalogTrackName(client: object, sbn: number): string | undefined {\n const owner = this.#decoderBlockOwners.get(client)?.get(sbn)\n return owner?.conflicted ? undefined : owner?.catalogTrackName\n }\n\n #resetDecoderBlockOwners(client: object, catalogTrackName: string): void {\n const blocks = this.#decoderBlockOwners.get(client)\n if (!blocks) return\n for (const [sbn, owner] of blocks) {\n if (owner.catalogTrackName === catalogTrackName || owner.conflicted) {\n blocks.delete(sbn)\n }\n }\n if (blocks.size === 0) this.#decoderBlockOwners.delete(client)\n }\n\n #discardFecDecoderClient(client: WasmFecDecoder): void {\n for (const [key, entry] of this.#fecDecoders) {\n if (entry.client === client) this.#fecDecoders.delete(key)\n }\n this.#decoderBlockOwners.delete(client)\n }\n\n #assertExclusiveDecoderClient(\n streamKey: string,\n client: object,\n registrations: Iterable<readonly [string, object]>,\n kind: 'decoder' | 'worker',\n ): void {\n for (const [registeredKey, registeredClient] of registrations) {\n if (registeredClient !== client || registeredKey === streamKey) continue\n throw new Error(\n `[FECManager] ${kind} client is already registered for stream ${registeredKey}; ` +\n `create one client per physical stream`,\n )\n }\n }\n\n #statsForPacket(packetId: number): MutableFecCounters {\n let stats = this.#fecTrackStats.get(packetId)\n if (!stats) {\n stats = newFecCounters()\n this.#fecTrackStats.set(packetId, stats)\n }\n return stats\n }\n\n #markPathBlockComplete(sbn: number, packetId: number, catalogTrackName?: string): void {\n const block = this.#blockPathCounts.get(this.#blockKey(sbn, packetId, catalogTrackName))\n if (block) block.complete = true\n }\n\n /** Cancel and forget a block's D-28 repair-deadline timer. */\n #clearRepairDeadline(key: string): void {\n const timer = this.#repairDeadlines.get(key)\n if (timer?.handle != null) clearTimeout(timer.handle)\n this.#repairDeadlines.delete(key)\n this.#repairDeadlineAtMs.delete(key)\n }\n\n /**\n * Move a block's D-28 repair deadline to `delayMs` from now. The timer\n * already armed for an earlier moment fires first and re-arms for the\n * remainder, so repeated source progress costs no timer operation.\n */\n #armRepairDeadline(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n delayMs: number,\n ): void {\n const deadlineMs = performance.now() + delayMs\n this.#repairDeadlineAtMs.set(key, deadlineMs)\n const armed = this.#repairDeadlines.get(key)\n if (armed?.handle != null) {\n if (armed.fireAtMs <= deadlineMs) return\n clearTimeout(armed.handle)\n }\n const timer: DeadlineTimer = { handle: null, fireAtMs: deadlineMs }\n const onFire = (): void => {\n if (this.#repairDeadlines.get(key) !== timer) return\n const currentDeadlineMs = this.#repairDeadlineAtMs.get(key)\n const nowMs = performance.now()\n if (currentDeadlineMs !== undefined && currentDeadlineMs > nowMs) {\n timer.fireAtMs = currentDeadlineMs\n timer.handle = setTimeout(onFire, Math.ceil(currentDeadlineMs - nowMs))\n return\n }\n timer.handle = null\n this.#requestRepair(sbn, packetId, catalogTrackName)\n }\n timer.handle = setTimeout(onFire, delayMs)\n this.#repairDeadlines.set(key, timer)\n }\n\n #requestRepair(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n /**\n * Ordered-relay settlement only: the stream has proved the block final,\n * so its multicast wait ends now. A datagram block keeps the wait its\n * accepted symbols armed.\n */\n settledByOrder = false,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (\n this.#completedBlocks.has(key) ||\n !this.onRepairNeeded ||\n this.#repairRequestedBlocks.has(key)\n ) return\n\n const deadline = this.#repairDeadlines.get(key)\n if (deadline?.handle != null) clearTimeout(deadline.handle)\n this.#repairDeadlines.set(key, null)\n this.#repairDeadlineAtMs.delete(key)\n this.#repairRequestedBlocks.add(key)\n\n // The request itself grants no time (BLO-35597). The multicast wait keeps\n // running, extended by any late symbol; the repair scheduler extends the\n // block past it (holdBlockForRepair) only for a FETCH it admits, i.e. one\n // the measured reliable-path RTT says can land before the block's\n // presentation deadline. A request it declines leaves the block on the\n // multicast wait alone, so no doomed FETCH is sent and none is aborted\n // for want of a grace that could never have covered the round trip.\n if (settledByOrder) this.#armBlockTimeout(sbn, packetId, catalogTrackName, 0)\n this.#traceLifecycle('repair-requested', sbn, packetId, catalogTrackName)\n this.onRepairNeeded(\n sbn,\n packetId,\n catalogTrackName,\n this.blockClassFor(sbn, packetId, catalogTrackName),\n )\n }\n\n // \u2500\u2500 Keyframe-protected repair policy \u2500\u2500\n\n /**\n * The classifier geometry of one stream. A stream-scoped configuration\n * wins. Once any stream of a packetId was configured with its own geometry,\n * a sibling stream without one has none that provably belongs to it: the\n * packet-level geometry may be another rung's (a different framerate or\n * GOP), so it runs all-protected (`stream-geometry-absent`).\n */\n #geometryFor(packetId: number, catalogTrackName?: string): BlockClassifierGeometry {\n const own = this.#streamGeometries.get(this.#streamKey(packetId, catalogTrackName))\n if (own) return own\n const config = this.#trackConfigs.get(packetId)\n if (config?.trackKind === 'audio') return { trackKind: 'audio' }\n for (const key of this.#streamGeometries.keys()) {\n if (this.#belongsToPacket(key, packetId)) {\n return { trackKind: 'video', mode: 'all-protected', reason: 'stream-geometry-absent' }\n }\n }\n if (config?.geometry) return config.geometry\n // No catalog cadence reached this track: every video block is protected.\n return { trackKind: 'video', mode: 'all-protected', reason: 'keyframe-cadence-absent' }\n }\n\n #classifierFor(packetId: number, catalogTrackName?: string): BlockClassifier {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let classifier = this.#classifiers.get(streamKey)\n if (!classifier) {\n classifier = new BlockClassifier(this.#geometryFor(packetId, catalogTrackName))\n this.#classifiers.set(streamKey, classifier)\n if (classifier.geometry.trackKind === 'video') {\n this.onClassifierMode?.(packetId, catalogTrackName, classifier.mode, classifier.reason)\n }\n }\n return classifier\n }\n\n #applyClassifierChanges(\n packetId: number,\n catalogTrackName: string | undefined,\n changes: readonly ClassifierChange[],\n ): void {\n for (const change of changes) {\n if (change.kind === 'mode') {\n this.onClassifierMode?.(packetId, catalogTrackName, change.mode, change.reason)\n } else {\n this.onBlockReclassified?.(\n change.sbn,\n packetId,\n catalogTrackName,\n change.from,\n change.to,\n change.evidence,\n )\n }\n }\n }\n\n /**\n * Freeze a block's class at resolution and drop its classifier state. The\n * frozen class answers every later query (a recovery counted after the\n * block was cancelled, a failure event) exactly.\n */\n #retireBlockClass(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): BlockClass {\n const frozen = this.#resolvedBlockClasses.get(key)\n if (frozen !== undefined) return frozen\n const blockClass = this.#peekClass(sbn, packetId, catalogTrackName)\n this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.forget(sbn)\n this.#resolvedBlockClasses.set(key, blockClass)\n this.#materializedBlocks.delete(key)\n return blockClass\n }\n\n /** A block's live class without creating classifier state for the stream. */\n #peekClass(sbn: number, packetId: number, catalogTrackName?: string): BlockClass {\n const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))\n if (classifier) return classifier.classOf(sbn)\n return this.#geometryFor(packetId, catalogTrackName).trackKind === 'audio' ? 'audio' : 'unknown'\n }\n\n #viaFor(key: string): RepairVia {\n return (this.#blockPathCounts.get(key)?.moqSymbols ?? 0) > 0 ? 'quic' : 'inband'\n }\n\n #noteBlockRepaired(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n if (this.#repairedBlocks.has(key)) return\n this.#repairedBlocks.add(key)\n this.onBlockRepaired?.(\n sbn,\n packetId,\n catalogTrackName,\n this.blockClassFor(sbn, packetId, catalogTrackName),\n this.#viaFor(key),\n )\n }\n\n // Deliberately one block tighter than deriveFecRetentionBlocks over the same\n // inputs (that function's `+ 1`). Different question: the retention horizon\n // asks how long the decoder still ACCEPTS symbols for a block, this asks how\n // far back a block that vanished entirely may still be repairable (see\n // #noteSbnSeen).\n // The extra block of retention is slack for a block still being fed; a block\n // nothing has been heard from does not get it. Not the units defect \u2014 but it\n // is the next place the two can drift, so change both or neither.\n #liveWindowBlocks(config: ResolvedFECTrackConfig): number | null {\n if (config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS || config.timeoutMs <= 0) return null\n return Math.ceil(config.timeoutMs / config.interleaveDepthMs)\n }\n\n /**\n * Record that a symbol for block `sbn` arrived and materialise any blocks\n * that vanished entirely between the stream's highest SBN and this one.\n *\n * The newest ceil(timeoutMs / interleaveDepthMs) vanished blocks may still\n * be repairable, so they get the normal repair and fail deadlines, armed\n * from now. Older ones are already past their fail bound and fail now, as\n * class `unknown`. A SOURCE symbol whose SBN goes back further than one\n * live window, or jumps further than the stream could have produced since\n * its last symbol, is a source discontinuity, not loss. A repair symbol can\n * only advance the frontier: a D-28 fetch legitimately answers for an old\n * block, so it never signals a discontinuity.\n *\n * The producer closes a block at its Dth media-unit boundary OR when K\n * source symbols filled it, so one interleave span can start several\n * blocks (every large keyframe does). What bounds a forward jump is\n * therefore packet evidence, not a block rate: with the MMTP\n * packet_sequence_number (contiguous per packet_id, ISO/IEC 23008-1) of\n * both source symbols, every block strictly between them holds at least one\n * of the packets sent strictly between them, and K-closes can add at most\n * one block per K of those packets on top of the D-closes elapsed time\n * allows. Without it, only the D-close rate is known.\n */\n #noteSbnSeen(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n symbol: 'source' | 'repair',\n packetSequenceNumber?: number,\n ): void {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n const now = performance.now()\n const previous = this.#highestSbn.get(streamKey)\n const anchor = this.#sourceAnchors.get(streamKey)\n const psn = symbol === 'source' &&\n packetSequenceNumber !== undefined &&\n Number.isSafeInteger(packetSequenceNumber) &&\n packetSequenceNumber >= 0 &&\n packetSequenceNumber <= 0xffff_ffff\n ? packetSequenceNumber\n : undefined\n const anchorHere = (): void => {\n if (psn !== undefined && (anchor === undefined || !this.#blockPrecedes(sbn, anchor.sbn, packetId))) {\n this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now })\n }\n }\n const discontinuity = (): void => {\n // The classifier compares authored order with SBN order only within\n // one continuous SBN sequence.\n this.#classifiers.get(streamKey)?.noteSourceDiscontinuity()\n this.#highestSbn.set(streamKey, { sbn, atMs: now })\n if (psn === undefined) this.#sourceAnchors.delete(streamKey)\n else this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now })\n this.onSourceDiscontinuity?.(packetId, catalogTrackName)\n }\n if (previous === undefined) {\n this.#highestSbn.set(streamKey, { sbn, atMs: now })\n anchorHere()\n return\n }\n const config = this.#resolveTrackConfig(packetId)\n const window = this.#liveWindowBlocks(config)\n if (!this.#blockPrecedes(previous.sbn, sbn, packetId)) {\n if (symbol === 'source' && window !== null &&\n this.#blocksBehind(sbn, previous.sbn, packetId) > window) {\n discontinuity()\n return\n }\n anchorHere()\n return\n }\n const missing = this.#blocksBehind(previous.sbn, sbn, packetId) - 1\n let plausible = true\n if (window !== null) {\n // A packet_sequence_number that did not advance says nothing (a\n // producer that does not maintain it); only then is the D-close rate\n // the whole bound.\n if (this.#isIso(packetId) && psn !== undefined && anchor !== undefined &&\n this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {\n // iso-ssbg1-v1: SS_ID advances by one per FEC source packet (C.5.2)\n // and packet_sequence_number by one per packet of the packet_id, so a\n // block start can move at most the packets sent since the anchor,\n // plus the anchor's own offset into its block (< K).\n const sent = (psn - anchor.psn) >>> 0\n plausible =\n sent <= 0x7fff_ffff &&\n serial32Distance(anchor.sbn, sbn) <= sent + config.k\n } else if (psn !== undefined && anchor !== undefined &&\n this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {\n // Packets sent from the anchor packet to this one, serial arithmetic\n // (RFC 1982): more than half the space means it went backwards.\n const sent = (psn - anchor.psn) >>> 0\n const between = this.#blocksBehind(anchor.sbn, sbn, packetId) - 1\n const dCloses = window + Math.ceil((now - anchor.atMs) / config.interleaveDepthMs)\n // Each K-close consumes K of the packets sent; with K unknown,\n // every packet may have closed one.\n const kCloses = config.k > 0 ? Math.ceil(sent / config.k) : sent\n plausible =\n sent <= 0x7fff_ffff &&\n between <= sent - 1 &&\n between <= dCloses + kCloses\n } else {\n plausible = missing <= window + Math.ceil((now - previous.atMs) / config.interleaveDepthMs)\n }\n }\n if (!plausible) {\n if (symbol === 'repair') return\n discontinuity()\n return\n }\n this.#highestSbn.set(streamKey, { sbn, atMs: now })\n anchorHere()\n // iso-ssbg1-v1 cannot enumerate vanished blocks by key: their starts are\n // unknown. The SS_IDs between two stated blocks become one repair-less\n // span when the later block's repair arrives (placeIsoRepair).\n if (this.#isIso(packetId)) return\n if (missing <= 0 || window === null) return\n let vanished = previous.sbn\n for (let i = 0; i < missing; i++) {\n vanished = this.#nextBlock(vanished, packetId)\n this.#materializeBlock(\n vanished,\n packetId,\n catalogTrackName,\n this.#blocksBehind(vanished, sbn, packetId) <= window,\n )\n }\n }\n\n /*\n * Block order for one stream (BLO-35597 iso:5). SS_ID is a 32-bit serial\n * number (ISO/IEC 23008-1 C.5.2), so blocks are ordered by RFC 1982\n * comparison of their first SS_ID (sbn * K mod 2^32). That is identical to\n * comparing SBNs with < for every stream that has not wrapped SS_ID; only\n * the order across the wrap changes. A block outside K-stride geometry has\n * no SS_ID position, so it keeps the SBN order it always had.\n */\n #blockPrecedes(a: number, b: number, packetId: number): boolean {\n // iso-ssbg1-v1 keys a block by its SS_Start: plain RFC 1982 order.\n if (this.#isIso(packetId)) return serial32Precedes(a, b)\n const k = this.kForPacket(packetId)\n return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlockPrecedes(a, b, k) : a < b\n }\n\n /** Blocks `a` lies behind `b` (0 when it does not precede it). */\n #blocksBehind(a: number, b: number, packetId: number): number {\n // iso-ssbg1-v1 blocks vary in length, so only a lower bound is known: the\n // one metric the decoder and the worker retire by too.\n if (this.#isIso(packetId)) return isoBlocksBehindLowerBound(a, b, this.kForPacket(packetId))\n const k = this.kForPacket(packetId)\n return isKStrideBlock(a, k) && isKStrideBlock(b, k)\n ? kStrideBlocksBehind(a, b, k)\n : Math.max(0, b - a)\n }\n\n /** The block after `sbn`, stepping to block 0 where SS_ID wraps. */\n #nextBlock(sbn: number, packetId: number): number {\n const k = this.kForPacket(packetId)\n return isKStrideBlock(sbn, k) ? kStrideNextBlock(sbn, k) : sbn + 1\n }\n\n #materializeBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n repairable: boolean,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (this.#blockResolutionFor(key) !== 'untracked') return\n this.#materializedBlocks.add(key)\n this.#classifierFor(packetId, catalogTrackName).touch(sbn)\n this.onBlockVanished?.(sbn, packetId, catalogTrackName)\n if (repairable) {\n this.noteBlockActivity(sbn, packetId, catalogTrackName, true)\n } else {\n this.#failBlock(sbn, packetId, catalogTrackName)\n }\n }\n\n #blockResolutionFor(key: string): FecBlockResolution {\n if (this.#failedBlocks.has(key)) return 'failed'\n if (this.#completedBlocks.has(key)) return 'complete'\n if (\n this.#activeBlocks.has(key) ||\n this.#pendingWorkerCleanups.has(key) ||\n this.#repairDeadlines.has(key) ||\n this.#blockSourceCounts.has(key) ||\n this.#blockRepairRsIds.has(key) ||\n this.#materializedBlocks.has(key)\n ) {\n return 'pending'\n }\n return 'untracked'\n }\n\n #bumpEpoch(packetId: number): void {\n this.#packetEpochs.set(packetId, (this.#packetEpochs.get(packetId) ?? 0) + 1)\n }\n\n #traceLifecycle(\n event: FecLifecycleTrace['event'],\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n rsId?: number,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n const deadline = this.#activeBlockDeadlinesMs.get(key)\n const decoderPath = this.#syncDecoderBlockOwners.has(key)\n ? 'sync'\n : this.#workerForPacket(packetId, catalogTrackName)\n ? 'worker'\n : this.#decoderForPacket(packetId, catalogTrackName)\n ? 'sync'\n : 'none'\n this.#lifecycleTrace.push({\n atMs: Date.now(),\n event,\n packetId,\n sbn,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n sourceCount: this.#blockSourceCounts.get(key) ?? 0,\n sourceEsis: [...(this.#blockSourceEsis.get(key) ?? [])].sort((a, b) => a - b),\n repairRsIds: [...(this.#blockRepairRsIds.get(key) ?? [])].sort((a, b) => a - b),\n decoderPath,\n ...(deadline === undefined\n ? {}\n : { deadlineRemainingMs: Math.max(0, deadline - performance.now()) }),\n pendingCleanup: this.#pendingWorkerCleanups.has(key),\n ...(rsId === undefined ? {} : { rsId }),\n })\n if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32)\n }\n\n #defaultTrackConfig(): ResolvedFECTrackConfig {\n return {\n k: this.#fecK,\n symbolSize: this.#fecSymbolSize,\n interleaveDepthMs: this.#fecInterleaveDepthMs,\n fps: this.#fecFps,\n timeoutMs: this.#fecTimeoutMs,\n }\n }\n\n #resolveTrackConfig(packetId: number): ResolvedFECTrackConfig {\n return this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig()\n }\n\n #computeTimeout(config: {\n k: number\n interleaveDepthMs: number\n fps?: number\n timeoutMs?: number\n }): number {\n if (config.timeoutMs && config.timeoutMs > 0) return config.timeoutMs\n // Only a valid \u00A75.1 ms interleave (>= MIN_INTERLEAVE_DEPTH_MS) drives the\n // timeout. A sub-floor value is a raw frame count (no fps to convert with);\n // fall back to the established #fecTimeoutMs rather than collapse to ~0.\n if (config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {\n return Math.max(\n this.#fecTimeoutMs,\n computeFecDeliveryWindowMs({\n interleaveDepthMs: config.interleaveDepthMs,\n sourceSymbols: config.k,\n mediaUnitsPerSecond: config.fps,\n }),\n )\n }\n return this.#fecTimeoutMs\n }\n\n /**\n * The delivery-gap window is the longest catalog keyframe interval among\n * the configured video streams. A failed video block costs every frame up\n * to the next keyframe, so a receiver must remember a stall for at least\n * that long: forgetting it sooner would let the next recurrence fail a\n * block whose damage outlives the memory of the stall that caused it. It\n * comes from the catalog's `keyframeIntervalTicks`/`keyframeIntervalMs`\n * (via the classifier geometry), never from a constant. It also caps the\n * gap the wait may absorb; with no catalog cadence no gap is reported and\n * the catalog tolerance alone applies (see DeliveryGapEstimator).\n */\n #refreshDeliveryGapWindow(): void {\n let windowMs: number | undefined\n const consider = (geometry: BlockClassifierGeometry | undefined): void => {\n if (geometry?.trackKind !== 'video' || geometry.mode !== 'classifying') return\n const { ticks, timescale } = geometry.keyframeInterval\n const ms = (ticks / timescale) * 1000\n if (Number.isFinite(ms) && ms > 0 && (windowMs === undefined || ms > windowMs)) windowMs = ms\n }\n for (const geometry of this.#streamGeometries.values()) consider(geometry)\n for (const config of this.#trackConfigs.values()) consider(config.geometry)\n this.#deliveryGaps.setWindowMs(windowMs)\n }\n\n /**\n * The multicast late-symbol wait: the block span plus the larger of the\n * catalog's signaled tolerance and the receiver-measured delivery gap.\n *\n * `config.timeoutMs` already is span + `fec.reorderToleranceMs` (or the\n * wider envelope configure() applies), so it stays the floor; the measured\n * gap widens it only when the path stalls for longer than the catalog\n * allowed for. Datagram delivery only: an ordered relay settles blocks by\n * stream order and never feeds the estimator.\n */\n #multicastWaitMs(config: ResolvedFECTrackConfig): number {\n return this.#waitForGapMs(config, this.#deliveryGaps.peakGapMs(performance.now()))\n }\n\n /**\n * The widest #multicastWaitMs can become: the estimator reports at most its\n * catalog window, so this is a catalog quantity that no measurement moves.\n */\n #maxMulticastWaitMs(config: ResolvedFECTrackConfig): number {\n return this.#waitForGapMs(config, this.#deliveryGaps.windowMs ?? 0)\n }\n\n #waitForGapMs(config: ResolvedFECTrackConfig, gapMs: number): number {\n if (!(gapMs > 0) || !(config.interleaveDepthMs > 0)) return config.timeoutMs\n return Math.max(config.timeoutMs, Math.ceil(config.interleaveDepthMs + gapMs))\n }\n\n #repairDeadlineMs(\n config: ResolvedFECTrackConfig,\n waitMs = this.#multicastWaitMs(config),\n ): number {\n const halfFailBoundMs = Math.ceil(waitMs / 2)\n if (\n config.k > 0 &&\n config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS &&\n config.fps !== undefined &&\n Number.isFinite(config.fps) &&\n config.fps > 0\n ) {\n const mediaUnitMs = 1000 / config.fps\n const interleaveUnits = deriveFecInterleaveDepth(\n config.interleaveDepthMs,\n mediaUnitMs,\n )\n const sourceHorizonMs = Math.ceil(config.k * interleaveUnits * mediaUnitMs)\n if (sourceHorizonMs < waitMs) return sourceHorizonMs\n }\n return halfFailBoundMs\n }\n\n /**\n * One block lifetime: the inactivity fail bound plus the D-28 repair grace,\n * at the widest wait the catalog allows (#maxMulticastWaitMs). Everything\n * derived from it (terminal retention, the release gate's hold ceiling, the\n * duplicate ledgers, the owed-cleanup bound) is compared across time, so it\n * must not follow the live measurement.\n */\n #blockLifetimeMs(config: ResolvedFECTrackConfig): number {\n const waitMs = this.#maxMulticastWaitMs(config)\n return waitMs + this.#repairGraceMs(config, waitMs)\n }\n\n /**\n * The lifetime at the live wait, capped at #blockLifetimeMs: lifetime is not\n * monotone in the wait (the repair deadline switches from half the wait to\n * the source horizon), so the cap is what keeps it inside the retention.\n */\n #liveBlockLifetimeMs(config: ResolvedFECTrackConfig): number {\n const waitMs = this.#multicastWaitMs(config)\n return Math.min(waitMs + this.#repairGraceMs(config, waitMs), this.#blockLifetimeMs(config))\n }\n\n #repairGraceMs(config: ResolvedFECTrackConfig, waitMs: number): number {\n return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs))\n }\n\n #armBlockTimeout(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n delayMs: number,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n const waitAtMs = performance.now() + Math.max(1, Math.ceil(delayMs))\n // A held block's symbols (its own FETCH's repair symbols included) never\n // carry its wait past the hold: the hold is its presentation deadline.\n const holdAtMs = this.#repairHoldUntilMs.get(key)\n this.#blockWaitDeadlinesMs.set(key, holdAtMs === undefined ? waitAtMs : Math.min(waitAtMs, holdAtMs))\n this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)\n }\n\n /**\n * Arm (or move) a block's one fail timer at the later of its multicast\n * wait and its repair hold. Moving it later costs no timer operation: the\n * armed timer wakes no later than the new deadline and re-arms for the\n * remainder.\n */\n #applyBlockDeadline(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n const nowMs = performance.now()\n const deadlineMs = Math.max(\n this.#blockWaitDeadlinesMs.get(key) ?? nowMs + 1,\n this.#repairHoldUntilMs.get(key) ?? -Infinity,\n )\n this.#activeBlockDeadlinesMs.set(key, deadlineMs)\n const armed = this.#activeBlocks.get(key)\n if (armed?.handle != null) {\n if (armed.fireAtMs <= deadlineMs) return\n clearTimeout(armed.handle)\n }\n const timer: DeadlineTimer = { handle: null, fireAtMs: deadlineMs }\n const onFire = (): void => {\n // clearTimeout() cannot retract a callback that the browser already\n // moved onto its task queue. Only the timer that still owns this\n // block's deadline may fail it or enqueue Worker cleanup.\n if (this.#activeBlocks.get(key) !== timer) return\n const currentDeadlineMs = this.#activeBlockDeadlinesMs.get(key)\n const firedAtMs = performance.now()\n if (currentDeadlineMs !== undefined && currentDeadlineMs > firedAtMs) {\n timer.fireAtMs = currentDeadlineMs\n timer.handle = setTimeout(onFire, Math.ceil(currentDeadlineMs - firedAtMs))\n return\n }\n this.#activeBlocks.delete(key)\n this.#activeBlockDeadlinesMs.delete(key)\n this.#blockWaitDeadlinesMs.delete(key)\n this.#repairHoldUntilMs.delete(key)\n this.#traceLifecycle('timeout-queued', sbn, packetId, catalogTrackName)\n this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName)\n }\n timer.handle = setTimeout(onFire, Math.max(1, Math.ceil(deadlineMs - nowMs)))\n this.#activeBlocks.set(key, timer)\n this.#indexBlockKey(key, sbn, packetId)\n }\n\n /**\n * Hand a block whose delivery is over to its decoder for finalization: the\n * datagram deadline fired, or the ordered relay moved past it.\n */\n #finalizeExpiredBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n // A vanished block that never received a symbol holds no decoder state\n // to clean up; waiting on a Worker acknowledgement would never settle.\n if (this.#materializedBlocks.has(key) && !this.#blockPathCounts.has(key)) {\n this.#failBlock(sbn, packetId, catalogTrackName)\n return\n }\n\n // A block that began on the synchronous decoder remains owned by that\n // decoder even if Worker setup completed later. Cleanup must follow the\n // same ownership rule as source and repair symbol dispatch.\n const syncOwner = this.#syncDecoderBlockOwners.get(key)\n if (syncOwner) {\n this.#routeSyncCleanup(syncOwner, sbn, packetId, catalogTrackName)\n this.#failBlock(sbn, packetId, catalogTrackName)\n return\n }\n\n const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)\n if (workerClient) {\n this.#pendingWorkerCleanups.add(key)\n this.#pendingWorkerCleanupClients.set(key, workerClient)\n this.#boundOwedCleanup(key, packetId, workerClient)\n workerClient.cleanup(sbn)\n } else {\n const decoder = this.#decoderForPacket(packetId, catalogTrackName)?.client\n if (decoder) {\n this.#routeSyncCleanup(decoder, sbn, packetId, catalogTrackName)\n this.#failBlock(sbn, packetId, catalogTrackName)\n } else {\n this.#failBlock(sbn, packetId, catalogTrackName)\n }\n }\n }\n\n /**\n * Upsert one packetId's track configuration. With `catalogTrackName`, the\n * classifier geometry in `config` is that stream's own and is kept per\n * stream; every other field stays per packetId.\n */\n #upsertTrackConfig(\n packetId: number,\n config: FECManagerConfig,\n catalogTrackName?: string,\n ): ResolvedFECTrackConfig {\n const previous = this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig()\n const streamScopedGeometry =\n catalogTrackName !== undefined && config.geometry !== undefined ? config.geometry : undefined\n if (streamScopedGeometry !== undefined) {\n const { geometry: _streamGeometry, ...packetLevel } = config\n config = packetLevel\n }\n const timingChanged =\n config.k !== undefined ||\n config.interleaveDepthMs !== undefined ||\n config.fps !== undefined\n if (config.blockProfile !== undefined) {\n parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`)\n }\n const blockProfile = config.blockProfile ?? previous.blockProfile\n if (blockProfile === undefined) {\n throw new Error(\n `[FECManager] packetId=${packetId}: fec.blockProfile is required ` +\n '(\"k-padded-v1\" or \"iso-ssbg1-v1\"); configure the track from its catalog',\n )\n }\n if (blockProfile === 'iso-ssbg1-v1') {\n // iso-ssbg1-v1 block location keeps what it holds for a horizon derived\n // from this packet's catalog block span (#isoRetentionBlocks): without\n // one the locator would grow for the life of the session. The span is\n // this packet's own; the manager-wide default is never borrowed.\n const ownInterleaveDepthMs =\n config.interleaveDepthMs ?? this.#trackConfigs.get(packetId)?.interleaveDepthMs\n if (\n ownInterleaveDepthMs === undefined ||\n !Number.isFinite(ownInterleaveDepthMs) ||\n ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS\n ) {\n throw new Error(\n `[FECManager] packetId=${packetId}: iso-ssbg1-v1 requires the catalog fec.interleaveDepthMs ` +\n `(>= ${MIN_INTERLEAVE_DEPTH_MS} ms; got ${String(ownInterleaveDepthMs)}): it bounds how long ` +\n 'block location keeps stated blocks and held sources',\n )\n }\n }\n const next: ResolvedFECTrackConfig = {\n blockProfile,\n k: config.k ?? previous.k,\n symbolSize:\n config.symbolSize != null && config.symbolSize > 0\n ? config.symbolSize\n : previous.symbolSize,\n interleaveDepthMs: config.interleaveDepthMs ?? previous.interleaveDepthMs,\n fps: config.fps ?? previous.fps,\n timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),\n ...((config.trackKind ?? previous.trackKind) === undefined\n ? {}\n : { trackKind: config.trackKind ?? previous.trackKind }),\n ...((config.codec ?? previous.codec) === undefined\n ? {}\n : { codec: config.codec ?? previous.codec }),\n ...((config.geometry ?? previous.geometry) === undefined\n ? {}\n : { geometry: config.geometry ?? previous.geometry }),\n }\n next.timeoutMs = this.#computeTimeout(next)\n if (this.#multicastSymbolDelivery === 'ordered-relay') {\n // Error on bad init: an ordered-relay stream settles its blocks by the\n // decoder's interleave horizon, which this geometry must define.\n this.#orderedHorizonBlocksFor(packetId, next)\n }\n if (\n previous.blockProfile !== undefined &&\n (previous.blockProfile !== blockProfile || (blockProfile === 'iso-ssbg1-v1' && previous.k !== next.k))\n ) {\n // A different profile, or a different maximum K under iso-ssbg1-v1, is\n // a different block geometry: the stream's blocks must not be read\n // under the old one's rules.\n this.#dropIsoLocators(packetId)\n }\n this.#trackConfigs.set(packetId, next)\n this.#refreshDeliveryGapWindow()\n let policyChanged =\n (config.geometry !== undefined &&\n JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry)) ||\n (config.trackKind !== undefined && config.trackKind !== previous.trackKind)\n if (streamScopedGeometry !== undefined) {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n if (JSON.stringify(streamScopedGeometry) !== JSON.stringify(this.#streamGeometries.get(streamKey))) {\n this.#streamGeometries.set(streamKey, streamScopedGeometry)\n this.#refreshDeliveryGapWindow()\n policyChanged = true\n }\n }\n if (policyChanged) {\n // A new catalog epoch for this packet: every classifier whose geometry\n // changed rebuilds from the new one on its next symbol and announces\n // its mode again. A stream with its own geometry is untouched by a\n // sibling's change, but the first stream-scoped geometry on a packet\n // does change the siblings that have none (stream-geometry-absent).\n this.#bumpEpoch(packetId)\n for (const [key, classifier] of [...this.#classifiers]) {\n if (!this.#belongsToPacket(key, packetId)) continue\n const stream = this.#parseStreamKey(key)\n const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName)\n if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue\n this.#classifiers.delete(key)\n // Blocks still unresolved lose the evidence their class rested on:\n // under the fresh classifier they read `unknown`. Say so, so a queued\n // or running P2 repair for a former delta block is promoted to P0\n // instead of silently keeping a priority its class no longer has.\n this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire())\n }\n }\n return next\n }\n\n #normalizeSymbolData(packetId: number, data: Uint8Array): Uint8Array {\n const symbolSize = this.symbolSizeForPacket(packetId)\n if (symbolSize <= 0 || data.byteLength === symbolSize) return data\n const normalized = new Uint8Array(symbolSize)\n normalized.set(data.subarray(0, symbolSize))\n return normalized\n }\n\n #blockKeysFor(sbn: number, packetId?: number, catalogTrackName?: string): string[] {\n if (packetId != null && catalogTrackName !== undefined) {\n return [this.#blockKey(sbn, packetId, catalogTrackName)]\n }\n\n // Every key held by a block-keyed lookup set is indexed under its SBN\n // (#indexBlockKey), so this visits only the handful of streams sharing the\n // SBN instead of every retained block. #cleanlyCompletedBlocks is a subset\n // of #completedBlocks and needs no separate check.\n const keys: string[] = []\n const candidates = this.#blockKeysBySbn.get(sbn)\n if (candidates) {\n for (const [key, keyPacketId] of candidates) {\n if (packetId !== undefined && keyPacketId !== packetId) continue\n if (!this.#isBlockKeyHeld(key)) {\n candidates.delete(key)\n continue\n }\n keys.push(key)\n }\n if (candidates.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n\n if (packetId !== undefined) {\n if (keys.length === 0) return [this.#blockKey(sbn, packetId)]\n return keys.length === 1 ? keys : []\n }\n return keys.length > 0 ? keys : [this.#blockKey(sbn, 0, catalogTrackName)]\n }\n\n // -----------------------------------------------------------------------\n // Accessors\n // -----------------------------------------------------------------------\n\n /** Internal state for diagnostics \u2014 not part of the public stats API. */\n get diagnostics() {\n return {\n timeoutMs: this.#fecTimeoutMs,\n activeBlocks: this.#activeBlocks.size,\n completedBlocks: this.#completedBlocks.size,\n cleanlyCompletedBlocks: this.#cleanlyCompletedBlocks.size,\n pendingCounts: Object.fromEntries(this.#blockSourceCounts),\n fpsDetected: this.#fpsDetected,\n interleaveDepthMs: this.#fecInterleaveDepthMs,\n startupAlignedPackets: this.#packetIdsForStreamKeys(this.#startupAlignedPackets),\n startupPartialBlocks: this.#startupPartialBlocks.size,\n startupRepairReadyPackets: this.#packetIdsForStreamKeys(this.#startupRepairReadyPackets),\n startupRepairFailOpenPackets: this.#packetIdsForStreamKeys(this.#startupRepairFailOpenPackets),\n pendingWorkerCleanups: this.#pendingWorkerCleanups.size,\n decoderUnavailableEvents: this.#decoderUnavailableEvents,\n repairTrackedBlocks: this.#blockRepairRsIds.size,\n multicastSymbolDelivery: this.#multicastSymbolDelivery,\n orderedFrontiers: Object.fromEntries(this.#orderedFrontiers),\n orderedPendingBlocks: [...this.#orderedPendingBlocks.values()]\n .reduce((total, pending) => total + pending.size, 0),\n lifecycleTrace: this.#lifecycleTrace.map((entry) => ({ ...entry })),\n }\n }\n\n get fecStats(): FecStats {\n const s = this.#fecStats\n const affected = s.blocksRecovered + s.blocksFailed\n const total = s.blocksClean + affected\n const tracks = [...this.#fecTrackStats.entries()]\n .sort(([left], [right]) => left - right)\n .map(([packetId, stats]) => {\n const trackAffected = stats.blocksRecovered + stats.blocksFailed\n const trackTotal = stats.blocksClean + trackAffected\n return {\n ...stats,\n packetId,\n blocksComplete: trackTotal,\n k: this.#resolveTrackConfig(packetId).k,\n recoveryRate: trackAffected > 0\n ? (stats.blocksRecovered / trackAffected) * 100\n : 100,\n packetLossRate: trackTotal > 0 ? (trackAffected / trackTotal) * 100 : 0,\n }\n })\n return {\n ...s,\n blocksComplete: total,\n mixedPathBlocks: Array.from(this.#blockPathCounts.values()).filter(\n (block) => block.multicastSymbols > 0 && block.moqSymbols > 0,\n ).length,\n recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),\n k: this.#fecK,\n // True recovery rate: of blocks that HAD loss, how many were recovered?\n recoveryRate: affected > 0 ? (s.blocksRecovered / affected) * 100 : 100,\n packetLossRate: total > 0 ? (affected / total) * 100 : 0,\n tracks,\n workers: [...this.#fecWorkerStats.entries()]\n .sort(([left], [right]) => left - right)\n .map(([packetId, stats]) => ({\n ...stats,\n config: {\n ...stats.config,\n resolution: stats.config.resolution ? { ...stats.config.resolution } : undefined,\n },\n blocks: stats.blocks.map((block) => ({ ...block })),\n decoder: stats.decoder ? { ...stats.decoder } : undefined,\n altaFailedDetails: stats.altaFailedDetails?.map((detail) => ({ ...detail })),\n packetId,\n })),\n repairSymbolSizeMismatches: this.#repairSymbolSizeMismatchCount,\n workerErrors: Object.fromEntries(this.#workerErrorCounts),\n }\n }\n\n /**\n * Observe Worker-backed per-track snapshots without taking ownership of the\n * legacy single-callback slot. Infrastructure diagnostics and a player may\n * therefore consume the same source event without wrapping or replacing one\n * another's callback.\n */\n addFecStatsObserver(\n observer: (packetId: number, stats: FecTrackStats) => void,\n ): () => void {\n this.#fecStatsObservers.add(observer)\n return () => this.#fecStatsObservers.delete(observer)\n }\n\n /** Start a new multicast transport join without discarding track/decoder state. */\n beginMulticastJoin(): void {\n // A new subscription is a new relay: stream order is only defined within\n // one. Blocks still pending from the last join settle once the new\n // stream's own frontier passes them.\n this.#orderedFrontiers.clear()\n this.#startupAlignedPackets.clear()\n this.#startupPartialBlocks.clear()\n this.#startupRequireRepairReady = true\n this.#startupRepairReadyPackets.clear()\n this.#startupRepairBoundaryCounts.clear()\n this.#startupRepairFailOpenPackets.clear()\n }\n\n /** How the multicast leg currently delivers symbols. */\n get multicastSymbolDelivery(): MulticastSymbolDelivery {\n return this.#multicastSymbolDelivery\n }\n\n /**\n * Declare how the multicast leg delivers symbols (see\n * MulticastSymbolDelivery). The multicast provider owns this fact:\n * MulticastManager applies what the window.multicast subscription declares\n * on every join, and a consumer feeding packets itself declares its own leg.\n *\n * Switching to 'ordered-relay' requires every configured track to define\n * its interleave horizon and throws otherwise. Switching back to 'datagram'\n * gives every block awaiting ordered settlement the datagram inactivity\n * deadline from now.\n */\n setMulticastSymbolDelivery(delivery: MulticastSymbolDelivery): void {\n if (!isMulticastSymbolDelivery(delivery)) {\n throw new Error(\n `[FECManager] multicast symbol delivery must be 'datagram' or ` +\n `'ordered-relay', got ${String(delivery)}`,\n )\n }\n if (delivery === this.#multicastSymbolDelivery) return\n // A different delivery path: gaps measured on the old one say nothing\n // about it (and ordered-relay never feeds the estimator).\n this.#deliveryGaps.reset()\n if (delivery === 'ordered-relay') {\n for (const [packetId, config] of this.#trackConfigs) {\n this.#orderedHorizonBlocksFor(packetId, config)\n }\n this.#multicastSymbolDelivery = delivery\n return\n }\n this.#multicastSymbolDelivery = delivery\n const pending = [...this.#orderedPendingBlocks.values()]\n .flatMap((blocks) => [...blocks.values()])\n this.#orderedPendingBlocks.clear()\n this.#orderedFrontiers.clear()\n for (const key of pending) {\n const block = this.#parseBlockKey(key)\n this.noteBlockActivity(block.sbn, block.packetId, block.catalogTrackName)\n }\n }\n\n /** Begin a fresh decode epoch for one physical stream. */\n resetTrack(packetId: number, catalogTrackName: string): void {\n if (!Number.isInteger(packetId) || packetId < 0) {\n throw new Error(`[FECManager] invalid packetId for reset: ${packetId}`)\n }\n if (!catalogTrackName) {\n throw new Error('[FECManager] catalogTrackName is required for track reset')\n }\n const resetMap = <T>(map: Map<string, T>): void => {\n for (const key of map.keys()) {\n if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue\n map.delete(key)\n }\n }\n const resetTimerMap = (map: Map<string, DeadlineTimer | null>): void => {\n for (const [key, timer] of map) {\n if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue\n if (timer?.handle != null) clearTimeout(timer.handle)\n map.delete(key)\n }\n }\n const resetSet = (set: Set<string>): void => {\n for (const key of set) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) set.delete(key)\n }\n }\n\n resetTimerMap(this.#activeBlocks)\n resetTimerMap(this.#repairDeadlines)\n resetMap(this.#activeBlockDeadlinesMs)\n resetMap(this.#blockWaitDeadlinesMs)\n resetMap(this.#repairHoldUntilMs)\n resetMap(this.#repairDeadlineAtMs)\n // Queued terminal records of this stream no longer match an expiry entry,\n // so the retirement sweep skips them and cannot retire a reused key early.\n resetMap(this.#terminalExpiryMs)\n resetMap(this.#pendingWorkerCleanupClients)\n for (const key of [...this.#pendingWorkerCleanupBounds.keys()]) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) this.#clearOwedCleanupBound(key)\n }\n resetMap(this.#blockSourceCounts)\n resetMap(this.#blockSourceEsis)\n resetMap(this.#blockAuthoredLengths)\n resetMap(this.#blockRepairRsIds)\n resetMap(this.#blockPathCounts)\n resetMap(this.#blockAnchorsMs)\n resetMap(this.#blockCatalogTrackNames)\n resetMap(this.#syncDecoderBlockOwners)\n resetMap(this.#startupRepairBoundaryCounts)\n resetMap(this.#orderedFrontiers)\n resetMap(this.#orderedPendingBlocks)\n resetSet(this.#pendingWorkerCleanups)\n resetSet(this.#completedBlocks)\n resetSet(this.#cleanlyCompletedBlocks)\n resetSet(this.#repairRequestedBlocks)\n resetSet(this.#workerRecoveredBlocks)\n resetSet(this.#startupAlignedPackets)\n resetSet(this.#startupPartialBlocks)\n resetSet(this.#startupRepairReadyPackets)\n resetSet(this.#startupRepairFailOpenPackets)\n resetSet(this.#seenSymbols)\n resetSet(this.#failedBlocks)\n resetSet(this.#repairedBlocks)\n resetSet(this.#materializedBlocks)\n resetMap(this.#resolvedBlockClasses)\n resetMap(this.#highestSbn)\n resetMap(this.#isoLocators)\n resetMap(this.#sourceAnchors)\n this.#classifiers.delete(this.#streamKey(packetId, catalogTrackName))\n this.#bumpEpoch(packetId)\n for (const [sbn, keys] of this.#blockKeysBySbn) {\n for (const key of keys.keys()) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) keys.delete(key)\n }\n if (keys.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n\n const decoderClients = new Set<WasmFecDecoder>()\n for (const [key, entry] of this.#fecDecoders) {\n if (this.#belongsToStream(key, packetId, catalogTrackName)) {\n decoderClients.add(entry.client)\n }\n }\n const clients = new Set<object>()\n for (const client of decoderClients) {\n if (\n typeof client.flush === 'function' &&\n typeof client.reset_stats === 'function'\n ) {\n try {\n client.flush()\n client.reset_stats()\n clients.add(client)\n } catch {\n this.#discardFecDecoderClient(client)\n }\n } else {\n this.#discardFecDecoderClient(client)\n }\n }\n for (const [key, client] of [...this.#workerClients]) {\n if (!this.#belongsToStream(key, packetId, catalogTrackName)) continue\n if (client.disposed === true) {\n // reset() throws on a disposed client; evict it (reporting the\n // decoder unavailable) instead of resetting a Worker that is gone.\n this.#evictDisposedWorker(client)\n continue\n }\n client.reset()\n clients.add(client)\n }\n for (const client of clients) this.#resetDecoderBlockOwners(client, catalogTrackName)\n }\n\n /**\n * Retire one packet ID, drop-only: forget every stream on it (block\n * ledgers, deadlines and their timers, repair holds, terminal records,\n * classifiers, stream geometry), its per-packet configuration and\n * statistics, and take its decoders out of routing.\n *\n * This is the end of the packet ID's streams, not a new epoch of them (that\n * is resetTrack): afterwards isTrackConfigured() is false and nothing about\n * it remains until it is configured again. Nothing is reported, because\n * nothing happened to the media: no block fails, recovers or resolves, no\n * repair resolves, and no decoder becomes \"unavailable\" (a retired stream\n * has no symbols left to decode, so a decoder-unavailable event would\n * describe lost protection that no stream suffers). The epoch still\n * advances, so a gap decision made before the retirement never matches a\n * stream later configured on the same packet ID.\n *\n * Decoder and Worker clients stay caller-owned: each is flushed or reset so\n * it holds no block of the retired streams, then released from routing;\n * dispose it after this returns. Cumulative totals in fecStats keep what\n * the retired streams contributed.\n */\n releasePacket(packetId: number): void {\n if (!Number.isInteger(packetId) || packetId < 0) {\n throw new Error(`[FECManager] invalid packetId for release: ${packetId}`)\n }\n const onPacket = (key: string): boolean => this.#belongsToPacket(key, packetId)\n const dropMap = <T>(map: Map<string, T>): void => {\n for (const key of [...map.keys()]) if (onPacket(key)) map.delete(key)\n }\n const dropTimerMap = (map: Map<string, DeadlineTimer | null>): void => {\n for (const [key, timer] of [...map]) {\n if (!onPacket(key)) continue\n if (timer?.handle != null) clearTimeout(timer.handle)\n map.delete(key)\n }\n }\n const dropSet = (set: Set<string>): void => {\n for (const key of [...set]) if (onPacket(key)) set.delete(key)\n }\n\n dropTimerMap(this.#activeBlocks)\n dropTimerMap(this.#repairDeadlines)\n for (const key of [...this.#pendingWorkerCleanupBounds.keys()]) {\n if (onPacket(key)) this.#clearOwedCleanupBound(key)\n }\n dropMap(this.#activeBlockDeadlinesMs)\n dropMap(this.#blockWaitDeadlinesMs)\n dropMap(this.#repairHoldUntilMs)\n dropMap(this.#repairDeadlineAtMs)\n dropMap(this.#terminalExpiryMs)\n dropMap(this.#pendingWorkerCleanupClients)\n dropMap(this.#blockSourceCounts)\n dropMap(this.#blockSourceEsis)\n dropMap(this.#blockAuthoredLengths)\n dropMap(this.#blockRepairRsIds)\n dropMap(this.#blockPathCounts)\n dropMap(this.#blockAnchorsMs)\n dropMap(this.#blockCatalogTrackNames)\n dropMap(this.#syncDecoderBlockOwners)\n dropMap(this.#startupRepairBoundaryCounts)\n dropMap(this.#orderedFrontiers)\n dropMap(this.#orderedPendingBlocks)\n dropMap(this.#resolvedBlockClasses)\n dropMap(this.#highestSbn)\n dropMap(this.#isoLocators)\n dropMap(this.#sourceAnchors)\n dropMap(this.#classifiers)\n dropMap(this.#streamGeometries)\n dropSet(this.#pendingWorkerCleanups)\n dropSet(this.#completedBlocks)\n dropSet(this.#cleanlyCompletedBlocks)\n dropSet(this.#repairRequestedBlocks)\n dropSet(this.#workerRecoveredBlocks)\n dropSet(this.#startupAlignedPackets)\n dropSet(this.#startupPartialBlocks)\n dropSet(this.#startupRepairReadyPackets)\n dropSet(this.#startupRepairFailOpenPackets)\n dropSet(this.#seenSymbols)\n dropSet(this.#failedBlocks)\n dropSet(this.#repairedBlocks)\n dropSet(this.#materializedBlocks)\n for (const [sbn, keys] of [...this.#blockKeysBySbn]) {\n for (const [key, keyPacketId] of [...keys]) {\n if (keyPacketId === packetId) keys.delete(key)\n }\n if (keys.size === 0) this.#blockKeysBySbn.delete(sbn)\n }\n this.#terminalQueues.delete(packetId)\n\n // Bookkeeping first: a decoder or Worker client whose teardown throws must\n // not leave the packet ID configured or its epoch unbumped, or a gap\n // decision made before the release could match a stream later configured\n // on the same packet ID.\n this.#trackConfigs.delete(packetId)\n this.#fecTrackStats.delete(packetId)\n this.#fecWorkerStats.delete(packetId)\n this.#bumpEpoch(packetId)\n // The retired stream's keyframe cadence no longer widens the gap window.\n // The delivery-gap estimator's per-packet sequence is kept: it measures\n // the link, which the retired packet ID's datagrams still describe while\n // a rendition-stable allow-list keeps delivering them.\n this.#refreshDeliveryGapWindow()\n\n for (const [key, entry] of [...this.#fecDecoders]) {\n if (!onPacket(key)) continue\n this.#fecDecoders.delete(key)\n this.#decoderBlockOwners.delete(entry.client)\n try {\n entry.client.flush?.()\n } catch {\n // The client is leaving routing either way; a decoder that cannot\n // flush holds nothing this manager will ask it for again.\n }\n }\n for (const [key, client] of [...this.#workerClients]) {\n if (!onPacket(key)) continue\n this.#workerClients.delete(key)\n this.#decoderBlockOwners.delete(client)\n this.#releasedWorkerClients.add(client)\n try {\n if (client.disposed !== true) client.reset()\n } catch {\n // Already out of routing, and its late messages are ignored\n // (#releasedWorkerClients); a Worker that cannot reset holds nothing\n // this manager will ask it for again.\n }\n }\n }\n\n /** Record a transport symbol and reject later duplicates of the same packetId/SBN/ESI. */\n feedSymbolFromSource(\n packetId: number,\n sbn: number,\n esi: number,\n _data: Uint8Array,\n source: FecSymbolSource,\n catalogTrackName?: string,\n ): boolean {\n const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n if (source === 'multicast' && this.#multicastSymbolDelivery === 'ordered-relay') {\n // Every multicast symbol is a stream position on an ordered relay, even\n // one refused below as a join partial or a duplicate.\n this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName)\n }\n const k = this.kForPacket(packetId)\n // iso-ssbg1-v1 repair ESIs start at the block's own SSB_length (K'), which\n // its locator states; k-padded-v1 repair ESIs start at K.\n const sourceSymbols = this.#isIso(packetId)\n ? this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? k\n : k\n const isSourceSymbol = esi >= 0 && esi < sourceSymbols\n const isStartupPartial = this.#startupPartialBlocks.has(blockKey)\n const isReadyMulticastRepair =\n this.#startupRequireRepairReady &&\n source === 'multicast' &&\n !isSourceSymbol &&\n this.#startupRepairReadyPackets.has(streamKey)\n const awaitingMulticastRepair =\n this.#startupRequireRepairReady &&\n source === 'multicast' &&\n !this.#startupRepairReadyPackets.has(streamKey) &&\n !this.#startupRepairFailOpenPackets.has(streamKey)\n if (awaitingMulticastRepair) {\n if (!isSourceSymbol) {\n this.#startupRepairReadyPackets.add(streamKey)\n this.#startupRepairBoundaryCounts.delete(streamKey)\n this.#markStartupPartial(blockKey)\n return false\n }\n\n if (esi === 0) {\n const boundaries = (this.#startupRepairBoundaryCounts.get(streamKey) ?? 0) + 1\n this.#startupRepairBoundaryCounts.set(streamKey, boundaries)\n if (boundaries >= STARTUP_REPAIR_WAIT_SOURCE_BOUNDARIES) {\n this.#startupRepairBoundaryCounts.delete(streamKey)\n this.#startupRepairFailOpenPackets.add(streamKey)\n } else {\n this.#markStartupPartial(blockKey)\n return false\n }\n } else {\n this.#markStartupPartial(blockKey)\n return false\n }\n }\n if (isSourceSymbol && esi === 0) {\n // ESI=0 establishes a complete source boundary for this SBN. Remove\n // only this block: other interleaved SBNs may still be join partials.\n this.#startupPartialBlocks.delete(blockKey)\n this.#startupAlignedPackets.add(streamKey)\n } else if (\n (isStartupPartial && !isReadyMulticastRepair) ||\n (!this.#startupAlignedPackets.has(streamKey) && !isReadyMulticastRepair)\n ) {\n this.#markStartupPartial(blockKey)\n return false\n }\n\n // A completed block remains authoritative after its individual symbol\n // identities age out of the bounded #seenSymbols cache. Without this\n // guard, a delayed copy from the other transport can recreate the block\n // inside a Worker decoder; that orphan never receives the full source and\n // repair set, so cleanup later reports a false failure/green fill.\n if (this.#completedBlocks.has(blockKey)) {\n this.#fecStats.duplicateSymbols++\n this.#statsForPacket(packetId).duplicateSymbols++\n return false\n }\n\n const symbolKey = `${blockKey}:${esi}`\n if (this.#seenSymbols.has(symbolKey)) {\n this.#fecStats.duplicateSymbols++\n this.#statsForPacket(packetId).duplicateSymbols++\n return false\n }\n this.#seenSymbols.add(symbolKey)\n\n const counts = this.#blockPathCounts.get(blockKey) ?? {\n packetId,\n sbn,\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n multicastSymbols: 0,\n moqSymbols: 0,\n complete: false,\n }\n // The block's absolute anchor (fecBlockDeadlineMs): the receiver-local\n // time of its first accepted symbol, from which its presentation deadline\n // is measured.\n if (!this.#blockAnchorsMs.has(blockKey)) this.#blockAnchorsMs.set(blockKey, performance.now())\n if (source === 'multicast') counts.multicastSymbols++\n else counts.moqSymbols++\n this.#blockPathCounts.set(blockKey, counts)\n this.#observeBlockCatalogTrackName(blockKey, sbn, packetId, catalogTrackName)\n\n // Keep the newest entries of each bounded cache. Deleting the oldest in\n // place keeps exactly the entries a slice(-n) rebuild would, without\n // copying the whole collection into an array on the page thread.\n if (this.#seenSymbols.size > 4096) {\n dropOldest(this.#seenSymbols, this.#seenSymbols.size - 2048)\n }\n if (this.#blockPathCounts.size > 512) {\n dropOldest(this.#blockPathCounts, this.#blockPathCounts.size - 256)\n }\n if (this.#blockAnchorsMs.size > 512) {\n dropOldest(this.#blockAnchorsMs, this.#blockAnchorsMs.size - 256)\n }\n if (this.#blockCatalogTrackNames.size > 512) {\n for (const key of dropOldest(\n this.#blockCatalogTrackNames,\n this.#blockCatalogTrackNames.size - 256,\n )) {\n this.#unindexBlockKey(key)\n }\n }\n\n return true\n }\n\n #observeBlockCatalogTrackName(\n blockKey: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n if (catalogTrackName === undefined) return\n if (!this.#blockCatalogTrackNames.has(blockKey)) {\n this.#blockCatalogTrackNames.set(blockKey, catalogTrackName)\n this.#indexBlockKey(blockKey, sbn, packetId)\n return\n }\n if (this.#blockCatalogTrackNames.get(blockKey) !== catalogTrackName) {\n this.#blockCatalogTrackNames.set(blockKey, null)\n }\n }\n\n /** Exact catalog identity retained for async recovery, if unambiguous. */\n catalogTrackNameForBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): string | undefined {\n if (catalogTrackName !== undefined) {\n return this.#blockCatalogTrackNames.get(\n this.#blockKey(sbn, packetId, catalogTrackName),\n ) ?? undefined\n }\n const names = new Set<string>()\n for (const [key, name] of this.#blockCatalogTrackNames) {\n const block = this.#parseBlockKey(key)\n if (block.packetId === packetId && block.sbn === sbn && name !== null) names.add(name)\n }\n return names.size === 1 ? [...names][0] : undefined\n }\n\n #markStartupPartial(blockKey: string): void {\n this.#startupPartialBlocks.add(blockKey)\n if (this.#startupPartialBlocks.size > 128) {\n this.#startupPartialBlocks = new Set(\n Array.from(this.#startupPartialBlocks).slice(-64),\n )\n }\n }\n\n /** Per-block state snapshot for FecBlockGrid visualization.\n * Returns the most recent blocks (active + recently completed). */\n getBlockStates(): Array<{\n sbn: number;\n packetId?: number;\n state: 'collecting' | 'complete' | 'failed';\n sourceCount: number;\n k: number;\n progress: number;\n }> {\n const blocks: Array<{ sbn: number; packetId?: number; state: 'collecting' | 'complete' | 'failed'; sourceCount: number; k: number; progress: number }> = [];\n // Active blocks (still collecting symbols)\n for (const [key] of this.#activeBlocks) {\n const { packetId, sbn } = this.#parseBlockKey(key);\n const count = this.#blockSourceCounts.get(key) ?? 0;\n const k = this.#blockAuthoredLengths.get(key) ?? this.kForPacket(packetId);\n blocks.push({ sbn, packetId, state: 'collecting', sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });\n }\n // Recently completed blocks (last 16)\n const completed = [...this.#completedBlocks].slice(-16);\n for (const key of completed) {\n const { packetId, sbn } = this.#parseBlockKey(key);\n if (!this.#activeBlocks.has(key)) {\n const k = this.kForPacket(packetId);\n blocks.push({ sbn, packetId, state: 'complete', sourceCount: k, k, progress: 1 });\n }\n }\n blocks.sort((a, b) => a.sbn - b.sbn || (a.packetId ?? 0) - (b.packetId ?? 0));\n return blocks.slice(-24); // cap at 24 most recent\n }\n\n /** The raw WASM decoder for stats access (first registered decoder) */\n get wasmDecoder(): WasmFecDecoder | null {\n return this.#fecDecoders.values().next().value?.client ?? null\n }\n\n get k(): number { return this.#fecK }\n kForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).k }\n /** FEC symbol size in bytes. */\n get symbolSize(): number { return this.#fecSymbolSize }\n symbolSizeForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).symbolSize }\n /** FEC block span in milliseconds (0 = no interleaving). */\n get interleaveDepthMs(): number { return this.#fecInterleaveDepthMs }\n interleaveDepthMsForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).interleaveDepthMs }\n /**\n * Record one multicast MMTP packet arrival (every packet of the leg, FEC\n * symbol or not) for the delivery-gap measurement. `packetSequenceNumber`\n * is the MMTP packet_sequence_number, which increments by one per packet of\n * a packet_id: it is what separates a stall from a loss (see\n * DeliveryGapEstimator). Datagram delivery only.\n */\n noteMulticastArrival(packetId: number, packetSequenceNumber: number): void {\n if (this.#multicastSymbolDelivery !== 'datagram') return\n this.#deliveryGaps.noteArrival(performance.now(), packetId, packetSequenceNumber)\n }\n /**\n * Resolved per-track multicast late-symbol wait in milliseconds: the span\n * plus the larger of the catalog tolerance and the measured delivery gap.\n */\n timeoutMsForPacket(packetId: number): number {\n return this.#multicastWaitMs(this.#resolveTrackConfig(packetId))\n }\n /**\n * Largest multicast delivery gap measured over the current window (ms), and\n * that window (undefined: held since the last reset). Diagnostics.\n */\n multicastDeliveryGap(): { peakGapMs: number; windowMs: number | undefined } {\n return {\n peakGapMs: this.#deliveryGaps.peakGapMs(performance.now()),\n windowMs: this.#deliveryGaps.windowMs,\n }\n }\n /**\n * Receiver-local time of a block's first accepted symbol, or undefined for\n * a block no symbol reached (one materialized because a later SBN arrived).\n */\n blockAnchorAtMs(sbn: number, packetId: number, catalogTrackName?: string): number | undefined {\n return this.#blockAnchorsMs.get(this.#blockKey(sbn, packetId, catalogTrackName))\n }\n /** Absolute fail deadline of a still-open block, or undefined. */\n blockDeadlineAtMs(sbn: number, packetId: number, catalogTrackName?: string): number | undefined {\n return this.#activeBlockDeadlinesMs.get(this.#blockKey(sbn, packetId, catalogTrackName))\n }\n /**\n * Keep a block open until `untilAtMs` while a repair FETCH the scheduler\n * admitted is in flight. Never shortens the multicast wait already armed;\n * while set, the hold is the block's whole deadline, so no later symbol\n * extends it. Returns false when the block is no longer open (resolved, or\n * not yet tracked).\n */\n holdBlockForRepair(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n untilAtMs: number,\n ): boolean {\n if (!Number.isFinite(untilAtMs)) {\n throw new Error(`[FECManager] repair hold must be a finite time (got ${untilAtMs})`)\n }\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (!this.#activeBlocks.has(key)) return false\n const previous = this.#repairHoldUntilMs.get(key)\n if (previous !== undefined && previous >= untilAtMs) return true\n // The hold absorbs the wait already armed, so it never shortens it.\n this.#repairHoldUntilMs.set(\n key,\n Math.max(untilAtMs, this.#blockWaitDeadlinesMs.get(key) ?? -Infinity),\n )\n this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)\n return true\n }\n /**\n * Drop a block's repair hold: its FETCH ended without delivering. The block\n * falls back to its multicast wait, which may already have passed.\n */\n releaseRepairHold(sbn: number, packetId: number, catalogTrackName?: string): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n if (!this.#repairHoldUntilMs.delete(key) || !this.#activeBlocks.has(key)) return\n this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)\n }\n /** Resolved per-track D-28 repair request deadline in milliseconds. */\n repairDeadlineMsForPacket(packetId: number): number {\n return this.#repairDeadlineMs(this.#resolveTrackConfig(packetId))\n }\n /**\n * One block lifetime: the longest a block stays open after its last symbol\n * (inactivity fail bound plus D-28 repair grace).\n */\n blockLifetimeMsForPacket(packetId: number): number {\n return this.#blockLifetimeMs(this.#resolveTrackConfig(packetId))\n }\n /**\n * How long a terminal (clean, recovered or failed) block stays authoritative,\n * for late symbols and for the in-order release gate. It is also the gate's\n * hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as\n * untracked, which the gate counts as lost, so no frame may still be waiting\n * on a block that can retire. The window is one interleave depth for a gap's\n * last block to finish arriving, one block lifetime to fail it, and one more\n * for an owed Worker cleanup that can keep a failing block pending. Every\n * term comes from the track's catalog geometry; there is no separate cap.\n */\n terminalRetentionMsForPacket(packetId: number): number {\n const config = this.#resolveTrackConfig(packetId)\n return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config)\n }\n /** Remaining fetch budget for a specific repair-requested block. */\n repairFetchTimeoutMsForBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): number {\n const deadlineMs = this.#activeBlockDeadlinesMs.get(\n this.#blockKey(sbn, packetId, catalogTrackName),\n )\n if (deadlineMs !== undefined) {\n return Math.max(1, Math.ceil(deadlineMs - performance.now()))\n }\n const trackConfig = this.#resolveTrackConfig(packetId)\n return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig))\n }\n\n /** True once configureTrack/setFecDecoder/setFecWorker configured this packetId. */\n // -----------------------------------------------------------------------\n // iso-ssbg1-v1 block location (BLO-35597 iso:2/iso:3)\n // -----------------------------------------------------------------------\n\n /** The configured `fec.blockProfile` of a packetId (undefined until configured). */\n blockProfileForPacket(packetId: number): FecBlockProfile | undefined {\n return this.#trackConfigs.get(packetId)?.blockProfile\n }\n\n #isIso(packetId: number): boolean {\n return this.#trackConfigs.get(packetId)?.blockProfile === 'iso-ssbg1-v1'\n }\n\n #isoLocatorFor(packetId: number, catalogTrackName?: string): IsoBlockLocator<IsoHeldSource> {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n let locator = this.#isoLocators.get(streamKey)\n if (!locator) {\n locator = new IsoBlockLocator<IsoHeldSource>(this.kForPacket(packetId), this.#isoRetentionBlocks(packetId))\n this.#isoLocators.set(streamKey, locator)\n }\n return locator\n }\n\n /**\n * The locator a call is about to grow, with its retention brought up to\n * date: the horizon follows catalog timing, which a later configuration\n * (this track's or another video track's keyframe cadence) can change.\n */\n #growingIsoLocatorFor(packetId: number, catalogTrackName?: string): IsoBlockLocator<IsoHeldSource> {\n const locator = this.#isoLocatorFor(packetId, catalogTrackName)\n locator.retentionBlocks = this.#isoRetentionBlocks(packetId)\n return locator\n }\n\n #dropIsoLocators(packetId: number): void {\n for (const key of [...this.#isoLocators.keys()]) {\n if (this.#belongsToPacket(key, packetId)) this.#isoLocators.delete(key)\n }\n }\n\n /**\n * The iso-ssbg1-v1 locator horizon in blocks: every block the manager can\n * still hold terminal state for (`terminalRetentionMsForPacket`) over the\n * block span, plus the block being filled, measured with the shared\n * lower-bound metric so a retained block is never forgotten early. Every\n * term is the packet's catalog geometry, which configuration validated\n * (#upsertTrackConfig), so this never falls back: an underivable horizon\n * throws rather than leave the locator without retention.\n */\n #isoRetentionBlocks(packetId: number): number {\n const config = this.#trackConfigs.get(packetId)\n if (config?.blockProfile !== 'iso-ssbg1-v1' || !(config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS)) {\n throw new Error(\n `[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block span to derive block-location retention from`,\n )\n }\n const blocks = Math.ceil(this.terminalRetentionMsForPacket(packetId) / config.interleaveDepthMs) + 1\n if (!Number.isSafeInteger(blocks) || blocks <= 0) {\n throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`)\n }\n return blocks\n }\n\n /** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */\n isoSourceFits(packetId: number, packetLength: number): boolean {\n return ssbg1FitsSymbol(packetLength, this.symbolSizeForPacket(packetId))\n }\n\n /**\n * Locate an iso-ssbg1-v1 source by range membership in a block a repair\n * already stated. Null while its block is unknown: hold it (holdIsoSource).\n */\n locateIsoSource(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n ): { sbn: number; esi: number } | null {\n const located = this.#isoLocatorFor(packetId, catalogTrackName).locate(ssId)\n return located === null ? null : { sbn: located.block.start, esi: located.esi }\n }\n\n /**\n * Hold a first-arrival iso-ssbg1-v1 source whose block no repair has stated\n * yet. Returns false for a later copy of an SS_ID already held.\n */\n holdIsoSource(\n packetId: number,\n catalogTrackName: string | undefined,\n ssId: number,\n source: IsoHeldSource,\n ): boolean {\n const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName)\n const evictedBefore = locator.heldEvicted\n const held = locator.hold(ssId, source)\n const evicted = locator.heldEvicted - evictedBefore\n if (evicted > 0) {\n const before = this.#isoHeldEvictedCount\n this.#isoHeldEvictedCount += evicted\n if (before < 5 || Math.floor(before / 100) !== Math.floor(this.#isoHeldEvictedCount / 100)) {\n console.warn(\n `[FECManager] packetId=${packetId} iso-ssbg1-v1 held-source cap (${locator.heldCapacity}) ` +\n `evicted ${evicted} unplaced source(s); SS_IDs are not consecutive (C.5.2)`,\n )\n }\n }\n return held\n }\n\n /**\n * Apply an iso-ssbg1-v1 repair symbol's `(SS_Start, SSB_length)`: states the\n * block, and hands back the held sources it (and any repair-less span in\n * front of it) now places. The caller feeds them as first-arrival sources,\n * then the repair symbol, the order they had on the wire. A repair that\n * contradicts a known block is refused (counted) and must be dropped.\n */\n placeIsoRepair(\n packetId: number,\n catalogTrackName: string | undefined,\n ssStart: number,\n ssbLength: number,\n ): IsoRepairPlacement<IsoHeldSource> {\n const placement = this.#growingIsoLocatorFor(packetId, catalogTrackName).placeRepair(ssStart, ssbLength)\n if (placement.status !== 'placed') {\n this.#isoRepairRefusedCount++\n if (this.#isoRepairRefusedCount <= 5 || this.#isoRepairRefusedCount % 100 === 0) {\n console.warn(`[FECManager] packetId=${packetId} iso-ssbg1-v1 repair refused (${placement.status}): ${placement.reason}`)\n }\n return placement\n }\n // A repair-less span has no repair to state its length, so it is known\n // here: its sources make it clean exactly when all of them arrived. A\n // stated block learns its SSB_length from its own repair symbol, fed\n // after its placed sources exactly as on the wire (feedRepairSymbol).\n if (placement.span !== undefined) {\n const key = this.#blockKey(placement.span.block.start, packetId, catalogTrackName)\n if (!this.#blockAuthoredLengths.has(key)) this.#blockAuthoredLengths.set(key, placement.span.block.length)\n }\n\n if (placement.span !== undefined && placement.span.placed.length === 0) {\n // Every SS_ID of the span vanished, repair included: it resolves\n // through its deadline like any vanished block.\n this.#materializeBlock(placement.span.block.start, packetId, catalogTrackName, true)\n }\n return placement\n }\n\n /**\n * Resolution of the SS_IDs strictly between two released frames' SS_IDs on\n * an iso-ssbg1-v1 stream, for in-order frame release: null while a block\n * that could hold them is pending or not yet stated by a repair, else the\n * starts of the blocks that failed or vanished.\n *\n * `untracked` reads exactly as in the k-padded-v1 release gate\n * (MmtpRouter #createFecReleaseGate): a block holding either released\n * frame's SS_ID (`fromSsId` or `toSsId`) delivered that frame, so untracked\n * there means it resolved and retired; a block starting after the stream's frontier (`highestSbn`,\n * an SS_Start here) may still be heard from; a block at or behind the\n * frontier that no symbol of this manager ever reached vanished, and its\n * media is lost.\n */\n isoSpanResolution(\n packetId: number,\n catalogTrackName: string | undefined,\n fromSsId: number,\n toSsId: number,\n ): readonly number[] | null {\n if (!serial32Precedes(fromSsId, toSsId) || serial32Distance(fromSsId, toSsId) <= 1) return []\n const locator = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))\n if (!locator || !locator.coversBetween(fromSsId, toSsId)) return null\n const highest = this.highestSbn(packetId, catalogTrackName)\n const lost: number[] = []\n for (const block of locator.blocksBetween(fromSsId, toSsId)) {\n const resolution = this.blockResolution(block.start, packetId, catalogTrackName)\n if (resolution === 'pending') return null\n if (resolution === 'failed') {\n lost.push(block.start)\n } else if (resolution === 'untracked') {\n if (\n isoBlockContains(block.start, block.length, fromSsId) ||\n isoBlockContains(block.start, block.length, toSsId)\n ) {\n continue\n }\n if (highest === undefined || serial32Precedes(highest, block.start)) return null\n lost.push(block.start)\n }\n }\n return lost\n }\n\n /**\n * iso-ssbg1-v1 block location: repair symbols it refused, oversize sources,\n * the sources held now for a block no repair has stated yet, and the held\n * sources the capacity cap evicted (cumulative), and the sources a\n * synchronous decoder held unplaced and retired at its horizon (cumulative).\n */\n get isoDiagnostics(): {\n repairRefused: number\n sourceOversize: number\n heldSources: number\n heldEvicted: number\n decoderUnplacedRetired: number\n decoderUnplacedRetireFailed: number\n } {\n let heldSources = 0\n for (const locator of this.#isoLocators.values()) heldSources += locator.heldCount\n return {\n repairRefused: this.#isoRepairRefusedCount,\n sourceOversize: this.#isoSourceOversizeCount,\n heldSources,\n heldEvicted: this.#isoHeldEvictedCount,\n decoderUnplacedRetired: this.#isoDecoderUnplacedRetired,\n decoderUnplacedRetireFailed: this.#isoDecoderUnplacedRetireFailed,\n }\n }\n\n isTrackConfigured(packetId: number): boolean {\n return this.#trackConfigs.has(packetId)\n }\n\n /** Catalog/reset epoch of a packet's streams; gap decisions carry it. */\n epochFor(packetId: number): number {\n return this.#packetEpochs.get(packetId) ?? 0\n }\n\n /** Configured media kind of a packetId, when known. */\n trackKindFor(packetId: number): TrackKind | undefined {\n return this.#trackConfigs.get(packetId)?.trackKind\n }\n\n /** Configured codec of a packetId, when known. */\n codecFor(packetId: number): string | undefined {\n return this.#trackConfigs.get(packetId)?.codec\n }\n\n /**\n * Block classifier geometry of one stream: its own stream-scoped geometry,\n * else the packet's (fail-closed when absent, or when a sibling stream of\n * the packet has its own geometry and this one has none).\n */\n geometryFor(packetId: number, catalogTrackName?: string): BlockClassifierGeometry {\n return this.#geometryFor(packetId, catalogTrackName)\n }\n\n /** Current classifier mode of a stream. */\n classifierModeFor(\n packetId: number,\n catalogTrackName?: string,\n ): { mode: ClassifierMode; reason: ClassifierReason | null } {\n const classifier = this.#classifierFor(packetId, catalogTrackName)\n return { mode: classifier.mode, reason: classifier.reason }\n }\n\n /**\n * Whether an MPU boundary still bounds this stream's RAPs (see\n * `BlockClassifier.mpuBoundsRaps`). Gap decisions drop MPU evidence once\n * it does not. A stream with no classifier yet has shown nothing against it.\n */\n mpuBoundsRapsFor(packetId: number, catalogTrackName?: string): boolean {\n return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true\n }\n\n /** A block's class now, or frozen at its resolution. */\n blockClassFor(sbn: number, packetId: number, catalogTrackName?: string): BlockClass {\n const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName))\n if (frozen !== undefined) return frozen\n return this.#peekClass(sbn, packetId, catalogTrackName)\n }\n\n /**\n * Lifecycle resolution of one block for in-order frame release: `pending`\n * while it can still complete, `complete`/`failed` once terminal, and\n * `untracked` when no symbol of it was ever accepted.\n */\n blockResolution(sbn: number, packetId: number, catalogTrackName?: string): FecBlockResolution {\n return this.#blockResolutionFor(this.#blockKey(sbn, packetId, catalogTrackName))\n }\n\n /** Highest SBN any symbol of this stream carried in the current epoch. */\n highestSbn(packetId: number, catalogTrackName?: string): number | undefined {\n return this.#highestSbn.get(this.#streamKey(packetId, catalogTrackName))?.sbn\n }\n\n /**\n * Record a RAP frame released in order, for the classifier's RAP-spacing\n * invariant (consecutive MPUs exactly one keyframe interval apart).\n */\n noteReleasedRap(\n packetId: number,\n catalogTrackName: string | undefined,\n mpu: number,\n presentationTime: bigint,\n mediaTimescale: number,\n ): void {\n if (!this.#trackConfigs.has(packetId)) return\n this.#applyClassifierChanges(\n packetId,\n catalogTrackName,\n this.#classifierFor(packetId, catalogTrackName).noteReleasedRap(\n mpu,\n presentationTime,\n mediaTimescale,\n ),\n )\n }\n\n // -----------------------------------------------------------------------\n // Configuration\n // -----------------------------------------------------------------------\n\n /** Configure FEC parameters. */\n configure(config: FECManagerConfig): void {\n if (config.k != null) this.#fecK = config.k\n if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize\n if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs\n if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {\n this.#fecFps = config.fps\n }\n // interleaveDepthMs is in milliseconds per spec \u00A75.1.\n // Valid range: 33ms (interactive/gaming) to 10000ms (broadcast ATSC 3.0).\n // Warn if value looks like frame count instead of ms (common misconfiguration).\n if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {\n console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (\u00A75.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`)\n }\n if (config.timeoutMs != null && config.timeoutMs > 0) {\n this.#fecTimeoutMs = config.timeoutMs\n } else if (\n config.interleaveDepthMs != null &&\n config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS\n ) {\n this.#fecTimeoutMs = computeFecDeliveryWindowMs({\n interleaveDepthMs: config.interleaveDepthMs,\n sourceSymbols: this.#fecK,\n symbolSize: this.#fecSymbolSize,\n mediaUnitsPerSecond: config.fps,\n })\n }\n // A shared multicast parser receives every packetId. Keep existing track\n // deadlines at least as wide as the selected video delivery envelope so a\n // short audio interleave cannot expire during browser/AMT scheduling gaps.\n for (const [packetId, track] of this.#trackConfigs) {\n if (track.timeoutMs < this.#fecTimeoutMs) {\n this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs })\n }\n }\n }\n\n /**\n * Configure FEC parameters for one packetId without changing legacy\n * defaults. With `catalogTrackName`, `config.geometry` is that stream's\n * own classifier geometry (ABR rungs that share a packetId each get\n * theirs); K, T, timing, kind and codec stay per packetId.\n */\n configureTrack(packetId: number, config: FECManagerConfig, catalogTrackName?: string): void {\n this.#upsertTrackConfig(packetId, config, catalogTrackName)\n }\n\n /** Register a FEC decoder for a specific packetId (track). */\n setFecDecoder(\n packetId: number,\n client: WasmFecDecoder,\n k: number,\n config?: FECManagerConfig,\n catalogTrackName?: string,\n ): void {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n this.#assertExclusiveDecoderClient(\n streamKey,\n client,\n [...this.#fecDecoders].map(([key, entry]) => [key, entry.client] as const),\n 'decoder',\n )\n const trackConfig = this.#upsertTrackConfig(packetId, { ...config, k }, catalogTrackName)\n this.#fecDecoders.set(streamKey, { client, k: trackConfig.k })\n }\n\n /**\n * Register a Worker-backed FEC decoder for a specific packetId (track).\n *\n * Worker-backed tracks decode asynchronously \u2014 feedSourceSymbol/feedRepairSymbol\n * return null immediately, and recovered frames arrive via the onBlockRecovered callback.\n *\n * Takes precedence over sync WASM decoders for the same packetId.\n */\n setFecWorker(\n packetId: number,\n client: FecWorkerClient,\n config: FECManagerConfig,\n catalogTrackName?: string,\n ): void {\n const k = config?.k\n if (k === undefined || !Number.isFinite(k) || k <= 0) {\n throw new Error(`[FECManager] packetId=${packetId} Worker requires an explicit positive k`)\n }\n const symbolSize = config.symbolSize\n if (symbolSize === undefined || !Number.isFinite(symbolSize) || symbolSize <= 0) {\n throw new Error(\n `[FECManager] packetId=${packetId} Worker requires an explicit positive symbolSize`,\n )\n }\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n this.#assertExclusiveDecoderClient(\n streamKey,\n client,\n this.#workerClients,\n 'worker',\n )\n // Every Worker leaves routing through #retireWorker, which fails the\n // blocks it still owed and reports the stream's decoder unavailable.\n // Overwriting a registration would skip that path and strand those\n // blocks, so a second Worker under a routed stream is refused.\n const incumbent = this.#workerClients.get(streamKey)\n if (incumbent !== undefined && incumbent !== client) {\n throw new Error(\n `[FECManager] stream ${streamKey} already routes to a Worker; ` +\n `call clearFecWorker before registering another`,\n )\n }\n if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName)\n const workerK = this.#resolveTrackConfig(packetId).k\n this.#workerClients.set(streamKey, client)\n this.#releasedWorkerClients.delete(client)\n\n client.onFrame((data: ArrayBuffer, meta: FrameMeta) => {\n if (this.#releasedWorkerClients.has(client)) return\n if (meta.recovered) {\n // Recovered Worker events represent one exact source-symbol position.\n // Block-completion accounting needs that exact ESI, and counting an\n // ESI-less event as recovered would report healthy FEC while dropping\n // the media. Enforce the Worker contract before any recovery metric.\n // The Worker names the recovered block's authored SSB_length; ESIs\n // SSB_length..K-1 are encoder padding and are never emitted.\n if (\n !Number.isInteger(meta.ssbLength) ||\n meta.ssbLength! <= 0 ||\n meta.ssbLength! > workerK\n ) {\n const kind: FecWorkerErrorCode = 'recovered-symbol-ssb-length-invalid'\n const seen = this.#workerErrorCounts.get(kind) ?? 0\n this.#workerErrorCounts.set(kind, seen + 1)\n if (seen < WORKER_ERROR_LOG_CAP) {\n console.warn(\n `[FECManager] Dropping recovered symbol with invalid SSB_length: pid=${packetId} sbn=${meta.sbn} ssbLength=${String(meta.ssbLength)}`,\n )\n }\n return\n }\n if (\n !Number.isInteger(meta.esi) ||\n meta.esi! < 0 ||\n meta.esi! >= meta.ssbLength!\n ) {\n const kind = 'recovered-symbol-missing-esi'\n const seen = this.#workerErrorCounts.get(kind) ?? 0\n this.#workerErrorCounts.set(kind, seen + 1)\n if (seen < WORKER_ERROR_LOG_CAP) {\n console.warn(\n `[FECManager] Dropping recovered symbol without valid ESI: pid=${packetId} sbn=${meta.sbn} esi=${String(meta.esi)} ssbLength=${String(meta.ssbLength)}`,\n )\n }\n return\n }\n const esi = meta.esi!\n\n // Suppress duplicate routing when the worker fires recovered=true\n // for a block ALREADY CLEANLY COMPLETED by K source symbols.\n //\n // BLO-3323 L15: predicate was previously `#completedBlocks.has(sbn)`,\n // but `#completedBlocks` is populated by 5 paths (clean K-arrival,\n // failed timeout, every worker frame emit via markBlockCompleted below,\n // external markBlockCompleted, and cancelBlockTimeout). The first\n // worker frame for a block adds the SBN, so subsequent recovered=true\n // emits for the same block were always suppressed \u2192 blocksRecovered\n // stayed at 0 even when recovery actually fired. Switching to the\n // narrower #cleanlyCompletedBlocks set (populated only by trackBlock\n // when count >= K) preserves the original dedup intent (BLO-3035\n // layer 4 \u2014 don't double-route a block already received via source)\n // while letting genuinely-recovered blocks bump the counter.\n const recoveredCatalogTrackName = catalogTrackName\n ?? this.#decoderBlockCatalogTrackName(client, meta.sbn)\n if (!this.#claimDecoderBlock(client, meta.sbn, recoveredCatalogTrackName)) return\n const blockKey = this.#blockKey(meta.sbn, packetId, recoveredCatalogTrackName)\n const cleanlyCompleted = this.#cleanlyCompletedBlocks.has(blockKey)\n if (!cleanlyCompleted) {\n if (!this.#workerRecoveredBlocks.has(blockKey)) {\n this.#traceLifecycle(\n 'recovered',\n meta.sbn,\n packetId,\n recoveredCatalogTrackName,\n )\n this.#workerRecoveredBlocks.add(blockKey)\n this.#fecStats.blocksRecovered++\n this.#statsForPacket(packetId).blocksRecovered++\n if (this.#workerRecoveredBlocks.size > 1000) {\n dropOldest(this.#workerRecoveredBlocks, 500)\n }\n this.#noteBlockRepaired(blockKey, meta.sbn, packetId, recoveredCatalogTrackName)\n }\n\n // RaptorQ returns the full K-symbol block, not only erased symbols.\n // The direct path already parsed every source ESI in #seenSymbols;\n // routing those symbols again injects duplicate MFU fragments and can\n // leave a real FI=3 tail followed by duplicate FI=2 fragments.\n const symbolKey = `${blockKey}:${esi}`\n if (!this.#seenSymbols.has(symbolKey)) {\n this.#seenSymbols.add(symbolKey)\n this.onBlockRecovered?.(\n meta.sbn,\n packetId,\n data,\n meta,\n recoveredCatalogTrackName,\n )\n }\n }\n\n // The worker emits recovered source symbols in ESI order. Complete the\n // block only after its final authored slot so assemblers receive every\n // missing symbol before their completion callback flushes buffered media.\n if (\n esi >= meta.ssbLength! - 1 &&\n !this.#completedBlocks.has(blockKey)\n ) {\n this.cancelBlockTimeout(meta.sbn, packetId, recoveredCatalogTrackName)\n if (recoveredCatalogTrackName === undefined) {\n this.onBlockComplete?.(meta.sbn, packetId)\n } else {\n this.onBlockComplete?.(\n meta.sbn,\n packetId,\n recoveredCatalogTrackName,\n )\n }\n }\n } else {\n const completedCatalogTrackName = catalogTrackName\n ?? this.#decoderBlockCatalogTrackName(client, meta.sbn)\n this.markBlockCompleted(meta.sbn, packetId, completedCatalogTrackName)\n }\n })\n\n client.onSnapshot((stats: FecTrackStats) => {\n if (this.#releasedWorkerClients.has(client)) return\n this.#fecWorkerStats.set(packetId, stats)\n try {\n this.onFecStatsUpdate?.(packetId, stats)\n } catch (error) {\n console.warn('[FECManager] FEC stats callback failed:', error)\n }\n for (const observer of this.#fecStatsObservers) {\n try {\n observer(packetId, stats)\n } catch (error) {\n console.warn('[FECManager] FEC stats observer failed:', error)\n }\n }\n })\n\n client.onCleanupComplete((sbn: number) => {\n if (this.#releasedWorkerClients.has(client)) return\n const cleanupKeys = [...this.#pendingWorkerCleanups].filter((key) => {\n const block = this.#parseBlockKey(key)\n return block.packetId === packetId &&\n block.sbn === sbn &&\n this.#pendingWorkerCleanupClients.get(key) === client\n })\n for (const key of cleanupKeys) this.#settleOwedCleanup(key, 'cleanup-ack')\n })\n\n client.onError((msg: string, code?: FecWorkerErrorCode) => {\n const requestedKind = code ?? msg\n const kind =\n this.#workerErrorCounts.has(requestedKind) ||\n this.#workerErrorCounts.size < WORKER_ERROR_KIND_CAP - 1\n ? requestedKind\n : WORKER_ERROR_OVERFLOW_KIND\n const seen = this.#workerErrorCounts.get(kind) ?? 0\n this.#workerErrorCounts.set(kind, seen + 1)\n if (seen < WORKER_ERROR_LOG_CAP) {\n console.warn('[FECManager] Worker error:', msg)\n } else if (seen === WORKER_ERROR_LOG_CAP) {\n console.warn(\n `[FECManager] Worker error (suppressing further copies of ${kind}): ${msg}`,\n )\n }\n })\n }\n\n /**\n * Remove a stream's Worker decoder from routing.\n *\n * Returns false when no Worker is registered under that exact stream key.\n * On success every cleanup acknowledgement still owed by the client is\n * resolved by failing its block now (a Worker that is going away cannot\n * post it), the client's block ownership is released, and\n * onDecoderUnavailable fires once with reason 'cleared' and the decoder\n * that remains for the stream ('sync' or 'none'). Call this before\n * disposing the client; a client disposed while still registered is\n * evicted on its next symbol or reset with reason 'disposed'.\n */\n clearFecWorker(packetId: number, catalogTrackName?: string): boolean {\n const streamKey = this.#streamKey(packetId, catalogTrackName)\n const client = this.#workerClients.get(streamKey)\n if (!client) return false\n this.#retireWorker(streamKey, client, 'cleared')\n return true\n }\n\n /**\n * Take a Worker client out of routing under one stream key, fail the blocks\n * whose completion it still owed, and report the stream's decoder\n * unavailable together with whatever still decodes the stream.\n */\n #retireWorker(streamKey: string, client: FecWorkerClient, reason: FecDecoderUnavailableReason): void {\n this.#workerClients.delete(streamKey)\n this.#releaseWorkerClient(client)\n this.#decoderUnavailableEvents++\n const stream = this.#parseStreamKey(streamKey)\n const fallbackDecoder: FecFallbackDecoder =\n this.#decoderForPacket(stream.packetId, stream.catalogTrackName) ? 'sync' : 'none'\n this.onDecoderUnavailable?.({\n packetId: stream.packetId,\n ...(stream.catalogTrackName === undefined ? {} : { catalogTrackName: stream.catalogTrackName }),\n reason,\n fallbackDecoder,\n })\n }\n\n /**\n * Fail every block whose Worker cleanup acknowledgement is owed by `client`\n * and drop its block ownership, so no block waits on a message the Worker\n * will never post.\n */\n #releaseWorkerClient(client: FecWorkerClient): void {\n const owed = [...this.#pendingWorkerCleanupClients]\n .filter(([, owner]) => owner === client)\n .map(([key]) => key)\n for (const key of owed) this.#settleOwedCleanup(key, 'decoder-unavailable')\n this.#decoderBlockOwners.delete(client)\n }\n\n /**\n * Bound the wait for one Worker cleanup acknowledgement by the block's\n * catalog-derived terminal lifetime. When it elapses with the cleanup still\n * owed to the same client, the block fails as if the ack had arrived: its\n * fail deadline already passed, so failing it is the decision that deadline\n * made. A late ack then finds nothing owed and is a no-op.\n */\n #boundOwedCleanup(key: string, packetId: number, client: FecWorkerClient): void {\n this.#clearOwedCleanupBound(key)\n const bound = {\n handle: setTimeout(() => {\n if (this.#pendingWorkerCleanupBounds.get(key) !== bound) return\n this.#pendingWorkerCleanupBounds.delete(key)\n if (\n !this.#pendingWorkerCleanups.has(key) ||\n this.#pendingWorkerCleanupClients.get(key) !== client\n ) {\n return\n }\n this.#settleOwedCleanup(key, 'cleanup-ack-timeout')\n }, Math.ceil(this.#liveBlockLifetimeMs(this.#resolveTrackConfig(packetId)))),\n }\n this.#pendingWorkerCleanupBounds.set(key, bound)\n }\n\n #clearOwedCleanupBound(key: string): void {\n const bound = this.#pendingWorkerCleanupBounds.get(key)\n if (!bound) return\n clearTimeout(bound.handle)\n this.#pendingWorkerCleanupBounds.delete(key)\n }\n\n /**\n * Resolve one owed Worker cleanup: drop the pending entry and fail the block\n * unless it already completed. Shared by the cleanup acknowledgement and by\n * Worker retirement, so the two paths cannot drift in their bookkeeping.\n */\n #settleOwedCleanup(\n key: string,\n event: 'cleanup-ack' | 'cleanup-ack-timeout' | 'decoder-unavailable',\n ): void {\n this.#clearOwedCleanupBound(key)\n this.#pendingWorkerCleanups.delete(key)\n this.#pendingWorkerCleanupClients.delete(key)\n if (this.#completedBlocks.has(key)) return\n const block = this.#parseBlockKey(key)\n this.#traceLifecycle(event, block.sbn, block.packetId, block.catalogTrackName)\n this.#failBlock(block.sbn, block.packetId, block.catalogTrackName)\n }\n\n /** Evict a disposed client under every stream key that still maps to it. */\n #evictDisposedWorker(client: FecWorkerClient): void {\n for (const [key, registered] of [...this.#workerClients]) {\n if (registered !== client) continue\n const stream = this.#parseStreamKey(key)\n const track = stream.catalogTrackName === undefined ? '' : ` track=${stream.catalogTrackName}`\n console.warn(\n `[FECManager] packetId=${stream.packetId}${track} Worker decoder was disposed while still registered; evicting it`,\n )\n this.#retireWorker(key, client, 'disposed')\n }\n }\n\n /** Called by player when framerate is known (from decoder output). Refines FEC block timeout. */\n setFramerate(fps: number): void {\n if (this.#fpsDetected || fps <= 0) return\n this.#fpsDetected = true\n this.#fecFps = fps\n // Only a valid \u00A75.1 ms interleave refines the timeout; a sub-floor value is\n // a raw frame count and would collapse it (see MIN_INTERLEAVE_DEPTH_MS).\n if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {\n this.#fecTimeoutMs = computeFecDeliveryWindowMs({\n interleaveDepthMs: this.#fecInterleaveDepthMs,\n sourceSymbols: this.#fecK,\n symbolSize: this.#fecSymbolSize,\n mediaUnitsPerSecond: fps,\n })\n }\n for (const [packetId, config] of this.#trackConfigs) {\n if (config.fps !== undefined || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue\n this.#trackConfigs.set(packetId, {\n ...config,\n fps,\n timeoutMs: Math.max(\n this.#fecTimeoutMs,\n computeFecDeliveryWindowMs({\n interleaveDepthMs: config.interleaveDepthMs,\n sourceSymbols: config.k,\n symbolSize: config.symbolSize,\n mediaUnitsPerSecond: fps,\n }),\n ),\n })\n }\n }\n\n // -----------------------------------------------------------------------\n // Symbol feeding (codec-agnostic)\n // -----------------------------------------------------------------------\n\n /**\n * Feed a source symbol to the WASM FEC decoder.\n *\n * Does NOT track block source counts or fire lifecycle events \u2014 call\n * `trackBlock()` separately BEFORE this method (MmtpRouter does this).\n *\n * @returns Recovered block data if WASM completed the block, else null.\n * Caller is responsible for routing recovered data and updating\n * block completion state via `markBlockCompleted()`.\n */\n feedSourceSymbol(\n packetId: number,\n ssId: number,\n symbolData: Uint8Array,\n alta?: { auth: Uint8Array; payloadEnd: number },\n catalogTrackName?: string,\n ): Uint8Array | null {\n this.#fecStats.sourcePackets++\n this.#statsForPacket(packetId).sourcePackets++\n let sbn: number\n let fecData: Uint8Array\n if (this.#isIso(packetId)) {\n // iso-ssbg1-v1: the decoder builds the ssbg_mode1 symbol from the exact\n // protected packet, so the packet travels unpadded. Its block must\n // already be stated (MmtpRouter holds a source until a repair places it).\n const locator = this.#isoLocatorFor(packetId, catalogTrackName)\n const located = locator.locate(ssId)\n if (located === null) {\n throw new Error(\n `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; ` +\n 'hold it until a repair places it (placeIsoRepair)',\n )\n }\n locator.observe(ssId)\n if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {\n this.#isoSourceOversizeCount++\n return null\n }\n sbn = located.block.start\n fecData = symbolData\n } else {\n fecData = this.#normalizeSymbolData(packetId, symbolData)\n sbn = Math.floor(ssId / this.kForPacket(packetId))\n }\n const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)\n const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey)\n\n // Worker-backed track: dispatch async, return null (recovery via onFrame callback)\n const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)\n if (workerClient && !syncBlockOwner) {\n if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null\n // Transfer an ArrayBuffer nothing else references. A symbol this manager\n // just zero-padded to T already is one; copying it again cost a second\n // T-byte allocation per short packet (BLO-36599). A caller's buffer may\n // be a view into a larger packet, so it is copied first (T-03-06).\n const buf = fecData !== symbolData\n ? fecData.buffer as ArrayBuffer\n : fecData.buffer.slice(\n fecData.byteOffset,\n fecData.byteOffset + fecData.byteLength,\n ) as ArrayBuffer\n // Pass the flat SS_ID through to the worker. The WASM decoder derives\n // SBN/ESI internally per ISO 23008-1 \u00A7C.5.2; the WASM add_source\n // signature is (ss_id, data, timestamp) \u2014 pre-computing sbn/esi on this\n // side produces wrong WASM argument alignment and silently breaks\n // block assembly.\n if (alta) {\n // Copy auth into a fresh ArrayBuffer to transfer zero-copy. The\n // underlying buffer of the parsed trailer view belongs to the wire\n // packet, which may be transferred separately or reused.\n const authBuf = alta.auth.buffer.slice(\n alta.auth.byteOffset,\n alta.auth.byteOffset + alta.auth.byteLength,\n ) as ArrayBuffer\n workerClient.feedSource(ssId, buf, performance.now(), {\n auth: authBuf,\n payloadEnd: alta.payloadEnd,\n })\n } else {\n workerClient.feedSource(ssId, buf, performance.now())\n }\n return null\n }\n\n // Synchronous WASM path.\n const entry = this.#decoderForPacket(packetId, catalogTrackName)\n const client = syncBlockOwner ?? entry?.client\n if (!client) return null\n if (!this.#claimDecoderBlock(client, sbn, catalogTrackName)) return null\n this.#syncDecoderBlockOwners.set(blockKey, client)\n\n let recovered: Uint8Array | null = null\n try {\n recovered = client.add_source(ssId, fecData, performance.now())\n } catch {\n // WASM RefCell panic \u2014 decoder instance shared with multicast repair path\n }\n this.#retireIsoUnplaced(client, packetId, catalogTrackName)\n if (recovered || this.#completedBlocks.has(blockKey)) {\n this.#syncDecoderBlockOwners.delete(blockKey)\n }\n\n return recovered\n }\n\n /**\n * Feed a repair symbol to the WASM FEC decoder.\n *\n * @returns Recovered block data if repair completed the block, else null.\n * Caller is responsible for routing recovered data and updating\n * block completion state via `markBlockCompleted()`.\n */\n feedRepairSymbol(\n packetId: number,\n ssStart: number,\n ssbLength: number,\n rsId: number,\n repairData: Uint8Array,\n catalogTrackName?: string,\n ): Uint8Array | null {\n this.#fecStats.repairPackets++\n this.#statsForPacket(packetId).repairPackets++\n const symbolSize = this.symbolSizeForPacket(packetId)\n if (symbolSize > 0 && repairData.byteLength !== symbolSize) {\n this.#repairSymbolSizeMismatchCount++\n if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {\n console.warn(\n `[FECManager] Dropping repair symbol with invalid size: got ${repairData.byteLength}B, expected ${symbolSize}B (packetId=${packetId}, ssStart=${ssStart}, ssbLength=${ssbLength}, rsId=${rsId})`,\n )\n }\n return null\n }\n const fecData = repairData\n\n // SSB_length is the authored source-symbol count in this block, not the\n // decoder's configured block width. Short authored blocks are padded to\n // the configured K, so all internal keys and lifecycle coordinates use K.\n // Keep the wire tuple unchanged when calling the decoder below.\n const k = this.kForPacket(packetId)\n let sbn: number\n if (this.#isIso(packetId)) {\n // iso-ssbg1-v1 (C.5.3): the block is [SS_Start, SS_Start + SSB_length),\n // K' = SSB_length, and the catalog K is only its maximum. MmtpRouter\n // placed the block before feeding its repair (placeIsoRepair).\n const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart)\n if (ssbLength <= 0 || ssbLength > k || block === undefined || block.span || block.length !== ssbLength) {\n return null\n }\n sbn = ssStart\n } else {\n if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null\n sbn = Math.floor(ssStart / k)\n }\n this.#noteSbnSeen(sbn, packetId, catalogTrackName, 'repair')\n\n // Repair symbols are block activity: refresh the inactivity fail bound so\n // a block being actively repaired over MoQ is not failed mid-recovery.\n // The multicast in-band path already does this at the router; this covers\n // the MoQ backfill path, which bypasses the router's packet accounting.\n this.noteBlockActivity(sbn, packetId, catalogTrackName)\n const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)\n if (this.#completedBlocks.has(blockKey)) return null\n if (!this.#noteAuthoredLength(blockKey, sbn, packetId, ssbLength, catalogTrackName)) {\n return null\n }\n let repairIds = this.#blockRepairRsIds.get(blockKey)\n if (!repairIds) {\n repairIds = new Set()\n this.#blockRepairRsIds.set(blockKey, repairIds)\n }\n repairIds.add(rsId)\n if (this.#repairRequestedBlocks.has(blockKey)) {\n this.#traceLifecycle('repair-arrived', sbn, packetId, catalogTrackName, rsId)\n }\n\n // Every authored source symbol already arrived: the block is clean now\n // that its SSB_length is known. The decoder still receives the repair so\n // it retires its own copy of the block, but whatever it returns is the\n // source the direct path already routed, not a recovery.\n const cleanOnArrival =\n (this.#blockSourceCounts.get(blockKey) ?? 0) >= ssbLength\n const recovered = this.#dispatchRepairSymbol(\n blockKey,\n sbn,\n packetId,\n ssStart,\n ssbLength,\n rsId,\n fecData,\n catalogTrackName,\n )\n if (cleanOnArrival) {\n this.#completeCleanBlock(blockKey, sbn, packetId, catalogTrackName)\n return null\n }\n return recovered\n }\n\n /**\n * Record a block's authored SSB_length from a repair symbol. Returns false,\n * leaving the block unchanged, when the symbol contradicts the block: a\n * different SSB_length than an earlier repair symbol declared is dropped,\n * and an SSB_length that places an already-received source ESI inside the\n * zero padding fails the block, since its geometry can no longer be trusted.\n */\n #noteAuthoredLength(\n blockKey: string,\n sbn: number,\n packetId: number,\n ssbLength: number,\n catalogTrackName?: string,\n ): boolean {\n const known = this.#blockAuthoredLengths.get(blockKey)\n if (known === ssbLength) return true\n if (known !== undefined) {\n this.#ssbLengthConflictCount++\n if (this.#ssbLengthConflictCount <= 5 || this.#ssbLengthConflictCount % 100 === 0) {\n console.warn(\n `[FECManager] Dropping repair symbol whose SSB_length ${ssbLength} conflicts with ${known} for packetId=${packetId} sbn=${sbn}`,\n )\n }\n return false\n }\n const sourceEsis = this.#blockSourceEsis.get(blockKey)\n if (sourceEsis && [...sourceEsis].some((esi) => esi >= ssbLength)) {\n this.#failBlock(sbn, packetId, catalogTrackName)\n return false\n }\n this.#blockAuthoredLengths.set(blockKey, ssbLength)\n return true\n }\n\n #dispatchRepairSymbol(\n blockKey: string,\n sbn: number,\n packetId: number,\n ssStart: number,\n ssbLength: number,\n rsId: number,\n fecData: Uint8Array,\n catalogTrackName?: string,\n ): Uint8Array | null {\n const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey)\n // Worker-backed track: dispatch async, return null (recovery via onFrame callback)\n const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)\n if (workerClient && !syncBlockOwner) {\n if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null\n // Create clean ArrayBuffer copy (T-03-06)\n const buf = fecData.buffer.slice(\n fecData.byteOffset,\n fecData.byteOffset + fecData.byteLength,\n ) as ArrayBuffer\n // Forward the wire-level Repair FEC Payload ID (ISO 23008-1 \u00A7C.5.3)\n // directly. WASM add_repair signature is\n // (ss_start, ssb_length, rs_id, data, timestamp) \u2014 pre-computing\n // (sbn, esi) on this side misaligns WASM args and produces 0% recovery.\n workerClient.feedRepair(ssStart, ssbLength, rsId, buf, performance.now())\n return null\n }\n\n // Synchronous WASM path.\n const entry = this.#decoderForPacket(packetId, catalogTrackName)\n const client = syncBlockOwner ?? entry?.client\n if (!client) return null\n if (!this.#claimDecoderBlock(client, sbn, catalogTrackName)) return null\n this.#syncDecoderBlockOwners.set(blockKey, client)\n\n let recovered: Uint8Array | null = null\n try {\n recovered = client.add_repair(ssStart, ssbLength, rsId, fecData, performance.now())\n } catch {\n // WASM add_repair failure \u2014 block will timeout and be marked failed\n }\n this.#retireIsoUnplaced(client, packetId, catalogTrackName)\n if (recovered) this.#syncDecoderBlockOwners.delete(blockKey)\n\n return recovered\n }\n\n #routeSyncCleanup(\n decoder: WasmFecDecoder,\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n // Retire the block from the decoder. Concealment of a lost block is the\n // sink's job (hold last good frame), so nothing is routed from here.\n try {\n decoder.cleanup_blocks_detailed(Uint32Array.of(sbn))\n } catch (error) {\n console.warn(`[FECManager] Scoped cleanup failed for SBN ${sbn}:`, error)\n }\n this.#retireIsoUnplaced(decoder, packetId, catalogTrackName)\n }\n\n /**\n * iso-ssbg1-v1: a placed source the decoder took before its block's repair\n * (fed in wire order, sources first) waits unplaced in the decoder; one\n * whose repair never reaches it, such as a source of a repair-less span,\n * would stay there. Retire those by the decoder's horizon behind the newest\n * SS_ID the stream's locator has seen. Run after every feed that grows the\n * decoder and when a block is retired, as the fec-worker sweeps its own\n * pool: a stream that loses only repair symbols never expires a block, so\n * a sweep tied to expiry alone would never run there.\n */\n #retireIsoUnplaced(\n decoder: WasmFecDecoder,\n packetId: number,\n catalogTrackName: string | undefined,\n ): void {\n if (!this.#isIso(packetId)) return\n const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest\n if (newest === undefined) return\n try {\n this.#isoDecoderUnplacedRetired += decoder.retire_unplaced(newest)\n } catch (error) {\n this.#isoDecoderUnplacedRetireFailed++\n const failed = this.#isoDecoderUnplacedRetireFailed\n if (failed <= 5 || failed % 100 === 0) {\n console.warn(\n `[FECManager] iso-ssbg1-v1 unplaced retirement failed for packetId=${packetId} (${failed} total):`,\n error,\n )\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Block lifecycle\n // -----------------------------------------------------------------------\n\n /**\n * Track a first-arrival network source symbol for an FEC block. Refreshes\n * the inactivity deadline on each symbol, records the symbol's in-band\n * classification evidence, and marks the block clean when all of its\n * authored source symbols arrived: the SSB_length a repair symbol declared,\n * otherwise K.\n *\n * Throws for a packetId that was never configured, so a classified block\n * never reads the manager-global K or timeout.\n */\n trackBlock(\n sbn: number,\n packetId: number,\n catalogTrackName: string | undefined,\n esi: number,\n evidence: SourceSymbolEvidence,\n ): void {\n const trackConfig = this.#trackConfigs.get(packetId)\n if (trackConfig === undefined) {\n throw new Error(\n `[FECManager] trackBlock for packetId=${packetId}, which was never configured; ` +\n 'configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first',\n )\n }\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n const k = trackConfig.k\n\n // Skip blocks already recovered \u2014 late source symbols must not restart timeouts\n if (this.#completedBlocks.has(key)) return\n\n this.#noteSbnSeen(sbn, packetId, catalogTrackName, 'source', evidence.packetSequenceNumber)\n\n // A source ESI inside the zero padding contradicts the block's authored\n // SSB_length; its geometry can no longer be trusted, so fail it closed.\n const authoredLength = this.#blockAuthoredLengths.get(key) ?? k\n if (Number.isInteger(esi) && esi! >= authoredLength) {\n this.#failBlock(sbn, packetId, catalogTrackName)\n return\n }\n\n this.#applyClassifierChanges(\n packetId,\n catalogTrackName,\n this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence),\n )\n\n // Count source symbols per block\n const count = (this.#blockSourceCounts.get(key) ?? 0) + 1\n this.#blockSourceCounts.set(key, count)\n if (count === 1) this.#indexBlockKey(key, sbn, packetId)\n if (Number.isInteger(esi) && esi! >= 0 && esi! < k) {\n let sourceEsis = this.#blockSourceEsis.get(key)\n if (!sourceEsis) {\n sourceEsis = new Set()\n this.#blockSourceEsis.set(key, sourceEsis)\n }\n sourceEsis.add(esi!)\n }\n if (count === 1) {\n if (catalogTrackName === undefined) {\n this.onBlockStarted?.(sbn, packetId)\n } else {\n this.onBlockStarted?.(sbn, packetId, catalogTrackName)\n }\n }\n\n // When every authored source symbol arrived (SSB_length once a repair\n // symbol declared it, otherwise K), the block is clean: no loss, no FEC.\n if (k > 0 && count >= authoredLength) {\n this.#completeCleanBlock(key, sbn, packetId, catalogTrackName)\n return\n }\n\n this.noteBlockActivity(sbn, packetId, catalogTrackName, true)\n\n // Source ESI is intentionally not a repair trigger. Multicast delivery is\n // unordered, so K-1 may precede an interior symbol in a clean block. The\n // progress-refreshed source horizon above is the earliest loss signal.\n }\n\n #completeCleanBlock(\n key: string,\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n this.#fecStats.blocksClean++\n this.#statsForPacket(packetId).blocksClean++\n this.cancelBlockTimeout(sbn, packetId, catalogTrackName)\n this.#blockSourceCounts.delete(key)\n this.#blockSourceEsis.delete(key)\n this.#blockAuthoredLengths.delete(key)\n this.#blockRepairRsIds.delete(key)\n // BLO-3323 L15: track which SBNs reached K source-symbol arrivals so the\n // worker recovery suppression can distinguish \"block completed by source\"\n // (legitimately suppress duplicate recovery) from \"block touched by any\n // prior emit / timeout / external mark\" (do NOT suppress recovery).\n // #markTerminal retires both memberships together (BLO-36599).\n this.#markTerminal(key, sbn, packetId, true)\n this.#markPathBlockComplete(sbn, packetId, catalogTrackName)\n if (catalogTrackName === undefined) {\n this.onBlockComplete?.(sbn, packetId)\n } else {\n this.onBlockComplete?.(sbn, packetId, catalogTrackName)\n }\n // MmtpRouter feeds the K-th source immediately after this callback.\n // Defer cleanup so that synchronous feed selects the same decoder; this\n // also clears blocks whose K-th packet was too large for decoder input.\n queueMicrotask(() => this.#syncDecoderBlockOwners.delete(key))\n }\n\n /** Refresh deadlines after accepted block activity. */\n noteBlockActivity(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n sourceProgress = false,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n\n // Late symbols must not resurrect a block that already completed or failed.\n if (this.#completedBlocks.has(key)) return\n // The worker cleanup command is already queued after all symbols that made\n // the inactivity deadline. Symbols arriving after that boundary are late\n // and must not arm a second timer before the acknowledgement resolves it.\n if (this.#pendingWorkerCleanups.has(key)) return\n\n const repairRequested = this.#repairRequestedBlocks.has(key)\n // An ordered relay already decided a requested block by stream order; its\n // fate is the scheduler's hold (or its absence), never a re-hold.\n if (repairRequested && this.#settlesByOrder(key)) return\n\n // An ordered relay's pauses say nothing about loss; stream order settles\n // the block (see MulticastSymbolDelivery). No timer is armed for it.\n if (this.#settlesByOrder(key)) {\n this.#holdForOrderedSettlement(key, sbn, packetId, catalogTrackName)\n return\n }\n\n const trackConfig = this.#resolveTrackConfig(packetId)\n const timeoutMs = this.#multicastWaitMs(trackConfig)\n // #computeTimeout never yields <= 0 (computeFecDeliveryWindowMs throws on\n // a non-positive interleave or K). Returning here would leave a block\n // with source counted but no fail deadline, 'pending' forever, so a\n // violated invariant must fail loudly instead.\n if (!(timeoutMs > 0)) {\n throw new Error(`[FECManager] packetId=${packetId} has no positive fail bound (timeoutMs=${timeoutMs})`)\n }\n\n // D-28: accepted source progress refreshes the pre-request horizon. There\n // are at most K such arrivals; repair traffic never refreshes this timer.\n // A requested block's accepted symbols keep extending its multicast wait\n // (a late symbol can still complete it) but never re-arm the request: at\n // most K+P accepted symbols can move the wait, so the block stays bounded.\n const repairDeadline = this.#repairDeadlines.get(key)\n if (\n this.onRepairNeeded &&\n !repairRequested &&\n (repairDeadline === undefined || sourceProgress)\n ) {\n this.#armRepairDeadline(\n key,\n sbn,\n packetId,\n catalogTrackName,\n this.#repairDeadlineMs(trackConfig),\n )\n }\n\n this.#armBlockTimeout(sbn, packetId, catalogTrackName, timeoutMs)\n }\n\n #failBlock(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): void {\n const key = this.#blockKey(sbn, packetId, catalogTrackName)\n this.#traceLifecycle('failed', sbn, packetId, catalogTrackName)\n this.#clearRepairDeadline(key)\n this.#clearBlockTimer(key)\n const blockClass = this.#retireBlockClass(key, sbn, packetId, catalogTrackName)\n if (this.#repairRequestedBlocks.delete(key)) {\n this.onRepairResolved?.(sbn, packetId, 'failed', catalogTrackName, null)\n }\n this.#blockSourceCounts.delete(key)\n this.#blockSourceEsis.delete(key)\n this.#blockAuthoredLengths.delete(key)\n this.#blockRepairRsIds.delete(key)\n this.#syncDecoderBlockOwners.delete(key)\n this.#markTerminal(key, sbn, packetId, false)\n this.#failedBlocks.add(key)\n this.#markPathBlockComplete(sbn, packetId, catalogTrackName)\n this.#fecStats.blocksFailed++\n this.#statsForPacket(packetId).blocksFailed++\n this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass)\n this.onBlockResolved?.(sbn, packetId, catalogTrackName)\n }\n\n /**\n * Resolve a block as completed (clean, recovered, or externally marked).\n * A block that had a D-28 repair outstanding counts as repaired: it was\n * short of its source at the repair deadline and completed anyway.\n */\n #resolveCompleted(key: string): void {\n const block = this.#parseBlockKey(key)\n this.#markTerminal(key, block.sbn, block.packetId, false)\n this.#clearOwedCleanupBound(key)\n this.#pendingWorkerCleanups.delete(key)\n this.#pendingWorkerCleanupClients.delete(key)\n this.#markPathBlockComplete(block.sbn, block.packetId, block.catalogTrackName)\n this.#clearRepairDeadline(key)\n if (this.#repairRequestedBlocks.delete(key)) {\n this.#noteBlockRepaired(key, block.sbn, block.packetId, block.catalogTrackName)\n this.onRepairResolved?.(\n block.sbn,\n block.packetId,\n 'completed',\n block.catalogTrackName,\n this.#viaFor(key),\n )\n }\n this.#retireBlockClass(key, block.sbn, block.packetId, block.catalogTrackName)\n this.#blockSourceCounts.delete(key)\n this.#blockSourceEsis.delete(key)\n this.#blockAuthoredLengths.delete(key)\n this.#blockRepairRsIds.delete(key)\n this.#clearBlockTimer(key)\n this.onBlockResolved?.(block.sbn, block.packetId, block.catalogTrackName)\n }\n\n /** Check whether a block has already been completed (clean or recovered). */\n isBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): boolean {\n return this.#blockKeysFor(sbn, packetId, catalogTrackName).some((key) => this.#completedBlocks.has(key))\n }\n\n /** Mark a block as completed externally (e.g., after routing recovered data). */\n markBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): void {\n for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {\n this.#resolveCompleted(key)\n }\n }\n\n /**\n * Increment blocksRecovered (called by MmtpRouter after routing a\n * synchronous recovery). With the block's coordinates the recovery is also\n * reported through onBlockRepaired, once per block.\n */\n countRecovery(packetId?: number, sbn?: number, catalogTrackName?: string): void {\n this.#fecStats.blocksRecovered++\n if (packetId != null) this.#statsForPacket(packetId).blocksRecovered++\n if (packetId != null && sbn != null) {\n this.#noteBlockRepaired(\n this.#blockKey(sbn, packetId, catalogTrackName),\n sbn,\n packetId,\n catalogTrackName,\n )\n }\n }\n\n /**\n * Strict delivery provenance for one FEC block.\n *\n * A recovered symbol is a function of every symbol that entered its block,\n * so it can only be attributed to multicast when NO symbol in that block\n * arrived over MoQ. Repair symbols routinely arrive over reliable QUIC\n * (`MmtpRouter.feedRepairPacket`), so a block that recovered with MoQ help\n * produced bytes that never crossed a multicast socket.\n *\n * An untracked block returns 'unknown' rather than a guess: absence of\n * evidence is not evidence of multicast.\n */\n blockDeliveryProvenance(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): MmtpDeliveryProvenance {\n const counts = this.blockPathCount(sbn, packetId, catalogTrackName)\n if (!counts) return 'unknown'\n if (counts.moqSymbols > 0) return 'mixed'\n if (counts.multicastSymbols > 0) return 'multicast'\n return 'unknown'\n }\n\n /** Per-path symbol counts for one block (D-28 gate logging), if tracked. */\n blockPathCount(\n sbn: number,\n packetId: number,\n catalogTrackName?: string,\n ): FecBlockPathCount | null {\n return this.#blockPathCounts.get(\n this.#blockKey(sbn, packetId, catalogTrackName),\n ) ?? null\n }\n\n /** Cancel block timeout (block completed via clean reception or FEC recovery). */\n cancelBlockTimeout(sbn: number, packetId?: number, catalogTrackName?: string): void {\n for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {\n this.#resolveCompleted(key)\n }\n }\n\n // -----------------------------------------------------------------------\n // Cleanup\n // -----------------------------------------------------------------------\n\n dispose(): void {\n for (const timer of this.#activeBlocks.values()) {\n if (timer.handle != null) clearTimeout(timer.handle)\n }\n this.#activeBlocks.clear()\n this.#activeBlockDeadlinesMs.clear()\n this.#blockWaitDeadlinesMs.clear()\n this.#repairHoldUntilMs.clear()\n this.#deliveryGaps.reset()\n this.#pendingWorkerCleanups.clear()\n this.#pendingWorkerCleanupClients.clear()\n for (const bound of this.#pendingWorkerCleanupBounds.values()) clearTimeout(bound.handle)\n this.#pendingWorkerCleanupBounds.clear()\n for (const timer of this.#repairDeadlines.values()) {\n if (timer?.handle != null) clearTimeout(timer.handle)\n }\n this.#repairDeadlines.clear()\n this.#repairDeadlineAtMs.clear()\n this.#terminalExpiryMs.clear()\n this.#terminalQueues.clear()\n this.#blockKeysBySbn.clear()\n this.#blockSourceCounts.clear()\n this.#blockSourceEsis.clear()\n this.#blockAuthoredLengths.clear()\n this.#blockRepairRsIds.clear()\n this.#completedBlocks.clear()\n this.#cleanlyCompletedBlocks.clear()\n this.#repairRequestedBlocks.clear()\n this.#workerRecoveredBlocks.clear()\n this.#syncDecoderBlockOwners.clear()\n this.#startupAlignedPackets.clear()\n this.#startupPartialBlocks.clear()\n this.#startupRequireRepairReady = false\n this.#startupRepairReadyPackets.clear()\n this.#startupRepairBoundaryCounts.clear()\n this.#startupRepairFailOpenPackets.clear()\n this.#orderedFrontiers.clear()\n this.#orderedPendingBlocks.clear()\n this.#seenSymbols.clear()\n this.#blockPathCounts.clear()\n this.#blockAnchorsMs.clear()\n this.#blockCatalogTrackNames.clear()\n this.#classifiers.clear()\n this.#streamGeometries.clear()\n this.#failedBlocks.clear()\n this.#resolvedBlockClasses.clear()\n this.#repairedBlocks.clear()\n this.#materializedBlocks.clear()\n this.#highestSbn.clear()\n this.#isoLocators.clear()\n this.#sourceAnchors.clear()\n this.#trackConfigs.clear()\n this.#fecDecoders.clear()\n // Dispose Worker clients (T-03-07: prevent Worker leak)\n for (const client of new Set(this.#workerClients.values())) client.dispose()\n this.#workerClients.clear()\n }\n}\n", "/**\n * Zero-dependency helper for extracting the detached ALTA authenticator trailer\n * from an MMTP source packet.\n *\n * Lives in its own leaf package so both `@blockcast/mmt-container` (wire-path\n * parsing) and `@blockcast/fec-worker` (FEC-recovered-source parsing) can share\n * one implementation without creating a package cycle between them.\n *\n * Spec: ISO/IEC 23008-1:2023 + draft-ramadan-moq-mmt \u00A712 (detached trailer, Path 2).\n */\n\n// --- Private MMTP/MPU/MFU constants (ISO/IEC 23008-1 \u00A79.2.2\u20139.2.3) ---\n// Duplicated here as immutable spec values so this package stays a leaf with\n// zero `@blockcast/*` dependencies. Re-deriving from `mmt-container` would\n// reintroduce the package cycle this split exists to avoid.\n\nconst MMTP_HEADER_SIZE = 12\nconst MPU_HEADER_SIZE = 8\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst TIMED_MFU_DU_HEADER_SIZE = 14\nconst NONTIMED_MFU_DU_HEADER_SIZE = 4\nconst PACKET_TYPE_MPU = 0x00\n\n// --- Public ALTA trailer constants ---\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\nexport const MMTP_EXT_TYPE_ALTA_AUTH = 0xf0a1\n\n/** Fixed size of the trailer's `[ext_type][ext_length]` prefix (2 + 2 BE). */\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = 4\n\n/** Result of extracting the detached ALTA authenticator from an MMTP packet. */\nexport interface AltaTrailer {\n /** Authenticator bytes (no ext_type/ext_length prefix, no trailing padding). */\n auth: Uint8Array\n /**\n * Wire-packet byte offset where the signed payload ends, i.e. where the\n * trailer begins. Caller uses this to produce\n * `payload = wirePacket.subarray(0, signedEnd)` for verify_detached.\n */\n signedEnd: number\n}\n\n/**\n * Extract the detached ALTA authenticator trailer from an MMTP source packet.\n *\n * Wire layout:\n * `[MMTP hdr][length(2)][remaining MPU hdr(6)][DU?][media][TRAILER][SS_ID?]`\n * `payload_length = 6 + DU bytes + media bytes`\n *\n * Returns `null` when:\n * - the packet is too small / malformed,\n * - `payload_length` cannot contain the remaining six MPU-header bytes,\n * - the declared payload overruns the packet,\n * - no bytes remain between `payload_length` and the SS_ID,\n * - the trailer's `ext_type` isn't {@link MMTP_EXT_TYPE_ALTA_AUTH},\n * - the declared `ext_length` would extend past the SS_ID boundary.\n *\n * Non-MPU packets (repair, signaling) always return `null`.\n *\n * @param options.skipFecId When true, do NOT reserve 4 trailing bytes for the\n * Source FEC Payload ID. Use on FEC-recovered symbols where the PID has\n * already been stripped upstream.\n */\nexport function extractAltaTrailer(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): AltaTrailer | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n // ISO/IEC 23008-1:2023 Figure 8 places V=0 packet_type in the low six bits.\n const packetType = mmtpPacket[1]! & 0x3f\n if (packetType !== PACKET_TYPE_MPU) return null\n\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n let headerLength = MMTP_HEADER_SIZE\n if (fecType < 2 && (mmtpPacket[0]! & 0x20) !== 0) headerLength += 4\n if (fecType < 2 && (mmtpPacket[0]! & 0x02) !== 0) {\n if (headerLength + 4 > mmtpPacket.length) return null\n const extensionLength = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + headerLength + 2,\n 2,\n ).getUint16(0, false)\n headerLength += 4 + extensionLength\n }\n if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length) return null\n\n const payload = mmtpPacket.subarray(headerLength)\n\n // Inline a minimal MPU-header read \u2014 only the fields extractAltaTrailer needs.\n // payloadLength: 16 BE at bytes 0-1.\n // Byte 2 packs FT(4) | T(1) | FI(2) | A(1).\n const payloadLength = (payload[0]! << 8) | payload[1]!\n const flagsByte = payload[2]!\n const fragmentType = (flagsByte >> 4) & 0x0f\n const timed = ((flagsByte >> 3) & 0x01) === 1\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n\n // Only MFU packets (FT=2) carry the detached trailer.\n if (fragmentType !== 2) return null\n\n // FI=0/1 structurally carries a DU header; FI=2/3 does not. Require at\n // least one media byte after that deterministic layout so the bytes at the\n // declared boundary cannot impersonate ALTA for a header-only fragment.\n const dataUnitHeaderSize = fragmentationIndicator <= 1\n ? (timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE)\n : 0\n if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize) return null\n\n if (fecType > 1) return null\n const hasFecId = !options?.skipFecId && fecType === 1\n const payloadEnd = hasFecId ? payload.length - 4 : payload.length\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (declaredEnd > payloadEnd) return null\n\n if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd) return null\n const extType = (payload[declaredEnd]! << 8) | payload[declaredEnd + 1]!\n if (extType !== MMTP_EXT_TYPE_ALTA_AUTH) return null\n const extLen = (payload[declaredEnd + 2]! << 8) | payload[declaredEnd + 3]!\n\n const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE\n if (authStart + extLen !== payloadEnd) return null\n\n return {\n auth: payload.subarray(authStart, payloadEnd),\n signedEnd: headerLength + declaredEnd,\n }\n}\n", "import {\n MMTP_EXT_TYPE_ALTA_AUTH as _LEAF_MMTP_EXT_TYPE_ALTA_AUTH,\n MMTP_EXT_TRAILER_HEADER_SIZE as _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE,\n extractAltaTrailer as _leafExtractAltaTrailer,\n type AltaTrailer as _LeafAltaTrailer,\n} from '@blockcast/mmt-alta-parse'\n\n/**\n * @mmt/container - MMTP Parser\n *\n * MMTP (MMT Protocol) packet parser based on ISO/IEC 23008-1:2023 Section 9.2\n *\n * MMTP header format (12 bytes):\n * - Byte 0: V(2) | C(1) | FEC_type(2) | r(1) | X(1) | R(1)\n * - Byte 1: reserved(2) | packet_type(6)\n * - Bytes 2-3: packet_id (big-endian)\n * - Bytes 4-7: timestamp (big-endian, 32-bit)\n * - Bytes 8-11: sequence_number (big-endian)\n *\n * FEC_type values:\n * - 0: No FEC / source packet\n * - 1: FEC mode 0 (source with FEC payload ID)\n * - 2: FEC repair packet mode 0\n * - 3: FEC repair packet mode 1\n *\n * @packageDocumentation\n */\n\nexport interface MMTPHeader {\n version: number\n /** Packet type: 0x00=MPU, 0x02=Signaling, 0x03=Repair */\n packetType: number\n packetId: number\n timestamp: bigint\n sequenceNumber: number\n payloadLength: number\n fecType: number\n /** C flag: header carries a 32-bit packet_counter. */\n packetCounterFlag: boolean\n packetCounter?: number\n /** X flag: header carries an extension header and value. */\n extensionFlag: boolean\n extension?: { type: number; data: Uint8Array }\n /** Byte offset at which the MMTP payload begins. */\n headerLength: number\n /** Random Access Point flag - indicates keyframe (ISO 23008-1, byte 0 bit 0) */\n rapFlag: boolean\n}\n\nexport interface MPUHeader {\n mpuSequenceNumber: number\n fragmentationIndicator: number // FI: 0=complete, 1=first, 2=middle, 3=last\n /** 8-bit MPU fragment_counter from byte 3. */\n fragmentCounter: number\n /** T flag: 1=timed media (14-byte MFU DU header), 0=non-timed (4-byte header) */\n timedFlag: number\n /** A flag: aggregated MPU payload. This strict profile requires A=0. */\n aggregationFlag: boolean\n /** MPU payload_length (bytes 0-1): bytes after the length field, including\n * the remaining six MPU-header bytes and all data-unit bytes. */\n payloadLength: number\n}\n\nexport interface MFUDuHeader {\n movieFragmentSequenceNumber: number\n sampleNumber: number\n offset: number\n priority: number\n dependencyCounter: number\n}\n\nexport interface MMTPPacket {\n header: MMTPHeader\n payload: Uint8Array\n}\n\nexport const MMTP_HEADER_SIZE = 12\nexport const MPU_HEADER_SIZE = 8\nexport const MFU_DU_HEADER_SIZE_TIMED = 14 // T=1: timed media (video/audio samples)\nexport const MFU_DU_HEADER_SIZE_NONTIMED = 4 // T=0: non-timed media (item_id only)\n\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst SOURCE_FEC_PAYLOAD_ID_SIZE = 4\n\n// MMTP Packet Types (ISO/IEC 23008-1:2023 Section 9.2.2)\nexport const PACKET_TYPE_MPU = 0x00\nexport const PACKET_TYPE_SIGNALING = 0x02\nexport const PACKET_TYPE_REPAIR = 0x03\n\n// AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)\nexport const ALFEC_MESSAGE_ID = 0x0203\n\n/**\n * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING\n * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`\n * muxer sends that message on this packet_id into the same multicast group as\n * the media, and moqcast-draft (PR #44) makes advertising it for a track a\n * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.\n */\nexport const MMTP_SIGNALING_PACKET_ID = 0\n\n/** Largest value of the 16-bit MMTP `packet_id` header field. */\nexport const MMTP_PACKET_ID_MAX = 0xffff\n\n// FEC Code IDs\nexport const FEC_CODE_XOR = 1\nexport const FEC_CODE_RAPTORQ = 2\n\n/**\n * Parse MMTP header from buffer\n */\nexport function parseMMTPHeader(buffer: Uint8Array): MMTPHeader {\n if (buffer.length < MMTP_HEADER_SIZE) {\n throw new Error(`MMTP packet too small: ${buffer.length} bytes`)\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n\n const version = (buffer[0]! >> 6) & 0x03\n if (version !== 0) {\n throw new Error(`Unsupported MMTP version: ${version}`)\n }\n\n // Extract FEC type from byte 0, bits 3-4\n const fecType = (buffer[0]! >> 3) & 0x03\n const packetCounterFlag = (buffer[0]! & 0x20) !== 0\n const extensionFlag = (buffer[0]! & 0x02) !== 0\n\n // Extract RAP flag from byte 0, bit 0 (R bit - Random Access Point)\n const rapFlag = (buffer[0]! & 0x01) === 1\n\n // ISO/IEC 23008-1:2023 Figure 8 (V=0) places reserved(2) before\n // packet_type(6), so packet_type occupies the low six bits of byte 1.\n const packetType = buffer[1]! & 0x3F\n\n let offset = 2\n\n // Packet ID (2 bytes, big-endian)\n const packetId = view.getUint16(offset, false)\n offset += 2\n\n // Timestamp (4 bytes, big-endian)\n const timestamp = BigInt(view.getUint32(offset, false))\n offset += 4\n\n // Sequence number (4 bytes, big-endian)\n const sequenceNumber = view.getUint32(offset, false)\n offset += 4\n\n let packetCounter: number | undefined\n if (packetCounterFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in packet_counter')\n packetCounter = view.getUint32(offset, false)\n offset += 4\n }\n\n let extension: { type: number; data: Uint8Array } | undefined\n if (extensionFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in header extension')\n const type = view.getUint16(offset, false)\n const length = view.getUint16(offset + 2, false)\n offset += 4\n if (offset + length > buffer.length) throw new Error('MMTP packet truncated in header extension value')\n extension = { type, data: buffer.subarray(offset, offset + length) }\n offset += length\n }\n\n return {\n version,\n packetType,\n packetId,\n timestamp,\n sequenceNumber,\n payloadLength: buffer.length - offset,\n fecType,\n packetCounterFlag,\n packetCounter,\n extensionFlag,\n extension,\n headerLength: offset,\n rapFlag,\n }\n}\n\n/**\n * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would\n * throw for it (short buffer, V != 0, truncated packet_counter or header\n * extension).\n *\n * This is the per-packet filter a receiver runs before any other work, so it\n * builds no header object, DataView or BigInt timestamp (BLO-36599). Use\n * parseMMTPHeader when any other header field is needed.\n */\nexport function readMMTPPacketId(buffer: Uint8Array): number | null {\n if (buffer.length < MMTP_HEADER_SIZE) return null\n const flags = buffer[0]!\n if (((flags >> 6) & 0x03) !== 0) return null\n let offset = MMTP_HEADER_SIZE\n if ((flags & 0x20) !== 0) {\n if (offset + 4 > buffer.length) return null\n offset += 4\n }\n if ((flags & 0x02) !== 0) {\n if (offset + 4 > buffer.length) return null\n const length = (buffer[offset + 2]! << 8) | buffer[offset + 3]!\n if (offset + 4 + length > buffer.length) return null\n }\n return (buffer[2]! << 8) | buffer[3]!\n}\n\n/**\n * Unwrap MMTP packet to extract payload\n */\nexport function unwrapMMTP(buffer: Uint8Array): MMTPPacket {\n const header = parseMMTPHeader(buffer)\n const payload = buffer.subarray(header.headerLength)\n\n return { header, payload }\n}\n\n/**\n * Parse MPU header from payload\n * ISO/IEC 23008-1:2023 Section 9.2.3.3\n *\n * MPU Header format (8 bytes) as written by FFmpeg/libmmt:\n * - Bytes 0-1: payload_length (16 bits, big-endian)\n * - Byte 2: FT(4) | T(1) | FI(2) | A(1)\n * - Byte 3: fragment_counter (8 bits)\n * - Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n */\nexport function parseMPUHeader(payload: Uint8Array): MPUHeader {\n if (payload.length < MPU_HEADER_SIZE) {\n throw new Error(`Payload too small for MPU header: ${payload.length} bytes`)\n }\n\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n\n // Bytes 0-1: payload_length (16 bits, big-endian)\n const payloadLength = view.getUint16(0, false)\n\n // Byte 2: FT(4) | T(1) | FI(2) | A(1)\n const flagsByte = payload[2]!\n const timedFlag = (flagsByte >> 3) & 0x01\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n const aggregationFlag = (flagsByte & 0x01) !== 0\n\n // Byte 3: fragment_counter (8 bits). The high bit is part of the counter in\n // the current libmmt/FFmpeg writer ABI.\n const fragmentCounter = payload[3]!\n\n // Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n const mpuSequenceNumber = view.getUint32(4, false)\n\n return {\n mpuSequenceNumber,\n fragmentationIndicator,\n fragmentCounter,\n timedFlag,\n aggregationFlag,\n payloadLength,\n }\n}\n\nexport function parseTimedMfuDuHeader(payload: Uint8Array, offset: number): MFUDuHeader | null {\n if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length) return null\n const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED)\n return {\n movieFragmentSequenceNumber: view.getUint32(0, false),\n sampleNumber: view.getUint32(4, false),\n offset: view.getUint32(8, false),\n priority: payload[offset + 12]!,\n dependencyCounter: payload[offset + 13]!,\n }\n}\n\n/**\n * Why `extractMFUPayload` refused a packet.\n *\n * These identifiers are part of the public contract: callers surface them in\n * diagnostics, so renaming one is a breaking change.\n */\nexport type MFURejectionGate =\n | 'packet-shorter-than-mmtp-plus-mpu-header'\n | 'mmtp-header-unparseable'\n | 'not-an-mpu-packet'\n | 'payload-shorter-than-mpu-header'\n | 'aggregation-flag-set'\n | 'mpu-payload-length-below-header-remainder'\n | 'mpu-declared-end-exceeds-payload'\n | 'mfu-offset-at-or-past-declared-end'\n | 'source-fec-payload-id-width-mismatch'\n | 'declared-trailers-unresolved'\n\nexport interface MFURejection {\n gate: MFURejectionGate\n /** Human-readable detail, including the byte arithmetic that decided it. */\n detail: string\n}\n\ninterface MfuPayloadLayout {\n hasMfuDuHeader: boolean\n mfuOffset: number\n dataEnd: number\n}\n\nfunction resolveMfuPayloadLayout(\n mpu: MPUHeader,\n fragmentType: number,\n payload: Uint8Array,\n): { layout: MfuPayloadLayout } | { rejection: MFURejection } {\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {\n return {\n rejection: {\n gate: 'mpu-payload-length-below-header-remainder',\n detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`,\n },\n }\n }\n\n // ISO/IEC 23008-1 defines payload_length from immediately after its own\n // two-byte field. It therefore includes the remaining six MPU-header bytes.\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n if (declaredEnd > payload.length) {\n return {\n rejection: {\n gate: 'mpu-declared-end-exceeds-payload',\n detail:\n `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, ` +\n `payload is ${payload.length} bytes`,\n },\n }\n }\n\n const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1\n const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED\n const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0)\n if (mfuOffset >= declaredEnd) {\n return {\n rejection: {\n gate: 'mfu-offset-at-or-past-declared-end',\n detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`,\n },\n }\n }\n\n return {\n layout: {\n hasMfuDuHeader: hasHeader,\n mfuOffset,\n dataEnd: declaredEnd,\n },\n }\n}\n\nfunction isEmptyOrExactAltaTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE > end) {\n return false\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset + start,\n end - start,\n )\n if (view.getUint16(0, false) !== _LEAF_MMTP_EXT_TYPE_ALTA_AUTH) return false\n\n const authLength = view.getUint16(2, false)\n return start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end\n}\n\ninterface DeclaredTrailerLayout {\n hasFecId: boolean\n}\n\nfunction resolveDeclaredTrailers(\n payload: Uint8Array,\n declaredEnd: number,\n fecType: number,\n options?: { skipFecId?: boolean },\n): DeclaredTrailerLayout | null {\n if (declaredEnd > payload.length) return null\n\n if (options?.skipFecId) {\n return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)\n ? { hasFecId: false }\n : null\n }\n\n if (fecType === 1) {\n const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {\n return { hasFecId: false }\n }\n\n return null\n}\n\n/**\n * Extract NAL unit payload from MMTP packet\n */\nexport function extractNALUnit(mmtpPacket: Uint8Array): Uint8Array {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Malformed MMTP MPU payload')\n return extraction.data\n}\n\n/** Result of extracting MFU payload from an MMTP packet */\nexport interface MFUExtraction {\n /** MMTP header fields */\n header: MMTPHeader\n /** MPU header fields */\n mpu: MPUHeader\n /** Fragment type (FT field, 4 bits): 0=metadata, 2=MFU data */\n fragmentType: number\n /** MFU payload data (video/audio bytes, headers stripped, FEC ID excluded) */\n data: Uint8Array\n /** Parsed timed MFU DU header when present, otherwise null */\n mfuDuHeader: MFUDuHeader | null\n /** Non-timed MFU Item_ID when the 4-byte DU header is present. */\n itemId: number | null\n /** Source FEC Payload ID if present (`FEC_type == 1`), or null */\n fecPayloadId: { ssId: number } | null\n}\n\n/**\n * Extract MFU payload from a raw MMTP packet.\n *\n * Handles all the byte arithmetic for:\n * - MMTP header (12 bytes)\n * - MPU sub-header (8 bytes, payloadLength field)\n * - MFU DU header (14 bytes timed / 4 bytes non-timed, only for FI \u2264 1)\n * - Source FEC Payload ID (4 bytes at end, only when `FEC_type == 1`)\n *\n * `payloadLength` is the ISO length-after-field value: six remaining MPU-header\n * bytes, then the MFU DU header when present, then media. ALTA and Source FEC\n * trailers are outside that declared span and must consume the exact suffix.\n *\n * @param mmtpPacket Raw MMTP packet (from multicast or MoQ)\n * @param options.skipFecId When true, do NOT subtract the 4-byte Source FEC Payload ID\n * from the payload boundary, even if fecType >= 1 in the MMTP header. Use this for\n * FEC-recovered symbols where the FEC PID was already stripped before WASM decoding\n * but the fecType bits in byte 0 still reflect the original packet (FEC Type=1 per\n * draft-ramadan-moq-mmt-00 \u00A73.1).\n * @returns Extracted MFU data + metadata, or null if packet is too small\n */\ninterface MfuPlan {\n header: MMTPHeader\n payload: Uint8Array\n mpu: MPUHeader\n fragmentType: number\n layout: MfuPayloadLayout\n trailerLayout: DeclaredTrailerLayout\n}\n\n/**\n * Single source of truth for whether a packet yields an MFU payload.\n *\n * `extractMFUPayload` and `explainMFURejection` both delegate here, so the\n * reason reported to operators can never drift from the decision the parser\n * actually made.\n */\nfunction planMfuExtraction(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n parsedHeader?: MMTPHeader,\n): { plan: MfuPlan } | { rejection: MFURejection } {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'packet-shorter-than-mmtp-plus-mpu-header',\n detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`,\n },\n }\n }\n\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch (error) {\n return {\n rejection: {\n gate: 'mmtp-header-unparseable',\n detail: error instanceof Error ? error.message : String(error),\n },\n }\n }\n }\n if (header.packetType !== PACKET_TYPE_MPU) {\n return {\n rejection: {\n gate: 'not-an-mpu-packet',\n detail: `packetType=0x${header.packetType.toString(16).padStart(2, '0')}`,\n },\n }\n }\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'payload-shorter-than-mpu-header',\n detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`,\n },\n }\n }\n\n const mpu = parseMPUHeader(payload)\n if (mpu.aggregationFlag) {\n return {\n rejection: { gate: 'aggregation-flag-set', detail: 'strict profile requires A=0' },\n }\n }\n\n // Fragment type from byte 2 upper nibble (not returned by parseMPUHeader)\n const fragmentType = (payload[2]! >> 4) & 0x0f\n\n const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload)\n if ('rejection' in layoutResult) return layoutResult\n const { layout } = layoutResult\n\n const trailerLayout = resolveDeclaredTrailers(\n payload,\n layout.dataEnd,\n header.fecType,\n options,\n )\n if (!trailerLayout) {\n // The most common cause is a producer whose Source FEC Payload ID width\n // disagrees with ISO/IEC 23008-1 \u00A7C.5.2. Name that explicitly rather than\n // leaving operators to infer it from a generic trailer error.\n if (!options?.skipFecId && header.fecType === 1) {\n const observed = payload.length - layout.dataEnd\n if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {\n return {\n rejection: {\n gate: 'source-fec-payload-id-width-mismatch',\n detail:\n `producer wrote a ${observed}-byte Source FEC Payload ID, ` +\n `ISO/IEC 23008-1 \u00A7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} ` +\n `(payload=${payload.length}, declaredEnd=${layout.dataEnd})`,\n },\n }\n }\n }\n return {\n rejection: {\n gate: 'declared-trailers-unresolved',\n detail:\n `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty ` +\n `nor an exact ALTA trailer (fecType=${header.fecType})`,\n },\n }\n }\n\n return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } }\n}\n\n/**\n * Report why `extractMFUPayload` would refuse this packet, or null if it would\n * accept it.\n *\n * `extractMFUPayload` returns bare `null` on ten distinct conditions, which\n * makes a producer/consumer framing disagreement indistinguishable from a\n * publisher that never started \u2014 both render as silence. Use this wherever\n * that distinction matters: diagnostics, contract tests, and operator-facing\n * logs.\n */\nexport function explainMFURejection(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFURejection | null {\n const result = planMfuExtraction(mmtpPacket, options)\n return 'rejection' in result ? result.rejection : null\n}\n\nexport function extractMFUPayload(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options))\n}\n\n/**\n * extractMFUPayload for a caller that already holds this packet's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the\n * result is then identical to `extractMFUPayload(mmtpPacket, options)`.\n */\nexport function extractMFUPayloadWithHeader(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options, header))\n}\n\nfunction extractPlannedMfu(\n mmtpPacket: Uint8Array,\n result: { plan: MfuPlan } | { rejection: MFURejection },\n): MFUExtraction | null {\n if ('rejection' in result) return null\n const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan\n\n const mfuDuHeaderPresent = layout.hasMfuDuHeader\n const mfuOffset = layout.mfuOffset\n const dataEnd = layout.dataEnd\n\n const mfuDuHeader =\n mfuDuHeaderPresent && mpu.timedFlag === 1\n ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE)\n : null\n const itemId =\n mfuDuHeaderPresent && mpu.timedFlag === 0\n ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false)\n : null\n\n const data = payload.subarray(mfuOffset, dataEnd)\n\n const fecPayloadId = trailerLayout.hasFecId\n ? {\n ssId: new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n ).getUint32(0, false),\n }\n : null\n\n return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId }\n}\n\n/**\n * Extract timestamp from MMTP packet\n */\nexport function extractTimestamp(mmtpPacket: Uint8Array): bigint {\n const { header } = unwrapMMTP(mmtpPacket)\n return header.timestamp\n}\n\n/**\n * Check if MMTP packet is an FEC repair packet\n * FEC_type 2 and 3 are repair packets\n */\nexport function isFecRepairPacket(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < 1) return false\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n return fecType >= 2\n}\n\n/**\n * Extract Source FEC Payload ID (SS_ID) from the last 4 bytes of an MMTP\n * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.\n *\n * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet\n * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the\n * configured K; repair SS_Start identifies the first SS_ID of the block and\n * SSB_length records the authored source-symbol count:\n * SBN = floor(SS_ID / K), ESI = SS_ID % K\n * SS_Start = SBN * K (first SS_ID of the block)\n *\n * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec \u00A76.2),\n * and ATSC A/331 \u00A78.1.2.5 which defers to ISO 23008-1 Annex C.\n */\nexport function extractFecPayloadId(mmtpPacket: Uint8Array): { ssId: number } | null {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header ? sourceFecPayloadIdOf(mmtpPacket, header) : null\n}\n\n/**\n * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU\n * payload contains a four-byte Source FEC Payload ID.\n */\nexport function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header !== null && sourceFecPayloadIdOf(mmtpPacket, header) !== null\n}\n\nfunction parseHeaderForSourceFecId(mmtpPacket: Uint8Array): MMTPHeader | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n try {\n return parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n}\n\n/**\n * The Source FEC Payload ID of a packet whose MMTP header the caller already\n * parsed, or null when the packet carries none: present only when FEC_type=1\n * and the exact suffix after the ISO-declared MPU payload holds the four-byte\n * ID (ISO/IEC 23008-1 \u00A7C.5.2). Same answer as extractFecPayloadId, without a\n * second header parse (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.\n */\nexport function sourceFecPayloadIdOf(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n): { ssId: number } | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n if (header.packetType !== PACKET_TYPE_MPU) return null\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) return null\n const payloadLength = (payload[0]! << 8) | payload[1]!\n if (payloadLength < MPU_HEADER_REMAINDER_SIZE) return null\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId) return null\n\n // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id\n const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n return {\n ssId:\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0,\n }\n}\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\n// Re-export the ALTA trailer helper from the zero-dep leaf package. The leaf\n// owns the implementation so container and FEC consumers share one parser\n// without creating a package cycle.\nexport const MMTP_EXT_TYPE_ALTA_AUTH = _LEAF_MMTP_EXT_TYPE_ALTA_AUTH\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE\nexport type AltaTrailer = _LeafAltaTrailer\nexport const extractAltaTrailer = _leafExtractAltaTrailer\n\n/** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */\nexport const REPAIR_FEC_PID_SIZE = 13\n\n/**\n * Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header.\n * `parsedHeader`, when given, must be `parseMMTPHeader(mmtpPacket)` of this\n * same packet; it spares the second header parse (BLO-36599).\n */\nfunction readRepairFecPayloadIdFields(mmtpPacket: Uint8Array, parsedHeader?: MMTPHeader): {\n packetId: number; ssStart: number; rsbLength: number; rsId: number; ssbLength: number; dataOffset: number\n} | null {\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n }\n if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2) return null\n\n const off = header.headerLength\n if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE) return null\n const ssStart =\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0 // 32 bits\n const rsbLength = (mmtpPacket[off + 4]! << 16) | (mmtpPacket[off + 5]! << 8) | mmtpPacket[off + 6]! // 24 bits\n const rsId = (mmtpPacket[off + 7]! << 16) | (mmtpPacket[off + 8]! << 8) | mmtpPacket[off + 9]! // 24 bits\n const ssbLength = (mmtpPacket[off + 10]! << 16) | (mmtpPacket[off + 11]! << 8) | mmtpPacket[off + 12]! // 24 bits\n return {\n packetId: header.packetId,\n ssStart,\n rsbLength,\n rsId,\n ssbLength,\n dataOffset: off + REPAIR_FEC_PID_SIZE,\n }\n}\n\n/**\n * Extract Repair FEC Payload ID from a raw-multicast MMTP repair packet.\n * Per ISO/IEC 23008-1:2023 Section C.5.3 (ssbg_mode0, one-stage, no FFSRP_TS):\n * [MMTP header][SS_Start:4][RSB_length:3][RS_ID:3][SSB_length:3][repair data]\n * OTI is delivered via AL-FEC signaling message (Table C.3), not per-packet.\n *\n * Byte 1 is reserved(2)|packet_type(6) for V=0, so repair type 0x03 is 0x03.\n * Pre-migration high-bits bytes (0x0c) are rejected \u2014 no compatibility decode.\n */\nexport function extractRepairFecPayloadId(mmtpPacket: Uint8Array): {\n ssStart: number; rsbLength: number; rsId: number; ssbLength: number\n} | null {\n const fields = readRepairFecPayloadIdFields(mmtpPacket)\n if (!fields) return null\n return {\n ssStart: fields.ssStart,\n rsbLength: fields.rsbLength,\n rsId: fields.rsId,\n ssbLength: fields.ssbLength,\n }\n}\n\n/** Parsed MoQ repair frame per ISO/IEC 23008-1 Section C.4.3 + C.5.3 */\nexport interface MoqRepairFrame {\n /** MMTP packetId (1=video, 2=audio) \u2014 for routing to correct FEC decoder */\n packetId: number\n /** SS_Start (32 bits): SS_ID of first source symbol in block */\n ssStart: number\n /** RSB_length (24 bits): number of repair symbols (P) */\n rsbLength: number\n /** RS_ID (24 bits): repair symbol index (0-based) */\n rsId: number\n /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */\n ssbLength: number\n /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */\n symbolData: Uint8Array\n}\n\n/**\n * Parse a MoQ repair frame (ISO/IEC 23008-1 Annex C format).\n *\n * Wire format per ISO 23008-1 \u00A7C.4.3 + \u00A7C.5.3:\n * MMTP Header: fixed 12 bytes plus C/X optional fields\n * then SS_Start (4 bytes) \u2192 block identifier\n * then RSB_length (3 bytes) \u2192 P\n * then RS_ID (3 bytes) \u2192 ESI = K + RS_ID\n * then SSB_length (3 bytes) \u2192 authored source-symbol count (1..=K)\n * then Repair Symbol Data (T bytes)\n *\n * @param frame - Raw MoQ repair frame bytes\n * @returns Parsed frame or null if invalid\n */\nexport function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame))\n}\n\n/**\n * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599). The router's\n * multicast repair branch uses it: one repair packet in every K + P otherwise\n * paid two header parses.\n *\n * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is\n * then identical to `parseMoqRepairFrame(frame)`.\n */\nexport function parseMoqRepairFrameWithHeader(\n frame: Uint8Array,\n header: MMTPHeader,\n): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header))\n}\n\nfunction moqRepairFrameFrom(\n frame: Uint8Array,\n repairId: ReturnType<typeof readRepairFecPayloadIdFields>,\n): MoqRepairFrame | null {\n if (!repairId) return null\n\n const symbolData = frame.subarray(repairId.dataOffset)\n if (\n repairId.rsbLength === 0 ||\n repairId.ssbLength === 0 ||\n repairId.rsId >= repairId.rsbLength ||\n symbolData.length === 0\n ) return null\n\n return {\n packetId: repairId.packetId,\n ssStart: repairId.ssStart,\n rsbLength: repairId.rsbLength,\n rsId: repairId.rsId,\n ssbLength: repairId.ssbLength,\n symbolData,\n }\n}\n\n// The signalling-message parsers (9.3.4.2 payload header, AL-FEC message,\n// PA/MPT) live in ./signalling.ts.\n", "/**\n * NTP short format (RFC 5905 \u00A76): an unsigned 32-bit, 16.16 fixed-point count\n * of seconds. The MMTP header `timestamp` field (ISO/IEC 23008-1 \u00A79.2.2)\n * carries each packet's UTC sending instant in this format, so the value wraps\n * to 0 every 2^16 s (~18.2 h).\n *\n * Plain numeric order on these values breaks at every wrap: a retained\n * `Math.max` stays at the pre-wrap value and every later instant compares as\n * older. Order send times only with {@link compareNtpShort}, which applies\n * RFC 1982 serial-number arithmetic over the field's width.\n */\n\n/** Width in bits of the MMTP `timestamp` field (NTP short). */\nexport const NTP_SHORT_BITS = 32\n\n/** Number of distinct NTP-short values; the field wraps to 0 here. */\nexport const NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS\n\n/**\n * RFC 1982 \u00A73.2's `2^(SERIAL_BITS - 1)`. Two values whose forward distance is\n * below this are ordered; exactly this far apart they are not ordered at all.\n * Two instants must be less than this far apart (2^15 s, ~9.1 h of send time)\n * for the comparison to give their true order.\n */\nexport const NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1)\n\n/**\n * Serial order of one NTP-short value relative to another.\n *\n * `'unordered'` is RFC 1982's undefined case: the values are exactly\n * {@link NTP_SHORT_HALF_RANGE} apart, so neither precedes the other. It is\n * never folded into `'before'` or `'after'`; each caller decides what an\n * unorderable pair means for it.\n */\nexport type NtpShortOrder = 'before' | 'equal' | 'after' | 'unordered'\n\n/** True when `value` is an NTP-short value: an integer in [0, NTP_SHORT_MODULUS). */\nexport function isNtpShort(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value < NTP_SHORT_MODULUS\n}\n\n/**\n * RFC 1982 serial comparison of NTP-short `a` against `b`: `'after'` when `a`\n * is the later instant, `'before'` when it is the earlier one.\n *\n * The answer is the true order only while the two instants are less than\n * {@link NTP_SHORT_HALF_RANGE} apart. Past that a 32-bit field cannot tell\n * later from earlier, and the result names whichever direction is nearer.\n * This is not a total order (it is not transitive across the whole space), so\n * it must never be used to sort.\n *\n * @throws {RangeError} if either operand is not an NTP-short value\n */\nexport function compareNtpShort(a: number, b: number): NtpShortOrder {\n if (!isNtpShort(a) || !isNtpShort(b)) {\n throw new RangeError(\n `NTP-short values must be integers in [0, 2^${NTP_SHORT_BITS}), got ${String(a)} and ${String(b)}`,\n )\n }\n if (a === b) return 'equal'\n // Forward distance from b to a, reduced into [1, NTP_SHORT_MODULUS). Both\n // operands are below 2^32, so every step is exact in a double.\n const forward = (a - b + NTP_SHORT_MODULUS) % NTP_SHORT_MODULUS\n if (forward < NTP_SHORT_HALF_RANGE) return 'after'\n if (forward > NTP_SHORT_HALF_RANGE) return 'before'\n return 'unordered'\n}\n", "/**\n * RFC 1982 serial-number arithmetic for the 32-bit MMTP AL-FEC symbol\n * counters (ISO/IEC 23008-1:2023 C.5.2 SS_ID, C.5.3 SS_Start).\n *\n * SS_ID is a 32-bit field, so a stream that runs long enough wraps it to 0.\n * Ordering two SS_IDs (or two blocks by their first SS_ID) with `<` reads\n * every post-wrap value as older than every pre-wrap one. Serial order\n * agrees with `<` whenever the two values are less than 2^31 apart, so for\n * every stream that has not reached the wrap the result is unchanged; only\n * the order across the wrap point differs (BLO-35597 iso:5).\n */\n\n/** 2^SERIAL_BITS for SERIAL_BITS = 32. */\nexport const SERIAL32_SPAN = 0x1_0000_0000\n\n/** RFC 1982 \u00A73.2's 2^(SERIAL_BITS - 1). */\nexport const SERIAL32_HALF = 0x8000_0000\n\nfunction assertUint32(value: number, name: string): void {\n if (!Number.isInteger(value) || value < 0 || value >= SERIAL32_SPAN) {\n throw new RangeError(`${name} must be a uint32 (got ${value})`)\n }\n}\n\n/**\n * RFC 1982 \u00A73.1 addition, extended to negative `n` for the matching\n * subtraction: `(s + n) mod 2^32`. `n` must lie in (-2^31, 2^31).\n */\nexport function serial32Add(s: number, n: number): number {\n assertUint32(s, 's')\n if (!Number.isInteger(n) || n <= -SERIAL32_HALF || n >= SERIAL32_HALF) {\n throw new RangeError(`serial addend must lie in (-2^31, 2^31) (got ${n})`)\n }\n return (s + n + SERIAL32_SPAN) % SERIAL32_SPAN\n}\n\n/** Forward distance from `from` to `to`: `(to - from) mod 2^32`. */\nexport function serial32Distance(from: number, to: number): number {\n return (to - from) >>> 0\n}\n\n/**\n * RFC 1982 \u00A73.2: `a` precedes `b`. Values exactly 2^31 apart are unordered\n * (neither precedes the other).\n */\nexport function serial32Precedes(a: number, b: number): boolean {\n const distance = serial32Distance(a, b)\n return distance !== 0 && distance < SERIAL32_HALF\n}\n\n/**\n * First SS_ID of block `sbn` under the K-stride numbering every current\n * sender uses (`SS_ID = SBN * K + ESI`, C.5.2), reduced mod 2^32.\n */\nexport function kStrideBlockStart(sbn: number, k: number): number {\n assertUint32(sbn, 'sbn')\n if (!Number.isInteger(k) || k < 1 || k >= SERIAL32_HALF) {\n throw new RangeError(`k must be a positive integer below 2^31 (got ${k})`)\n }\n // sbn < 2^32 and k < 2^31 keep the product below 2^63, but a double is\n // exact only to 2^53; split the product so every step is exact.\n const high = Math.floor(sbn / 0x1_0000) // < 2^16\n const low = sbn % 0x1_0000 // < 2^16\n return (((high * k) % SERIAL32_SPAN) * 0x1_0000 + low * k) % SERIAL32_SPAN\n}\n\n/**\n * Whether block `a` precedes block `b`, ordering the blocks by their first\n * SS_ID in serial arithmetic. Identical to `a < b` for any two blocks of a\n * stream that has not wrapped SS_ID.\n */\nexport function kStrideBlockPrecedes(a: number, b: number, k: number): boolean {\n return serial32Precedes(kStrideBlockStart(a, k), kStrideBlockStart(b, k))\n}\n\n/**\n * How many blocks `a` lies behind `b`: 0 when `a` does not precede `b`,\n * otherwise `ceil(distance / K)` over the SS_ID distance between their first\n * symbols. Without a wrap that is exactly `b - a`; across the wrap the one\n * short pre-wrap block (2^32 mod K symbols) still counts as one block.\n */\nexport function kStrideBlocksBehind(a: number, b: number, k: number): number {\n const from = kStrideBlockStart(a, k)\n const to = kStrideBlockStart(b, k)\n if (!serial32Precedes(from, to)) return 0\n return Math.ceil(serial32Distance(from, to) / k)\n}\n\n/**\n * Whether `sbn` and `k` describe a block of the K-stride numbering:\n * `sbn` a uint32, `k` an integer in 1..2^31-1.\n */\nexport function isKStrideBlock(sbn: number, k: number): boolean {\n return Number.isInteger(sbn) && sbn >= 0 && sbn < SERIAL32_SPAN &&\n Number.isInteger(k) && k >= 1 && k < SERIAL32_HALF\n}\n\n/**\n * The block after `sbn`: `sbn + 1`, or 0 once `(sbn + 1) * K` reaches 2^32\n * and the next SS_ID wraps (C.5.2). There are `ceil(2^32 / K)` blocks per\n * wrap, the last of them short when K does not divide 2^32.\n */\nexport function kStrideNextBlock(sbn: number, k: number): number {\n assertUint32(sbn, 'sbn')\n kStrideBlockStart(sbn, k) // validates k\n return (sbn + 1) * k >= SERIAL32_SPAN ? 0 : sbn + 1\n}\n", "/**\n * FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C).\n *\n * Every catalog `fec` object that declares an FEC instance names exactly one\n * block profile in `fec.blockProfile`. A receiver applies all of a profile's\n * rules or none of them, so the token is the one explicit per-stream\n * discriminator: it is never defaulted and never inferred from the wire.\n *\n * - `k-padded-v1` (legacy, Blockcast-private): a source symbol is the\n * protected MMTP packet zero-padded to T with no length field; every block\n * is a K-symbol RaptorQ block whose ESIs `SSB_length..K-1` are implicit\n * zero padding; `SS_ID = SBN*K + ESI`, so `SS_Start = SBN*K`; repair\n * ESI = K + RS_ID; the MoQ repair group id is the SBN.\n * - `iso-ssbg1-v1`: ssbg_mode1 (C.3.3): a source symbol is\n * `u16 length || protected packet || zero padding` to T; a block is a\n * K' = SSB_length RaptorQ block (C.5.3) and repair ESI = SSB_length + RS_ID;\n * SS_ID increments by one per FEC source packet and wraps at 2^32 (C.5.2),\n * so a block is located by membership in `[SS_Start, SS_Start + SSB_length)`\n * under RFC 1982 serial arithmetic; the MoQ repair group id is the 62-bit\n * wrap-epoch-extended SS_Start. The AL-FEC coding byte for this profile is\n * 0x14 (one-stage, ssbg_mode1, fec_payload_id_mode 0, Amd 1:2025 Table C.3).\n */\nimport { serial32Add, serial32Distance, serial32Precedes } from './serial-number.js'\n\n/** The two block profiles a catalog `fec.blockProfile` may name. */\nexport type FecBlockProfile = 'k-padded-v1' | 'iso-ssbg1-v1'\n\n/** Every accepted `fec.blockProfile` token, legacy first. */\nexport const FEC_BLOCK_PROFILES: readonly FecBlockProfile[] = Object.freeze([\n 'k-padded-v1',\n 'iso-ssbg1-v1',\n])\n\n/** True for a known block-profile token. */\nexport function isFecBlockProfile(value: unknown): value is FecBlockProfile {\n return value === 'k-padded-v1' || value === 'iso-ssbg1-v1'\n}\n\n/**\n * Read a required `fec.blockProfile`. Throws on an absent or unknown token:\n * there is no default and nothing is inferred, because the two profiles carry\n * identical wire tuples whose repair math differs.\n *\n * @param where the catalog path of the field, for the error message.\n */\nexport function parseFecBlockProfile(value: unknown, where: string): FecBlockProfile {\n if (isFecBlockProfile(value)) return value\n if (value === undefined) {\n throw new Error(\n `${where} is required: name the FEC block profile (\"k-padded-v1\" or \"iso-ssbg1-v1\")`,\n )\n }\n throw new Error(\n `${where} must be \"k-padded-v1\" or \"iso-ssbg1-v1\" (got ${JSON.stringify(value)})`,\n )\n}\n\n/** ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet packet length field. */\nexport const SSBG1_LENGTH_FIELD_BYTES = 2\n\n/** The largest protected packet an ssbg_mode1 symbol of size T can carry. */\nexport function ssbg1MaxPacketLength(symbolSize: number): number {\n return symbolSize - SSBG1_LENGTH_FIELD_BYTES\n}\n\n/** Whether a protected packet of `packetLength` bytes fits one T-byte ssbg_mode1 symbol. */\nexport function ssbg1FitsSymbol(packetLength: number, symbolSize: number): boolean {\n return (\n Number.isSafeInteger(packetLength) &&\n packetLength >= 0 &&\n packetLength <= ssbg1MaxPacketLength(symbolSize) &&\n packetLength <= 0xffff\n )\n}\n\n/**\n * Build one ssbg_mode1 source symbol (C.3.3): the protected packet's length\n * in two octets, network byte order, then the packet, then zero octets up to\n * T. The protected packet is the MMTP packet before its Source FEC Payload ID\n * is appended (C.4.2).\n *\n * @throws when `length + 2 > T`: the packet cannot be protected under this\n * profile, and silently truncating it would corrupt every repair symbol.\n */\nexport function buildSsbg1SourceSymbol(packet: Uint8Array, symbolSize: number): Uint8Array {\n if (!Number.isSafeInteger(symbolSize) || symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {\n throw new Error(`ssbg_mode1 symbol size must exceed ${SSBG1_LENGTH_FIELD_BYTES} (got ${symbolSize})`)\n }\n if (!ssbg1FitsSymbol(packet.byteLength, symbolSize)) {\n throw new Error(\n `ssbg_mode1 source symbol needs ${packet.byteLength} + ${SSBG1_LENGTH_FIELD_BYTES} bytes but T is ${symbolSize}`,\n )\n }\n const symbol = new Uint8Array(symbolSize)\n symbol[0] = (packet.byteLength >>> 8) & 0xff\n symbol[1] = packet.byteLength & 0xff\n symbol.set(packet, SSBG1_LENGTH_FIELD_BYTES)\n return symbol\n}\n\n/**\n * Recover the protected packet from one ssbg_mode1 source symbol: the bytes\n * the length field names. Returns null, never a guess, when the length field\n * points past the symbol or any padding octet is non-zero (C.3.3 padding is\n * all 00h), since either means the recovered symbol is not what was sent.\n */\nexport function trimSsbg1SourceSymbol(symbol: Uint8Array): Uint8Array | null {\n if (symbol.byteLength < SSBG1_LENGTH_FIELD_BYTES) return null\n const length = (symbol[0]! << 8) | symbol[1]!\n const end = SSBG1_LENGTH_FIELD_BYTES + length\n if (end > symbol.byteLength) return null\n for (let i = end; i < symbol.byteLength; i++) {\n if (symbol[i] !== 0) return null\n }\n return symbol.subarray(SSBG1_LENGTH_FIELD_BYTES, end)\n}\n\n/** The SS_ID after the last source symbol of `[ssStart, ssStart + ssbLength)`. */\nexport function isoBlockEnd(ssStart: number, ssbLength: number): number {\n return serial32Add(ssStart, ssbLength)\n}\n\n/**\n * Range membership (C.5.2, C.5.3): whether `ssId` is one of the\n * `ssbLength` consecutive SS_IDs starting at `ssStart`, modulo 2^32.\n */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n return serial32Distance(ssStart, ssId) < ssbLength\n}\n\n/**\n * A lower bound on how many blocks the block starting at `a` lies behind the\n * one starting at `b` (0 when `a` does not precede `b`). An iso-ssbg1-v1\n * block holds at most `kMax` source symbols (the catalog `sourceSymbols`, the\n * AL-FEC maximum_k_for_repair_flow), so `n * kMax` SS_IDs always span at least\n * `n` blocks. Every interleave horizon in the receiver (decoder retention,\n * worker retirement, ordered-relay settlement) uses this one bound, so the\n * manager's horizon can never fall inside the decoder's.\n */\nexport function isoBlocksBehindLowerBound(a: number, b: number, kMax: number): number {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`)\n }\n return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0\n}\n", "/**\n * iso-ssbg1-v1 block location for one FEC stream (BLO-35597 iso:2/iso:3/iso:5).\n *\n * Under ISO/IEC 23008-1 C.5.2 the SS_ID increments by one per FEC source\n * packet and wraps at 2^32, and a block is the SS_ID range\n * `[SS_Start, SS_Start + SSB_length)` its Repair FEC Payload ID states (C.5.3).\n * Block lengths vary, so `floor(SS_ID / K)` locates nothing: a source symbol\n * belongs to a block only once a repair symbol has stated that block's range.\n * A source that arrives first is held, unplaced, and handed back when a repair\n * places it. Nothing here guesses a boundary.\n *\n * Coverage grows contiguously from the first block a repair states. When a\n * repair states a block that starts after the end of known coverage, the SS_IDs\n * between are one or more blocks whose every repair symbol was lost: they are\n * kept as one repair-less span, which completes clean once all of its SS_IDs\n * arrived (C.5.2 makes a gap in consecutive SS_IDs directly visible) and\n * otherwise resolves through its catalog-derived deadline. That is the only\n * way a block without repair completes: on the next block's SS_Start, never on\n * a count of K source symbols.\n */\nimport {\n isoBlockContains,\n isoBlockEnd,\n isoBlocksBehindLowerBound,\n serial32Add,\n serial32Distance,\n serial32Precedes,\n} from '@blockcast/mmt-base'\n\n/** One block (or repair-less span) the locator knows the exact range of. */\nexport interface IsoBlockRange {\n readonly start: number\n readonly length: number\n /** True for SS_IDs between two stated blocks whose own repair never came. */\n readonly span: boolean\n}\n\n/** A held source symbol handed back with its block coordinate. */\nexport interface IsoPlacedSource<T> {\n readonly ssId: number\n readonly esi: number\n readonly payload: T\n}\n\n/** What one repair symbol's `(SS_Start, SSB_length)` did to the locator. */\nexport type IsoRepairPlacement<T> =\n | {\n readonly status: 'placed'\n readonly block: IsoBlockRange\n /** Sources this repair placed into `block`, in SS_ID order. */\n readonly placed: readonly IsoPlacedSource<T>[]\n /** The repair-less span this repair closed in front of `block`, if any. */\n readonly span?: { readonly block: IsoBlockRange; readonly placed: readonly IsoPlacedSource<T>[] }\n }\n /** Not a block this stream can use; the repair symbol is dropped. */\n | { readonly status: 'conflict' | 'overlap' | 'stale'; readonly reason: string }\n\n/**\n * Retention: the locator is never without one. Every call that can grow it\n * (`hold`, `placeRepair`) retires, behind the position it just saw, every\n * block, span and held source more than `retentionBlocks` blocks back, by the\n * one ISO horizon metric (`isoBlocksBehindLowerBound`). A block is measured\n * from its last SS_ID, so it stays while any SS_ID of it is inside the\n * horizon: a repair-less span has no length bound, and one measured from its\n * start could be retired the moment it is created, before the sources it\n * placed are fed. Held sources are additionally capped at the SS_IDs the\n * horizon spans (`(retentionBlocks + 1) * kMax`): consecutive SS_IDs (C.5.2)\n * cannot hold more inside it, so the cap only ever bites on a stream whose\n * SS_IDs are not consecutive, and then the oldest arrival goes first.\n */\nexport class IsoBlockLocator<T> {\n /** Known blocks and spans, in serial order of their start. */\n readonly #blocks: IsoBlockRange[] = []\n /** Held first-arrival sources whose block no repair has stated yet. */\n readonly #held = new Map<number, T>()\n /** End of contiguous coverage: the SS_ID the next unknown block starts at. */\n #coverageEnd: number | undefined\n readonly #kMax: number\n #retentionBlocks: number\n #heldEvicted = 0\n #newest: number | undefined\n\n /**\n * @param kMax the catalog maximum K (`sourceSymbols`), the bound on SSB_length.\n * @param retentionBlocks how many blocks behind the newest position the\n * locator keeps, derived by its owner from catalog timing; required, so\n * no locator exists without retention.\n */\n constructor(kMax: number, retentionBlocks: number) {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`[IsoBlockLocator] maximum K must be a positive integer (got ${kMax})`)\n }\n this.#kMax = kMax\n this.#retentionBlocks = IsoBlockLocator.#checkedRetention(retentionBlocks)\n }\n\n static #checkedRetention(retentionBlocks: number): number {\n if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {\n throw new Error(\n `[IsoBlockLocator] retention must be a positive integer number of blocks (got ${retentionBlocks})`,\n )\n }\n return retentionBlocks\n }\n\n get kMax(): number {\n return this.#kMax\n }\n\n /** Blocks behind the newest position the locator keeps. */\n get retentionBlocks(): number {\n return this.#retentionBlocks\n }\n\n /** Update the retention when the catalog timing it derives from changes. */\n set retentionBlocks(retentionBlocks: number) {\n this.#retentionBlocks = IsoBlockLocator.#checkedRetention(retentionBlocks)\n }\n\n /** The most sources held at once: the SS_IDs the retention horizon spans. */\n get heldCapacity(): number {\n return (this.#retentionBlocks + 1) * this.#kMax\n }\n\n /**\n * The newest position the stream has reached (RFC 1982 order): the newest\n * SS_ID of a source held, or fed once located (`observe`), or SS_Start of a\n * placed repair, so no source the stream has fed follows it. Undefined\n * before the first one.\n */\n get newest(): number | undefined {\n return this.#newest\n }\n\n /** Advance `newest` to `position` if it is newer; retires nothing. */\n observe(position: number): void {\n if (this.#newest === undefined || serial32Precedes(this.#newest, position)) this.#newest = position\n }\n\n /** Held sources evicted by `heldCapacity` (never by the horizon itself). */\n get heldEvicted(): number {\n return this.#heldEvicted\n }\n\n /** Number of known blocks and spans. */\n get blockCount(): number {\n return this.#blocks.length\n }\n\n /** End of contiguous coverage, or undefined before the first repair. */\n get coverageEnd(): number | undefined {\n return this.#coverageEnd\n }\n\n /** Number of sources held for a block no repair has stated yet. */\n get heldCount(): number {\n return this.#held.size\n }\n\n /** The known block or span containing `ssId`, with its ESI. */\n locate(ssId: number): { block: IsoBlockRange; esi: number } | null {\n for (let i = this.#blocks.length - 1; i >= 0; i--) {\n const block = this.#blocks[i]!\n if (isoBlockContains(block.start, block.length, ssId)) {\n return { block, esi: serial32Distance(block.start, ssId) }\n }\n }\n return null\n }\n\n /** The known block or span starting exactly at `start`. */\n blockAt(start: number): IsoBlockRange | undefined {\n return this.#blocks.find((block) => block.start === start)\n }\n\n /** Known blocks and spans overlapping the SS_IDs strictly between `a` and `b`. */\n blocksBetween(a: number, b: number): IsoBlockRange[] {\n const first = (a + 1) >>> 0\n const count = serial32Distance(a, b) - 1\n if (!serial32Precedes(a, b) || count <= 0) return []\n return this.#blocks.filter((block) => {\n const toBlock = serial32Distance(first, block.start)\n const fromBlock = serial32Distance(block.start, first)\n return toBlock < count || fromBlock < block.length\n })\n }\n\n /**\n * Whether every SS_ID strictly between `a` and `b` lies in known coverage,\n * so no block that could hold them is still to be stated by a repair.\n */\n coversBetween(a: number, b: number): boolean {\n if (this.#coverageEnd === undefined) return false\n if (!serial32Precedes(a, b) || serial32Distance(a, b) <= 1) return true\n // The last SS_ID strictly before b must precede the coverage end.\n return serial32Precedes((b - 1) >>> 0, this.#coverageEnd)\n }\n\n /**\n * Hold a first-arrival source symbol whose block is not yet known. Returns\n * false for a second copy of an SS_ID already held, so the first copy wins.\n * Retires behind `ssId` (see the class note).\n */\n hold(ssId: number, payload: T): boolean {\n if (this.#held.has(ssId)) return false\n this.#held.set(ssId, payload)\n this.retireBehind(ssId)\n for (const oldest of this.#held.keys()) {\n if (this.#held.size <= this.heldCapacity) break\n this.#held.delete(oldest)\n this.#heldEvicted++\n }\n return true\n }\n\n /** Whether `ssId` is held unplaced. */\n isHeld(ssId: number): boolean {\n return this.#held.has(ssId)\n }\n\n /**\n * Apply one repair symbol's `(SS_Start, SSB_length)`. The first repair of a\n * block states its range and places every held source in it; later repair\n * symbols of the same block change nothing. A range that contradicts a\n * known block (a different length, or an overlap with another block or a\n * span) fails closed: the repair symbol is dropped and no block changes.\n * A newly stated block retires behind its SS_Start (see the class note).\n */\n placeRepair(start: number, length: number): IsoRepairPlacement<T> {\n if (!Number.isSafeInteger(length) || length <= 0 || length > this.#kMax) {\n return { status: 'conflict', reason: `SSB_length ${length} is outside 1..${this.#kMax}` }\n }\n const known = this.blockAt(start)\n if (known !== undefined) {\n if (known.span) {\n return { status: 'overlap', reason: `repair block ${start}+${length} falls inside a repair-less span` }\n }\n if (known.length !== length) {\n return {\n status: 'conflict',\n reason: `repair SSB_length ${length} conflicts with ${known.length} for SS_Start ${start}`,\n }\n }\n return { status: 'placed', block: known, placed: [] }\n }\n for (const block of this.#blocks) {\n if (\n isoBlockContains(block.start, block.length, start) ||\n isoBlockContains(start, length, block.start)\n ) {\n return {\n status: 'overlap',\n reason: `repair block ${start}+${length} overlaps known block ${block.start}+${block.length}`,\n }\n }\n }\n let span: { block: IsoBlockRange; placed: IsoPlacedSource<T>[] } | undefined\n if (this.#coverageEnd === undefined) {\n // Reception starts at the first stated block. Held sources before it\n // are the tail of blocks that began before the join: their range is\n // never stated, so they are not FEC symbols of this receiver.\n for (const ssId of this.#held.keys()) {\n if (serial32Precedes(ssId, start)) this.#held.delete(ssId)\n }\n this.#coverageEnd = start\n } else if (serial32Precedes(start, this.#coverageEnd)) {\n return { status: 'stale', reason: `repair block ${start}+${length} precedes known coverage` }\n } else if (start !== this.#coverageEnd) {\n const spanBlock: IsoBlockRange = {\n start: this.#coverageEnd,\n length: serial32Distance(this.#coverageEnd, start),\n span: true,\n }\n span = { block: spanBlock, placed: this.#claim(spanBlock) }\n this.#blocks.push(spanBlock)\n }\n const block: IsoBlockRange = { start, length, span: false }\n const placed = this.#claim(block)\n this.#blocks.push(block)\n this.#coverageEnd = isoBlockEnd(start, length)\n this.retireBehind(start)\n return span === undefined\n ? { status: 'placed', block, placed }\n : { status: 'placed', block, placed, span }\n }\n\n /**\n * Retire every block and span whose last SS_ID, and every held source,\n * lies more than `retentionBlocks` blocks behind `position`, with the shared\n * lower-bound metric (`isoBlocksBehindLowerBound`) every ISO interleave\n * horizon uses. Returns the retired block starts.\n */\n retireBehind(position: number): number[] {\n this.observe(position)\n const horizon = this.#retentionBlocks\n const retired: number[] = []\n for (let i = this.#blocks.length - 1; i >= 0; i--) {\n const block = this.#blocks[i]!\n const last = serial32Add(block.start, block.length - 1)\n if (isoBlocksBehindLowerBound(last, position, this.#kMax) > horizon) {\n retired.push(block.start)\n this.#blocks.splice(i, 1)\n }\n }\n for (const ssId of [...this.#held.keys()]) {\n if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon) this.#held.delete(ssId)\n }\n return retired\n }\n\n /** Forget everything (publisher restart, track reset). */\n reset(): void {\n this.#blocks.length = 0\n this.#held.clear()\n this.#coverageEnd = undefined\n this.#newest = undefined\n }\n\n #claim(block: IsoBlockRange): IsoPlacedSource<T>[] {\n const placed: IsoPlacedSource<T>[] = []\n for (const [ssId, payload] of this.#held) {\n if (!isoBlockContains(block.start, block.length, ssId)) continue\n placed.push({ ssId, esi: serial32Distance(block.start, ssId), payload })\n this.#held.delete(ssId)\n }\n placed.sort((x, y) => x.esi - y.esi)\n return placed\n }\n}\n", "// Typed FEC state shared by every player (hang/moq-lib, moqtail, Shaka MSF).\n//\n// A player's FEC counters are `null` in three unrelated situations: the\n// session is on reliable MoQ, where FEC has nothing to protect (FEC protects\n// only lossy multicast/AMT datagrams); no multicast bridge exists, so no lossy\n// leg can be joined; or the catalog declares no FEC for the tracks the player\n// selected. `null` also meant \"nothing decoded yet\" and \"the pipeline broke\",\n// so an idle session and a broken one looked identical.\n//\n// `resolveFecState` names which case holds. Each reason is read from a fact the\n// player already owns -- its catalog, its transport policy, its bridge probe\n// and its physical subscription -- and never from counters, console text,\n// timers or elapsed time. The rules live here so the three players cannot\n// disagree about precedence.\n\nimport { isFecBlockProfile } from '@blockcast/mmt-base'\n\n/**\n * Minimum valid FEC interleave depth in milliseconds, per spec \u00A75.1\n * (the interactive/gaming floor; broadcast/ATSC 3.0 ranges up to 10000ms).\n *\n * A protocol constant, NOT a tuning knob. Values below this floor are almost\n * certainly a raw frame count (e.g. `2`) that a caller passed when no framerate\n * was available to convert to ms (common on audio tracks). Feeding such a value\n * into computeFecTimeout treats it as `2ms`, yielding a near-zero FEC block\n * timeout that abandons startup FEC blocks before their repair symbols arrive\n * (measured: moqtail startupFailed=4, shaka dropped=5.8% on startup). Timing\n * derivations MUST reject sub-floor values and fall back to the established\n * timeout instead of collapsing.\n */\nexport const MIN_INTERLEAVE_DEPTH_MS = 33\n\n/** Why FEC is not protecting this session. */\nexport type FecInactiveReason = 'no-catalog-fec' | 'reliable-moq' | 'no-multicast-bridge'\n\n/** Every inactive reason, in resolution order. */\nexport const FEC_INACTIVE_REASONS: readonly FecInactiveReason[] = Object.freeze([\n 'no-catalog-fec',\n 'reliable-moq',\n 'no-multicast-bridge',\n] as const)\n\n/** The transport policy vocabulary every player accepts. */\nexport type FecTransportPolicy = 'auto' | 'moq-only' | 'multicast-only'\n\nconst TRANSPORT_POLICIES: ReadonlySet<string> = new Set<FecTransportPolicy>(['auto', 'moq-only', 'multicast-only'])\n\n/** FEC is not protecting this session; `reason` says why. Carries no counters. */\nexport interface FecInactiveState {\n readonly state: 'inactive'\n readonly reason: FecInactiveReason\n}\n\n/**\n * FEC is the protection in force for this session's delivery path.\n *\n * Flat on purpose: the player's existing counters stay at the paths every\n * consumer already reads (`fec.blocksRecovered`, `fec.tracks`, `fec.blocks`),\n * with `state` added beside them. A consumer that ignores `state` reads an\n * active report exactly as before and an inactive one as \"no counters\", which\n * is what `null` meant to it.\n */\nexport type FecActiveState<S extends object> = S & { readonly state: 'active' }\n\nexport type FecState<S extends object> = FecActiveState<S> | FecInactiveState\n\n/** An active state whose decoder has not reported any counter yet. */\nexport type NoFecCounters = Record<never, never>\n\n/** The authoritative facts `resolveFecState` reads. */\nexport interface FecStateEvidence<S extends object> {\n /**\n * The counters the player reports today, or null/undefined when it has none.\n * Payload only: it never decides the state, because moqtail and Shaka report\n * a zeroed snapshot as soon as a MulticastManager exists, which is before any\n * bridge or subscription does.\n */\n readonly stats: S | null | undefined\n /**\n * Whether the catalog declares FEC (see `catalogDeclaresFec`) for at least\n * one media track the player selected.\n */\n readonly catalogFec: boolean\n /** The player's configured transport policy. */\n readonly transportPolicy: FecTransportPolicy\n /** Whether a multicast bridge (`window.multicast`) is available to the page. */\n readonly bridgeAvailable: boolean\n /**\n * Whether a physical multicast/AMT subscription is live: the bridge accepted\n * it and it has not been closed. A requested leg is not a joined one.\n */\n readonly multicastLegJoined: boolean\n}\n\nconst INACTIVE: Readonly<Record<FecInactiveReason, FecInactiveState>> = Object.freeze({\n 'no-catalog-fec': Object.freeze({ state: 'inactive', reason: 'no-catalog-fec' }),\n 'reliable-moq': Object.freeze({ state: 'inactive', reason: 'reliable-moq' }),\n 'no-multicast-bridge': Object.freeze({ state: 'inactive', reason: 'no-multicast-bridge' }),\n})\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n}\n\nfunction isPositiveSafeInteger(value: unknown): boolean {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\n\n/**\n * Whether one catalog `fec` block (a hang rendition's `fec` or an MSF/CMSF\n * track's `fec`) declares an FEC instance a receiver can configure: source\n * symbols per block (K) and a symbol size (T) are both positive integers, the\n * algorithm is not `none`, and `blockProfile` names a known block profile\n * (BLO-35597). Total: it never throws, and answers false for any other value.\n *\n * `resolveCatalogTrackFec` reads this predicate, so the players' parameters and\n * this state never disagree. Of the blocks this rejects, it returns no FEC\n * geometry only for `algorithm: \"none\"`. One with any other algorithm is a\n * malformed catalog (`FecCatalogConfig` marks both fields REQUIRED; the MSF\n * schema and `validateCmsfMmtpCatalog` enforce them on `tracks[]`, but the\n * hang dialect's `renditions` schema is permissive, so this is where a hang\n * block fails closed), and `resolveCatalogTrackFec` throws a `RepairContractError` for\n * it (`track_fec` unless another repair-contract violation fires first),\n * whether or not the source has repair layers, rather than silently dropping\n * the track's repair subscription.\n *\n * The block profile is part of this predicate because the profile selects the\n * block math and is never defaulted: without a known one no receiver can\n * configure the instance. `resolveCatalogTrackFec` resolves such a track to no\n * FEC with a typed `fecUnavailable` reason (`track_fec_block_profile`), scoped\n * to that track, so its parameters and this state still agree. The CMSF\n * conformance validator (`validateCmsfMmtpCatalog`) still rejects the catalog\n * with `track_fec_block_profile`. An `iso-ssbg1-v1` instance without a usable\n * block span (`isoBlockSpanUsable`) is likewise no FEC for its track alone,\n * with `fecUnavailable` code `track_fec` on its `interleaveDepthMs`.\n *\n * The repair-contract resolvers (`resolveRepairLayers`) gate a `depends` target\n * on `algorithm` alone and never read a source's `symbolSize`. They check its\n * `sourceSymbols` for the RaptorQ K bound, and require a positive integer\n * `sourceSymbols` and `repairSymbols` only when the source has repair layers or\n * an overlay beyond layer 0.\n */\nexport function catalogDeclaresFec(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['algorithm'] === 'none') return false\n return (\n isPositiveSafeInteger(block['sourceSymbols']) &&\n isPositiveSafeInteger(block['symbolSize']) &&\n isFecBlockProfile(block['blockProfile']) &&\n isoBlockSpanUsable(block)\n )\n}\n\n/**\n * Whether a `fec` block that names `iso-ssbg1-v1` carries the block span its\n * block location is bounded by: `interleaveDepthMs`, a finite number of\n * milliseconds no smaller than `MIN_INTERLEAVE_DEPTH_MS`. Without it a receiver\n * cannot bound the sources it holds before a repair states their block, so the\n * instance cannot be configured (BLO-35597). Any other block needs no span here.\n */\nexport function isoBlockSpanUsable(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['blockProfile'] !== 'iso-ssbg1-v1') return true\n const ms = block['interleaveDepthMs']\n return typeof ms === 'number' && Number.isFinite(ms) && ms >= MIN_INTERLEAVE_DEPTH_MS\n}\n\nfunction requireBoolean(value: unknown, field: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`resolveFecState: ${field} must be a boolean, got ${typeof value}`)\n }\n return value\n}\n\n/**\n * Resolve a player's FEC state. First match wins:\n *\n * 1. The catalog declares no FEC for a selected track: `no-catalog-fec`. FEC\n * geometry comes only from the catalog, so there is nothing to configure on\n * any path.\n * 2. The policy is `moq-only`: `reliable-moq`. Media travels only over QUIC,\n * which is reliable, so FEC has no loss to repair, bridge or not.\n * 3. No multicast bridge: `no-multicast-bridge`. No lossy leg can exist, and on\n * `auto` this is the reason the session is on reliable MoQ.\n * 4. The policy is `multicast-only`, or a lossy subscription is live: active.\n * `multicast-only` commits the session to the lossy leg, so FEC is in force\n * before the join completes; whether the join succeeded is the physical\n * attestation's question, not this one. The counters may be absent until\n * the decoder reports.\n * 5. Otherwise (`auto`, a bridge, no live subscription): `reliable-moq`.\n *\n * Throws on malformed evidence rather than picking a reason for it.\n */\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S> & { readonly stats: S }): FecState<S>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters> {\n const catalogFec = requireBoolean(evidence.catalogFec, 'catalogFec')\n const bridgeAvailable = requireBoolean(evidence.bridgeAvailable, 'bridgeAvailable')\n const multicastLegJoined = requireBoolean(evidence.multicastLegJoined, 'multicastLegJoined')\n const { transportPolicy, stats } = evidence\n if (typeof transportPolicy !== 'string' || !TRANSPORT_POLICIES.has(transportPolicy)) {\n throw new TypeError(`resolveFecState: unsupported transportPolicy ${JSON.stringify(transportPolicy)}`)\n }\n if (stats !== null && stats !== undefined && recordOf(stats) === undefined) {\n throw new TypeError('resolveFecState: stats must be a plain object, null or undefined')\n }\n\n if (!catalogFec) return INACTIVE['no-catalog-fec']\n if (transportPolicy === 'moq-only') return INACTIVE['reliable-moq']\n if (!bridgeAvailable) return INACTIVE['no-multicast-bridge']\n if (transportPolicy === 'multicast-only' || multicastLegJoined) {\n // The discriminant is written last so a stats field of the same name can\n // never masquerade as a state.\n return { ...(stats ?? {}), state: 'active' } as FecActiveState<S | NoFecCounters>\n }\n return INACTIVE['reliable-moq']\n}\n", "/**\n * Keyframe-protected FEC repair policy.\n *\n * Every FEC block still gets in-band FEC decode. What this module decides is\n * (1) which blocks may hold a keyframe (RAP), so a block short of K at its\n * repair deadline can be repaired over QUIC with the right priority, and\n * (2) what a sink must do with the first frame released after a gap: stall\n * until the next RAP, or conceal and keep playing.\n *\n * The two decisions are deliberately independent. Classification only sets\n * repair priority. The stall/conceal decision is made again per released gap\n * from the media that is actually missing, so a wrong classification can only\n * lower a block's repair priority; it can never make playback wrong.\n *\n * Everything here is derived from catalog-authored timing and in-band MMTP\n * evidence. There are no timers, no cadence heuristics, and no defaults: a\n * track whose geometry cannot be derived exactly runs in the defined\n * fail-closed mode `all-protected`, where every block is treated as possibly\n * keyframe-bearing.\n *\n * All three players (moqtail, hang, Shaka) consume this one implementation.\n */\n\nimport { deriveFecInterleaveDepth } from '@blockcast/mmt-fec'\n\n// \u2500\u2500\u2500 Classes and priorities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Repair class of one FEC block.\n *\n * - `keyframe`: the block holds (or held) a RAP / MPU start.\n * - `delta`: the block provably holds only non-RAP video frames.\n * - `unknown`: neither could be proven; treated exactly like `keyframe`.\n * - `audio`: an audio block.\n */\nexport type BlockClass = 'keyframe' | 'delta' | 'unknown' | 'audio'\n\n/** Repair priority. Lower is more urgent. */\nexport type RepairPriority = 0 | 1 | 2\n\nexport type TrackKind = 'video' | 'audio'\n\n/** P0 = keyframe/unknown (stall-preventing), P1 = audio, P2 = delta. */\nexport function repairPriorityForClass(blockClass: BlockClass): RepairPriority {\n switch (blockClass) {\n case 'keyframe':\n case 'unknown':\n return 0\n case 'audio':\n return 1\n case 'delta':\n return 2\n }\n}\n\n// \u2500\u2500\u2500 Geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type ClassifierMode = 'classifying' | 'all-protected'\n\n/** Why a track runs `all-protected`. */\nexport type ClassifierReason =\n // Geometry (catalog) reasons.\n | 'framerate-absent'\n | 'keyframe-cadence-absent'\n | 'keyframe-interval-not-integral'\n | 'block-spans-gop'\n /**\n * The catalog keyframe interval is not an exact integer number of ticks\n * in the media track's own timescale (ISO-BMFF `mdhd`), so released RAP\n * spacing cannot be verified and hold-last cannot predict the next RAP.\n * Nothing is wrong with the stream; the cadence is simply unprovable.\n */\n | 'keyframe-interval-not-media-integral'\n /**\n * Another stream on this packetId was configured with its own geometry\n * and this one was not, so no geometry provably belongs to it (ABR rungs\n * sharing a packetId).\n */\n | 'stream-geometry-absent'\n // In-band invariant violations (positive evidence only).\n | 'rap-not-mpu-start'\n | 'mpu-longer-than-gop'\n | 'block-not-contiguous'\n | 'rap-spacing-mismatch'\n | 'mpu-multiple-fragments'\n | 'block-wider-than-interleave'\n\n/** Raw catalog timing for one track. Never a resolved cadence. */\nexport interface BlockClassifierGeometryInput {\n readonly trackKind: TrackKind\n /** Catalog `framerate` (frames per second). */\n readonly framerate?: number\n /** Catalog `fec.interleaveDepthMs`: the full FEC block span (\u00A75.1). */\n readonly interleaveDepthMs: number\n /** Catalog track `timescale` for `keyframeIntervalTicks`. */\n readonly timescale?: number\n /** Catalog `keyframeIntervalTicks` (preferred when the timescale is present). */\n readonly keyframeIntervalTicks?: number\n /** Catalog `keyframeIntervalMs`. */\n readonly keyframeIntervalMs?: number\n}\n\n/** Exact keyframe interval as a rational number of seconds: ticks / timescale. */\nexport interface KeyframeInterval {\n readonly ticks: number\n readonly timescale: number\n}\n\nexport type BlockClassifierGeometry =\n | { readonly trackKind: 'audio' }\n | {\n readonly trackKind: 'video'\n readonly mode: 'classifying'\n /** F: one frame's duration in milliseconds (1000 / framerate). */\n readonly frameDurationMs: number\n /** D: media units per FEC block (deriveFecInterleaveDepth). */\n readonly interleaveUnits: number\n /** G: frames per GOP. */\n readonly gopFrames: number\n readonly keyframeInterval: KeyframeInterval\n }\n | {\n readonly trackKind: 'video'\n readonly mode: 'all-protected'\n readonly reason: ClassifierReason\n }\n\n/**\n * Integer value of `numerator / denominator` when it is one, else null.\n *\n * Catalog framerates are JSON numbers, so an NTSC rate arrives as the double\n * nearest 30000/1001. One multiply and one divide each add at most half an\n * ulp of relative error (IEEE-754), so a quotient within two ulps of an\n * integer IS that integer; anything further is genuinely non-integral.\n */\nfunction exactInteger(numerator: number, denominator: number): number | null {\n if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator === 0) {\n return null\n }\n const quotient = numerator / denominator\n const rounded = Math.round(quotient)\n if (rounded < 1) return null\n const representationError = 2 * Number.EPSILON * Math.abs(quotient)\n return Math.abs(quotient - rounded) <= representationError ? rounded : null\n}\n\nfunction isPositiveFinite(value: number | undefined): value is number {\n return value !== undefined && Number.isFinite(value) && value > 0\n}\n\n/**\n * Derive the per-track classifier geometry from raw catalog fields.\n *\n * `all-protected` is the policy's defined fail-closed state, not a default:\n * it is returned with the exact reason. The only throw is for a value the\n * catalog validator already rejects (a non-positive block span).\n */\nexport function deriveBlockClassifierGeometry(\n input: BlockClassifierGeometryInput,\n): BlockClassifierGeometry {\n if (!isPositiveFinite(input.interleaveDepthMs)) {\n throw new Error(\n `[BlockPolicy] interleaveDepthMs=${input.interleaveDepthMs} is not a positive block span; ` +\n 'the catalog validator rejects this',\n )\n }\n if (input.trackKind === 'audio') return { trackKind: 'audio' }\n\n if (!isPositiveFinite(input.framerate)) {\n return { trackKind: 'video', mode: 'all-protected', reason: 'framerate-absent' }\n }\n const framerate = input.framerate\n\n let keyframeInterval: KeyframeInterval\n if (isPositiveFinite(input.keyframeIntervalTicks) && isPositiveFinite(input.timescale)) {\n keyframeInterval = { ticks: input.keyframeIntervalTicks, timescale: input.timescale }\n } else if (isPositiveFinite(input.keyframeIntervalMs)) {\n keyframeInterval = { ticks: input.keyframeIntervalMs, timescale: 1000 }\n } else {\n return { trackKind: 'video', mode: 'all-protected', reason: 'keyframe-cadence-absent' }\n }\n\n const gopFrames = exactInteger(keyframeInterval.ticks * framerate, keyframeInterval.timescale)\n if (gopFrames === null) {\n return {\n trackKind: 'video',\n mode: 'all-protected',\n reason: 'keyframe-interval-not-integral',\n }\n }\n\n const frameDurationMs = 1000 / framerate\n const interleaveUnits = deriveFecInterleaveDepth(input.interleaveDepthMs, frameDurationMs)\n if (interleaveUnits >= gopFrames) {\n return { trackKind: 'video', mode: 'all-protected', reason: 'block-spans-gop' }\n }\n\n return {\n trackKind: 'video',\n mode: 'classifying',\n frameDurationMs,\n interleaveUnits,\n gopFrames,\n keyframeInterval,\n }\n}\n\n/**\n * The keyframe interval expressed in a media track's timescale, or null when\n * the geometry is not `classifying` or the interval is not an exact integer\n * number of media ticks.\n */\nexport function keyframeIntervalMediaTicks(\n geometry: BlockClassifierGeometry,\n mediaTimescale: number,\n): bigint | null {\n if (geometry.trackKind !== 'video' || geometry.mode !== 'classifying') return null\n if (!Number.isSafeInteger(mediaTimescale) || mediaTimescale <= 0) return null\n const { ticks, timescale } = geometry.keyframeInterval\n if (!Number.isSafeInteger(ticks) || !Number.isSafeInteger(timescale)) return null\n const scaled = BigInt(ticks) * BigInt(mediaTimescale)\n const divisor = BigInt(timescale)\n if (scaled % divisor !== 0n) return null\n return scaled / divisor\n}\n\n// \u2500\u2500\u2500 Evidence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * In-band evidence from ONE first-arrival source symbol received from the\n * network. Recovered symbols and repair symbols never contribute, and the\n * MFU `priority > 0x80` hint is not evidence.\n */\nexport interface SourceSymbolEvidence {\n /** The MMTP header R (RAP) bit. */\n readonly rap: boolean\n /** MPU_sequence_number, when parsed from this packet. */\n readonly mpu?: number\n /**\n * 0-based sample position within the MPU: the MFU DU-header\n * `sample_number` minus one (ISO/IEC 23008-1 numbers samples from 1, and\n * the parser rejects `sample_number` 0). Present only when this packet\n * carried a DU header.\n */\n readonly idx?: number\n /** movie_fragment_sequence_number from the DU header, when present. */\n readonly movieFragment?: number\n /**\n * DU-header `offset`: the data unit's authored byte position within its\n * sample (0 for a whole-sample data unit, the partition boundary for a\n * subsample data unit). Present only with `idx`; absent reads as 0.\n */\n readonly offset?: number\n /**\n * The MMTP header packet_sequence_number (contiguous per packet_id). Not\n * classification evidence: FECManager uses it to bound how many blocks the\n * producer can have started between two source symbols, so an outage is\n * never mistaken for a source restart.\n */\n readonly packetSequenceNumber?: number\n}\n\nexport type ReclassificationEvidence = 'rap' | 'mpu-span' | 'window' | 'classifier-disabled'\n\nexport type ClassifierChange =\n | {\n readonly kind: 'reclassified'\n readonly sbn: number\n readonly from: BlockClass\n readonly to: BlockClass\n readonly evidence: ReclassificationEvidence\n }\n | {\n readonly kind: 'mode'\n readonly mode: 'all-protected'\n readonly reason: ClassifierReason\n }\n\ninterface BlockEvidence {\n rapSeen: boolean\n mpus: Set<number>\n idxMin?: number\n idxMax?: number\n cls: BlockClass\n}\n\n/** A data-unit start (a packet carrying a DU header) and the block it is in. */\ninterface DataUnitStart {\n readonly movieFragment: number\n readonly idx: number\n readonly offset: number\n readonly sbn: number\n}\n\n/** Authored order of two data-unit starts of one MPU. */\nfunction compareDataUnitStarts(\n a: Omit<DataUnitStart, 'sbn'>,\n b: Omit<DataUnitStart, 'sbn'>,\n): number {\n return a.movieFragment - b.movieFragment || a.idx - b.idx || a.offset - b.offset\n}\n\n/**\n * Per-stream block classifier.\n *\n * Classes only move toward more protection or narrower evidence:\n * `unknown\u2192delta` and `unknown\u2192keyframe` / `delta\u2192keyframe` on evidence, and\n * `delta\u2192unknown` when an invariant disables classification. `keyframe` is\n * terminal.\n *\n * The delta window rule rests on three premises, each guarded by an\n * invariant that fires on positive evidence only:\n *\n * - P1: the source packets of one block are one contiguous run of the\n * stream's authored order, and blocks follow each other in SBN order\n * (`block-not-contiguous`). The producer assigns SS_IDs in authored order\n * and a block is the SS_ID range [SBN*K, SBN*K + K), so this holds for a\n * block that closes at K symbols in the middle of a frame: that frame's\n * fragments then land in two ADJACENT blocks, which is still one run each.\n * A frame split across adjacent blocks is not a violation.\n * - P2: that run touches at most D media units, counting a frame continued\n * from the previous block (`block-wider-than-interleave`). The producer\n * counts a media-unit boundary only once the open block holds a symbol.\n * - P3: an MPU starts with its only RAP and holds at most G frames\n * (`rap-not-mpu-start`, `mpu-longer-than-gop`, `rap-spacing-mismatch`), in\n * one movie fragment (`mpu-multiple-fragments`).\n *\n * P1 and P2 are what make a block's frames contiguous and lie within\n * [idxMax \u2212 D + 1, idxMin + D \u2212 1] of its MPU. What refutes P1 is a pair of\n * source packets whose wire-stated authored order and SBN order disagree:\n * a later MPU in an earlier block, a later data unit of one MPU (by\n * movie_fragment_sequence_number, sample_number, then DU offset) in an\n * earlier block, or one data unit in two blocks. That is the signature of a\n * round-robin interleave, which scatters one run over several concurrent\n * blocks. Non-adjacency alone is not: a sample larger than K packets\n * legitimately spans blocks SBN..SBN+n.\n */\nexport class BlockClassifier {\n #geometry: BlockClassifierGeometry\n #mode: ClassifierMode\n #reason: ClassifierReason | null\n /**\n * False once the stream showed that an MPU boundary does not bound its\n * RAPs (a RAP inside an MPU, or an MPU longer than a GOP). Recorded in\n * every mode: the stall/conceal decision relies on it even when the\n * catalog geometry already runs the classifier `all-protected`.\n */\n #mpuBoundsRaps = true\n #blocks = new Map<number, BlockEvidence>()\n /**\n * P1 ledger, kept for the MPUs within the classification horizon (the\n * highest MPU seen and the one before it): the SBN span of every source\n * packet of each MPU, and each MPU's data-unit starts in authored order.\n * Valid only within one continuous SBN sequence; a source discontinuity\n * clears it (`noteSourceDiscontinuity`).\n */\n #mpuSbnSpans = new Map<number, { min: number; max: number }>()\n #dataUnitStarts = new Map<number, DataUnitStart[]>()\n #highestMpu: number | null = null\n /** MPU \u2192 movie_fragment_sequence_number observed in it. */\n #mpuFragments = new Map<number, number>()\n #lastRap: { mpu: number; presentationTime: bigint; mediaTimescale: number } | null = null\n\n constructor(geometry: BlockClassifierGeometry) {\n this.#geometry = geometry\n this.#mode = 'classifying'\n this.#reason = null\n this.reset(geometry)\n }\n\n get geometry(): BlockClassifierGeometry {\n return this.#geometry\n }\n\n get mode(): ClassifierMode {\n return this.#mode\n }\n\n get reason(): ClassifierReason | null {\n return this.#reason\n }\n\n /**\n * Whether \"same MPU on both sides of a gap\" still proves the gap held no\n * RAP. It stops holding for the rest of the epoch once a RAP was received\n * inside an MPU or an MPU ran longer than a GOP.\n */\n get mpuBoundsRaps(): boolean {\n return this.#mpuBoundsRaps\n }\n\n /** Begin a fresh epoch (catalog change or track reset). */\n reset(geometry: BlockClassifierGeometry = this.#geometry): void {\n this.#geometry = geometry\n this.#blocks.clear()\n this.#clearContiguityLedger()\n this.#mpuFragments.clear()\n this.#lastRap = null\n this.#mpuBoundsRaps = true\n if (geometry.trackKind === 'video' && geometry.mode === 'all-protected') {\n this.#mode = 'all-protected'\n this.#reason = geometry.reason\n } else {\n this.#mode = 'classifying'\n this.#reason = null\n }\n }\n\n /**\n * Retire this classifier (its stream moves to a new catalog epoch). Every\n * block it had classified `delta` loses the evidence behind that class and\n * reads `unknown` from now on; the returned changes say so.\n */\n retire(): ClassifierChange[] {\n const changes: ClassifierChange[] = []\n for (const [sbn, block] of this.#blocks) {\n if (block.cls !== 'delta') continue\n block.cls = 'unknown'\n changes.push({\n kind: 'reclassified',\n sbn,\n from: 'delta',\n to: 'unknown',\n evidence: 'classifier-disabled',\n })\n }\n return changes\n }\n\n /**\n * The stream's SBN sequence restarted (FECManager source discontinuity:\n * a publisher restart, or the SS_ID space wrapped). Authored order and SBN\n * order are comparable only within one continuous sequence, so the P1\n * ledger starts again.\n */\n noteSourceDiscontinuity(): void {\n this.#clearContiguityLedger()\n }\n\n /** Register a block that exists without evidence (a vanished block). */\n touch(sbn: number): void {\n this.#blockFor(sbn)\n }\n\n /** Forget a resolved block. */\n forget(sbn: number): void {\n this.#blocks.delete(sbn)\n }\n\n classOf(sbn: number): BlockClass {\n if (this.#geometry.trackKind === 'audio') return 'audio'\n return this.#blocks.get(sbn)?.cls ?? 'unknown'\n }\n\n /** Record one first-arrival source symbol's evidence for block `sbn`. */\n observe(sbn: number, evidence: SourceSymbolEvidence): ClassifierChange[] {\n if (this.#geometry.trackKind === 'audio') return []\n const changes: ClassifierChange[] = []\n const block = this.#blockFor(sbn)\n\n if (evidence.rap) block.rapSeen = true\n if (evidence.mpu !== undefined) block.mpus.add(evidence.mpu)\n if (evidence.idx !== undefined) {\n block.idxMin = block.idxMin === undefined ? evidence.idx : Math.min(block.idxMin, evidence.idx)\n block.idxMax = block.idxMax === undefined ? evidence.idx : Math.max(block.idxMax, evidence.idx)\n }\n\n // A RAP inside an MPU refutes \"an MPU boundary bounds RAPs\" whatever the\n // classifier mode, because gap decisions rely on it in every mode.\n if (evidence.rap && evidence.idx !== undefined && evidence.idx !== 0) {\n this.#mpuBoundsRaps = false\n }\n\n const violation = this.#checkInvariants(sbn, block, evidence)\n if (violation !== null) changes.push(...this.#disable(violation))\n\n const next = this.#classify(block)\n if (next !== block.cls && this.#allowed(block.cls, next)) {\n changes.push({\n kind: 'reclassified',\n sbn,\n from: block.cls,\n to: next,\n evidence: this.#evidenceFor(block, next),\n })\n block.cls = next\n }\n return changes\n }\n\n /**\n * Record a RAP frame released in order. Released MPU-start RAPs of MPUs m\n * and m+n must be exactly n keyframe intervals apart in the media\n * timescale, compared as integers.\n *\n * Comparing only consecutive MPUs would miss exactly the case the window\n * rule cannot survive: a short MPU whose successor's RAP was lost (that\n * RAP-bearing block is the one a wrong G calls delta), because the next\n * released RAP then belongs to m+2. Every MPU is one interval long, so the\n * span to any later released RAP proves or refutes all MPUs in between.\n */\n noteReleasedRap(\n mpu: number,\n presentationTime: bigint,\n mediaTimescale: number,\n ): ClassifierChange[] {\n if (this.#geometry.trackKind === 'audio' || this.#mode === 'all-protected') {\n return []\n }\n // Verification compares integers in the media timescale. When the\n // catalog interval is not an exact tick count there, spacing is\n // unprovable: fail closed, naming the cause, rather than blaming the\n // encoder with rap-spacing-mismatch on a perfectly spaced RAP.\n const interval = keyframeIntervalMediaTicks(this.#geometry, mediaTimescale)\n if (interval === null) return this.#disable('keyframe-interval-not-media-integral')\n const previous = this.#lastRap\n // A further RAP inside an already-anchored MPU is not an MPU start; the\n // MPU keeps its first RAP as the spacing anchor (rap-not-mpu-start owns\n // mid-MPU RAPs).\n if (previous !== null && previous.mpu === mpu && previous.mediaTimescale === mediaTimescale) {\n return []\n }\n this.#lastRap = { mpu, presentationTime, mediaTimescale }\n if (\n previous === null ||\n mpu < previous.mpu ||\n previous.mediaTimescale !== mediaTimescale\n ) {\n return []\n }\n if (\n presentationTime - previous.presentationTime === interval * BigInt(mpu - previous.mpu)\n ) {\n return []\n }\n return this.#disable('rap-spacing-mismatch')\n }\n\n #blockFor(sbn: number): BlockEvidence {\n let block = this.#blocks.get(sbn)\n if (!block) {\n block = { rapSeen: false, mpus: new Set(), cls: 'unknown' }\n this.#blocks.set(sbn, block)\n }\n return block\n }\n\n #checkInvariants(\n sbn: number,\n block: BlockEvidence,\n evidence: SourceSymbolEvidence,\n ): ClassifierReason | null {\n if (this.#mode === 'all-protected' || this.#geometry.trackKind !== 'video') return null\n const geometry = this.#geometry\n if (geometry.mode !== 'classifying') return null\n if (evidence.rap && evidence.idx !== undefined && evidence.idx !== 0) {\n return 'rap-not-mpu-start'\n }\n if (evidence.idx !== undefined && evidence.idx >= geometry.gopFrames) {\n return 'mpu-longer-than-gop'\n }\n // A block closes at the latest after D media units, so the DU headers it\n // carries from one MPU lie within D consecutive positions. A wider span\n // means the producer closes blocks some other way (at K symbols, or\n // round-robin across concurrent blocks) and the window rule is void.\n if (\n block.mpus.size === 1 &&\n block.idxMin !== undefined &&\n block.idxMax !== undefined &&\n block.idxMax - block.idxMin >= geometry.interleaveUnits\n ) {\n return 'block-wider-than-interleave'\n }\n if (evidence.mpu !== undefined && evidence.movieFragment !== undefined) {\n const fragment = this.#mpuFragments.get(evidence.mpu)\n if (fragment === undefined) {\n this.#mpuFragments.set(evidence.mpu, evidence.movieFragment)\n // One entry per MPU still inside the classification horizon.\n for (const mpu of this.#mpuFragments.keys()) {\n if (mpu + 1 < evidence.mpu) this.#mpuFragments.delete(mpu)\n }\n } else if (fragment !== evidence.movieFragment) {\n // sample_number is then a position within a movie fragment, not\n // within the MPU, and the window rule's geometry does not hold.\n return 'mpu-multiple-fragments'\n }\n }\n if (evidence.mpu !== undefined && this.#contradictsAuthoredOrder(sbn, evidence.mpu, evidence)) {\n return 'block-not-contiguous'\n }\n return null\n }\n\n #clearContiguityLedger(): void {\n this.#mpuSbnSpans.clear()\n this.#dataUnitStarts.clear()\n this.#highestMpu = null\n }\n\n /**\n * P1 check for one source packet of MPU `mpu` in block `sbn`, then record\n * it. True when some recorded packet's authored order and SBN order\n * disagree with this one's (see the class comment). Only positions the\n * wire states are compared: MPU order for every packet, and data-unit\n * order for packets that carry a DU header. A packet of one data unit\n * that continues into the next block carries no DU header and so only\n * takes part through its MPU, which is exactly what makes an adjacent\n * straddle consistent.\n */\n #contradictsAuthoredOrder(sbn: number, mpu: number, evidence: SourceSymbolEvidence): boolean {\n // Every packet of MPU m precedes every packet of MPU m' > m.\n for (const [other, span] of this.#mpuSbnSpans) {\n if (other < mpu && span.max > sbn) return true\n if (other > mpu && span.min < sbn) return true\n }\n const inHorizon = this.#highestMpu === null || mpu + 1 >= this.#highestMpu\n let insertAt = -1\n let starts: DataUnitStart[] | undefined\n let start: DataUnitStart | undefined\n if (evidence.idx !== undefined) {\n start = {\n movieFragment: evidence.movieFragment ?? 0,\n idx: evidence.idx,\n offset: evidence.offset ?? 0,\n sbn,\n }\n starts = this.#dataUnitStarts.get(mpu)\n if (starts !== undefined) {\n let lo = 0\n let hi = starts.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (compareDataUnitStarts(starts[mid]!, start) < 0) lo = mid + 1\n else hi = mid\n }\n const at = starts[lo]\n if (at !== undefined && compareDataUnitStarts(at, start) === 0) {\n // One data unit has one first packet: seen in another block, the\n // SBN-to-authored-order map is not a function.\n if (at.sbn !== sbn) return true\n } else {\n const before = starts[lo - 1]\n if (before !== undefined && before.sbn > sbn) return true\n if (at !== undefined && at.sbn < sbn) return true\n insertAt = lo\n }\n }\n }\n if (!inHorizon) return false\n const span = this.#mpuSbnSpans.get(mpu)\n if (span === undefined) this.#mpuSbnSpans.set(mpu, { min: sbn, max: sbn })\n else {\n if (sbn < span.min) span.min = sbn\n if (sbn > span.max) span.max = sbn\n }\n if (start !== undefined) {\n if (starts === undefined) this.#dataUnitStarts.set(mpu, [start])\n else if (insertAt >= 0) starts.splice(insertAt, 0, start)\n }\n if (this.#highestMpu === null || mpu > this.#highestMpu) {\n this.#highestMpu = mpu\n for (const other of [...this.#mpuSbnSpans.keys()]) {\n if (other + 1 < mpu) {\n this.#mpuSbnSpans.delete(other)\n this.#dataUnitStarts.delete(other)\n }\n }\n }\n return false\n }\n\n #disable(reason: ClassifierReason): ClassifierChange[] {\n if (reason === 'rap-not-mpu-start' || reason === 'mpu-longer-than-gop') {\n this.#mpuBoundsRaps = false\n }\n if (this.#mode === 'all-protected') return []\n this.#mode = 'all-protected'\n this.#reason = reason\n const changes: ClassifierChange[] = [{ kind: 'mode', mode: 'all-protected', reason }]\n for (const [sbn, block] of this.#blocks) {\n if (block.cls !== 'delta') continue\n block.cls = 'unknown'\n changes.push({\n kind: 'reclassified',\n sbn,\n from: 'delta',\n to: 'unknown',\n evidence: 'classifier-disabled',\n })\n }\n return changes\n }\n\n #classify(block: BlockEvidence): BlockClass {\n if (block.rapSeen || block.mpus.size >= 2) return 'keyframe'\n if (this.#mode === 'all-protected') return 'unknown'\n const geometry = this.#geometry\n if (geometry.trackKind !== 'video' || geometry.mode !== 'classifying') return 'unknown'\n if (\n block.mpus.size === 1 &&\n block.idxMin !== undefined &&\n block.idxMax !== undefined\n ) {\n // A block closes every D media units, so its frames are contiguous and\n // lie in [idxMax \u2212 D + 1, idxMin + D \u2212 1] of MPU m. Index 0 is the only\n // RAP of an MPU and G bounds its length.\n const span = geometry.interleaveUnits - 1\n if (block.idxMax - span >= 1 && block.idxMin + span <= geometry.gopFrames - 1) {\n return 'delta'\n }\n }\n return 'unknown'\n }\n\n #allowed(from: BlockClass, to: BlockClass): boolean {\n if (from === 'keyframe') return false\n if (from === 'unknown') return to === 'delta' || to === 'keyframe'\n if (from === 'delta') return to === 'keyframe'\n return false\n }\n\n #evidenceFor(block: BlockEvidence, to: BlockClass): ReclassificationEvidence {\n if (to === 'delta') return 'window'\n return block.rapSeen ? 'rap' : 'mpu-span'\n }\n}\n\n// \u2500\u2500\u2500 Stall vs conceal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface GapStreamIdentity {\n readonly packetId: number\n /** Non-empty catalog identity, or null when the producer has no track name. */\n readonly catalogTrackName: string | null\n}\n\n/** What the sink knows about one side of a released gap. */\nexport interface GapFrameEvidence {\n /**\n * Codec-aware RAP check: `MmtpContainerParser.isKeyframe(data, rapFlag,\n * codec)`, bitstream-only for recovered frames. Never the priority hint.\n */\n readonly rap: boolean\n /**\n * MPU_sequence_number of the frame. Omit it once the stream showed that\n * an MPU boundary does not bound its RAPs (`BlockClassifier.mpuBoundsRaps`):\n * same-MPU then no longer proves a gap lost only deltas.\n */\n readonly mpu?: number\n /** 0-based sample position within the MPU, when the geometry verifies it. */\n readonly idx?: number\n /** Authored presentation time in `timescale` ticks. */\n readonly pts?: bigint\n /** Authored sample duration in `timescale` ticks. */\n readonly durationTicks?: number\n /** The media timescale of `pts` and `durationTicks`. */\n readonly timescale?: number\n}\n\nexport type FrameGapAction = 'stall' | 'conceal'\n\nexport type FrameGapReason =\n | 'rap-lost'\n | 'rap-unknown'\n | 'discontinuity'\n | 'delta-lost'\n | 'rap-next'\n | 'audio-gap'\n | 'audio-discontinuity'\n\n/**\n * The decision attached to the first frame released after a gap\n * (`RoutedFrameMeta.gapBefore`). It is the same frozen object the manager\n * emits as its `gap` repair lifecycle event, so a sink that acts on the frame\n * and an observer that counts events see one fact.\n */\nexport interface FrameGapDecision {\n readonly version: 2\n readonly type: 'gap'\n readonly stream: GapStreamIdentity\n readonly trackKind: TrackKind\n readonly action: FrameGapAction\n readonly reason: FrameGapReason\n /** FEC blocks in the gap whose media was lost (failed or vanished). */\n readonly blocks: readonly number[]\n /** Missing video frames or audio access units, when exactly known. */\n readonly missingFrames: number | null\n /** Authored duration of the missing media in milliseconds, when known. */\n readonly missingMs: number | null\n /** Manager epoch of the stream when the decision was made. */\n readonly epoch: number\n}\n\nexport interface DecideGapInput {\n readonly stream: GapStreamIdentity\n readonly trackKind: TrackKind\n /** Last frame released before the gap. */\n readonly before: GapFrameEvidence\n /** First frame released after the gap. */\n readonly after: GapFrameEvidence\n /** The stream restarted (FEC SBN went backwards or the publisher reset). */\n readonly discontinuity: boolean\n readonly blocks: readonly number[]\n /**\n * The most media units the gap can hold: the FEC blocks spanned from the\n * frame before to the frame after, times D (media units per block,\n * ceil(interleaveDepthMs / unit duration), \u00A75.1). A block touches at most D\n * units whether it closes at D or at K symbols. Null when the span or D is\n * unknown. An audio PTS delta that implies more AUs than this is a clock\n * jump, not loss, and re-anchors instead of concealing every slot.\n */\n readonly maxMissingUnits: number | null\n readonly epoch: number\n}\n\nfunction durationMs(units: number, before: GapFrameEvidence): number | null {\n if (\n before.durationTicks === undefined ||\n before.timescale === undefined ||\n before.timescale <= 0\n ) {\n return null\n }\n return (units * before.durationTicks * 1000) / before.timescale\n}\n\n/**\n * Pure stall/conceal decision for the first frame released after a gap.\n *\n * Video stalls only when a RAP may be inside the gap: the frame after it is\n * not a RAP and either sits in another MPU (`rap-lost`) or the MPU cannot be\n * proven on both sides (`rap-unknown`), or the stream restarted\n * (`discontinuity`). A gap inside one MPU lost only deltas (`delta-lost`) and\n * a RAP after the gap resynchronises by itself (`rap-next`). Audio never\n * stalls: an exact gap is concealed per access unit (`audio-gap`), anything\n * else re-anchors at the next frame (`audio-discontinuity`).\n */\nexport function decideGap(input: DecideGapInput): FrameGapDecision {\n const { before, after } = input\n let action: FrameGapAction\n let reason: FrameGapReason\n let missingFrames: number | null = null\n\n if (input.trackKind === 'audio') {\n action = 'conceal'\n reason = 'audio-discontinuity'\n if (\n !input.discontinuity &&\n before.pts !== undefined &&\n after.pts !== undefined &&\n before.durationTicks !== undefined &&\n Number.isSafeInteger(before.durationTicks) &&\n before.durationTicks > 0\n ) {\n const auDuration = BigInt(before.durationTicks)\n const missingTicks = after.pts - before.pts - auDuration\n const bound = input.maxMissingUnits\n if (\n missingTicks > 0n &&\n missingTicks % auDuration === 0n &&\n bound !== null &&\n missingTicks / auDuration <= BigInt(bound)\n ) {\n reason = 'audio-gap'\n missingFrames = Number(missingTicks / auDuration)\n }\n }\n } else if (input.discontinuity) {\n action = 'stall'\n reason = 'discontinuity'\n } else if (after.rap) {\n action = 'conceal'\n reason = 'rap-next'\n } else if (before.mpu === undefined || after.mpu === undefined) {\n action = 'stall'\n reason = 'rap-unknown'\n } else if (before.mpu !== after.mpu) {\n action = 'stall'\n reason = 'rap-lost'\n } else {\n action = 'conceal'\n reason = 'delta-lost'\n if (before.idx !== undefined && after.idx !== undefined) {\n const missing = after.idx - before.idx - 1\n missingFrames = missing > 0 ? missing : null\n }\n }\n\n return Object.freeze({\n version: 2 as const,\n type: 'gap' as const,\n stream: Object.freeze({\n packetId: input.stream.packetId,\n catalogTrackName: input.stream.catalogTrackName,\n }),\n trackKind: input.trackKind,\n action,\n reason,\n blocks: Object.freeze([...input.blocks]),\n missingFrames,\n missingMs: missingFrames === null ? null : durationMs(missingFrames, before),\n epoch: input.epoch,\n })\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["deriveFecInterleaveDepth", "MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "MPU_HEADER_SIZE", "block", "span", "deriveFecInterleaveDepth"]
7
+ }