@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,938 @@
1
+ /**
2
+ * @mmt/container - Sample-level combining of subsample data units (moqcast #37)
3
+ *
4
+ * {@link MFUReassembler} reassembles exactly one data unit -- a whole-sample
5
+ * DU, or one subsample DU of a partitioned sample. When a timed sample
6
+ * exceeds one data unit's 256-payload capacity, the origin packetizer
7
+ * partitions it into multiple subsample data units (each its own FI chain,
8
+ * its own authored base offset). This module combines those
9
+ * already-reassembled data units back into one contiguous sample.
10
+ *
11
+ * Container-blind, matching {@link MFUReassembler}'s own doctrine: it never
12
+ * parses ISOBMFF itself. The authored sample size a subsample-partitioned
13
+ * sample must total to comes from the host's own FT=1 (movie-fragment-
14
+ * metadata) parse, supplied via {@link SampleCombiner.registerAuthoredSize}.
15
+ * Completion is a protocol event -- every constituent data unit reassembled,
16
+ * byte-adjacent from zero, totaling the registered size -- never a
17
+ * timer-based guess; on lossy paths the existing per-data-unit deadline in
18
+ * {@link MFUReassembler} already bounds each constituent, so this module adds
19
+ * no second timer tier, matching moqcast #37 section 5.2.1.
20
+ *
21
+ * A whole-sample data unit whose length already equals the registered
22
+ * authored size passes through immediately, with no buffering. When
23
+ * `requireAuthoredSize` is false, an offset-zero data unit with no registered
24
+ * size at all also passes through immediately -- the pre-amendment shape, for
25
+ * a host that does not drive FT=1. When true, that same data unit instead
26
+ * buffers and waits for a size to be registered: FT=1 always publishes before
27
+ * any FT=2 sample referencing it (moqcast #37), but on a lossy path FT=1 can
28
+ * still *arrive* after an offset-zero data unit (reordering, or FT=1's own DU
29
+ * FEC-recovered late) -- treating "no size yet" as "whole sample" in that
30
+ * window would silently truncate a subsample-partitioned sample.
31
+ *
32
+ * Both `#pending` (incomplete samples) and `#authoredSizes` (registered but
33
+ * not yet consumed) are bounded. `#pending` is bounded by `maxBuffered`
34
+ * samples and `maxSegmentsPerSample` data units per sample; a full table
35
+ * evicts its oldest sample by insertion order rather than throwing, so one
36
+ * FEC-lost subsample DU cannot wedge every future sample. `#authoredSizes` is
37
+ * bounded by retiring each key the moment its sample completes, keeping only a
38
+ * bounded recent-retirement set so a late duplicate or FEC-recovered
39
+ * retransmission for that same sample is dropped rather than silently
40
+ * re-emitted or misread as a fresh whole sample.
41
+ *
42
+ * @packageDocumentation
43
+ */
44
+
45
+ import type {
46
+ MmtpSourcePath,
47
+ ReassembledMFU,
48
+ MmtpDeliveryProvenance,
49
+ } from './mfu-reassembler.js'
50
+
51
+ /** Identifies one authored sample: `(catalogTrackName, mpu, movieFragmentSequenceNumber, sampleNumber)`. */
52
+ type SampleKey = string
53
+
54
+ function sampleKey(
55
+ catalogTrackName: string | undefined,
56
+ mpuSequenceNumber: number,
57
+ movieFragmentSequenceNumber: number,
58
+ sampleNumber: number,
59
+ ): SampleKey {
60
+ return JSON.stringify([catalogTrackName ?? null, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber])
61
+ }
62
+
63
+ /** The `catalogTrackName` a key was built from, or undefined when it had none. */
64
+ function trackNameOfKey(key: SampleKey): string | undefined {
65
+ const name = (JSON.parse(key) as [string | null, number, number, number])[0]
66
+ return name ?? undefined
67
+ }
68
+
69
+ /** The full authored-sample identity a key was built from. */
70
+ function identityOfKey(key: SampleKey): AbandonedSample {
71
+ const [name, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber] =
72
+ JSON.parse(key) as [string | null, number, number, number]
73
+ return {
74
+ catalogTrackName: name ?? undefined,
75
+ mpuSequenceNumber,
76
+ movieFragmentSequenceNumber,
77
+ sampleNumber,
78
+ }
79
+ }
80
+
81
+ /**
82
+ * An authored sample that provably will not be delivered, reported to the
83
+ * owner of any per-sample state keyed on this identity so that state can be
84
+ * released. See {@link SampleCombinerOptions.onAbandon}.
85
+ */
86
+ export interface AbandonedSample {
87
+ catalogTrackName: string | undefined
88
+ mpuSequenceNumber: number
89
+ movieFragmentSequenceNumber: number
90
+ sampleNumber: number
91
+ }
92
+
93
+ /** One authored sample combined from its constituent data unit(s). */
94
+ export interface CombinedSample {
95
+ mpuSequenceNumber: number
96
+ movieFragmentSequenceNumber: number
97
+ sampleNumber: number
98
+ /** The full sample's media bytes, in ascending-offset order. */
99
+ data: Uint8Array
100
+ /** Raw MMTP short-NTP header timestamp for the offset-zero data unit. */
101
+ mmtpTimestamp?: bigint
102
+ timestamp: bigint
103
+ /**
104
+ * The sample's RAP flag is the RAP flag of the FI=0 or FI=1 object of its
105
+ * offset-0 data unit (moqcast #37).
106
+ */
107
+ rapFlag: boolean
108
+ priority?: number
109
+ /** Every authoritative source-packet sequence across all constituent data units, in offset order. */
110
+ packetSequenceNumbers?: number[]
111
+ /** Subset of packetSequenceNumbers reconstructed by FEC. */
112
+ repairedPacketSequenceNumbers?: number[]
113
+ catalogTrackName?: string
114
+ /** Number of subsample data units combined (1 for a whole-sample DU or a non-timed passthrough). */
115
+ subsampleCount: number
116
+ /** MMTP fragments that formed the sample, summed across constituent data units. */
117
+ fragmentCount: number
118
+ /**
119
+ * Transport that supplied the offset-zero data unit's head fragment. Taken
120
+ * from that data unit alone -- like {@link CombinedSample.rapFlag} -- rather
121
+ * than from whichever sibling happened to complete last.
122
+ */
123
+ source?: MmtpSourcePath
124
+ /**
125
+ * Strict provenance folded across EVERY constituent data unit, and within
126
+ * each of them across every MMTP fragment. Unlike {@link CombinedSample.source},
127
+ * which is the offset-zero DU's routing tag, this is whole-sample evidence:
128
+ * only 'multicast' proves that no byte of this sample arrived another way.
129
+ */
130
+ deliveryProvenance: MmtpDeliveryProvenance
131
+ }
132
+
133
+ export const SAMPLE_COMBINE_ERROR_CODES = [
134
+ 'duplicate-subsample-offset',
135
+ 'overlapping-subsample',
136
+ 'exceeds-authored-size',
137
+ 'too-many-subsample-segments',
138
+ ] as const
139
+
140
+ export type SampleCombineErrorCode = typeof SAMPLE_COMBINE_ERROR_CODES[number]
141
+
142
+ /**
143
+ * Fail-closed rejection reasons for subsample combining. Distinct from
144
+ * {@link MfuReassemblyError}, which governs one data unit's own fragment
145
+ * chain; these govern combining already-reassembled data units into a
146
+ * sample.
147
+ */
148
+ export class SampleCombineError extends Error {
149
+ readonly name = 'SampleCombineError'
150
+
151
+ constructor(readonly code: SampleCombineErrorCode, message: string) {
152
+ super(message)
153
+ }
154
+ }
155
+
156
+ export interface SampleCombinerStats {
157
+ combinedSamples: number
158
+ passthroughSamples: number
159
+ timedOutSamples: number
160
+ /** Incomplete samples discarded to enforce `maxBuffered`. */
161
+ evictedSamples: number
162
+ }
163
+
164
+ export interface SampleCombinerOptions {
165
+ /**
166
+ * Max concurrent incomplete samples. Enforced by evicting the oldest by
167
+ * insertion order, never by throwing, so a host that never calls
168
+ * {@link SampleCombiner.cleanupTimeouts} still recovers from a permanently
169
+ * lost subsample data unit instead of wedging.
170
+ */
171
+ maxBuffered: number
172
+ /**
173
+ * When true, an offset-zero data unit with no registered authored size
174
+ * buffers and waits for one rather than passing through immediately. See
175
+ * the module doc.
176
+ */
177
+ requireAuthoredSize: boolean
178
+ /**
179
+ * Max subsample data units held for one sample at once. Bounds both memory
180
+ * and the per-insert overlap scan; a legitimate subsample-partitioned
181
+ * sample needs at most `ceil(sampleSize / (256 * maxPayload))` data units.
182
+ */
183
+ maxSegmentsPerSample: number
184
+ /**
185
+ * Called once per sample discarded by eviction or timeout -- the two paths
186
+ * where a sample is abandoned while its stream keeps running.
187
+ *
188
+ * Completion and passthrough are deliberately excluded: those samples were
189
+ * delivered, and their consumer releases whatever per-sample state it held.
190
+ * Wholesale resets (`flush`, `resetStream`) are excluded too, because they
191
+ * already discard that state in bulk.
192
+ *
193
+ * Exists because per-sample state elsewhere -- notably the ISO-BMFF
194
+ * timing-sample budget -- is otherwise only released on delivery, making it
195
+ * a one-way ratchet that wedges the stream once it saturates. The combiner
196
+ * is where abandonment is actually detected, so it is where the signal has
197
+ * to come from; inferring it downstream would require a timer or an age
198
+ * bound, neither of which is derivable from the catalog.
199
+ */
200
+ onAbandon?: (sample: AbandonedSample) => void
201
+ }
202
+
203
+ interface PendingSample {
204
+ /** Held data units keyed by their authored base offset. */
205
+ segments: Map<number, ReassembledMFU>
206
+ startedAt: number
207
+ }
208
+
209
+ /** Combines subsample data units of one sample into a contiguous whole. */
210
+ export class SampleCombiner {
211
+ #maxBuffered: number
212
+ #requireAuthoredSize: boolean
213
+ #maxSegmentsPerSample: number
214
+ #onAbandon: ((sample: AbandonedSample) => void) | undefined
215
+ #authoredSizes: Map<SampleKey, number> = new Map()
216
+ /** Map iteration is insertion-ordered, so the first key is the oldest. */
217
+ #pending: Map<SampleKey, PendingSample> = new Map()
218
+ /**
219
+ * Bounded, insertion-ordered set of recently-completed sample keys, so a
220
+ * late duplicate or FEC-recovered retransmission is dropped rather than
221
+ * re-emitted or misread as a fresh whole sample.
222
+ */
223
+ #retired: Set<SampleKey> = new Set()
224
+ #stats: SampleCombinerStats = {
225
+ combinedSamples: 0,
226
+ passthroughSamples: 0,
227
+ timedOutSamples: 0,
228
+ evictedSamples: 0,
229
+ }
230
+
231
+ constructor(options: SampleCombinerOptions) {
232
+ const { maxBuffered, requireAuthoredSize, maxSegmentsPerSample, onAbandon } = options
233
+ if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {
234
+ throw new Error('[SampleCombiner] maxBuffered must be a positive integer')
235
+ }
236
+ if (!Number.isInteger(maxSegmentsPerSample) || maxSegmentsPerSample <= 0) {
237
+ throw new Error('[SampleCombiner] maxSegmentsPerSample must be a positive integer')
238
+ }
239
+ this.#maxBuffered = maxBuffered
240
+ this.#requireAuthoredSize = requireAuthoredSize
241
+ this.#maxSegmentsPerSample = maxSegmentsPerSample
242
+ this.#onAbandon = onAbandon
243
+ }
244
+
245
+ /** Report one abandoned sample to the owner of any state keyed on it. */
246
+ #abandon(key: SampleKey): void {
247
+ this.#onAbandon?.(identityOfKey(key))
248
+ }
249
+
250
+ /**
251
+ * Register the authored sample size from the host's own FT=1 parse. Per
252
+ * moqcast #37, FT=1 for a movie fragment publishes before any FT=2 sample
253
+ * referencing it, so a host driving FT=1 as it arrives registers each
254
+ * sample's size before that sample's own data unit(s) arrive.
255
+ *
256
+ * On a lossy path FT=1 can still *arrive* late, after data units this
257
+ * combiner has already buffered. Registering a size therefore re-drives the
258
+ * completion check and returns the combined sample when the buffered data
259
+ * units already satisfy the newly-known size. Callers MUST consume the
260
+ * return value: for a sample carried by a single data unit there is no
261
+ * later {@link SampleCombiner.addMfu} to re-enter, so discarding it
262
+ * silently drops the sample.
263
+ *
264
+ * Registering also clears the key from the retired set. Retirement exists
265
+ * to drop data units that arrive *passively* after a sample completed — a
266
+ * late duplicate, an FEC-recovered retransmission. A host calling this
267
+ * method is instead asserting that a sample with this identity is authored
268
+ * now, which is what happens when a publisher restarts and its MPU
269
+ * sequence numbers begin again, or when MPU sequence wraps. Without this,
270
+ * such a stream would silently drop every sample whose identity collided
271
+ * with a retired one until the bounded set aged the key out.
272
+ */
273
+ registerAuthoredSize(
274
+ catalogTrackName: string | undefined,
275
+ mpuSequenceNumber: number,
276
+ movieFragmentSequenceNumber: number,
277
+ sampleNumber: number,
278
+ size: number,
279
+ ): CombinedSample | null {
280
+ const key = sampleKey(catalogTrackName, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber)
281
+ this.#retired.delete(key)
282
+ this.#authoredSizes.set(key, size)
283
+ return this.#tryComplete(key, size)
284
+ }
285
+
286
+ /**
287
+ * Add one reassembled data unit. Returns the combined sample once every
288
+ * constituent data unit has arrived and totals the authored size.
289
+ */
290
+ addMfu(mfu: ReassembledMFU): CombinedSample | null {
291
+ if (mfu.movieFragmentSequenceNumber === undefined || mfu.sampleNumber === undefined) {
292
+ // Non-timed (Item) MFU: sample combining does not apply.
293
+ this.#stats.passthroughSamples++
294
+ return fromPassthrough(mfu)
295
+ }
296
+ const offset = mfu.sampleOffset ?? 0
297
+ const key = sampleKey(
298
+ mfu.catalogTrackName,
299
+ mfu.mpuSequenceNumber,
300
+ mfu.movieFragmentSequenceNumber,
301
+ mfu.sampleNumber,
302
+ )
303
+
304
+ if (this.#retired.has(key)) {
305
+ // Duplicate or late data unit for an already-retired sample (a
306
+ // FEC-recovered retransmission, reordering after completion, or a
307
+ // sample discarded by eviction/timeout). Drop it rather than
308
+ // re-emitting a duplicate downstream or misreading a stray fragment as
309
+ // a fresh whole sample.
310
+ return null
311
+ }
312
+
313
+ const dataLen = mfu.data.byteLength
314
+ const authoredSize = this.#authoredSizes.get(key)
315
+
316
+ if (offset === 0 && !this.#pending.has(key)) {
317
+ const passesThrough = authoredSize === undefined
318
+ ? !this.#requireAuthoredSize
319
+ : dataLen === authoredSize
320
+ if (passesThrough) {
321
+ this.#stats.passthroughSamples++
322
+ this.#retire(key)
323
+ return fromPassthrough(mfu)
324
+ }
325
+ // Either shorter than the authored total (a subsample), or awaiting a
326
+ // size under requireAuthoredSize -- buffer below.
327
+ }
328
+
329
+ // Reject BEFORE mutating any state. An earlier revision allocated the
330
+ // pending entry (evicting the oldest sample to make room) and only then
331
+ // validated, so a data unit the combiner went on to reject could destroy
332
+ // an unrelated in-flight sample and leave an empty entry in its slot.
333
+ const thisEnd = offset + dataLen
334
+ if (authoredSize !== undefined && thisEnd > authoredSize) {
335
+ throw new SampleCombineError(
336
+ 'exceeds-authored-size',
337
+ 'combined subsample data would exceed the registered authored sample size',
338
+ )
339
+ }
340
+ const existing = this.#pending.get(key)
341
+ if (existing !== undefined) {
342
+ if (existing.segments.has(offset)) {
343
+ throw new SampleCombineError(
344
+ 'duplicate-subsample-offset',
345
+ 'subsample data unit duplicates an offset already held for this sample',
346
+ )
347
+ }
348
+ if (existing.segments.size >= this.#maxSegmentsPerSample) {
349
+ throw new SampleCombineError(
350
+ 'too-many-subsample-segments',
351
+ 'a sample already holds the maximum number of subsample data units',
352
+ )
353
+ }
354
+ for (const [segOffset, seg] of existing.segments) {
355
+ const segEnd = segOffset + seg.data.byteLength
356
+ if (offset < segEnd && segOffset < thisEnd) {
357
+ throw new SampleCombineError(
358
+ 'overlapping-subsample',
359
+ "subsample data unit overlaps a preceding data unit's byte range",
360
+ )
361
+ }
362
+ }
363
+ }
364
+
365
+ let pending = existing
366
+ if (pending === undefined) {
367
+ if (this.#pending.size >= this.#maxBuffered) this.#evictOldestPending()
368
+ pending = { segments: new Map(), startedAt: Date.now() }
369
+ this.#pending.set(key, pending)
370
+ }
371
+ pending.segments.set(offset, mfu)
372
+
373
+ if (authoredSize === undefined) return null
374
+ return this.#tryComplete(key, authoredSize)
375
+ }
376
+
377
+ /**
378
+ * Emit the sample when its held data units are byte-adjacent from zero and
379
+ * total `size`. Completion is a protocol event, never a timer.
380
+ */
381
+ #tryComplete(key: SampleKey, size: number): CombinedSample | null {
382
+ const pending = this.#pending.get(key)
383
+ if (pending === undefined) return null
384
+ let expected = 0
385
+ const sortedOffsets = [...pending.segments.keys()].sort((a, b) => a - b)
386
+ for (const segOffset of sortedOffsets) {
387
+ if (segOffset !== expected) return null // gap: not yet contiguous from zero.
388
+ expected += pending.segments.get(segOffset)!.data.byteLength
389
+ }
390
+ if (expected !== size) return null // contiguous so far, short of the authored total.
391
+
392
+ this.#pending.delete(key)
393
+ this.#stats.combinedSamples++
394
+ this.#retire(key)
395
+ return fromSegments(pending.segments, sortedOffsets)
396
+ }
397
+
398
+ /**
399
+ * Retire a sample's key: free its registered authored size and remember the
400
+ * key in a bounded recency set. Called on every path that removes a sample
401
+ * -- completion, passthrough, eviction, and timeout -- so `#authoredSizes`
402
+ * cannot accumulate entries for samples that never complete, and so a later
403
+ * data unit for a discarded sample is dropped rather than starting a fresh
404
+ * buffer or being misread as a whole sample.
405
+ *
406
+ * The recency set holds `maxBuffered` keys. A duplicate delayed by more
407
+ * than that many retirements escapes it; on a reliable path there is no
408
+ * such duplicate, and on a lossy path the per-data-unit deadline in
409
+ * {@link MFUReassembler} bounds the delay well inside this window.
410
+ */
411
+ #retire(key: SampleKey): void {
412
+ this.#authoredSizes.delete(key)
413
+ this.#retired.add(key)
414
+ while (this.#retired.size > this.#maxBuffered) {
415
+ const oldest = this.#retired.values().next().value
416
+ if (oldest === undefined) break
417
+ this.#retired.delete(oldest)
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Discard the oldest pending sample by insertion order. This is the only
423
+ * backstop against one FEC-lost subsample data unit wedging
424
+ * {@link SampleCombiner.addMfu} for every future sample once `maxBuffered`
425
+ * is reached, for a host that never drives
426
+ * {@link SampleCombiner.cleanupTimeouts}.
427
+ */
428
+ #evictOldestPending(): void {
429
+ const oldest = this.#pending.keys().next().value
430
+ if (oldest === undefined) return
431
+ this.#pending.delete(oldest)
432
+ this.#stats.evictedSamples++
433
+ this.#retire(oldest)
434
+ this.#abandon(oldest)
435
+ }
436
+
437
+ /**
438
+ * Discard samples whose oldest held segment has waited past `timeoutMs`.
439
+ * Mirrors {@link MFUReassembler}'s clock-driven shape; the per-data-unit
440
+ * deadline inside {@link MFUReassembler} already bounds each constituent,
441
+ * so this is a backstop against a host that registers an authored size but
442
+ * never supplies every subsample DU.
443
+ */
444
+ cleanupTimeouts(timeoutMs: number): void {
445
+ const now = Date.now()
446
+ for (const [key, pending] of [...this.#pending]) {
447
+ if (now - pending.startedAt > timeoutMs) {
448
+ this.#pending.delete(key)
449
+ this.#stats.timedOutSamples++
450
+ this.#retire(key)
451
+ this.#abandon(key)
452
+ }
453
+ }
454
+ }
455
+
456
+ flush(): void {
457
+ this.#pending.clear()
458
+ this.#authoredSizes.clear()
459
+ this.#retired.clear()
460
+ }
461
+
462
+ /**
463
+ * Drop one stream's in-flight, registered-size, and retired state while
464
+ * preserving every other stream (mirrors
465
+ * {@link MFUReassembler.resetStream}).
466
+ */
467
+ resetStream(catalogTrackName: string): void {
468
+ for (const key of this.#pending.keys()) {
469
+ if (trackNameOfKey(key) === catalogTrackName) this.#pending.delete(key)
470
+ }
471
+ for (const key of this.#authoredSizes.keys()) {
472
+ if (trackNameOfKey(key) === catalogTrackName) this.#authoredSizes.delete(key)
473
+ }
474
+ for (const key of this.#retired) {
475
+ if (trackNameOfKey(key) === catalogTrackName) this.#retired.delete(key)
476
+ }
477
+ }
478
+
479
+ get stats(): SampleCombinerStats {
480
+ return { ...this.#stats }
481
+ }
482
+
483
+ get pendingCount(): number {
484
+ return this.#pending.size
485
+ }
486
+
487
+ /**
488
+ * Registered authored sizes still held. Diagnostic only: it exists so tests
489
+ * and health reporting can assert this table stays bounded, since an
490
+ * unbounded one is invisible from `pendingCount` alone.
491
+ */
492
+ get debugAuthoredSizeCount(): number {
493
+ return this.#authoredSizes.size
494
+ }
495
+
496
+ /** Retired keys still remembered. Diagnostic only, bounded by `maxBuffered`. */
497
+ get debugRetiredCount(): number {
498
+ return this.#retired.size
499
+ }
500
+ }
501
+
502
+ function fromPassthrough(mfu: ReassembledMFU): CombinedSample {
503
+ return {
504
+ mpuSequenceNumber: mfu.mpuSequenceNumber,
505
+ movieFragmentSequenceNumber: mfu.movieFragmentSequenceNumber ?? 0,
506
+ sampleNumber: mfu.sampleNumber ?? 0,
507
+ data: mfu.data,
508
+ mmtpTimestamp: mfu.timestamp,
509
+ timestamp: mfu.timestamp,
510
+ rapFlag: mfu.rapFlag,
511
+ priority: mfu.priority,
512
+ packetSequenceNumbers: mfu.packetSequenceNumbers,
513
+ repairedPacketSequenceNumbers: mfu.repairedPacketSequenceNumbers,
514
+ catalogTrackName: mfu.catalogTrackName,
515
+ subsampleCount: 1,
516
+ fragmentCount: mfu.fragmentCount,
517
+ source: mfu.source,
518
+ // One data unit in, one sample out: its provenance IS the sample's.
519
+ deliveryProvenance: mfu.deliveryProvenance
520
+ }
521
+ }
522
+
523
+ function fromSegments(
524
+ segments: Map<number, ReassembledMFU>,
525
+ sortedOffsets: number[],
526
+ ): CombinedSample {
527
+ const totalSize = sortedOffsets.reduce((sum, offset) => sum + segments.get(offset)!.data.byteLength, 0)
528
+ const data = new Uint8Array(totalSize)
529
+ let packetSequenceNumbers: number[] | undefined
530
+ let repairedPacketSequenceNumbers: number[] | undefined
531
+ let writeOffset = 0
532
+ let first: ReassembledMFU | undefined
533
+ let fragmentCount = 0
534
+ // Fold across data units the same way the reassembler folds across the
535
+ // fragments inside one: a sample is multicast-exclusive only if every
536
+ // subsample that formed it was.
537
+ let combinedProvenance: MmtpDeliveryProvenance =
538
+ sortedOffsets.length === 0 ? 'unknown' : 'multicast'
539
+ for (const offset of sortedOffsets) {
540
+ const seg = segments.get(offset)!
541
+ combinedProvenance =
542
+ combinedProvenance === 'mixed' || seg.deliveryProvenance === 'mixed'
543
+ ? 'mixed'
544
+ : combinedProvenance === 'unknown' || seg.deliveryProvenance === 'unknown'
545
+ ? 'unknown'
546
+ : 'multicast'
547
+ data.set(seg.data, writeOffset)
548
+ writeOffset += seg.data.byteLength
549
+ fragmentCount += seg.fragmentCount
550
+ if (seg.packetSequenceNumbers !== undefined) {
551
+ (packetSequenceNumbers ??= []).push(...seg.packetSequenceNumbers)
552
+ }
553
+ if (seg.repairedPacketSequenceNumbers !== undefined) {
554
+ (repairedPacketSequenceNumbers ??= []).push(...seg.repairedPacketSequenceNumbers)
555
+ }
556
+ if (first === undefined) first = seg
557
+ }
558
+ return {
559
+ mpuSequenceNumber: first!.mpuSequenceNumber,
560
+ movieFragmentSequenceNumber: first!.movieFragmentSequenceNumber!,
561
+ sampleNumber: first!.sampleNumber!,
562
+ data,
563
+ mmtpTimestamp: first!.timestamp,
564
+ timestamp: first!.timestamp,
565
+ rapFlag: first!.rapFlag,
566
+ priority: first!.priority,
567
+ packetSequenceNumbers,
568
+ repairedPacketSequenceNumbers,
569
+ catalogTrackName: first!.catalogTrackName,
570
+ subsampleCount: segments.size,
571
+ fragmentCount,
572
+ source: first!.source,
573
+ deliveryProvenance: combinedProvenance,
574
+ }
575
+ }
576
+
577
+ // =============================================================================
578
+ // Inline Tests (vitest)
579
+ // =============================================================================
580
+
581
+ if (import.meta.vitest) {
582
+ const { describe, test, expect } = import.meta.vitest
583
+
584
+ const combiner = (overrides: Partial<SampleCombinerOptions> = {}) =>
585
+ new SampleCombiner({
586
+ maxBuffered: 10,
587
+ requireAuthoredSize: false,
588
+ maxSegmentsPerSample: 8,
589
+ ...overrides,
590
+ })
591
+
592
+ const mfu = (
593
+ mpu: number,
594
+ movie: number,
595
+ sample: number,
596
+ offset: number,
597
+ data: number[],
598
+ timestamp: bigint,
599
+ rapFlag: boolean,
600
+ deliveryProvenance: MmtpDeliveryProvenance = 'multicast',
601
+ ): ReassembledMFU => ({
602
+ mpuSequenceNumber: mpu,
603
+ data: new Uint8Array(data),
604
+ timestamp,
605
+ fragmentCount: 1,
606
+ rapFlag,
607
+ sampleNumber: sample,
608
+ movieFragmentSequenceNumber: movie,
609
+ sampleOffset: offset,
610
+ packetSequenceNumbers: [offset],
611
+ deliveryProvenance,
612
+ })
613
+
614
+ describe('SampleCombiner', () => {
615
+ test('whole-sample DU passes through immediately with no registered size', () => {
616
+ const c = combiner()
617
+ const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))
618
+ expect(out).not.toBeNull()
619
+ expect(out!.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
620
+ expect(out!.subsampleCount).toBe(1)
621
+ expect(c.pendingCount).toBe(0)
622
+ })
623
+
624
+ test('whole-sample DU passes through when it matches the registered size', () => {
625
+ const c = combiner()
626
+ c.registerAuthoredSize(undefined, 1, 9, 17, 5)
627
+ const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))
628
+ expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
629
+ })
630
+
631
+ test('subsample data units combine once the authored size is met', () => {
632
+ const c = combiner()
633
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
634
+
635
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
636
+ expect(c.pendingCount).toBe(1)
637
+
638
+ const out = c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))
639
+ expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
640
+ expect(out?.subsampleCount).toBe(2)
641
+ expect(out?.rapFlag).toBe(true) // offset-0 DU's RAP flag, per moqcast #37.
642
+ expect(out?.timestamp).toBe(1000n)
643
+ expect(c.pendingCount).toBe(0)
644
+ expect(c.stats.combinedSamples).toBe(1)
645
+ })
646
+
647
+ test('subsample data units combine out of order', () => {
648
+ const c = combiner()
649
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
650
+
651
+ expect(c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))).toBeNull()
652
+ const out = c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))
653
+ expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
654
+ })
655
+
656
+ test('subsample without a registered size never completes', () => {
657
+ // A non-zero-offset DU always buffers (it can never be a whole
658
+ // sample). Without a registered authored size there is nothing to
659
+ // combine against, so it -- and a sibling that arrives after it --
660
+ // stay pending rather than being guessed complete.
661
+ const c = combiner()
662
+ expect(c.addMfu(mfu(1, 9, 17, 3, [1, 2, 3], 1001n, false))).toBeNull()
663
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3], 1000n, true))).toBeNull()
664
+ expect(c.pendingCount).toBe(1)
665
+ })
666
+
667
+ test('requireAuthoredSize buffers an offset-zero DU instead of guessing complete', () => {
668
+ // A moqcast-#37-aware host opts into fail-closed buffering: on a lossy
669
+ // path FT=1 can arrive after an offset-zero data unit (reordering, or
670
+ // FT=1's own DU recovered late by FEC). Treating "no size yet" as
671
+ // "whole sample" would silently truncate a partitioned sample.
672
+ const c = combiner({ requireAuthoredSize: true })
673
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
674
+ expect(c.pendingCount).toBe(1)
675
+
676
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
677
+ const out = c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))
678
+ expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
679
+ })
680
+
681
+ test('duplicate offset is rejected', () => {
682
+ const c = combiner()
683
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
684
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
685
+ expect(() => c.addMfu(mfu(1, 9, 17, 0, [9, 9, 9, 9, 9], 2000n, true)))
686
+ .toThrow(SampleCombineError)
687
+ })
688
+
689
+ test('overlapping subsample is rejected', () => {
690
+ const c = combiner()
691
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
692
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
693
+ expect(() => c.addMfu(mfu(1, 9, 17, 3, [9, 9, 9, 9, 9], 2000n, true)))
694
+ .toThrow(SampleCombineError)
695
+ })
696
+
697
+ test('exceeding the authored size is rejected', () => {
698
+ const c = combiner()
699
+ c.registerAuthoredSize(undefined, 1, 9, 17, 8)
700
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
701
+ expect(() => c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 2000n, false)))
702
+ .toThrow(SampleCombineError)
703
+ })
704
+
705
+ test('exceeding maxSegmentsPerSample is rejected', () => {
706
+ const c = combiner({ maxSegmentsPerSample: 2 })
707
+ c.registerAuthoredSize(undefined, 1, 9, 17, 100)
708
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2], 1000n, true))).toBeNull()
709
+ expect(c.addMfu(mfu(1, 9, 17, 2, [3, 4], 1001n, false))).toBeNull()
710
+ expect(() => c.addMfu(mfu(1, 9, 17, 4, [5, 6], 1002n, false)))
711
+ .toThrow(SampleCombineError)
712
+ })
713
+
714
+ test('reports every abandoned sample so its per-sample state can be freed', () => {
715
+ // Regression (BLO-31103): eviction and timeout are the two paths where
716
+ // a sample is abandoned while its stream keeps running. State keyed on
717
+ // that sample elsewhere -- notably the ISO-BMFF timing-sample budget --
718
+ // is otherwise released only on delivery, so an abandoned sample holds
719
+ // its slot forever and the budget becomes a one-way ratchet that wedges
720
+ // the stream at saturation.
721
+ const abandoned: AbandonedSample[] = []
722
+ const c = combiner({ maxBuffered: 2, onAbandon: s => abandoned.push(s) })
723
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
724
+ c.registerAuthoredSize(undefined, 1, 9, 18, 10)
725
+ c.registerAuthoredSize(undefined, 1, 9, 19, 10)
726
+
727
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
728
+ expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
729
+ expect(abandoned).toEqual([])
730
+
731
+ // Eviction reports the evicted identity, not the incoming one.
732
+ expect(c.addMfu(mfu(1, 9, 19, 0, [3, 3, 3, 3, 3], 1002n, true))).toBeNull()
733
+ expect(abandoned).toEqual([{
734
+ catalogTrackName: undefined,
735
+ mpuSequenceNumber: 1,
736
+ movieFragmentSequenceNumber: 9,
737
+ sampleNumber: 17,
738
+ }])
739
+
740
+ // Timeout reports too; both survivors are still incomplete here.
741
+ c.cleanupTimeouts(-1)
742
+ expect(abandoned.map(s => s.sampleNumber)).toEqual([17, 18, 19])
743
+
744
+ // A delivered sample is NOT reported: its consumer releases that state.
745
+ const d = combiner({ maxBuffered: 2, onAbandon: s => abandoned.push(s) })
746
+ d.registerAuthoredSize(undefined, 1, 9, 20, 10)
747
+ expect(d.addMfu(mfu(1, 9, 20, 0, [4, 4, 4, 4, 4], 1003n, true))).toBeNull()
748
+ expect(d.addMfu(mfu(1, 9, 20, 5, [5, 5, 5, 5, 5], 1004n, false))).not.toBeNull()
749
+ expect(abandoned.map(s => s.sampleNumber)).toEqual([17, 18, 19])
750
+ })
751
+
752
+ test('a full combiner evicts the oldest pending sample instead of throwing', () => {
753
+ // Eviction is by insertion order, which is also the only backstop
754
+ // against one permanently-lost subsample DU wedging every future
755
+ // sample once maxBuffered incomplete samples accumulate.
756
+ const c = combiner({ maxBuffered: 2 })
757
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
758
+ c.registerAuthoredSize(undefined, 1, 9, 18, 10)
759
+ c.registerAuthoredSize(undefined, 1, 9, 19, 10)
760
+
761
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
762
+ expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
763
+ expect(c.pendingCount).toBe(2)
764
+
765
+ // A third distinct incomplete sample evicts sample 17, the oldest.
766
+ expect(c.addMfu(mfu(1, 9, 19, 0, [3, 3, 3, 3, 3], 1002n, true))).toBeNull()
767
+ expect(c.pendingCount).toBe(2)
768
+ expect(c.stats.evictedSamples).toBe(1)
769
+
770
+ // The surviving samples are undisturbed and still complete normally.
771
+ expect(c.addMfu(mfu(1, 9, 18, 5, [8, 8, 8, 8, 8], 2001n, false))?.data)
772
+ .toEqual(new Uint8Array([2, 2, 2, 2, 2, 8, 8, 8, 8, 8]))
773
+ expect(c.addMfu(mfu(1, 9, 19, 5, [9, 9, 9, 9, 9], 2002n, false))?.data)
774
+ .toEqual(new Uint8Array([3, 3, 3, 3, 3, 9, 9, 9, 9, 9]))
775
+
776
+ // The combiner still accepts new work rather than being wedged.
777
+ c.registerAuthoredSize(undefined, 1, 9, 20, 10)
778
+ expect(c.addMfu(mfu(1, 9, 20, 0, [4, 4, 4, 4, 4], 3000n, true))).toBeNull()
779
+ expect(c.addMfu(mfu(1, 9, 20, 5, [7, 7, 7, 7, 7], 3001n, false))?.data)
780
+ .toEqual(new Uint8Array([4, 4, 4, 4, 4, 7, 7, 7, 7, 7]))
781
+ })
782
+
783
+ test('a completed sample frees its authored size and drops a late duplicate', () => {
784
+ const c = combiner()
785
+ c.registerAuthoredSize(undefined, 1, 9, 17, 10)
786
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
787
+ expect(c.addMfu(mfu(1, 9, 17, 5, [6, 7, 8, 9, 10], 1001n, false))).not.toBeNull()
788
+
789
+ // A FEC-recovered retransmission of the completed sample's offset-0
790
+ // data unit must not be re-emitted as a second complete sample.
791
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 2000n, true))).toBeNull()
792
+ })
793
+
794
+ test('resetStream drops one track and preserves the others', () => {
795
+ const c = combiner()
796
+ c.registerAuthoredSize('video/720p', 1, 9, 17, 10)
797
+ c.registerAuthoredSize('audio', 1, 9, 17, 10)
798
+ const video = { ...mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true), catalogTrackName: 'video/720p' }
799
+ const audio = { ...mfu(1, 9, 17, 0, [2, 2, 2, 2, 2], 1000n, true), catalogTrackName: 'audio' }
800
+ expect(c.addMfu(video)).toBeNull()
801
+ expect(c.addMfu(audio)).toBeNull()
802
+
803
+ c.resetStream('video/720p')
804
+ expect(c.pendingCount).toBe(1)
805
+
806
+ const audioTail = { ...mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 1001n, false), catalogTrackName: 'audio' }
807
+ expect(c.addMfu(audioTail)?.data).toEqual(new Uint8Array([2, 2, 2, 2, 2, 3, 3, 3, 3, 3]))
808
+ })
809
+
810
+ test('flush clears registered authored sizes', () => {
811
+ const c = combiner()
812
+ c.registerAuthoredSize(undefined, 1, 9, 17, 5)
813
+ c.flush()
814
+ // A now-unregistered size means an offset-zero DU passes through
815
+ // immediately again (requireAuthoredSize is false here) rather than
816
+ // waiting on a size flush() already discarded.
817
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))?.data)
818
+ .toEqual(new Uint8Array([1, 2, 3, 4, 5]))
819
+ })
820
+
821
+ test('a healthy stream accumulates no bookkeeping', () => {
822
+ // Regression: retirement ran only on completion paths, so an
823
+ // abandoned sample's registered size stayed resident forever.
824
+ const c = combiner({ maxBuffered: 4, requireAuthoredSize: true })
825
+ for (let i = 0; i < 1000; i++) {
826
+ expect(c.registerAuthoredSize(undefined, 1, 9, i, 10)).toBeNull()
827
+ expect(c.addMfu(mfu(1, 9, i, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
828
+ expect(c.addMfu(mfu(1, 9, i, 5, [2, 2, 2, 2, 2], 1001n, false))).not.toBeNull()
829
+ }
830
+ expect(c.stats.combinedSamples).toBe(1000)
831
+ expect(c.pendingCount).toBe(0)
832
+ expect(c.debugAuthoredSizeCount).toBe(0)
833
+ expect(c.debugRetiredCount).toBeLessThanOrEqual(4)
834
+ })
835
+
836
+ test('samples that never complete release their authored size', () => {
837
+ const c = combiner({ maxBuffered: 4, requireAuthoredSize: true })
838
+ for (let i = 0; i < 1000; i++) {
839
+ expect(c.registerAuthoredSize(undefined, 1, 9, i, 10)).toBeNull()
840
+ expect(c.addMfu(mfu(1, 9, i, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
841
+ // Deliberately never supply the tail.
842
+ }
843
+ c.cleanupTimeouts(-1) // expire everything still held
844
+ expect(c.pendingCount).toBe(0)
845
+ expect(c.debugAuthoredSizeCount).toBe(0)
846
+ })
847
+
848
+ test('a late authored size completes an already-buffered sample', () => {
849
+ // Regression: with requireAuthoredSize the completion check ran only
850
+ // inside addMfu, so a sample carried by a SINGLE data unit whose FT=1
851
+ // arrived afterwards had no later data unit to re-enter on and was
852
+ // silently lost.
853
+ const c = combiner({ requireAuthoredSize: true })
854
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 2, 3, 4, 5], 1000n, true))).toBeNull()
855
+ expect(c.pendingCount).toBe(1)
856
+
857
+ const out = c.registerAuthoredSize(undefined, 1, 9, 17, 5)
858
+ expect(out?.data).toEqual(new Uint8Array([1, 2, 3, 4, 5]))
859
+ expect(out?.subsampleCount).toBe(1)
860
+ expect(c.pendingCount).toBe(0)
861
+ })
862
+
863
+ test('a rejected data unit does not evict a live sample', () => {
864
+ // Regression: the pending slot was allocated (evicting the oldest
865
+ // sample to make room) BEFORE validation, so a data unit the combiner
866
+ // went on to reject destroyed an unrelated in-flight sample.
867
+ const c = combiner({ maxBuffered: 2 })
868
+ for (const s of [17, 18, 19]) c.registerAuthoredSize(undefined, 1, 9, s, 10)
869
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
870
+ expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
871
+ expect(c.pendingCount).toBe(2)
872
+
873
+ // 8 + 5 = 13 exceeds sample 19's authored size of 10.
874
+ expect(() => c.addMfu(mfu(1, 9, 19, 8, [9, 9, 9, 9, 9], 2000n, false)))
875
+ .toThrow(SampleCombineError)
876
+ expect(c.stats.evictedSamples).toBe(0)
877
+ expect(c.pendingCount).toBe(2)
878
+
879
+ // Sample 17 is untouched and still completes.
880
+ expect(c.addMfu(mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 2001n, false))?.data)
881
+ .toEqual(new Uint8Array([1, 1, 1, 1, 1, 3, 3, 3, 3, 3]))
882
+ })
883
+
884
+ test('an evicted sample is retired rather than reopened', () => {
885
+ const c = combiner({ maxBuffered: 1 })
886
+ for (const s of [17, 18]) c.registerAuthoredSize(undefined, 1, 9, s, 10)
887
+ expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull()
888
+ expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull()
889
+ expect(c.stats.evictedSamples).toBe(1)
890
+
891
+ // Sample 17's tail arrives after its eviction; it must not reopen.
892
+ expect(c.addMfu(mfu(1, 9, 17, 5, [3, 3, 3, 3, 3], 2000n, false))).toBeNull()
893
+ expect(c.pendingCount).toBe(1)
894
+ })
895
+ })
896
+
897
+ describe('sample-level delivery provenance', () => {
898
+ test('one non-multicast subsample makes the whole sample non-exclusive', () => {
899
+ // A 4K/8K sample is split across subsample DUs. If any one of them
900
+ // arrived over QUIC, the assembled sample contains bytes that never
901
+ // crossed a multicast socket, so the sample is not physical evidence
902
+ // even though every other DU was multicast.
903
+ const c = combiner()
904
+ c.registerAuthoredSize(undefined, 1, 1, 1, 4)
905
+ expect(c.addMfu(mfu(1, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()
906
+ const done = c.addMfu(mfu(1, 1, 1, 2, [3, 4], 100n, false, 'mixed'))
907
+ expect(done).not.toBeNull()
908
+ expect(done!.subsampleCount).toBe(2)
909
+ expect(done!.deliveryProvenance).toBe('mixed')
910
+ })
911
+
912
+ test('an unattributed subsample degrades the sample to unknown', () => {
913
+ const c = combiner()
914
+ c.registerAuthoredSize(undefined, 2, 1, 1, 4)
915
+ expect(c.addMfu(mfu(2, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()
916
+ const done = c.addMfu(mfu(2, 1, 1, 2, [3, 4], 100n, false, 'unknown'))
917
+ expect(done).not.toBeNull()
918
+ expect(done!.deliveryProvenance).toBe('unknown')
919
+ })
920
+
921
+ test('an all-multicast sample stays multicast-exclusive', () => {
922
+ const c = combiner()
923
+ c.registerAuthoredSize(undefined, 3, 1, 1, 4)
924
+ expect(c.addMfu(mfu(3, 1, 1, 0, [1, 2], 100n, true, 'multicast'))).toBeNull()
925
+ const done = c.addMfu(mfu(3, 1, 1, 2, [3, 4], 100n, false, 'multicast'))
926
+ expect(done).not.toBeNull()
927
+ expect(done!.deliveryProvenance).toBe('multicast')
928
+ })
929
+
930
+ test('a passthrough sample inherits its data unit provenance verbatim', () => {
931
+ const c = combiner()
932
+ const done = c.addMfu(mfu(4, 1, 1, 0, [1, 2], 100n, true, 'mixed'))
933
+ expect(done).not.toBeNull()
934
+ expect(done!.subsampleCount).toBe(1)
935
+ expect(done!.deliveryProvenance).toBe('mixed')
936
+ })
937
+ })
938
+ }