@blockcast/mmt-container 0.1.0-main.0399efef1f07

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 (694) hide show
  1. package/README.md +432 -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 +93 -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/browser-runtime.js +20087 -0
  22. package/dist/browser-runtime.js.map +7 -0
  23. package/dist/carp-catalog.d.ts +318 -0
  24. package/dist/carp-catalog.d.ts.map +1 -0
  25. package/dist/carp-catalog.js +873 -0
  26. package/dist/carp-catalog.js.map +7 -0
  27. package/dist/catalog-fec-params.d.ts +202 -0
  28. package/dist/catalog-fec-params.d.ts.map +1 -0
  29. package/dist/catalog-fec-params.js +568 -0
  30. package/dist/catalog-fec-params.js.map +7 -0
  31. package/dist/catalog-network-source.d.ts +19 -0
  32. package/dist/catalog-network-source.d.ts.map +1 -0
  33. package/dist/catalog-network-source.js +22 -0
  34. package/dist/catalog-network-source.js.map +7 -0
  35. package/dist/catalog-validator.d.ts +18 -0
  36. package/dist/catalog-validator.d.ts.map +1 -0
  37. package/dist/catalog-validator.js +7747 -0
  38. package/dist/catalog-validator.js.map +7 -0
  39. package/dist/cmaf-block-assembler.d.ts +217 -0
  40. package/dist/cmaf-block-assembler.d.ts.map +1 -0
  41. package/dist/cmaf-block-assembler.js +1769 -0
  42. package/dist/cmaf-block-assembler.js.map +7 -0
  43. package/dist/cmaf-group-batcher.d.ts +38 -0
  44. package/dist/cmaf-group-batcher.d.ts.map +1 -0
  45. package/dist/cmaf-group-batcher.js +195 -0
  46. package/dist/cmaf-group-batcher.js.map +7 -0
  47. package/dist/cmcd-producer.d.ts +104 -0
  48. package/dist/cmcd-producer.d.ts.map +1 -0
  49. package/dist/cmcd-producer.js +207 -0
  50. package/dist/cmcd-producer.js.map +7 -0
  51. package/dist/codec-utils.d.ts +248 -0
  52. package/dist/codec-utils.d.ts.map +1 -0
  53. package/dist/codec-utils.js +1268 -0
  54. package/dist/codec-utils.js.map +7 -0
  55. package/dist/fec-codec.d.ts +32 -0
  56. package/dist/fec-codec.d.ts.map +1 -0
  57. package/dist/fec-codec.js +47 -0
  58. package/dist/fec-codec.js.map +7 -0
  59. package/dist/fec-manager.d.ts +307 -0
  60. package/dist/fec-manager.d.ts.map +1 -0
  61. package/dist/fec-manager.js +1530 -0
  62. package/dist/fec-manager.js.map +7 -0
  63. package/dist/fec-starvation-detector.d.ts +59 -0
  64. package/dist/fec-starvation-detector.d.ts.map +1 -0
  65. package/dist/fec-starvation-detector.js +75 -0
  66. package/dist/fec-starvation-detector.js.map +7 -0
  67. package/dist/fec-worker-setup.d.ts +88 -0
  68. package/dist/fec-worker-setup.d.ts.map +1 -0
  69. package/dist/fec-worker-setup.js +614 -0
  70. package/dist/fec-worker-setup.js.map +7 -0
  71. package/dist/fmp4-completion.d.ts +30 -0
  72. package/dist/fmp4-completion.d.ts.map +1 -0
  73. package/dist/fmp4-completion.js +21 -0
  74. package/dist/fmp4-completion.js.map +7 -0
  75. package/dist/fmp4-reader.d.ts +37 -0
  76. package/dist/fmp4-reader.d.ts.map +1 -0
  77. package/dist/fmp4-reader.js +100 -0
  78. package/dist/fmp4-reader.js.map +7 -0
  79. package/dist/index.d.ts +47 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +380 -0
  82. package/dist/index.js.map +7 -0
  83. package/dist/iso-bmff-timing.d.ts +78 -0
  84. package/dist/iso-bmff-timing.d.ts.map +1 -0
  85. package/dist/iso-bmff-timing.js +618 -0
  86. package/dist/iso-bmff-timing.js.map +7 -0
  87. package/dist/loc.d.ts +68 -0
  88. package/dist/loc.d.ts.map +1 -0
  89. package/dist/loc.js +143 -0
  90. package/dist/loc.js.map +7 -0
  91. package/dist/manifest-auth-plan.d.ts +30 -0
  92. package/dist/manifest-auth-plan.d.ts.map +1 -0
  93. package/dist/manifest-auth-plan.js +1285 -0
  94. package/dist/manifest-auth-plan.js.map +7 -0
  95. package/dist/media-router.d.ts +251 -0
  96. package/dist/media-router.d.ts.map +1 -0
  97. package/dist/media-router.js +624 -0
  98. package/dist/media-router.js.map +7 -0
  99. package/dist/mfu-reassembler.d.ts +396 -0
  100. package/dist/mfu-reassembler.d.ts.map +1 -0
  101. package/dist/mfu-reassembler.js +3085 -0
  102. package/dist/mfu-reassembler.js.map +7 -0
  103. package/dist/mfu-reassembly-policy.d.ts +24 -0
  104. package/dist/mfu-reassembly-policy.d.ts.map +1 -0
  105. package/dist/mfu-reassembly-policy.js +99 -0
  106. package/dist/mfu-reassembly-policy.js.map +7 -0
  107. package/dist/mmtp-container-parser.d.ts +434 -0
  108. package/dist/mmtp-container-parser.d.ts.map +1 -0
  109. package/dist/mmtp-container-parser.js +6164 -0
  110. package/dist/mmtp-container-parser.js.map +7 -0
  111. package/dist/mmtp-container-parser.no-modules.js +8999 -0
  112. package/dist/mmtp-container-parser.no-modules.js.map +7 -0
  113. package/dist/mmtp-decode-pipeline.d.ts +132 -0
  114. package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
  115. package/dist/mmtp-decode-pipeline.js +6413 -0
  116. package/dist/mmtp-decode-pipeline.js.map +7 -0
  117. package/dist/mmtp-router.d.ts +390 -0
  118. package/dist/mmtp-router.d.ts.map +1 -0
  119. package/dist/mmtp-router.js +9242 -0
  120. package/dist/mmtp-router.js.map +7 -0
  121. package/dist/mmtp-to-cmaf.d.ts +181 -0
  122. package/dist/mmtp-to-cmaf.d.ts.map +1 -0
  123. package/dist/mmtp-to-cmaf.js +802 -0
  124. package/dist/mmtp-to-cmaf.js.map +7 -0
  125. package/dist/multicast-auth-catalog.d.ts +115 -0
  126. package/dist/multicast-auth-catalog.d.ts.map +1 -0
  127. package/dist/multicast-auth-catalog.js +202 -0
  128. package/dist/multicast-auth-catalog.js.map +7 -0
  129. package/dist/multicast-input.d.ts +107 -0
  130. package/dist/multicast-input.d.ts.map +1 -0
  131. package/dist/multicast-input.js +256 -0
  132. package/dist/multicast-input.js.map +7 -0
  133. package/dist/multicast-manager.d.ts +369 -0
  134. package/dist/multicast-manager.d.ts.map +1 -0
  135. package/dist/multicast-manager.js +13400 -0
  136. package/dist/multicast-manager.js.map +7 -0
  137. package/dist/multicast-transport.d.ts +61 -0
  138. package/dist/multicast-transport.d.ts.map +1 -0
  139. package/dist/multicast-transport.js +1665 -0
  140. package/dist/multicast-transport.js.map +7 -0
  141. package/dist/multicast-unwrap.d.ts +57 -0
  142. package/dist/multicast-unwrap.d.ts.map +1 -0
  143. package/dist/multicast-unwrap.js +384 -0
  144. package/dist/multicast-unwrap.js.map +7 -0
  145. package/dist/no-modules.d.ts +9 -0
  146. package/dist/no-modules.d.ts.map +1 -0
  147. package/dist/no-modules.js +8965 -0
  148. package/dist/no-modules.js.map +7 -0
  149. package/dist/phase14-catalog-override.d.ts +126 -0
  150. package/dist/phase14-catalog-override.d.ts.map +1 -0
  151. package/dist/phase14-catalog-override.js +155 -0
  152. package/dist/phase14-catalog-override.js.map +7 -0
  153. package/dist/repair-fetch.d.ts +58 -0
  154. package/dist/repair-fetch.d.ts.map +1 -0
  155. package/dist/repair-fetch.js +127 -0
  156. package/dist/repair-fetch.js.map +7 -0
  157. package/dist/repair-layers.d.ts +141 -0
  158. package/dist/repair-layers.d.ts.map +1 -0
  159. package/dist/repair-layers.js +303 -0
  160. package/dist/repair-layers.js.map +7 -0
  161. package/dist/sample-combiner.d.ts +212 -0
  162. package/dist/sample-combiner.d.ts.map +1 -0
  163. package/dist/sample-combiner.js +616 -0
  164. package/dist/sample-combiner.js.map +7 -0
  165. package/dist/signaling.d.ts +29 -0
  166. package/dist/signaling.d.ts.map +1 -0
  167. package/dist/signaling.js +232 -0
  168. package/dist/signaling.js.map +7 -0
  169. package/dist/source-auth.d.ts +2 -0
  170. package/dist/source-auth.d.ts.map +1 -0
  171. package/dist/source-auth.js +1238 -0
  172. package/dist/source-auth.js.map +7 -0
  173. package/dist/switching-set-alignment.d.ts +61 -0
  174. package/dist/switching-set-alignment.d.ts.map +1 -0
  175. package/dist/switching-set-alignment.js +85 -0
  176. package/dist/switching-set-alignment.js.map +7 -0
  177. package/dist/timestamp-codec.d.ts +34 -0
  178. package/dist/timestamp-codec.d.ts.map +1 -0
  179. package/dist/timestamp-codec.js +456 -0
  180. package/dist/timestamp-codec.js.map +7 -0
  181. package/dist/track-pipeline-setup.d.ts +72 -0
  182. package/dist/track-pipeline-setup.d.ts.map +1 -0
  183. package/dist/track-pipeline-setup.js +306 -0
  184. package/dist/track-pipeline-setup.js.map +7 -0
  185. package/dist/track-pipeline.d.ts +121 -0
  186. package/dist/track-pipeline.d.ts.map +1 -0
  187. package/dist/track-pipeline.js +229 -0
  188. package/dist/track-pipeline.js.map +7 -0
  189. package/dist/track-session.d.ts +398 -0
  190. package/dist/track-session.d.ts.map +1 -0
  191. package/dist/track-session.js +767 -0
  192. package/dist/track-session.js.map +7 -0
  193. package/dist/transport-pool.d.ts +69 -0
  194. package/dist/transport-pool.d.ts.map +1 -0
  195. package/dist/transport-pool.js +88 -0
  196. package/dist/transport-pool.js.map +7 -0
  197. package/dist/wasm-mfu-reassembler.d.ts +92 -0
  198. package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
  199. package/dist/wasm-mfu-reassembler.js +2139 -0
  200. package/dist/wasm-mfu-reassembler.js.map +7 -0
  201. package/dist/wasm-mmtp-container-parser.d.ts +10 -0
  202. package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
  203. package/dist/wasm-mmtp-container-parser.js +6418 -0
  204. package/dist/wasm-mmtp-container-parser.js.map +7 -0
  205. package/node_modules/@blockcast/fec-worker/README.md +213 -0
  206. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  207. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +87 -0
  208. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
  209. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +279 -0
  210. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
  211. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +252 -0
  212. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
  213. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
  214. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
  215. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
  216. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
  217. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +565 -0
  218. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
  219. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
  220. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
  221. package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
  222. package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
  223. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
  224. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
  225. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +349 -0
  226. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
  227. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
  228. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
  229. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
  230. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
  231. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +179 -0
  232. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
  233. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +47 -0
  234. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
  235. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
  236. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
  237. package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
  238. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
  239. package/node_modules/@blockcast/fec-worker/package.json +59 -0
  240. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +319 -0
  241. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +268 -0
  242. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1048 -0
  243. package/node_modules/@blockcast/fec-worker/src/index.ts +32 -0
  244. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +558 -0
  245. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
  246. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +194 -0
  247. package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
  248. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  249. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  250. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  251. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  252. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  253. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  254. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  255. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  256. package/node_modules/@blockcast/mmt-base/README.md +120 -0
  257. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  258. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  259. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  260. package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  261. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  262. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
  263. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  264. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
  265. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  266. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  267. package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  268. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
  269. package/node_modules/@blockcast/mmt-fec/README.md +270 -0
  270. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  271. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +82 -0
  272. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
  273. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +296 -0
  274. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
  275. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +72 -0
  276. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
  277. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +192 -0
  278. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
  279. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
  280. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
  281. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
  282. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
  283. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
  284. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
  285. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
  286. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
  287. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +185 -0
  288. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
  289. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +273 -0
  290. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
  291. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +22 -0
  292. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
  293. package/node_modules/@blockcast/mmt-fec/dist/index.js +22 -0
  294. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
  295. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
  296. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
  297. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
  298. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
  299. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +172 -0
  300. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
  301. package/node_modules/@blockcast/mmt-fec/dist/oti.js +254 -0
  302. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
  303. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
  304. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
  305. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
  306. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +120 -0
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
  324. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  325. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
  326. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  327. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
  328. package/node_modules/@blockcast/mmt-fec/package.json +79 -0
  329. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +371 -0
  330. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +277 -0
  331. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
  332. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
  333. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +332 -0
  334. package/node_modules/@blockcast/mmt-fec/src/index.ts +77 -0
  335. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
  336. package/node_modules/@blockcast/mmt-fec/src/oti.ts +317 -0
  337. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
  338. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  339. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
  351. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +99 -0
  352. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
  353. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +492 -0
  354. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
  355. package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
  356. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
  357. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
  358. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
  359. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +669 -0
  360. package/node_modules/@noble/hashes/LICENSE +21 -0
  361. package/node_modules/@noble/hashes/README.md +521 -0
  362. package/node_modules/@noble/hashes/_assert.d.ts +17 -0
  363. package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
  364. package/node_modules/@noble/hashes/_assert.js +18 -0
  365. package/node_modules/@noble/hashes/_assert.js.map +1 -0
  366. package/node_modules/@noble/hashes/_blake.d.ts +14 -0
  367. package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
  368. package/node_modules/@noble/hashes/_blake.js +50 -0
  369. package/node_modules/@noble/hashes/_blake.js.map +1 -0
  370. package/node_modules/@noble/hashes/_md.d.ts +51 -0
  371. package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
  372. package/node_modules/@noble/hashes/_md.js +162 -0
  373. package/node_modules/@noble/hashes/_md.js.map +1 -0
  374. package/node_modules/@noble/hashes/_u64.d.ts +55 -0
  375. package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
  376. package/node_modules/@noble/hashes/_u64.js +90 -0
  377. package/node_modules/@noble/hashes/_u64.js.map +1 -0
  378. package/node_modules/@noble/hashes/argon2.d.ts +32 -0
  379. package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
  380. package/node_modules/@noble/hashes/argon2.js +401 -0
  381. package/node_modules/@noble/hashes/argon2.js.map +1 -0
  382. package/node_modules/@noble/hashes/blake1.d.ts +106 -0
  383. package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
  384. package/node_modules/@noble/hashes/blake1.js +459 -0
  385. package/node_modules/@noble/hashes/blake1.js.map +1 -0
  386. package/node_modules/@noble/hashes/blake2.d.ts +116 -0
  387. package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
  388. package/node_modules/@noble/hashes/blake2.js +420 -0
  389. package/node_modules/@noble/hashes/blake2.js.map +1 -0
  390. package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
  391. package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
  392. package/node_modules/@noble/hashes/blake2b.js +14 -0
  393. package/node_modules/@noble/hashes/blake2b.js.map +1 -0
  394. package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
  395. package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
  396. package/node_modules/@noble/hashes/blake2s.js +24 -0
  397. package/node_modules/@noble/hashes/blake2s.js.map +1 -0
  398. package/node_modules/@noble/hashes/blake3.d.ts +54 -0
  399. package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
  400. package/node_modules/@noble/hashes/blake3.js +255 -0
  401. package/node_modules/@noble/hashes/blake3.js.map +1 -0
  402. package/node_modules/@noble/hashes/crypto.d.ts +2 -0
  403. package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
  404. package/node_modules/@noble/hashes/crypto.js +5 -0
  405. package/node_modules/@noble/hashes/crypto.js.map +1 -0
  406. package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
  407. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
  408. package/node_modules/@noble/hashes/cryptoNode.js +18 -0
  409. package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
  410. package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
  411. package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
  412. package/node_modules/@noble/hashes/eskdf.js +166 -0
  413. package/node_modules/@noble/hashes/eskdf.js.map +1 -0
  414. package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
  415. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
  416. package/node_modules/@noble/hashes/esm/_assert.js +15 -0
  417. package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
  418. package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
  419. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
  420. package/node_modules/@noble/hashes/esm/_blake.js +45 -0
  421. package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
  422. package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
  423. package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
  424. package/node_modules/@noble/hashes/esm/_md.js +155 -0
  425. package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
  426. package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
  427. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
  428. package/node_modules/@noble/hashes/esm/_u64.js +67 -0
  429. package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
  430. package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
  431. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
  432. package/node_modules/@noble/hashes/esm/argon2.js +392 -0
  433. package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
  434. package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
  435. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
  436. package/node_modules/@noble/hashes/esm/blake1.js +452 -0
  437. package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
  438. package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
  439. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
  440. package/node_modules/@noble/hashes/esm/blake2.js +413 -0
  441. package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
  442. package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
  443. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
  444. package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
  445. package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
  446. package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
  447. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
  448. package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
  449. package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
  450. package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
  451. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
  452. package/node_modules/@noble/hashes/esm/blake3.js +251 -0
  453. package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
  454. package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
  455. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
  456. package/node_modules/@noble/hashes/esm/crypto.js +2 -0
  457. package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
  458. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
  459. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
  460. package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
  461. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
  462. package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
  463. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
  464. package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
  465. package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
  466. package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
  467. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
  468. package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
  469. package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
  470. package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
  471. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
  472. package/node_modules/@noble/hashes/esm/hmac.js +86 -0
  473. package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
  474. package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
  475. package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
  476. package/node_modules/@noble/hashes/esm/index.js +33 -0
  477. package/node_modules/@noble/hashes/esm/index.js.map +1 -0
  478. package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
  479. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
  480. package/node_modules/@noble/hashes/esm/legacy.js +281 -0
  481. package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
  482. package/node_modules/@noble/hashes/esm/package.json +10 -0
  483. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
  484. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
  485. package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
  486. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
  487. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
  488. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
  489. package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
  490. package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
  491. package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
  492. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
  493. package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
  494. package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
  495. package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
  496. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
  497. package/node_modules/@noble/hashes/esm/sha1.js +11 -0
  498. package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
  499. package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
  500. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
  501. package/node_modules/@noble/hashes/esm/sha2.js +375 -0
  502. package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
  503. package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
  504. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
  505. package/node_modules/@noble/hashes/esm/sha256.js +20 -0
  506. package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
  507. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
  508. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
  509. package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
  510. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
  511. package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
  512. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
  513. package/node_modules/@noble/hashes/esm/sha3.js +234 -0
  514. package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
  515. package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
  516. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
  517. package/node_modules/@noble/hashes/esm/sha512.js +26 -0
  518. package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
  519. package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
  520. package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
  521. package/node_modules/@noble/hashes/esm/utils.js +281 -0
  522. package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
  523. package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
  524. package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
  525. package/node_modules/@noble/hashes/hkdf.js +88 -0
  526. package/node_modules/@noble/hashes/hkdf.js.map +1 -0
  527. package/node_modules/@noble/hashes/hmac.d.ts +35 -0
  528. package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
  529. package/node_modules/@noble/hashes/hmac.js +91 -0
  530. package/node_modules/@noble/hashes/hmac.js.map +1 -0
  531. package/node_modules/@noble/hashes/index.d.ts +1 -0
  532. package/node_modules/@noble/hashes/index.d.ts.map +1 -0
  533. package/node_modules/@noble/hashes/index.js +33 -0
  534. package/node_modules/@noble/hashes/index.js.map +1 -0
  535. package/node_modules/@noble/hashes/legacy.d.ts +71 -0
  536. package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
  537. package/node_modules/@noble/hashes/legacy.js +287 -0
  538. package/node_modules/@noble/hashes/legacy.js.map +1 -0
  539. package/node_modules/@noble/hashes/package.json +266 -0
  540. package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
  541. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
  542. package/node_modules/@noble/hashes/pbkdf2.js +101 -0
  543. package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
  544. package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
  545. package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
  546. package/node_modules/@noble/hashes/ripemd160.js +16 -0
  547. package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
  548. package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
  549. package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
  550. package/node_modules/@noble/hashes/scrypt.js +232 -0
  551. package/node_modules/@noble/hashes/scrypt.js.map +1 -0
  552. package/node_modules/@noble/hashes/sha1.d.ts +11 -0
  553. package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
  554. package/node_modules/@noble/hashes/sha1.js +14 -0
  555. package/node_modules/@noble/hashes/sha1.js.map +1 -0
  556. package/node_modules/@noble/hashes/sha2.d.ts +159 -0
  557. package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
  558. package/node_modules/@noble/hashes/sha2.js +384 -0
  559. package/node_modules/@noble/hashes/sha2.js.map +1 -0
  560. package/node_modules/@noble/hashes/sha256.d.ts +20 -0
  561. package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
  562. package/node_modules/@noble/hashes/sha256.js +23 -0
  563. package/node_modules/@noble/hashes/sha256.js.map +1 -0
  564. package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
  565. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
  566. package/node_modules/@noble/hashes/sha3-addons.js +402 -0
  567. package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
  568. package/node_modules/@noble/hashes/sha3.d.ts +53 -0
  569. package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
  570. package/node_modules/@noble/hashes/sha3.js +239 -0
  571. package/node_modules/@noble/hashes/sha3.js.map +1 -0
  572. package/node_modules/@noble/hashes/sha512.d.ts +26 -0
  573. package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
  574. package/node_modules/@noble/hashes/sha512.js +29 -0
  575. package/node_modules/@noble/hashes/sha512.js.map +1 -0
  576. package/node_modules/@noble/hashes/src/_assert.ts +22 -0
  577. package/node_modules/@noble/hashes/src/_blake.ts +50 -0
  578. package/node_modules/@noble/hashes/src/_md.ts +176 -0
  579. package/node_modules/@noble/hashes/src/_u64.ts +91 -0
  580. package/node_modules/@noble/hashes/src/argon2.ts +497 -0
  581. package/node_modules/@noble/hashes/src/blake1.ts +534 -0
  582. package/node_modules/@noble/hashes/src/blake2.ts +486 -0
  583. package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
  584. package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
  585. package/node_modules/@noble/hashes/src/blake3.ts +272 -0
  586. package/node_modules/@noble/hashes/src/crypto.ts +9 -0
  587. package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
  588. package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
  589. package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
  590. package/node_modules/@noble/hashes/src/hmac.ts +94 -0
  591. package/node_modules/@noble/hashes/src/index.ts +31 -0
  592. package/node_modules/@noble/hashes/src/legacy.ts +293 -0
  593. package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
  594. package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
  595. package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
  596. package/node_modules/@noble/hashes/src/sha1.ts +10 -0
  597. package/node_modules/@noble/hashes/src/sha2.ts +402 -0
  598. package/node_modules/@noble/hashes/src/sha256.ts +24 -0
  599. package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
  600. package/node_modules/@noble/hashes/src/sha3.ts +258 -0
  601. package/node_modules/@noble/hashes/src/sha512.ts +34 -0
  602. package/node_modules/@noble/hashes/src/utils.ts +395 -0
  603. package/node_modules/@noble/hashes/utils.d.ts +161 -0
  604. package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
  605. package/node_modules/@noble/hashes/utils.js +313 -0
  606. package/node_modules/@noble/hashes/utils.js.map +1 -0
  607. package/package.json +203 -0
  608. package/schemas/catalog.schema.json +360 -0
  609. package/src/align.ts +239 -0
  610. package/src/audio-init-parser.ts +189 -0
  611. package/src/audio-ring-buffer.ts +120 -0
  612. package/src/authored-timing.ts +63 -0
  613. package/src/carp-catalog.ts +845 -0
  614. package/src/catalog-fec-params.ts +535 -0
  615. package/src/catalog-network-source.ts +49 -0
  616. package/src/catalog-validator.ts +34 -0
  617. package/src/cmaf-block-assembler.ts +1167 -0
  618. package/src/cmaf-group-batcher.ts +104 -0
  619. package/src/cmcd-producer.ts +334 -0
  620. package/src/codec-utils.ts +1611 -0
  621. package/src/fec-codec.ts +61 -0
  622. package/src/fec-manager.ts +2081 -0
  623. package/src/fec-starvation-detector.ts +128 -0
  624. package/src/fec-worker-setup.ts +213 -0
  625. package/src/fmp4-completion.ts +47 -0
  626. package/src/fmp4-reader.ts +112 -0
  627. package/src/index.ts +486 -0
  628. package/src/iso-bmff-timing.ts +813 -0
  629. package/src/loc.ts +181 -0
  630. package/src/manifest-auth-plan.ts +107 -0
  631. package/src/media-router.ts +795 -0
  632. package/src/mfu-reassembler.ts +3741 -0
  633. package/src/mfu-reassembly-policy.ts +134 -0
  634. package/src/mmtp-container-parser.ts +1836 -0
  635. package/src/mmtp-decode-pipeline.ts +389 -0
  636. package/src/mmtp-router.ts +3136 -0
  637. package/src/mmtp-to-cmaf.ts +1024 -0
  638. package/src/multicast-auth-catalog.ts +340 -0
  639. package/src/multicast-input.ts +439 -0
  640. package/src/multicast-manager.ts +1720 -0
  641. package/src/multicast-transport.ts +321 -0
  642. package/src/multicast-unwrap.ts +132 -0
  643. package/src/no-modules.ts +20 -0
  644. package/src/phase14-catalog-override.ts +336 -0
  645. package/src/repair-fetch.ts +238 -0
  646. package/src/repair-layers.ts +490 -0
  647. package/src/sample-combiner.ts +938 -0
  648. package/src/signaling.ts +78 -0
  649. package/src/source-auth.ts +1 -0
  650. package/src/switching-set-alignment.ts +109 -0
  651. package/src/timestamp-codec.ts +265 -0
  652. package/src/track-pipeline-setup.ts +189 -0
  653. package/src/track-pipeline.ts +332 -0
  654. package/src/track-session.ts +994 -0
  655. package/src/transport-pool.ts +128 -0
  656. package/src/wasm-mfu-reassembler.ts +389 -0
  657. package/src/wasm-mmtp-container-parser.ts +23 -0
  658. package/test-vectors/align.json +199 -0
  659. package/test-vectors/catalog/README.md +40 -0
  660. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +27 -0
  661. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +32 -0
  662. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +30 -0
  663. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +31 -0
  664. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +33 -0
  665. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +23 -0
  666. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +32 -0
  667. package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
  668. package/test-vectors/catalog/negative/media-track-repair-layer.json +16 -0
  669. package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
  670. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
  671. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
  672. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
  673. package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
  674. package/test-vectors/catalog/negative/network-source-object.json +25 -0
  675. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +23 -0
  676. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +26 -0
  677. package/test-vectors/catalog/negative/repair-track-legacy-shape.json +19 -0
  678. package/test-vectors/catalog/negative/track-role-empty.json +16 -0
  679. package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
  680. package/test-vectors/catalog/positive/fec-layered-repair.json +104 -0
  681. package/test-vectors/catalog/positive/fec-multicast.json +51 -0
  682. package/test-vectors/catalog/positive/flat-av.json +30 -0
  683. package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
  684. package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
  685. package/test-vectors/catalog/positive/track-role.json +49 -0
  686. package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
  687. package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
  688. package/test-vectors/catalog-multicast-auth-v1.json +26 -0
  689. package/test-vectors/catalog-validation.json +148 -0
  690. package/test-vectors/catalog-validator-public-api.mjs +25 -0
  691. package/test-vectors/generate-source-auth-fixture.mjs +114 -0
  692. package/test-vectors/source-auth-v1.json +61 -0
  693. package/test-vectors/source-contiguity-v1.json +131 -0
  694. package/test-vectors/subsample-placement-v1.json +230 -0
@@ -0,0 +1,1720 @@
1
+ /**
2
+ * Facade composing FECManager + MmtpRouter + MulticastTransport.
3
+ *
4
+ * Public methods delegate to the decomposed components. Applications that
5
+ * need individual layers can use them directly:
6
+ * - FECManager — codec-agnostic FEC decode
7
+ * - MmtpRouter — MMTP packet parsing + frame routing
8
+ * - MulticastTransport — SSM join/socket/watchdog
9
+ */
10
+
11
+ import {
12
+ FECManager,
13
+ MIN_INTERLEAVE_DEPTH_MS,
14
+ type FECManagerConfig,
15
+ type WasmFecDecoder,
16
+ type FecDecoderUnavailableReason,
17
+ type FecFallbackDecoder,
18
+ } from "./fec-manager.js";
19
+ import {
20
+ MmtpRouter,
21
+ type FrameCallback,
22
+ type ManifestIdentityResolver,
23
+ type MmtpIngressContext,
24
+ type TrackPacketCallback,
25
+ } from "./mmtp-router.js";
26
+ import { MulticastTransport } from "./multicast-transport.js";
27
+ import { computeFecDeliveryWindowMs } from "./track-session.js";
28
+ import { deriveFecInterleaveDepth, FecBlockMapper } from "@blockcast/mmt-fec";
29
+ import type {
30
+ FecBlockSnapshot,
31
+ FecTrackConfig,
32
+ FecTrackStats,
33
+ FecWorkerClient,
34
+ } from "@blockcast/fec-worker";
35
+ import type { MfuReassemblerBackend } from "./mfu-reassembler.js";
36
+ import type { MfuReassemblyPolicy } from "./mfu-reassembly-policy.js";
37
+ import type {
38
+ ManifestObjectIdentity,
39
+ ManifestVerifier,
40
+ } from "@blockcast/mmt-manifest-verify";
41
+ import type {
42
+ BlockAssemblerConfig,
43
+ CmafBlockSample,
44
+ } from "./cmaf-block-assembler.js";
45
+ import {
46
+ hasSwitchingSetRole,
47
+ parseTrackRole,
48
+ validateSwitchingSet,
49
+ isFecCatalogConfig,
50
+ type FecCatalogConfig,
51
+ type TrackRole,
52
+ } from "./carp-catalog.js";
53
+
54
+ export interface MulticastEndpointConfig {
55
+ sourceAddress: string;
56
+ groupAddress: string;
57
+ port: number;
58
+ /** Explicit gateway transport policy. Omitted means gateway-selected auto mode. */
59
+ mode?: 'auto' | 'native' | 'tunnel';
60
+ /** AMT relay hostname/IP from catalog endpoint. */
61
+ relayAddress?: string;
62
+ /** AMT relay port from catalog endpoint. */
63
+ relayPort?: number;
64
+ /** AMT relay hostname/IP from player/UI overrides. */
65
+ amtRelay?: string;
66
+ amtRelayPort?: number;
67
+ /** MMTP packet IDs to forward before data enters the page transport queue. */
68
+ packetIds?: number[];
69
+ /** Exact catalog tracks carried by this physical multicast endpoint. */
70
+ tracks?: MmtpIngressContext["tracks"];
71
+ fecK?: number;
72
+ /** Number of repair symbols per FEC block (P). Used to size recovery reorder latency. */
73
+ fecRepairSymbols?: number;
74
+ /** Symbol size in bytes (T). NOT repair count. */
75
+ fecSymbolSize?: number;
76
+ /** Catalog fec.reorderToleranceMs — signaled jitter/reorder allowance, ADDED
77
+ * to the derived delivery window. Never a multiplier on the span, so one
78
+ * network yields the same slack on a wide video span and a narrow audio one.
79
+ *
80
+ * Absence widens the window by zero rather than throwing, unlike the datagram
81
+ * MFU deadline in hang-mmt-fec. The distinction is which way absence fails:
82
+ * there the tolerance IS the deadline's slack term and guessing one invents
83
+ * a reorder budget, so it must fail loud; here the window is already fully
84
+ * derived from other catalog values and the tolerance only widens it, so
85
+ * adding nothing is the identity and errs toward earlier failback. Do not
86
+ * "harmonize" this into a throw, and do not turn the zero into a default. */
87
+ fecReorderToleranceMs?: number;
88
+ fecInterleaveDepthMs?: number;
89
+ /** Aggregate endpoint bandwidth in bits per second from the multicast catalog. */
90
+ bandwidth?: number;
91
+ fps?: number;
92
+ /** Catalog-derived lower bound for packet-silence failback (for example, keyframe repair cadence). */
93
+ minimumFailbackWindowMs?: number;
94
+ }
95
+ export interface MulticastManagerOptions {
96
+ /**
97
+ * MFU reassembly timeout (ms). Threaded to the inner MmtpRouter (and its
98
+ * MmtpContainerParser). Caller derives from catalog `fec.interleaveDepthMs`,
99
+ * which is already in milliseconds — `moqenc_mmt.c` converts
100
+ * `depth_ms = fec_depth * (1000/fps)` before populating the catalog.
101
+ * Defaults to 1000ms (with parser warning) when omitted.
102
+ */
103
+ mfuTimeoutMs?: number;
104
+ reassemblyPolicy?: MfuReassemblyPolicy;
105
+ reassembler?: MfuReassemblerBackend;
106
+ maxBufferedMfus?: number;
107
+ manifestVerifier?: ManifestVerifier;
108
+ manifestVerifierOwnership?: "caller" | "router";
109
+ resolveManifestIdentity?: (
110
+ packetId: number,
111
+ sbn: number,
112
+ esi: number,
113
+ catalogTrackName?: string,
114
+ ) => ManifestObjectIdentity | null;
115
+ resolveManifestIdentityForRoute?: ManifestIdentityResolver;
116
+ }
117
+
118
+ export interface RepairStreamIdentity {
119
+ readonly packetId: number;
120
+ /** Non-empty catalog identity, or null when the producer has no track name. */
121
+ readonly catalogTrackName: string | null;
122
+ }
123
+
124
+ export interface RepairBlockIdentity extends RepairStreamIdentity {
125
+ readonly sbn: number;
126
+ }
127
+
128
+ export type RepairLifecycleEvent =
129
+ | {
130
+ readonly version: 1;
131
+ readonly type: "requested";
132
+ readonly block: RepairBlockIdentity;
133
+ }
134
+ | {
135
+ readonly version: 1;
136
+ readonly type: "recovered" | "failed";
137
+ readonly block: RepairBlockIdentity;
138
+ readonly symbols: {
139
+ readonly multicast: number;
140
+ readonly moq: number;
141
+ };
142
+ }
143
+ | {
144
+ readonly version: 1;
145
+ /**
146
+ * The stream's Worker decoder left routing (FECManager.clearFecWorker,
147
+ * or a disposed client evicted on its next symbol). `fallbackDecoder`
148
+ * says what still decodes the stream: 'sync' when a synchronous WASM
149
+ * decoder registered for the same stream keeps serving it, 'none' when
150
+ * nothing does. Only 'none' means the stream's symbols reach no decoder
151
+ * until a host registers one again; consumers fail closed on that
152
+ * instead of reading the following silence as a clean stream.
153
+ */
154
+ readonly type: "decoder-unavailable";
155
+ readonly stream: RepairStreamIdentity;
156
+ readonly reason: FecDecoderUnavailableReason;
157
+ readonly fallbackDecoder: FecFallbackDecoder;
158
+ };
159
+
160
+ export type RepairLifecycleObserver = (event: RepairLifecycleEvent) => void;
161
+
162
+ export interface RepairLifecycleSource {
163
+ addRepairLifecycleObserver(observer: RepairLifecycleObserver): () => void;
164
+ }
165
+
166
+ /** Minimal event shape accepted by application-owned event targets. */
167
+ export interface RepairLifecycleApplicationEvent {
168
+ readonly type: string;
169
+ readonly detail: RepairLifecycleEvent;
170
+ }
171
+
172
+ /** DOM EventTarget event shape, including the Node 18 Event fallback. */
173
+ export interface RepairLifecycleDomEvent extends Event {
174
+ readonly detail: RepairLifecycleEvent;
175
+ }
176
+
177
+ /** Structural target contract shared by DOM and Closure-style event targets. */
178
+ export interface RepairLifecycleEventTarget<
179
+ TEvent extends RepairLifecycleApplicationEvent,
180
+ > {
181
+ dispatchEvent(event: TEvent): unknown;
182
+ }
183
+
184
+ export interface RepairLifecycleEventOptions<
185
+ TEvent extends RepairLifecycleApplicationEvent = RepairLifecycleDomEvent,
186
+ > {
187
+ bubbles?: boolean;
188
+ composed?: boolean;
189
+ createEvent?: RepairLifecycleEventFactory<TEvent>;
190
+ }
191
+
192
+ export type RepairLifecycleEventFactory<
193
+ TEvent extends RepairLifecycleApplicationEvent = RepairLifecycleDomEvent,
194
+ > = (
195
+ type: typeof REPAIR_LIFECYCLE_EVENT_TYPE,
196
+ detail: RepairLifecycleEvent,
197
+ options: Readonly<{ bubbles: boolean; composed: boolean }>,
198
+ ) => TEvent;
199
+
200
+ /** Public EventTarget event carrying an immutable RepairLifecycleEvent detail. */
201
+ export const REPAIR_LIFECYCLE_EVENT_TYPE = "repairlifecycle";
202
+
203
+ /** Diagnostic JSON-lines prefix. Never consume logs as a lifecycle API. */
204
+ export const REPAIR_LIFECYCLE_LOG_PREFIX =
205
+ "[MulticastManager] repair-lifecycle ";
206
+
207
+ function createDefaultRepairLifecycleEvent(
208
+ detail: RepairLifecycleEvent,
209
+ options: Readonly<{ bubbles: boolean; composed: boolean }>,
210
+ ): RepairLifecycleDomEvent {
211
+ if (typeof globalThis.CustomEvent === "function") {
212
+ return new CustomEvent<RepairLifecycleEvent>(REPAIR_LIFECYCLE_EVENT_TYPE, {
213
+ detail,
214
+ ...options,
215
+ });
216
+ }
217
+
218
+ const event = new Event(REPAIR_LIFECYCLE_EVENT_TYPE, options);
219
+ Object.defineProperty(event, "detail", {
220
+ value: detail,
221
+ enumerable: true,
222
+ });
223
+ return event as RepairLifecycleDomEvent;
224
+ }
225
+
226
+ function isRecord(value: unknown): value is Record<string, unknown> {
227
+ return value !== null && typeof value === "object" && !Array.isArray(value);
228
+ }
229
+
230
+ function isRepairStreamIdentity(value: unknown): value is RepairStreamIdentity {
231
+ if (!isRecord(value)) return false;
232
+ const packetId = value["packetId"];
233
+ const catalogTrackName = value["catalogTrackName"];
234
+ return (
235
+ Number.isSafeInteger(packetId) &&
236
+ (packetId as number) >= 0 &&
237
+ (catalogTrackName === null ||
238
+ (typeof catalogTrackName === "string" && catalogTrackName.length > 0))
239
+ );
240
+ }
241
+
242
+ function isRepairBlockIdentity(value: unknown): value is RepairBlockIdentity {
243
+ if (!isRecord(value)) return false;
244
+ const sbn = value["sbn"];
245
+ return (
246
+ isRepairStreamIdentity(value) &&
247
+ Number.isSafeInteger(sbn) &&
248
+ (sbn as number) >= 0
249
+ );
250
+ }
251
+
252
+ function isDecoderUnavailableReason(
253
+ value: unknown,
254
+ ): value is FecDecoderUnavailableReason {
255
+ return value === "cleared" || value === "disposed";
256
+ }
257
+
258
+ function isFecFallbackDecoder(value: unknown): value is FecFallbackDecoder {
259
+ return value === "sync" || value === "none";
260
+ }
261
+
262
+ /** Runtime guard for observer, DOM-event, and cross-realm consumers. */
263
+ export function isRepairLifecycleEvent(
264
+ value: unknown,
265
+ ): value is RepairLifecycleEvent {
266
+ if (!isRecord(value)) return false;
267
+ const version = value["version"];
268
+ const type = value["type"];
269
+ const block = value["block"];
270
+ const symbols = value["symbols"];
271
+ if (version !== 1) return false;
272
+ if (type === "decoder-unavailable") {
273
+ return (
274
+ isRepairStreamIdentity(value["stream"]) &&
275
+ isDecoderUnavailableReason(value["reason"]) &&
276
+ isFecFallbackDecoder(value["fallbackDecoder"]) &&
277
+ block === undefined &&
278
+ symbols === undefined
279
+ );
280
+ }
281
+ if (
282
+ (type !== "requested" && type !== "recovered" && type !== "failed") ||
283
+ !isRepairBlockIdentity(block) ||
284
+ value["stream"] !== undefined ||
285
+ value["reason"] !== undefined ||
286
+ value["fallbackDecoder"] !== undefined
287
+ ) {
288
+ return false;
289
+ }
290
+ if (type === "requested") return symbols === undefined;
291
+ if (!isRecord(symbols)) return false;
292
+ const multicast = symbols["multicast"];
293
+ const moq = symbols["moq"];
294
+ return (
295
+ Number.isSafeInteger(multicast) &&
296
+ (multicast as number) >= 0 &&
297
+ Number.isSafeInteger(moq) &&
298
+ (moq as number) >= 0
299
+ );
300
+ }
301
+
302
+ /**
303
+ * Forward typed repair lifecycle observations through an application-owned
304
+ * EventTarget. The adapter retains no history; the returned function detaches
305
+ * the observer.
306
+ */
307
+ export function forwardRepairLifecycleEvents<
308
+ TEvent extends RepairLifecycleApplicationEvent,
309
+ >(
310
+ source: RepairLifecycleSource,
311
+ target: RepairLifecycleEventTarget<TEvent>,
312
+ options: RepairLifecycleEventOptions<TEvent> & {
313
+ createEvent: RepairLifecycleEventFactory<TEvent>;
314
+ },
315
+ ): () => void;
316
+ export function forwardRepairLifecycleEvents(
317
+ source: RepairLifecycleSource,
318
+ target: EventTarget,
319
+ options?: RepairLifecycleEventOptions,
320
+ ): () => void;
321
+ export function forwardRepairLifecycleEvents(
322
+ source: RepairLifecycleSource,
323
+ target:
324
+ | EventTarget
325
+ | RepairLifecycleEventTarget<RepairLifecycleApplicationEvent>,
326
+ options: RepairLifecycleEventOptions<RepairLifecycleApplicationEvent> = {},
327
+ ): () => void {
328
+ return source.addRepairLifecycleObserver((event) => {
329
+ const eventOptions = {
330
+ bubbles: options.bubbles ?? false,
331
+ composed: options.composed ?? false,
332
+ };
333
+ const forwardedEvent = options.createEvent
334
+ ? options.createEvent(REPAIR_LIFECYCLE_EVENT_TYPE, event, eventOptions)
335
+ : createDefaultRepairLifecycleEvent(event, eventOptions);
336
+ (
337
+ target as RepairLifecycleEventTarget<RepairLifecycleApplicationEvent>
338
+ ).dispatchEvent(forwardedEvent);
339
+ });
340
+ }
341
+
342
+ function repairStreamIdentity(
343
+ packetId: number,
344
+ catalogTrackName?: string,
345
+ ): RepairStreamIdentity {
346
+ if (!Number.isSafeInteger(packetId) || packetId < 0) {
347
+ throw new TypeError("packetId must be a non-negative safe integer");
348
+ }
349
+ if (
350
+ catalogTrackName !== undefined &&
351
+ (typeof catalogTrackName !== "string" || catalogTrackName.length === 0)
352
+ ) {
353
+ throw new TypeError("catalogTrackName must be a non-empty string when provided");
354
+ }
355
+ return {
356
+ packetId,
357
+ catalogTrackName: catalogTrackName ?? null,
358
+ };
359
+ }
360
+
361
+ function repairBlockIdentity(
362
+ packetId: number,
363
+ sbn: number,
364
+ catalogTrackName?: string,
365
+ ): RepairBlockIdentity {
366
+ const stream = repairStreamIdentity(packetId, catalogTrackName);
367
+ if (!Number.isSafeInteger(sbn) || sbn < 0) {
368
+ throw new TypeError("sbn must be a non-negative safe integer");
369
+ }
370
+ return { ...stream, sbn };
371
+ }
372
+
373
+ /**
374
+ * Reject a catalog geometry value that is present but not usable. Absent stays
375
+ * absent — an omitted field is resolved from the previously configured track or
376
+ * left unset — but a supplied non-finite/non-positive value is a catalog defect
377
+ * and must fail at configuration time rather than collapse a derived deadline
378
+ * to ~0 packets later.
379
+ */
380
+ function assertPositiveGeometry(
381
+ label: string,
382
+ value: number | undefined,
383
+ ): void {
384
+ if (value === undefined) return;
385
+ if (!Number.isFinite(value) || value <= 0) {
386
+ throw new Error(
387
+ `[MulticastManager] ${label}=${value}. ` +
388
+ `Derive a positive ${label} from the stream catalog.`,
389
+ );
390
+ }
391
+ }
392
+
393
+ /** Repair-symbol count (P) may legitimately be 0; only reject malformed values. */
394
+ function assertNonNegativeGeometry(
395
+ label: string,
396
+ value: number | undefined,
397
+ ): void {
398
+ if (value === undefined) return;
399
+ if (!Number.isFinite(value) || value < 0) {
400
+ throw new Error(
401
+ `[MulticastManager] ${label}=${value}. ` +
402
+ `Derive a non-negative ${label} from the stream catalog.`,
403
+ );
404
+ }
405
+ }
406
+
407
+ /**
408
+ * One contributor to the stream-global FEC timing reduction.
409
+ *
410
+ * FEC geometry is per-track, but a MulticastManager owns exactly one transport
411
+ * and one router, so the failback watchdog and the router's MFU/reorder timing
412
+ * are stream-global. They are reduced with `max` across every contributor:
413
+ * failback must not fire, and the router must not evict, while ANY configured
414
+ * track's FEC block could still complete.
415
+ */
416
+ interface FecTimingContribution {
417
+ deliveryWindowMs?: number;
418
+ interleaveDepthMs?: number;
419
+ fps?: number;
420
+ k?: number;
421
+ repairSymbols?: number;
422
+ minimumFailbackWindowMs?: number;
423
+ }
424
+
425
+ function fecReorderWindowFrames(
426
+ fecInterleaveDepthMs?: number,
427
+ fps?: number,
428
+ _k?: number,
429
+ _repairSymbols?: number,
430
+ ): number {
431
+ if (!fecInterleaveDepthMs || fecInterleaveDepthMs <= 0 || !fps || fps <= 0) return 0;
432
+ const frameMs = 1000 / fps;
433
+ // Reorder across the FEC block span: fecInterleaveDepthMs IS the full block
434
+ // span (§5.1), so this window admits any FEC-recovered frame from the block
435
+ // without holding frames longer than one block.
436
+ return deriveFecInterleaveDepth(fecInterleaveDepthMs, frameMs);
437
+ }
438
+
439
+ /** Catalog track descriptor for configureMultiTrack(). */
440
+ export interface MultiTrackCatalogTrack {
441
+ name: string;
442
+ altGroup?: number;
443
+ framerate?: number;
444
+ fec?: unknown;
445
+ /** PacketId for testability — in production, learned at subscribe time (Plan 03). */
446
+ packetId?: number;
447
+ /** `"fec-repair"` marks a self-describing repair track (draft-ramadan-moq-fec
448
+ * §6.3). Absent on media tracks and on naming-era catalogs. */
449
+ packaging?: string;
450
+ /** The one source track this repair track carries repair symbols for. Layers
451
+ * 1..n and a keyframe overlay are reachable only through `depends`; layer 0
452
+ * is additionally named by the source's `fec.repairTrack`. */
453
+ depends?: string[];
454
+ /** Base layer index. Absent on a keyframe overlay, which is a separate FEC
455
+ * instance rather than a member of the layer chain. */
456
+ repairLayer?: number;
457
+ /** `"keyframe"` on an overlay track. */
458
+ scope?: string;
459
+ }
460
+
461
+ /** Multi-track catalog shape accepted by configureMultiTrack(). */
462
+ export interface MultiTrackCatalog {
463
+ tracks: MultiTrackCatalogTrack[];
464
+ }
465
+
466
+ /** Creates one stateful synchronous FEC decoder per physical catalog stream. */
467
+ export type WasmFecDecoderFactory = (
468
+ track: Readonly<MultiTrackCatalogTrack>,
469
+ ) => WasmFecDecoder;
470
+
471
+ const DEFAULT_ALT_GROUP = "__default__";
472
+
473
+ type FecDiagnosticsGateway = {
474
+ reportFecDiagnostics?: (snapshot: unknown) => void;
475
+ };
476
+
477
+ /** Active-block tail forwarded to the bridge; mirrors the IWA's bound. */
478
+ const FEC_DIAGNOSTIC_BLOCK_TAIL = 8;
479
+
480
+ /**
481
+ * Project a Worker block snapshot onto the bridge/IWA `subscription_diagnostics`
482
+ * contract. The contract names the per-block repair symbol count `t`; the
483
+ * Worker's `repairCount` is the same quantity (P) under its unambiguous name.
484
+ */
485
+ function projectFecDiagnosticBlock(block: FecBlockSnapshot) {
486
+ return {
487
+ sbn: block.sbn,
488
+ state: block.state,
489
+ sourceReceived: block.sourceReceived,
490
+ repairReceived: block.repairReceived,
491
+ k: block.k,
492
+ t: block.repairCount,
493
+ firstSymbolTs: block.firstSymbolTs,
494
+ lastSymbolTs: block.lastSymbolTs,
495
+ ...(block.completedTs === undefined ? {} : { completedTs: block.completedTs }),
496
+ deadlineTs: block.deadlineTs,
497
+ altaVerified: block.altaVerified,
498
+ relayBlockSigValid: block.relayBlockSigValid,
499
+ };
500
+ }
501
+
502
+ /**
503
+ * Project the Worker track config onto the bridge/IWA contract. Every key
504
+ * below is a named field of that contract (see pim-multicast-gateway
505
+ * `normalizeSubscriptionDiagnostics`); `k`/`t`/`symbolSize` are three
506
+ * independent axes there, so `repairCount` is emitted as `t` and never
507
+ * confused with the symbol size. Worker-internal fields such as
508
+ * `deliveryWindowMs` are not part of the contract and are not forwarded.
509
+ */
510
+ function projectFecDiagnosticConfig(config: FecTrackConfig) {
511
+ return {
512
+ codec: config.codec,
513
+ trackName: config.trackName,
514
+ trackType: config.trackType,
515
+ ...(config.resolution === undefined
516
+ ? {}
517
+ : { resolution: { w: config.resolution.w, h: config.resolution.h } }),
518
+ ...(config.framerate === undefined ? {} : { framerate: config.framerate }),
519
+ ...(config.sampleRate === undefined ? {} : { sampleRate: config.sampleRate }),
520
+ ...(config.algorithm === undefined ? {} : { algorithm: config.algorithm }),
521
+ k: config.k,
522
+ t: config.repairCount,
523
+ symbolSize: config.symbolSize,
524
+ interleaveDepth: config.interleaveDepth,
525
+ interleaveMs: config.interleaveMs,
526
+ fecMode: config.fecMode,
527
+ ...(config.repairTrack === undefined ? {} : { repairTrack: config.repairTrack }),
528
+ altaEnabled: config.altaEnabled,
529
+ relayBlockSigEnabled: config.relayBlockSigEnabled,
530
+ };
531
+ }
532
+
533
+ function publishFecDiagnostics(packetId: number, stats: FecTrackStats): boolean {
534
+ const gateway = (globalThis as typeof globalThis & {
535
+ multicast?: FecDiagnosticsGateway;
536
+ }).multicast;
537
+ if (typeof gateway?.reportFecDiagnostics !== "function") return false;
538
+
539
+ // Keep the control-plane payload bounded and JSON-safe. The Worker snapshot
540
+ // already bounds active blocks; the bridge needs only the current block tail
541
+ // and stable counters, not decoder internals or authentication failure text.
542
+ // The shape is the IWA contract, not the Worker's internal types: spreading
543
+ // FecTrackConfig raw once shipped `repairCount` with no `t`, and the IWA
544
+ // discarded every report ("fec_track.stats.config.t must be a finite
545
+ // non-negative number").
546
+ const snapshot = {
547
+ version: 1,
548
+ packetId,
549
+ observedAt: Date.now(),
550
+ stats: {
551
+ config: projectFecDiagnosticConfig(stats.config),
552
+ sourceSymbols: stats.sourceSymbols,
553
+ repairSymbols: stats.repairSymbols,
554
+ blocksComplete: stats.blocksComplete,
555
+ blocksRecovered: stats.blocksRecovered,
556
+ blocksFailed: stats.blocksFailed,
557
+ greenFillFrames: stats.greenFillFrames,
558
+ recoveryRate: stats.recoveryRate,
559
+ liveSbn: stats.liveSbn,
560
+ blocks: stats.blocks.slice(-FEC_DIAGNOSTIC_BLOCK_TAIL).map(projectFecDiagnosticBlock),
561
+ interleaveWindowBlocks: stats.interleaveWindowBlocks,
562
+ avgRecoveryMs: stats.avgRecoveryMs,
563
+ maxRecoveryMs: stats.maxRecoveryMs,
564
+ },
565
+ };
566
+
567
+ gateway.reportFecDiagnostics(snapshot);
568
+ return true;
569
+ }
570
+
571
+ function altGroupKey(track: MultiTrackCatalogTrack): string {
572
+ return track.altGroup === undefined ? DEFAULT_ALT_GROUP : String(track.altGroup);
573
+ }
574
+
575
+ /**
576
+ * Every repair track of each source: layer 0 named by the source's
577
+ * `fec.repairTrack`, plus every self-describing `packaging: "fec-repair"` track
578
+ * whose `depends` names that source (layers 1..n and a keyframe overlay), per
579
+ * draft-ramadan-moq-fec §6.3.
580
+ *
581
+ * Membership comes from the explicit pointer and from `depends`, never from a
582
+ * name suffix, so a publisher-chosen name like "parity-alpha" resolves.
583
+ *
584
+ * This deliberately does not call `resolveRepairLayers`: that validates the
585
+ * full layer contract and requires `packaging: "mmtp"` on the source plus a
586
+ * declared layer-0 repair track, neither of which a `MultiTrackCatalog`
587
+ * carries. Running it here would throw on every naming-era catalog. Ownership
588
+ * registration needs membership only; RS_ID offsets and layer ordering are the
589
+ * decoder's concern, not the router's.
590
+ */
591
+ function repairTrackNamesBySource(
592
+ tracks: readonly MultiTrackCatalogTrack[],
593
+ ): Map<string, Set<string>> {
594
+ const bySource = new Map<string, Set<string>>();
595
+ const add = (source: string, repair: string): void => {
596
+ if (!source || !repair) return;
597
+ const names = bySource.get(source) ?? new Set<string>();
598
+ names.add(repair);
599
+ bySource.set(source, names);
600
+ };
601
+ for (const track of tracks) {
602
+ if (
603
+ track.fec !== undefined &&
604
+ isFecCatalogConfig(track.fec) &&
605
+ track.fec.repairTrack
606
+ ) {
607
+ add(track.name, track.fec.repairTrack);
608
+ }
609
+ if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
610
+ add(track.depends[0]!, track.name);
611
+ }
612
+ }
613
+ return bySource;
614
+ }
615
+
616
+ export class MulticastManager {
617
+ readonly #fec: FECManager;
618
+ readonly #router: MmtpRouter;
619
+ readonly #transport: MulticastTransport<MmtpIngressContext>;
620
+ #reportedFecDiagnosticsFailure = false;
621
+
622
+ // ── Stream-global FEC timing reduction ──
623
+ //
624
+ // FEC geometry is per-track; the transport failback watchdog and the router's
625
+ // MFU/reorder timing are stream-global (one transport, one router). Both
626
+ // configuration entry points feed the same reduction so neither can silently
627
+ // clip the other: configureFec() supplies the baseline, configureTrackFec()
628
+ // supplies one entry per track, and #applyStreamGlobalFecTiming() takes the
629
+ // max across all of them.
630
+ #streamFecBaseline?: FecTimingContribution;
631
+ readonly #trackFecTiming = new Map<number, FecTimingContribution>();
632
+
633
+ // ── Phase 14: multi-track state (D-06, D-07, D-17, D-26, D-28) ──
634
+
635
+ /** Maps altGroup → track names sharing that altGroup. */
636
+ #switchingSets: Map<number, string[]> = new Map();
637
+
638
+ /** Repair packetIds that should not get their own FEC decoder (they ARE the repair source). */
639
+ #repairPacketIds: Set<number> = new Set();
640
+
641
+ /** Tracks physical-stream FEC blocks with an outstanding repair fetch. */
642
+ #repairInFlight: Set<string> = new Set();
643
+
644
+ /** Causal repair events; observers own any retention they require. */
645
+ #repairLifecycleObservers = new Set<RepairLifecycleObserver>();
646
+
647
+ #trackResetObservers = new Set<
648
+ (packetId: number, catalogTrackName: string) => void
649
+ >();
650
+
651
+ /** Callback fired when a repair fetch should be issued for a block. */
652
+ #onRepairSubscribeCb:
653
+ | ((sbn: number, packetId: number, catalogTrackName?: string) => void)
654
+ | null = null;
655
+
656
+ constructor(options?: MulticastManagerOptions) {
657
+ this.#fec = new FECManager();
658
+ this.#fec.addFecStatsObserver((packetId, stats) => {
659
+ try {
660
+ if (publishFecDiagnostics(packetId, stats)) {
661
+ this.#reportedFecDiagnosticsFailure = false;
662
+ }
663
+ } catch (error) {
664
+ if (!this.#reportedFecDiagnosticsFailure) {
665
+ this.#reportedFecDiagnosticsFailure = true;
666
+ console.warn('[MulticastManager] Failed to publish FEC diagnostics:', error);
667
+ }
668
+ }
669
+ });
670
+ this.#router = new MmtpRouter({
671
+ fecManager: this.#fec,
672
+ mfuTimeoutMs: options?.mfuTimeoutMs,
673
+ reassemblyPolicy: options?.reassemblyPolicy,
674
+ reassembler: options?.reassembler,
675
+ maxBufferedMfus: options?.maxBufferedMfus,
676
+ manifestVerifier: options?.manifestVerifier,
677
+ manifestVerifierOwnership: options?.manifestVerifierOwnership,
678
+ resolveManifestIdentity: options?.resolveManifestIdentity,
679
+ resolveManifestIdentityForRoute: options?.resolveManifestIdentityForRoute,
680
+ onBlockFailed: (sbn, packetId, catalogTrackName) => {
681
+ const pathCount = this.#fec.blockPathCount(
682
+ sbn,
683
+ packetId,
684
+ catalogTrackName,
685
+ );
686
+ this.#emitRepairLifecycle({
687
+ version: 1,
688
+ type: "failed",
689
+ block: repairBlockIdentity(packetId, sbn, catalogTrackName),
690
+ symbols: {
691
+ multicast: pathCount?.multicastSymbols ?? 0,
692
+ moq: pathCount?.moqSymbols ?? 0,
693
+ },
694
+ });
695
+ },
696
+ });
697
+ this.#transport = new MulticastTransport<MmtpIngressContext>((packet, ingress) => {
698
+ this.#router.feedPacket(packet, "multicast", undefined, ingress);
699
+ });
700
+
701
+ // Worker-path FEC recovery: route recovered source-symbol bytes through
702
+ // MmtpRouter so MFU reassembly + dedup runs. Without this wiring the
703
+ // FecWorkerClient's recovered frames fire FECManager.onBlockRecovered
704
+ // into a null callback and VideoDecoder never sees a real frame.
705
+ this.#fec.onBlockRecovered = (sbn, packetId, data, meta, recoveredTrackName) => {
706
+ const recovered = new Uint8Array(data);
707
+ const catalogTrackName = recoveredTrackName
708
+ ?? this.#fec.catalogTrackNameForBlock(sbn, packetId);
709
+ if (catalogTrackName === undefined) {
710
+ this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi);
711
+ } else {
712
+ this.#router.routeRecoveredSymbol(
713
+ sbn,
714
+ recovered,
715
+ packetId,
716
+ meta.esi,
717
+ catalogTrackName,
718
+ );
719
+ }
720
+ };
721
+
722
+ // D-28: release the in-flight repair slot when a repair-requested block
723
+ // resolves (completes, recovers, or fails), and emit the gate log line.
724
+ // The request-side bridge (FECManager.onRepairNeeded) is wired in
725
+ // onRepairSubscribe() so repair deadlines only arm once a player has
726
+ // registered a fetcher — consumers without D-28 pay no timer cost.
727
+ this.#fec.onRepairResolved = (sbn, packetId, outcome, catalogTrackName) => {
728
+ this.clearRepairForBlock(sbn, packetId, catalogTrackName);
729
+ if (outcome === "failed") return;
730
+ const pathCount = this.#fec.blockPathCount(
731
+ sbn,
732
+ packetId,
733
+ catalogTrackName,
734
+ );
735
+ const multicastSymbols = pathCount?.multicastSymbols ?? 0;
736
+ const moqSymbols = pathCount?.moqSymbols ?? 0;
737
+ this.#emitRepairLifecycle({
738
+ version: 1,
739
+ type: "recovered",
740
+ block: repairBlockIdentity(packetId, sbn, catalogTrackName),
741
+ symbols: {
742
+ multicast: multicastSymbols,
743
+ moq: moqSymbols,
744
+ },
745
+ });
746
+ };
747
+ this.#fec.onDecoderUnavailable = (event) => {
748
+ this.#emitRepairLifecycle({
749
+ version: 1,
750
+ type: "decoder-unavailable",
751
+ stream: repairStreamIdentity(event.packetId, event.catalogTrackName),
752
+ reason: event.reason,
753
+ fallbackDecoder: event.fallbackDecoder,
754
+ });
755
+ };
756
+ }
757
+
758
+ // ---------------------------------------------------------------------------
759
+ // Decomposed component accessors (for new code)
760
+ // ---------------------------------------------------------------------------
761
+
762
+ get fecManager(): FECManager {
763
+ return this.#fec;
764
+ }
765
+ get router(): MmtpRouter {
766
+ return this.#router;
767
+ }
768
+ get transport(): MulticastTransport {
769
+ return this.#transport;
770
+ }
771
+
772
+ // ---------------------------------------------------------------------------
773
+ // Stats
774
+ // ---------------------------------------------------------------------------
775
+
776
+ get reassembler() {
777
+ return this.#router.reassembler;
778
+ }
779
+ get diagnostics() {
780
+ return {
781
+ ...this.#router.getDiagnostics(),
782
+ _fec: this.#fec.diagnostics,
783
+ };
784
+ }
785
+ get stats() {
786
+ return this.#transport.stats;
787
+ }
788
+
789
+ get fecStats() {
790
+ return this.#fec.fecStats;
791
+ }
792
+
793
+ /** The raw WASM decoder for stats access */
794
+ get wasmDecoder() {
795
+ return this.#fec.wasmDecoder;
796
+ }
797
+
798
+ // ---------------------------------------------------------------------------
799
+ // FEC configuration (delegates to FECManager)
800
+ // ---------------------------------------------------------------------------
801
+
802
+ setFecDecoder(
803
+ packetId: number,
804
+ client: WasmFecDecoder,
805
+ k: number,
806
+ trackType: "video" | "audio" = "video",
807
+ config?: FECManagerConfig,
808
+ catalogTrackName?: string,
809
+ ) {
810
+ const fecConfig = { ...config, k };
811
+ this.#fec.setFecDecoder(packetId, client, k, fecConfig, catalogTrackName);
812
+ this.#configureTrackMapper(packetId, fecConfig);
813
+ this.#router.setPacketIdType(packetId, trackType);
814
+ }
815
+
816
+ /**
817
+ * Register a Worker-backed FEC decoder for a specific packetId (track).
818
+ * Worker-backed tracks decode asynchronously — recovered frames arrive via
819
+ * FECManager.onBlockRecovered callback instead of feedSourceSymbol return value.
820
+ */
821
+ setFecWorker(
822
+ packetId: number,
823
+ client: FecWorkerClient,
824
+ trackType: "video" | "audio",
825
+ config: FECManagerConfig,
826
+ catalogTrackName?: string,
827
+ ): void {
828
+ this.#fec.setFecWorker(packetId, client, config, catalogTrackName);
829
+ this.#configureTrackMapper(packetId, config);
830
+ this.#router.setPacketIdType(packetId, trackType);
831
+ }
832
+
833
+ /**
834
+ * Stop routing a stream's symbols to its Worker decoder. Returns false when
835
+ * none is registered; on success one `decoder-unavailable` repair lifecycle
836
+ * event with reason 'cleared' reaches every observer, carrying whether a
837
+ * synchronous WASM decoder still serves the stream. The packet mapping and
838
+ * track type are untouched: the track still exists, it just has no Worker
839
+ * decoder. Call this before disposing the client.
840
+ */
841
+ clearFecWorker(packetId: number, catalogTrackName?: string): boolean {
842
+ return this.#fec.clearFecWorker(packetId, catalogTrackName);
843
+ }
844
+
845
+ /** Register callback for Worker-level FecTrackStats updates. */
846
+ setOnFecStatsUpdate(
847
+ cb: (packetId: number, stats: FecTrackStats) => void,
848
+ ): void {
849
+ this.#fec.onFecStatsUpdate = cb;
850
+ }
851
+
852
+ setFramerate(fps: number) {
853
+ this.#router.setFramerate(fps);
854
+ }
855
+
856
+ #configureTrackMapper(packetId: number, config: FECManagerConfig): void {
857
+ const k = config.k;
858
+ const interleaveMs = config.interleaveDepthMs;
859
+ const fps = config.fps;
860
+ if (k && k > 0 && interleaveMs && interleaveMs > 0 && fps && fps > 0) {
861
+ this.#router.setFecMapper(
862
+ new FecBlockMapper({
863
+ k,
864
+ interleaveDepthMs: interleaveMs,
865
+ gopDurationMs: 1000 / fps,
866
+ }),
867
+ packetId,
868
+ );
869
+ }
870
+ }
871
+
872
+ /**
873
+ * Reduce every registered FEC timing contributor to the stream-global
874
+ * deadlines and apply them to the single transport and single router.
875
+ *
876
+ * `max`, not `min` or last-write-wins: a narrower track must never pull the
877
+ * failback watchdog or the router's reorder window in under a wider track's
878
+ * still-completable FEC block. Recomputed on every registration so a track
879
+ * that arrives late widens the stream rather than being clipped by it.
880
+ */
881
+ #applyStreamGlobalFecTiming(): void {
882
+ const contributions: FecTimingContribution[] = [
883
+ ...(this.#streamFecBaseline ? [this.#streamFecBaseline] : []),
884
+ ...this.#trackFecTiming.values(),
885
+ ];
886
+ if (contributions.length === 0) return;
887
+
888
+ let deliveryWindowMs: number | undefined;
889
+ let minimumFailbackWindowMs: number | undefined;
890
+ let mfuTimeoutMs: number | undefined;
891
+ let reorderWindowFrames: number | undefined;
892
+
893
+ for (const c of contributions) {
894
+ if (c.deliveryWindowMs !== undefined) {
895
+ deliveryWindowMs = Math.max(deliveryWindowMs ?? 0, c.deliveryWindowMs);
896
+ }
897
+ if (c.minimumFailbackWindowMs !== undefined) {
898
+ minimumFailbackWindowMs = Math.max(
899
+ minimumFailbackWindowMs ?? 0,
900
+ c.minimumFailbackWindowMs,
901
+ );
902
+ }
903
+ // Only a valid §5.1 ms interleave may drive router timing. A sub-floor
904
+ // value is a raw frame count (no fps to convert with) and would collapse
905
+ // the MFU timeout to ~0 and the reorder window to a single frame; skip
906
+ // the derivation rather than clamp it, leaving previously-configured
907
+ // router timing untouched.
908
+ const interleaveOk =
909
+ c.interleaveDepthMs !== undefined &&
910
+ c.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS;
911
+ if (interleaveOk && c.deliveryWindowMs) {
912
+ mfuTimeoutMs = Math.max(mfuTimeoutMs ?? 0, c.deliveryWindowMs);
913
+ }
914
+ if (interleaveOk && c.fps && c.fps > 0) {
915
+ reorderWindowFrames = Math.max(
916
+ reorderWindowFrames ?? 0,
917
+ fecReorderWindowFrames(
918
+ c.interleaveDepthMs,
919
+ c.fps,
920
+ c.k,
921
+ c.repairSymbols,
922
+ ),
923
+ );
924
+ }
925
+ }
926
+
927
+ const failbackWindowMs =
928
+ deliveryWindowMs === undefined
929
+ ? minimumFailbackWindowMs
930
+ : Math.max(deliveryWindowMs, minimumFailbackWindowMs ?? 0);
931
+ if (failbackWindowMs !== undefined) {
932
+ this.#transport.setFailbackWindowMs(failbackWindowMs);
933
+ }
934
+ if (mfuTimeoutMs !== undefined) {
935
+ this.#router.setMfuTimeoutMs(mfuTimeoutMs);
936
+ }
937
+ if (reorderWindowFrames !== undefined) {
938
+ this.#router.setReorderWindow(reorderWindowFrames);
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Configure one track's FEC geometry, block mapper, and block-inactivity
944
+ * deadline from that track's own catalog values, then re-reduce the
945
+ * stream-global deadlines across every registered track.
946
+ *
947
+ * This is the packet-specific replacement for the global `configureFec`
948
+ * side effects: per-track concerns stay per-track, and the genuinely
949
+ * stream-global ones (transport failback, router MFU timeout, router reorder
950
+ * window) become an explicit max reduction instead of a last-caller-wins race
951
+ * in which whichever track configured last silently set the whole stream's
952
+ * timing.
953
+ */
954
+ configureTrackFec(
955
+ packetId: number,
956
+ config: Pick<
957
+ MulticastEndpointConfig,
958
+ | "fecK"
959
+ | "fecRepairSymbols"
960
+ | "fecSymbolSize"
961
+ | "fecInterleaveDepthMs"
962
+ | "fecReorderToleranceMs"
963
+ | "fps"
964
+ | "bandwidth"
965
+ | "minimumFailbackWindowMs"
966
+ >,
967
+ ): void {
968
+ if (config.fecK === undefined || config.fecInterleaveDepthMs === undefined) {
969
+ throw new Error(
970
+ `[MulticastManager] configureTrackFec(${packetId}) requires fecK and ` +
971
+ "fecInterleaveDepthMs. Derive the per-track FEC geometry from the " +
972
+ "stream catalog.",
973
+ );
974
+ }
975
+ assertPositiveGeometry("fecK", config.fecK);
976
+ assertPositiveGeometry("fecInterleaveDepthMs", config.fecInterleaveDepthMs);
977
+ assertPositiveGeometry("fecSymbolSize", config.fecSymbolSize);
978
+ assertPositiveGeometry("fps", config.fps);
979
+ assertPositiveGeometry("bandwidth", config.bandwidth);
980
+ assertPositiveGeometry(
981
+ "minimumFailbackWindowMs",
982
+ config.minimumFailbackWindowMs,
983
+ );
984
+ assertNonNegativeGeometry("fecRepairSymbols", config.fecRepairSymbols);
985
+ // A tolerance is the network path's allowance, added to the encoder's
986
+ // span. These are public entry points: the catalog parser validates what it
987
+ // parses, but a direct caller reaches this arithmetic without passing
988
+ // through it. A negative value silently *shortens* the delivery window
989
+ // below the signaled span, and Infinity/NaN yields a window no block can
990
+ // ever miss -- the eviction bound stops bounding. Reuse the shared
991
+ // non-negative guard rather than an inline check: undefined must stay
992
+ // undefined (absent means the encoder never signaled one, and must not be
993
+ // defaulted), while a declared 0 is a real value that has to survive.
994
+ assertNonNegativeGeometry(
995
+ "fecReorderToleranceMs",
996
+ config.fecReorderToleranceMs,
997
+ );
998
+
999
+ // Same derivation as the stream-global path, but from THIS track's
1000
+ // geometry. FECManager already stores a per-track timeoutMs
1001
+ // (configureTrack/timeoutMsForPacket); it just never had the repair-symbol
1002
+ // and bandwidth inputs needed to compute one.
1003
+ const deliveryWindowMs =
1004
+ config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS
1005
+ ? computeFecDeliveryWindowMs({
1006
+ interleaveDepthMs: config.fecInterleaveDepthMs,
1007
+ sourceSymbols: config.fecK,
1008
+ repairSymbols: config.fecRepairSymbols,
1009
+ symbolSize: config.fecSymbolSize,
1010
+ mediaUnitsPerSecond: config.fps,
1011
+ bandwidthBps: config.bandwidth,
1012
+ jitterBufferMs: config.fecReorderToleranceMs,
1013
+ })
1014
+ : undefined;
1015
+
1016
+ const fecConfig: FECManagerConfig = {
1017
+ k: config.fecK,
1018
+ symbolSize: config.fecSymbolSize,
1019
+ interleaveDepthMs: config.fecInterleaveDepthMs,
1020
+ fps: config.fps,
1021
+ timeoutMs: deliveryWindowMs,
1022
+ };
1023
+ this.#fec.configureTrack(packetId, fecConfig);
1024
+ this.#configureTrackMapper(packetId, fecConfig);
1025
+
1026
+ this.#trackFecTiming.set(packetId, {
1027
+ deliveryWindowMs,
1028
+ interleaveDepthMs: config.fecInterleaveDepthMs,
1029
+ fps: config.fps,
1030
+ k: config.fecK,
1031
+ repairSymbols: config.fecRepairSymbols,
1032
+ minimumFailbackWindowMs: config.minimumFailbackWindowMs,
1033
+ });
1034
+ this.#applyStreamGlobalFecTiming();
1035
+ }
1036
+
1037
+ configureFec(
1038
+ config: Pick<
1039
+ MulticastEndpointConfig,
1040
+ | "fecK"
1041
+ | "fecRepairSymbols"
1042
+ | "fecSymbolSize"
1043
+ | "fecInterleaveDepthMs"
1044
+ | "fecReorderToleranceMs"
1045
+ | "fps"
1046
+ | "bandwidth"
1047
+ | "minimumFailbackWindowMs"
1048
+ >,
1049
+ ) {
1050
+ const minimumFailbackWindowMs = config.minimumFailbackWindowMs;
1051
+ if (
1052
+ minimumFailbackWindowMs !== undefined &&
1053
+ (!Number.isFinite(minimumFailbackWindowMs) || minimumFailbackWindowMs <= 0)
1054
+ ) {
1055
+ throw new Error(
1056
+ `[MulticastManager] minimumFailbackWindowMs=${minimumFailbackWindowMs}. ` +
1057
+ "Derive a positive failback floor from the stream catalog.",
1058
+ );
1059
+ }
1060
+ // A tolerance is the network path's allowance, added to the encoder's
1061
+ // span. These are public entry points: the catalog parser validates what it
1062
+ // parses, but a direct caller reaches this arithmetic without passing
1063
+ // through it. A negative value silently *shortens* the delivery window
1064
+ // below the signaled span, and Infinity/NaN yields a window no block can
1065
+ // ever miss -- the eviction bound stops bounding. Reuse the shared
1066
+ // non-negative guard rather than an inline check: undefined must stay
1067
+ // undefined (absent means the encoder never signaled one, and must not be
1068
+ // defaulted), while a declared 0 is a real value that has to survive.
1069
+ assertNonNegativeGeometry(
1070
+ "fecReorderToleranceMs",
1071
+ config.fecReorderToleranceMs,
1072
+ );
1073
+ const deliveryWindowMs =
1074
+ config.fecK &&
1075
+ config.fecK > 0 &&
1076
+ config.fecInterleaveDepthMs &&
1077
+ config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS
1078
+ ? computeFecDeliveryWindowMs({
1079
+ interleaveDepthMs: config.fecInterleaveDepthMs,
1080
+ sourceSymbols: config.fecK,
1081
+ repairSymbols: config.fecRepairSymbols,
1082
+ symbolSize: config.fecSymbolSize,
1083
+ mediaUnitsPerSecond: config.fps,
1084
+ bandwidthBps: config.bandwidth,
1085
+ jitterBufferMs: config.fecReorderToleranceMs,
1086
+ })
1087
+ : undefined;
1088
+ this.#fec.configure({
1089
+ k: config.fecK,
1090
+ symbolSize: config.fecSymbolSize,
1091
+ interleaveDepthMs: config.fecInterleaveDepthMs,
1092
+ fps: config.fps,
1093
+ timeoutMs: deliveryWindowMs,
1094
+ });
1095
+ // External-feed consumers own the multicast socket and call feedPacket()
1096
+ // directly, so they never enter join(). Configure the transport watchdog
1097
+ // here where both transport-owned and external-feed paths share the same
1098
+ // catalog-derived FEC timing.
1099
+ //
1100
+ // This call supplies the stream-global BASELINE contribution. Any track
1101
+ // registered via configureTrackFec() participates in the same max
1102
+ // reduction, so a per-track window can only widen these deadlines, never
1103
+ // narrow them below what this baseline already guarantees.
1104
+ this.#streamFecBaseline = {
1105
+ deliveryWindowMs,
1106
+ interleaveDepthMs: config.fecInterleaveDepthMs,
1107
+ fps: config.fps,
1108
+ k: config.fecK,
1109
+ repairSymbols: config.fecRepairSymbols,
1110
+ minimumFailbackWindowMs,
1111
+ };
1112
+ this.#applyStreamGlobalFecTiming();
1113
+ // Wire FecBlockMapper to MmtpRouter so SBN/ESI derivation works for FEC recovery.
1114
+ //
1115
+ // gopDurationMs MUST be the per-frame interval (1000/fps), NOT the
1116
+ // interleaveDepthMs. Passing interleaveDepthMs collapses D to 1, which
1117
+ // makes the mapper math equivalent to the no-mapper fallback
1118
+ // (floor(ssId/k), ssId%k) and breaks SBN/ESI derivation for any interleaved
1119
+ // FEC. Surfaced by BLO-3323 L14 (pim-multicast-gateway run 26373805710):
1120
+ // with K=32 + interleaveDepthMs=1000ms but gopDurationMs=1000ms, blocks
1121
+ // completed with only ~8 source + ~8 repair symbols (out of 40 expected at
1122
+ // 25-30fps), so the WASM decoder failed to recover any block at 10% loss.
1123
+ const k = config.fecK;
1124
+ const interleaveMs = config.fecInterleaveDepthMs;
1125
+ const fps = config.fps;
1126
+ if (k && k > 0 && interleaveMs && interleaveMs > 0 && fps && fps > 0) {
1127
+ const mapper = new FecBlockMapper({
1128
+ k,
1129
+ interleaveDepthMs: interleaveMs,
1130
+ gopDurationMs: 1000 / fps,
1131
+ });
1132
+ this.#router.setFecMapper(mapper);
1133
+ }
1134
+ }
1135
+
1136
+ // ---------------------------------------------------------------------------
1137
+ // Track configuration (delegates to MmtpRouter)
1138
+ // ---------------------------------------------------------------------------
1139
+
1140
+ // Track type discovery callback — fires when setPacketIdType is called.
1141
+ // Players wire this to MediaRouter.registerTrackTypeFromInit() for
1142
+ // init data authority (authoritative until next catalog update).
1143
+ #onTrackTypeDiscovered:
1144
+ | ((packetId: number, trackType: "video" | "audio") => void)
1145
+ | null = null;
1146
+
1147
+ /** Register callback for track type discovery (from init data / inline signaling). */
1148
+ onTrackTypeDiscovered(
1149
+ cb: (packetId: number, trackType: "video" | "audio") => void,
1150
+ ): void {
1151
+ this.#onTrackTypeDiscovered = cb;
1152
+ }
1153
+
1154
+ setPacketIdType(packetId: number, trackType: "video" | "audio") {
1155
+ this.#router.setPacketIdType(packetId, trackType);
1156
+ this.#onTrackTypeDiscovered?.(packetId, trackType);
1157
+ }
1158
+
1159
+ /**
1160
+ * Set the keyframe loss timeout on the inner MmtpRouter.
1161
+ * Must be called BEFORE setPacketIdType('video').
1162
+ * Derive from explicit keyframe/group duration:
1163
+ * Math.ceil(groupDurationMs * KEYFRAME_LOST_TIMEOUT_SLACK).
1164
+ */
1165
+ setKeyframeLostTimeoutMs(ms: number): void {
1166
+ this.#router.setKeyframeLostTimeoutMs(ms);
1167
+ }
1168
+
1169
+ setMfuTimeoutMs(ms: number): void {
1170
+ this.#router.setMfuTimeoutMs(ms);
1171
+ }
1172
+
1173
+ setTrackCodec(packetId: number, codec: string) {
1174
+ this.#router.setTrackCodec(packetId, codec);
1175
+ }
1176
+
1177
+ // ---------------------------------------------------------------------------
1178
+ // Callbacks (delegates to MmtpRouter)
1179
+ // ---------------------------------------------------------------------------
1180
+
1181
+ onVideoFrame(cb: FrameCallback) {
1182
+ this.#router.onVideoFrame(cb);
1183
+ }
1184
+ onAudioFrame(cb: FrameCallback) {
1185
+ this.#router.onAudioFrame(cb);
1186
+ }
1187
+ onTrackFrame(packetId: number, cb: FrameCallback) {
1188
+ this.#router.onTrackFrame(packetId, cb);
1189
+ }
1190
+ onTrackPacket(packetId: number, cb: TrackPacketCallback) {
1191
+ this.#router.onTrackPacket(packetId, cb);
1192
+ }
1193
+ onInitSegment(cb: (data: Uint8Array) => void) {
1194
+ this.#router.onInitSegment(cb);
1195
+ }
1196
+ onTrackInit(packetId: number, cb: (data: Uint8Array) => void) {
1197
+ this.#router.onTrackInit(packetId, cb);
1198
+ }
1199
+ onFecBlockStarted(cb: (sbn: number, packetId: number) => void) {
1200
+ this.#router.onFecBlockStarted(cb);
1201
+ }
1202
+ /** Unrecoverable-FEC-block signal. Players drive decoder recovery from this
1203
+ * (a starved decoder cannot resync on its own once the block is abandoned). */
1204
+ onFecBlockFailed(
1205
+ cb: (sbn: number, packetId: number, catalogTrackName?: string) => void,
1206
+ ) {
1207
+ this.#router.onFecBlockFailed(cb);
1208
+ }
1209
+
1210
+ // ---------------------------------------------------------------------------
1211
+ // MSE integration (delegates to MmtpRouter)
1212
+ // ---------------------------------------------------------------------------
1213
+
1214
+ enableMseAssembler(
1215
+ packetId: number,
1216
+ config: Omit<BlockAssemblerConfig, "k">,
1217
+ catalogTrackName?: string,
1218
+ ) {
1219
+ return this.#router.enableMseAssembler(packetId, config, catalogTrackName);
1220
+ }
1221
+
1222
+ onMseSegment(packetId: number, cb: (segment: Uint8Array) => void, catalogTrackName?: string) {
1223
+ this.#router.onMseSegment(packetId, cb, catalogTrackName);
1224
+ }
1225
+
1226
+ onMseSample(packetId: number, cb: (sample: CmafBlockSample) => void, catalogTrackName?: string) {
1227
+ this.#router.onMseSample(packetId, cb, catalogTrackName);
1228
+ }
1229
+
1230
+ onMseInitSegment(packetId: number, cb: (init: Uint8Array) => void, catalogTrackName?: string) {
1231
+ this.#router.onMseInitSegment(packetId, cb, catalogTrackName);
1232
+ }
1233
+
1234
+ emitMseInit(packetId: number, catalogTrackName?: string) {
1235
+ this.#router.emitMseInit(packetId, catalogTrackName);
1236
+ }
1237
+
1238
+ markExternalVideoSync(packetId?: number, catalogTrackName?: string) {
1239
+ this.#router.markExternalVideoSync(packetId, catalogTrackName);
1240
+ }
1241
+
1242
+ // ---------------------------------------------------------------------------
1243
+ // Transport (delegates to MulticastTransport)
1244
+ // ---------------------------------------------------------------------------
1245
+
1246
+ onTakeover(cb: () => void) {
1247
+ this.#transport.onTakeover(cb);
1248
+ }
1249
+ onFailback(cb: () => void) {
1250
+ this.#transport.onFailback(cb);
1251
+ }
1252
+ confirmVideo() {
1253
+ this.#transport.confirmVideo();
1254
+ }
1255
+ confirmAudio() {
1256
+ this.#transport.confirmAudio();
1257
+ }
1258
+
1259
+ static isAvailable(): boolean {
1260
+ return MulticastTransport.isAvailable();
1261
+ }
1262
+
1263
+ async join(config: MulticastEndpointConfig): Promise<boolean> {
1264
+ // Configure every FEC/MFU deadline before subscribing so direct-feed and
1265
+ // transport-owned consumers use the same catalog-derived delivery window.
1266
+ this.configureFec(config);
1267
+
1268
+ // Every transport join starts at an arbitrary multicast symbol. Reset only
1269
+ // join-boundary classification; retain configured tracks, decoders, and
1270
+ // cumulative diagnostics across failback/rejoin cycles.
1271
+ this.#fec.beginMulticastJoin();
1272
+ const selectedPacketIds = config.packetIds?.length
1273
+ ? new Set(config.packetIds)
1274
+ : null;
1275
+ const catalogTracks = config.tracks?.filter(({ packetId }) =>
1276
+ selectedPacketIds === null || selectedPacketIds.has(packetId));
1277
+ const ingressTracks = catalogTracks
1278
+ ? catalogTracks
1279
+ : selectedPacketIds === null
1280
+ ? undefined
1281
+ : [...selectedPacketIds].map((packetId) => ({ name: "", packetId }));
1282
+ const ingress = ingressTracks === undefined
1283
+ ? undefined
1284
+ : {
1285
+ tracks: Object.freeze(
1286
+ ingressTracks.map(({ name, packetId }) =>
1287
+ Object.freeze({ name, packetId }),
1288
+ ),
1289
+ ),
1290
+ };
1291
+ const transportConfig = {
1292
+ sourceAddress: config.sourceAddress,
1293
+ groupAddress: config.groupAddress,
1294
+ port: config.port,
1295
+ mode: config.mode,
1296
+ amtRelay: config.amtRelay ?? config.relayAddress,
1297
+ amtRelayPort: config.amtRelayPort ?? config.relayPort,
1298
+ packetIds: config.packetIds,
1299
+ tracks: catalogTracks?.map(({ name, packetId }) => ({ name, packetId })),
1300
+ };
1301
+ return ingress === undefined
1302
+ ? this.#transport.join(transportConfig)
1303
+ : this.#transport.join(transportConfig, ingress);
1304
+ }
1305
+
1306
+ /** Feed a raw packet from an external source (AMT tunnel, ASM subscription).
1307
+ *
1308
+ * For MoQ-unicast-fallback mode (no SSM bridge), call
1309
+ * `manager.router.feedPacket(packet, "moq")` directly to bypass
1310
+ * MulticastTransport's takeover/watchdog/failback gates while preserving
1311
+ * transport attribution in FEC path counters.
1312
+ * The `router` accessor above is already public.
1313
+ */
1314
+ feedPacket(data: ArrayBuffer | Uint8Array, ingress?: MmtpIngressContext) {
1315
+ this.#transport.feedPacket(data, ingress);
1316
+ }
1317
+
1318
+ /** Process a repair packet from MoQ (reliable QUIC). */
1319
+ processRepairFromMoQ(
1320
+ packet: Uint8Array,
1321
+ ownerPacketId?: number,
1322
+ catalogTrackName?: string,
1323
+ ) {
1324
+ this.#router.feedRepairPacket(packet, ownerPacketId, catalogTrackName);
1325
+ }
1326
+
1327
+ /** Route a Worker-recovered FEC block through the MMTP parser + dedup pipeline. */
1328
+ routeRecoveredBlock(
1329
+ blockId: number,
1330
+ data: Uint8Array,
1331
+ packetId: number,
1332
+ catalogTrackName?: string,
1333
+ ) {
1334
+ this.#router.routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
1335
+ }
1336
+
1337
+ /** Route one Worker-recovered source symbol through the MMTP parser + dedup pipeline. */
1338
+ routeRecoveredSymbol(
1339
+ blockId: number,
1340
+ data: Uint8Array,
1341
+ packetId: number,
1342
+ esi = 0,
1343
+ catalogTrackName?: string,
1344
+ ) {
1345
+ this.#router.routeRecoveredSymbol(blockId, data, packetId, esi, catalogTrackName);
1346
+ }
1347
+
1348
+ // ---------------------------------------------------------------------------
1349
+ // Phase 14: multi-track catalog ingest (D-04, D-06, D-07, D-17, D-26, D-28)
1350
+ // ---------------------------------------------------------------------------
1351
+
1352
+ /**
1353
+ * Configure the manager for a multi-track (base/delta/repair) CMAF switching set.
1354
+ *
1355
+ * D-04: Validates completeness via validateSwitchingSet — throws if incomplete.
1356
+ * D-07: Applies per-track FEC configs (base+delta get decoders; repair does not).
1357
+ * D-17: Builds altGroup → track-name index for getSwitchingSet().
1358
+ * D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
1359
+ *
1360
+ * @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
1361
+ * @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
1362
+ * a factory returning a distinct decoder per stream.
1363
+ */
1364
+ configureMultiTrack(
1365
+ catalog: MultiTrackCatalog,
1366
+ wasmDecoderSource?: WasmFecDecoder | WasmFecDecoderFactory,
1367
+ ): void {
1368
+ // D-06: Resolve explicit fec.repairTrack pointers FIRST. This allows
1369
+ // publisher-chosen repair track names (e.g. "video/repair-alt") that don't
1370
+ // match the standard "/repair" suffix convention. The explicit pointer wins
1371
+ // over the naming convention: a track pointed to by repairTrack is always
1372
+ // 'repair'. The pointer lives on the owner, and a repair leg is a raw
1373
+ // application/raptorq track the publisher never tags with an altGroup, so
1374
+ // the leg belongs to its owner's group regardless of what the leg itself
1375
+ // declares.
1376
+ //
1377
+ // Layers 1..n and a keyframe overlay are named only by their own `depends`
1378
+ // (draft-ramadan-moq-fec §6.3), so they are resolved here too. A layer that
1379
+ // did not reach this map would fall through to parseTrackRole and be
1380
+ // mis-roled or rejected on its name, and would land in its own altGroup
1381
+ // rather than its owner's.
1382
+ const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
1383
+ const repairOwnerGroup = new Map<string, string>();
1384
+ const repairOwnerAltGroup = new Map<string, number | undefined>();
1385
+ for (const track of catalog.tracks) {
1386
+ for (const repairName of repairNamesBySource.get(track.name) ?? []) {
1387
+ repairOwnerGroup.set(repairName, altGroupKey(track));
1388
+ repairOwnerAltGroup.set(repairName, track.altGroup);
1389
+ }
1390
+ }
1391
+ const groupKeyFor = (track: MultiTrackCatalogTrack): string =>
1392
+ repairOwnerGroup.get(track.name) ?? altGroupKey(track);
1393
+
1394
+ const trackNamesByGroup = new Map<string, string[]>();
1395
+ for (const track of catalog.tracks) {
1396
+ const groupKey = groupKeyFor(track);
1397
+ const groupNames = trackNamesByGroup.get(groupKey) ?? [];
1398
+ groupNames.push(track.name);
1399
+ trackNamesByGroup.set(groupKey, groupNames);
1400
+ }
1401
+
1402
+ const hasSwitchingSetByGroup = new Map<string, boolean>();
1403
+ for (const [groupKey, trackNames] of trackNamesByGroup) {
1404
+ hasSwitchingSetByGroup.set(groupKey, hasSwitchingSetRole(trackNames));
1405
+ }
1406
+
1407
+ // Resolve role for each track — explicit pointer wins over naming convention.
1408
+ // Normalize non-standard repair names to 'repair' before calling validateSwitchingSet,
1409
+ // which relies on parseTrackRole and would throw on custom repair-track names.
1410
+ const resolvedRoles: TrackRole[] = catalog.tracks.map((track) => {
1411
+ if (repairOwnerGroup.has(track.name)) return "repair";
1412
+ const hasSwitchingSet = hasSwitchingSetByGroup.get(altGroupKey(track)) ?? false;
1413
+ if (!hasSwitchingSet) return "single";
1414
+ return parseTrackRole(track.name);
1415
+ });
1416
+
1417
+ // D-04: Validate completeness per altGroup using resolved roles.
1418
+ // We replicate validateSwitchingSet logic here to avoid re-calling parseTrackRole
1419
+ // (which would throw on explicit-pointer repair track names like "video/repair-alt").
1420
+ const resolvedRolesByGroup = new Map<string, TrackRole[]>();
1421
+ for (let i = 0; i < catalog.tracks.length; i++) {
1422
+ const groupKey = groupKeyFor(catalog.tracks[i]!);
1423
+ const roles = resolvedRolesByGroup.get(groupKey) ?? [];
1424
+ roles.push(resolvedRoles[i]!);
1425
+ resolvedRolesByGroup.set(groupKey, roles);
1426
+ }
1427
+ for (const [groupKey, roles] of resolvedRolesByGroup) {
1428
+ if (!(hasSwitchingSetByGroup.get(groupKey) ?? false)) continue;
1429
+
1430
+ const presentRoles = new Set(roles.filter((r) => r !== "single"));
1431
+ const required: Array<"base" | "delta" | "repair"> = [
1432
+ "base",
1433
+ "delta",
1434
+ "repair",
1435
+ ];
1436
+ const missing = required.filter((r) => !presentRoles.has(r));
1437
+ if (missing.length > 0) {
1438
+ throw new Error(
1439
+ `[MulticastManager] incomplete switching set: missing ${missing.join(", ")}`,
1440
+ );
1441
+ }
1442
+ }
1443
+
1444
+ const fecTrackIndexes: number[] = [];
1445
+ for (let i = 0; i < catalog.tracks.length; i++) {
1446
+ const track = catalog.tracks[i]!;
1447
+ if (resolvedRoles[i] === "repair" || track.fec === undefined) continue;
1448
+ if (!isFecCatalogConfig(track.fec)) {
1449
+ throw new Error(
1450
+ `[MulticastManager] invalid fec config for track '${track.name}'`,
1451
+ );
1452
+ }
1453
+ if (track.packetId !== undefined) fecTrackIndexes.push(i);
1454
+ }
1455
+
1456
+ // Stateful decoders cannot span physical streams: resetting one stream
1457
+ // would otherwise clear or disable every peer sharing that instance.
1458
+ const fecDecoders = new Map<number, WasmFecDecoder>();
1459
+ if (wasmDecoderSource !== undefined && fecTrackIndexes.length > 0) {
1460
+ if (typeof wasmDecoderSource !== "function") {
1461
+ if (fecTrackIndexes.length > 1) {
1462
+ throw new Error(
1463
+ "[MulticastManager] multiple FEC streams require a decoder factory",
1464
+ );
1465
+ }
1466
+ fecDecoders.set(fecTrackIndexes[0]!, wasmDecoderSource);
1467
+ } else {
1468
+ const clients = new Set<WasmFecDecoder>();
1469
+ for (const index of fecTrackIndexes) {
1470
+ const client = wasmDecoderSource(catalog.tracks[index]!);
1471
+ if (clients.has(client)) {
1472
+ throw new Error(
1473
+ "[MulticastManager] decoder factory must return one client per physical stream",
1474
+ );
1475
+ }
1476
+ clients.add(client);
1477
+ fecDecoders.set(index, client);
1478
+ }
1479
+ }
1480
+ }
1481
+
1482
+ // Reset switching-set index and repair packetId set.
1483
+ this.#switchingSets.clear();
1484
+ this.#repairPacketIds.clear();
1485
+ this.#router.clearRepairPacketIdOwners();
1486
+ const catalogTracksByName = new Map(
1487
+ catalog.tracks.map((track) => [track.name, track] as const),
1488
+ );
1489
+
1490
+ for (let i = 0; i < catalog.tracks.length; i++) {
1491
+ const track = catalog.tracks[i]!;
1492
+ const role = resolvedRoles[i]!;
1493
+
1494
+ // D-17: Build altGroup → track-name index. A repair leg belongs to the
1495
+ // switching set of the owner that points at it.
1496
+ const altGroup = repairOwnerAltGroup.has(track.name)
1497
+ ? repairOwnerAltGroup.get(track.name)
1498
+ : track.altGroup;
1499
+ if (altGroup !== undefined) {
1500
+ const existing = this.#switchingSets.get(altGroup) ?? [];
1501
+ existing.push(track.name);
1502
+ this.#switchingSets.set(altGroup, existing);
1503
+ }
1504
+
1505
+ // D-26: Register packetId → role on MmtpRouter (if packetId provided at ingest time).
1506
+ if (track.packetId !== undefined) {
1507
+ this.#router.setPacketIdRole(track.packetId, role);
1508
+ }
1509
+
1510
+ if (role === "repair") {
1511
+ // D-06: Repair track is the repair source — no FEC decoder registered on it.
1512
+ if (track.packetId !== undefined) {
1513
+ this.#repairPacketIds.add(track.packetId);
1514
+ }
1515
+ continue;
1516
+ }
1517
+
1518
+ // D-07: Apply per-track FEC config for non-repair tracks.
1519
+ if (track.fec !== undefined) {
1520
+ const fecCfg = track.fec as FecCatalogConfig;
1521
+ // Every repair layer and the keyframe overlay route to this source's
1522
+ // decoder, not only layer 0. A layer whose packetId had no registered
1523
+ // owner would have its repair symbols dropped as an unknown owner, and
1524
+ // two sources whose RS_ID ranges overlap would be indistinguishable.
1525
+ if (track.packetId !== undefined) {
1526
+ for (const repairName of repairNamesBySource.get(track.name) ?? []) {
1527
+ const repairTrack = catalogTracksByName.get(repairName);
1528
+ if (repairTrack?.packetId !== undefined) {
1529
+ this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
1530
+ }
1531
+ }
1532
+ }
1533
+ const wasmDecoder = fecDecoders.get(i);
1534
+ if (track.packetId !== undefined && wasmDecoder !== undefined) {
1535
+ // trackType: base/delta/single video tracks are 'video'; audio would be 'audio'.
1536
+ // At this layer we treat all content tracks as 'video' — audio tracks typically
1537
+ // have no FEC in Phase 14 (D-26 covers video base+delta only). Callers that need
1538
+ // audio FEC can call setFecDecoder() directly after configureMultiTrack().
1539
+ this.#fec.setFecDecoder(
1540
+ track.packetId,
1541
+ wasmDecoder,
1542
+ fecCfg.sourceSymbols,
1543
+ {
1544
+ k: fecCfg.sourceSymbols,
1545
+ symbolSize: fecCfg.symbolSize,
1546
+ interleaveDepthMs: fecCfg.interleaveDepthMs,
1547
+ fps: track.framerate,
1548
+ },
1549
+ track.name,
1550
+ );
1551
+ this.#configureTrackMapper(track.packetId, {
1552
+ k: fecCfg.sourceSymbols,
1553
+ symbolSize: fecCfg.symbolSize,
1554
+ interleaveDepthMs: fecCfg.interleaveDepthMs,
1555
+ fps: track.framerate,
1556
+ });
1557
+ this.#router.setPacketIdType(track.packetId, "video");
1558
+ }
1559
+ }
1560
+ }
1561
+ }
1562
+
1563
+ /**
1564
+ * Return the track names belonging to the given altGroup switching set.
1565
+ * D-17: altGroup groups base+delta+repair into one switching set.
1566
+ *
1567
+ * @param altGroup - The altGroup number to look up.
1568
+ * @returns Array of track names in that switching set (empty if none).
1569
+ */
1570
+ getSwitchingSet(altGroup: number): string[] {
1571
+ return this.#switchingSets.get(altGroup) ?? [];
1572
+ }
1573
+
1574
+ /**
1575
+ * Register callback fired when a repair fetch should be issued for an FEC block.
1576
+ *
1577
+ * D-28: At most one outstanding repair fetch per (packetId, sbn) block.
1578
+ * clearRepairForBlock fires automatically when the block resolves (via
1579
+ * FECManager.onRepairResolved); call it manually only on fetch failure
1580
+ * paths the FEC layer cannot observe (fetch error, range not retained,
1581
+ * transport timeout) so a stuck fetch cannot wedge the block's slot.
1582
+ *
1583
+ * Registering also arms the FEC repair deadline: each block gets a one-shot
1584
+ * timer for the derived K-symbol source-production horizon; a block still
1585
+ * short of K when it fires requests repair here. Failure accounting timing
1586
+ * is unchanged.
1587
+ */
1588
+ onRepairSubscribe(
1589
+ cb: (sbn: number, packetId: number, catalogTrackName?: string) => void,
1590
+ ): void {
1591
+ this.#onRepairSubscribeCb = cb;
1592
+ this.#fec.onRepairNeeded = (sbn, packetId, catalogTrackName) => {
1593
+ this.requestRepairForBlock(sbn, packetId, catalogTrackName);
1594
+ };
1595
+ }
1596
+
1597
+ /**
1598
+ * Observe causal repair lifecycle events without retaining snapshots in the
1599
+ * transport. Returns an unsubscribe function.
1600
+ */
1601
+ addRepairLifecycleObserver(observer: RepairLifecycleObserver): () => void {
1602
+ this.#repairLifecycleObservers.add(observer);
1603
+ return () => this.#repairLifecycleObservers.delete(observer);
1604
+ }
1605
+
1606
+ /** Observe per-stream epoch resets (used by repair transports to abort I/O). */
1607
+ addTrackResetObserver(
1608
+ observer: (packetId: number, catalogTrackName: string) => void,
1609
+ ): () => void {
1610
+ this.#trackResetObservers.add(observer);
1611
+ return () => this.#trackResetObservers.delete(observer);
1612
+ }
1613
+
1614
+ /** Begin a fresh retained-replay epoch for one physical media stream. */
1615
+ resetTrack(packetId: number, catalogTrackName: string): void {
1616
+ if (!catalogTrackName) {
1617
+ throw new Error('[MulticastManager] catalogTrackName is required for track reset');
1618
+ }
1619
+ for (const observer of this.#trackResetObservers) {
1620
+ try {
1621
+ observer(packetId, catalogTrackName);
1622
+ } catch (error) {
1623
+ console.warn('[MulticastManager] track reset observer failed', error);
1624
+ }
1625
+ }
1626
+ for (const key of this.#repairInFlight) {
1627
+ try {
1628
+ const [keyPacketId, keyTrackName] = JSON.parse(key);
1629
+ if (keyPacketId === packetId && keyTrackName === catalogTrackName) {
1630
+ this.#repairInFlight.delete(key);
1631
+ }
1632
+ } catch {
1633
+ // Internal keys are JSON tuples; preserve unknown legacy entries.
1634
+ }
1635
+ }
1636
+ this.#fec.resetTrack(packetId, catalogTrackName);
1637
+ this.#router.resetTrack(packetId, catalogTrackName);
1638
+ }
1639
+
1640
+ /**
1641
+ * Request a repair-track fetch for the given FEC block.
1642
+ *
1643
+ * D-28: duplicate requests for the same (packetId, sbn) are suppressed
1644
+ * until clearRepairForBlock is called.
1645
+ *
1646
+ * @param sbn - Source Block Number identifying the FEC block needing repair.
1647
+ * @param packetId - MMTP packet_id owning the block (video/audio tracks have
1648
+ * independent SBN spaces, so the in-flight key must be composite).
1649
+ */
1650
+ requestRepairForBlock(
1651
+ sbn: number,
1652
+ packetId: number,
1653
+ catalogTrackName?: string,
1654
+ ): void {
1655
+ // D-28: at most one outstanding fetch per FEC block (per packetId:sbn).
1656
+ // Different blocks are independent and are NOT blocked by each other.
1657
+ const block = repairBlockIdentity(packetId, sbn, catalogTrackName);
1658
+ const key = JSON.stringify([packetId, block.catalogTrackName, sbn]);
1659
+ if (this.#repairInFlight.has(key)) return;
1660
+ this.#repairInFlight.add(key);
1661
+ this.#emitRepairLifecycle({
1662
+ version: 1,
1663
+ type: "requested",
1664
+ block,
1665
+ });
1666
+ if (catalogTrackName === undefined) {
1667
+ this.#onRepairSubscribeCb?.(sbn, packetId);
1668
+ } else {
1669
+ this.#onRepairSubscribeCb?.(sbn, packetId, catalogTrackName);
1670
+ }
1671
+ }
1672
+
1673
+ /**
1674
+ * Clear in-flight repair state for the given block.
1675
+ * Fired automatically when the block resolves; safe to call redundantly.
1676
+ */
1677
+ clearRepairForBlock(
1678
+ sbn: number,
1679
+ packetId: number,
1680
+ catalogTrackName?: string,
1681
+ ): void {
1682
+ this.#repairInFlight.delete(
1683
+ JSON.stringify([packetId, catalogTrackName ?? null, sbn]),
1684
+ );
1685
+ }
1686
+
1687
+ #emitRepairLifecycle(event: RepairLifecycleEvent): void {
1688
+ const immutableEvent = Object.freeze(
1689
+ event.type === "decoder-unavailable"
1690
+ ? {...event, stream: Object.freeze({...event.stream})}
1691
+ : {
1692
+ ...event,
1693
+ block: Object.freeze({...event.block}),
1694
+ ...(event.type === "requested"
1695
+ ? {}
1696
+ : {symbols: Object.freeze({...event.symbols})}),
1697
+ },
1698
+ ) as RepairLifecycleEvent;
1699
+ // Serialize before invoking observers so a re-entrant observer cannot log
1700
+ // a resolution before the request that caused it.
1701
+ console.info(
1702
+ `${REPAIR_LIFECYCLE_LOG_PREFIX}${JSON.stringify(immutableEvent)}`,
1703
+ );
1704
+ for (const observer of this.#repairLifecycleObservers) {
1705
+ try {
1706
+ observer(immutableEvent);
1707
+ } catch (error) {
1708
+ console.warn("[MulticastManager] repair lifecycle observer failed", error);
1709
+ }
1710
+ }
1711
+ }
1712
+
1713
+ dispose() {
1714
+ this.#trackResetObservers.clear();
1715
+ this.#repairLifecycleObservers.clear();
1716
+ this.#transport.dispose();
1717
+ this.#router.dispose();
1718
+ this.#fec.dispose();
1719
+ }
1720
+ }