@blockcast/mmt-container 0.3.0 → 0.4.0-main.1e94b11199ea

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 (606) hide show
  1. package/README.md +33 -16
  2. package/dist/browser-runtime.js +14816 -14254
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +24 -19
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +32 -14
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +88 -28
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +60 -23
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +1 -7
  13. package/dist/catalog-network-source.js.map +4 -4
  14. package/dist/cmaf-block-assembler.d.ts +0 -2
  15. package/dist/cmaf-block-assembler.d.ts.map +1 -1
  16. package/dist/cmaf-block-assembler.js +0 -1
  17. package/dist/cmaf-block-assembler.js.map +2 -2
  18. package/dist/cmcd-producer.d.ts.map +1 -1
  19. package/dist/cmcd-producer.js +0 -4
  20. package/dist/cmcd-producer.js.map +2 -2
  21. package/dist/delivery-catalog.d.ts +124 -0
  22. package/dist/delivery-catalog.d.ts.map +1 -0
  23. package/dist/delivery-catalog.js +1857 -0
  24. package/dist/delivery-catalog.js.map +7 -0
  25. package/dist/delta-fallback-config.d.ts +42 -0
  26. package/dist/delta-fallback-config.d.ts.map +1 -0
  27. package/dist/delta-fallback-config.js +59 -0
  28. package/dist/delta-fallback-config.js.map +7 -0
  29. package/dist/fec-manager.d.ts +52 -30
  30. package/dist/fec-manager.d.ts.map +1 -1
  31. package/dist/fec-manager.js +338 -293
  32. package/dist/fec-manager.js.map +4 -4
  33. package/dist/fec-state.d.ts +19 -31
  34. package/dist/fec-state.d.ts.map +1 -1
  35. package/dist/fec-state.js +4 -10
  36. package/dist/fec-state.js.map +4 -4
  37. package/dist/fec-worker-setup.d.ts +18 -14
  38. package/dist/fec-worker-setup.d.ts.map +1 -1
  39. package/dist/fec-worker-setup.js +81 -96
  40. package/dist/fec-worker-setup.js.map +4 -4
  41. package/dist/index.d.ts +5 -3
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +16 -0
  44. package/dist/index.js.map +2 -2
  45. package/dist/iso-block-locator.js +1 -7
  46. package/dist/iso-block-locator.js.map +4 -4
  47. package/dist/media-router.d.ts +20 -8
  48. package/dist/media-router.d.ts.map +1 -1
  49. package/dist/media-router.js +46 -21
  50. package/dist/media-router.js.map +2 -2
  51. package/dist/mfu-reassembler.js +24 -30
  52. package/dist/mfu-reassembler.js.map +4 -4
  53. package/dist/mmtp-container-parser.d.ts +14 -11
  54. package/dist/mmtp-container-parser.d.ts.map +1 -1
  55. package/dist/mmtp-container-parser.js +126 -99
  56. package/dist/mmtp-container-parser.js.map +4 -4
  57. package/dist/mmtp-container-parser.no-modules.js +154 -108
  58. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  59. package/dist/mmtp-decode-pipeline.js +126 -99
  60. package/dist/mmtp-decode-pipeline.js.map +4 -4
  61. package/dist/mmtp-router.d.ts +51 -29
  62. package/dist/mmtp-router.d.ts.map +1 -1
  63. package/dist/mmtp-router.js +260 -326
  64. package/dist/mmtp-router.js.map +4 -4
  65. package/dist/multicast-input.js +1 -7
  66. package/dist/multicast-input.js.map +4 -4
  67. package/dist/multicast-manager.d.ts +72 -6
  68. package/dist/multicast-manager.d.ts.map +1 -1
  69. package/dist/multicast-manager.js +882 -673
  70. package/dist/multicast-manager.js.map +4 -4
  71. package/dist/multicast-transport.d.ts +1 -1
  72. package/dist/multicast-transport.d.ts.map +1 -1
  73. package/dist/multicast-transport.js +44 -26
  74. package/dist/multicast-transport.js.map +4 -4
  75. package/dist/multicast-unwrap.js +29 -35
  76. package/dist/multicast-unwrap.js.map +4 -4
  77. package/dist/no-modules.js +150 -106
  78. package/dist/no-modules.js.map +4 -4
  79. package/dist/repair-fetch.d.ts +116 -21
  80. package/dist/repair-fetch.d.ts.map +1 -1
  81. package/dist/repair-fetch.js +114 -34
  82. package/dist/repair-fetch.js.map +4 -4
  83. package/dist/repair-layers.d.ts +18 -7
  84. package/dist/repair-layers.d.ts.map +1 -1
  85. package/dist/repair-layers.js +12 -10
  86. package/dist/repair-layers.js.map +4 -4
  87. package/dist/signaling.js +2 -11
  88. package/dist/signaling.js.map +4 -4
  89. package/dist/timestamp-codec.js +24 -30
  90. package/dist/timestamp-codec.js.map +4 -4
  91. package/dist/track-session.d.ts +7 -0
  92. package/dist/track-session.d.ts.map +1 -1
  93. package/dist/track-session.js +15 -8
  94. package/dist/track-session.js.map +2 -2
  95. package/dist/wasm-mfu-reassembler.js +1 -7
  96. package/dist/wasm-mfu-reassembler.js.map +4 -4
  97. package/dist/wasm-mmtp-container-parser.js +126 -99
  98. package/dist/wasm-mmtp-container-parser.js.map +4 -4
  99. package/package.json +7 -14
  100. package/schemas/catalog.schema.json +3 -3
  101. package/src/carp-catalog.ts +51 -24
  102. package/src/catalog-fec-params.ts +173 -64
  103. package/src/cmaf-block-assembler.ts +1 -4
  104. package/src/cmcd-producer.ts +0 -6
  105. package/src/delivery-catalog.ts +498 -0
  106. package/src/delta-fallback-config.ts +72 -0
  107. package/src/fec-manager.ts +457 -334
  108. package/src/fec-state.ts +20 -32
  109. package/src/fec-worker-setup.ts +109 -90
  110. package/src/index.ts +32 -0
  111. package/src/media-router.ts +59 -21
  112. package/src/mmtp-container-parser.ts +106 -20
  113. package/src/mmtp-router.ts +189 -330
  114. package/src/multicast-manager.ts +265 -86
  115. package/src/multicast-transport.ts +16 -9
  116. package/src/repair-fetch.ts +273 -43
  117. package/src/repair-layers.ts +28 -8
  118. package/src/track-session.ts +28 -9
  119. package/test-vectors/catalog/README.md +44 -18
  120. package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
  121. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +1 -1
  122. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +1 -1
  123. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +1 -1
  124. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +1 -1
  125. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +1 -1
  126. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +1 -1
  127. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +1 -1
  128. package/test-vectors/catalog/negative/multicast-endpoint-relay-address.json +39 -0
  129. package/test-vectors/catalog/negative/multicast-network-source-empty.json +1 -1
  130. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +1 -1
  131. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +2 -1
  132. package/test-vectors/catalog/positive/fec-layered-repair.json +2 -2
  133. package/test-vectors/catalog/positive/fec-multicast.json +2 -1
  134. package/test-vectors/catalog/positive/multicast-amt-manual-relay.json +38 -0
  135. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
  136. package/test-vectors/catalog/section-codes.json +33 -0
  137. package/test-vectors/catalog-timing.json +356 -0
  138. package/test-vectors/catalog-validation.json +40 -5
  139. package/test-vectors/delivery-catalog/README.md +11 -0
  140. package/test-vectors/delivery-catalog/negative/missing-block-profile.json +58 -0
  141. package/test-vectors/delivery-catalog/negative/packet-id-signaling.json +59 -0
  142. package/test-vectors/delivery-catalog/negative/retired-k-padded-block-profile.json +59 -0
  143. package/test-vectors/delivery-catalog/positive/dash.json +56 -0
  144. package/test-vectors/delivery-catalog/positive/mmtp-source-only.json +43 -0
  145. package/node_modules/@blockcast/fec-worker/README.md +0 -223
  146. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  147. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +0 -38
  148. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +0 -1
  149. package/node_modules/@blockcast/fec-worker/dist/block-order.js +0 -78
  150. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +0 -1
  151. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +0 -93
  152. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +0 -1
  153. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +0 -346
  154. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +0 -1
  155. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +0 -380
  156. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +0 -1
  157. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +0 -11
  158. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +0 -1
  159. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +0 -14
  160. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +0 -1
  161. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +0 -859
  162. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +0 -7
  163. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +0 -10
  164. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +0 -1
  165. package/node_modules/@blockcast/fec-worker/dist/index.js +0 -8
  166. package/node_modules/@blockcast/fec-worker/dist/index.js.map +0 -1
  167. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +0 -20
  168. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +0 -1
  169. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +0 -370
  170. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +0 -7
  171. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +0 -104
  172. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +0 -1
  173. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +0 -181
  174. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +0 -1
  175. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +0 -194
  176. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +0 -1
  177. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +0 -62
  178. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +0 -1
  179. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +0 -9
  180. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +0 -1
  181. package/node_modules/@blockcast/fec-worker/dist/transfer.js +0 -13
  182. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +0 -1
  183. package/node_modules/@blockcast/fec-worker/package.json +0 -59
  184. package/node_modules/@blockcast/fec-worker/src/block-order.ts +0 -93
  185. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +0 -392
  186. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +0 -399
  187. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +0 -1502
  188. package/node_modules/@blockcast/fec-worker/src/index.ts +0 -35
  189. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +0 -589
  190. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +0 -222
  191. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +0 -209
  192. package/node_modules/@blockcast/fec-worker/src/transfer.ts +0 -12
  193. package/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  194. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  195. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  196. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  197. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  198. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  199. package/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  200. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  201. package/node_modules/@blockcast/mmt-base/README.md +0 -134
  202. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  203. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  204. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  205. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  206. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  207. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  208. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  209. package/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  210. package/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  211. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  212. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  213. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  214. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  215. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  216. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  217. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  218. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  219. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  220. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  221. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  222. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  223. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  224. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  225. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  226. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  227. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  228. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  229. package/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  230. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  231. package/node_modules/@blockcast/mmt-base/package.json +0 -49
  232. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  233. package/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  234. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  235. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  236. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  237. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  238. package/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  239. package/node_modules/@blockcast/mmt-fec/README.md +0 -271
  240. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  241. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +0 -82
  242. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +0 -1
  243. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +0 -296
  244. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +0 -1
  245. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +0 -106
  246. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +0 -1
  247. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +0 -288
  248. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +0 -1
  249. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +0 -46
  250. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +0 -1
  251. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +0 -96
  252. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +0 -1
  253. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +0 -33
  254. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +0 -1
  255. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +0 -57
  256. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +0 -1
  257. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +0 -205
  258. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +0 -1
  259. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +0 -240
  260. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +0 -1
  261. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +0 -28
  262. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +0 -1
  263. package/node_modules/@blockcast/mmt-fec/dist/index.js +0 -28
  264. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +0 -1
  265. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +0 -20
  266. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +0 -1
  267. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +0 -28
  268. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +0 -1
  269. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +0 -258
  270. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +0 -1
  271. package/node_modules/@blockcast/mmt-fec/dist/oti.js +0 -382
  272. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +0 -1
  273. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +0 -41
  274. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +0 -1
  275. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +0 -81
  276. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +0 -1
  277. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +0 -82
  278. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +0 -1
  279. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +0 -170
  280. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +0 -1
  281. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  282. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  283. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  284. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  285. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  286. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  287. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  288. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  289. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +0 -134
  290. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  291. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  292. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  293. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  294. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  295. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  296. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  297. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  298. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  299. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  300. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  301. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  302. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +0 -49
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  324. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  325. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  326. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  327. package/node_modules/@blockcast/mmt-fec/package.json +0 -79
  328. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +0 -371
  329. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +0 -439
  330. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +0 -127
  331. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +0 -68
  332. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +0 -310
  333. package/node_modules/@blockcast/mmt-fec/src/index.ts +0 -94
  334. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +0 -34
  335. package/node_modules/@blockcast/mmt-fec/src/oti.ts +0 -463
  336. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +0 -101
  337. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +0 -195
  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 +0 -28
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +0 -1
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +0 -128
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +0 -1
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +0 -4
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +0 -1
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +0 -4
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +0 -1
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +0 -37
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +0 -1
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +0 -179
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +0 -1
  351. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +0 -99
  352. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +0 -1
  353. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +0 -492
  354. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +0 -1
  355. package/node_modules/@blockcast/mmt-manifest-verify/package.json +0 -45
  356. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +0 -171
  357. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +0 -3
  358. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +0 -230
  359. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +0 -669
  360. package/node_modules/@noble/hashes/LICENSE +0 -21
  361. package/node_modules/@noble/hashes/README.md +0 -521
  362. package/node_modules/@noble/hashes/_assert.d.ts +0 -17
  363. package/node_modules/@noble/hashes/_assert.d.ts.map +0 -1
  364. package/node_modules/@noble/hashes/_assert.js +0 -18
  365. package/node_modules/@noble/hashes/_assert.js.map +0 -1
  366. package/node_modules/@noble/hashes/_blake.d.ts +0 -14
  367. package/node_modules/@noble/hashes/_blake.d.ts.map +0 -1
  368. package/node_modules/@noble/hashes/_blake.js +0 -50
  369. package/node_modules/@noble/hashes/_blake.js.map +0 -1
  370. package/node_modules/@noble/hashes/_md.d.ts +0 -51
  371. package/node_modules/@noble/hashes/_md.d.ts.map +0 -1
  372. package/node_modules/@noble/hashes/_md.js +0 -162
  373. package/node_modules/@noble/hashes/_md.js.map +0 -1
  374. package/node_modules/@noble/hashes/_u64.d.ts +0 -55
  375. package/node_modules/@noble/hashes/_u64.d.ts.map +0 -1
  376. package/node_modules/@noble/hashes/_u64.js +0 -90
  377. package/node_modules/@noble/hashes/_u64.js.map +0 -1
  378. package/node_modules/@noble/hashes/argon2.d.ts +0 -32
  379. package/node_modules/@noble/hashes/argon2.d.ts.map +0 -1
  380. package/node_modules/@noble/hashes/argon2.js +0 -401
  381. package/node_modules/@noble/hashes/argon2.js.map +0 -1
  382. package/node_modules/@noble/hashes/blake1.d.ts +0 -106
  383. package/node_modules/@noble/hashes/blake1.d.ts.map +0 -1
  384. package/node_modules/@noble/hashes/blake1.js +0 -459
  385. package/node_modules/@noble/hashes/blake1.js.map +0 -1
  386. package/node_modules/@noble/hashes/blake2.d.ts +0 -116
  387. package/node_modules/@noble/hashes/blake2.d.ts.map +0 -1
  388. package/node_modules/@noble/hashes/blake2.js +0 -420
  389. package/node_modules/@noble/hashes/blake2.js.map +0 -1
  390. package/node_modules/@noble/hashes/blake2b.d.ts +0 -11
  391. package/node_modules/@noble/hashes/blake2b.d.ts.map +0 -1
  392. package/node_modules/@noble/hashes/blake2b.js +0 -14
  393. package/node_modules/@noble/hashes/blake2b.js.map +0 -1
  394. package/node_modules/@noble/hashes/blake2s.d.ts +0 -20
  395. package/node_modules/@noble/hashes/blake2s.d.ts.map +0 -1
  396. package/node_modules/@noble/hashes/blake2s.js +0 -24
  397. package/node_modules/@noble/hashes/blake2s.js.map +0 -1
  398. package/node_modules/@noble/hashes/blake3.d.ts +0 -54
  399. package/node_modules/@noble/hashes/blake3.d.ts.map +0 -1
  400. package/node_modules/@noble/hashes/blake3.js +0 -255
  401. package/node_modules/@noble/hashes/blake3.js.map +0 -1
  402. package/node_modules/@noble/hashes/crypto.d.ts +0 -2
  403. package/node_modules/@noble/hashes/crypto.d.ts.map +0 -1
  404. package/node_modules/@noble/hashes/crypto.js +0 -5
  405. package/node_modules/@noble/hashes/crypto.js.map +0 -1
  406. package/node_modules/@noble/hashes/cryptoNode.d.ts +0 -2
  407. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +0 -1
  408. package/node_modules/@noble/hashes/cryptoNode.js +0 -18
  409. package/node_modules/@noble/hashes/cryptoNode.js.map +0 -1
  410. package/node_modules/@noble/hashes/eskdf.d.ts +0 -47
  411. package/node_modules/@noble/hashes/eskdf.d.ts.map +0 -1
  412. package/node_modules/@noble/hashes/eskdf.js +0 -166
  413. package/node_modules/@noble/hashes/eskdf.js.map +0 -1
  414. package/node_modules/@noble/hashes/esm/_assert.d.ts +0 -17
  415. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +0 -1
  416. package/node_modules/@noble/hashes/esm/_assert.js +0 -15
  417. package/node_modules/@noble/hashes/esm/_assert.js.map +0 -1
  418. package/node_modules/@noble/hashes/esm/_blake.d.ts +0 -14
  419. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +0 -1
  420. package/node_modules/@noble/hashes/esm/_blake.js +0 -45
  421. package/node_modules/@noble/hashes/esm/_blake.js.map +0 -1
  422. package/node_modules/@noble/hashes/esm/_md.d.ts +0 -51
  423. package/node_modules/@noble/hashes/esm/_md.d.ts.map +0 -1
  424. package/node_modules/@noble/hashes/esm/_md.js +0 -155
  425. package/node_modules/@noble/hashes/esm/_md.js.map +0 -1
  426. package/node_modules/@noble/hashes/esm/_u64.d.ts +0 -55
  427. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +0 -1
  428. package/node_modules/@noble/hashes/esm/_u64.js +0 -67
  429. package/node_modules/@noble/hashes/esm/_u64.js.map +0 -1
  430. package/node_modules/@noble/hashes/esm/argon2.d.ts +0 -32
  431. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +0 -1
  432. package/node_modules/@noble/hashes/esm/argon2.js +0 -392
  433. package/node_modules/@noble/hashes/esm/argon2.js.map +0 -1
  434. package/node_modules/@noble/hashes/esm/blake1.d.ts +0 -106
  435. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +0 -1
  436. package/node_modules/@noble/hashes/esm/blake1.js +0 -452
  437. package/node_modules/@noble/hashes/esm/blake1.js.map +0 -1
  438. package/node_modules/@noble/hashes/esm/blake2.d.ts +0 -116
  439. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +0 -1
  440. package/node_modules/@noble/hashes/esm/blake2.js +0 -413
  441. package/node_modules/@noble/hashes/esm/blake2.js.map +0 -1
  442. package/node_modules/@noble/hashes/esm/blake2b.d.ts +0 -11
  443. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +0 -1
  444. package/node_modules/@noble/hashes/esm/blake2b.js +0 -11
  445. package/node_modules/@noble/hashes/esm/blake2b.js.map +0 -1
  446. package/node_modules/@noble/hashes/esm/blake2s.d.ts +0 -20
  447. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +0 -1
  448. package/node_modules/@noble/hashes/esm/blake2s.js +0 -21
  449. package/node_modules/@noble/hashes/esm/blake2s.js.map +0 -1
  450. package/node_modules/@noble/hashes/esm/blake3.d.ts +0 -54
  451. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +0 -1
  452. package/node_modules/@noble/hashes/esm/blake3.js +0 -251
  453. package/node_modules/@noble/hashes/esm/blake3.js.map +0 -1
  454. package/node_modules/@noble/hashes/esm/crypto.d.ts +0 -2
  455. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +0 -1
  456. package/node_modules/@noble/hashes/esm/crypto.js +0 -2
  457. package/node_modules/@noble/hashes/esm/crypto.js.map +0 -1
  458. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +0 -2
  459. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +0 -1
  460. package/node_modules/@noble/hashes/esm/cryptoNode.js +0 -15
  461. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +0 -1
  462. package/node_modules/@noble/hashes/esm/eskdf.d.ts +0 -47
  463. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +0 -1
  464. package/node_modules/@noble/hashes/esm/eskdf.js +0 -160
  465. package/node_modules/@noble/hashes/esm/eskdf.js.map +0 -1
  466. package/node_modules/@noble/hashes/esm/hkdf.d.ts +0 -36
  467. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +0 -1
  468. package/node_modules/@noble/hashes/esm/hkdf.js +0 -82
  469. package/node_modules/@noble/hashes/esm/hkdf.js.map +0 -1
  470. package/node_modules/@noble/hashes/esm/hmac.d.ts +0 -35
  471. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +0 -1
  472. package/node_modules/@noble/hashes/esm/hmac.js +0 -86
  473. package/node_modules/@noble/hashes/esm/hmac.js.map +0 -1
  474. package/node_modules/@noble/hashes/esm/index.d.ts +0 -2
  475. package/node_modules/@noble/hashes/esm/index.d.ts.map +0 -1
  476. package/node_modules/@noble/hashes/esm/index.js +0 -33
  477. package/node_modules/@noble/hashes/esm/index.js.map +0 -1
  478. package/node_modules/@noble/hashes/esm/legacy.d.ts +0 -71
  479. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +0 -1
  480. package/node_modules/@noble/hashes/esm/legacy.js +0 -281
  481. package/node_modules/@noble/hashes/esm/legacy.js.map +0 -1
  482. package/node_modules/@noble/hashes/esm/package.json +0 -10
  483. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +0 -23
  484. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +0 -1
  485. package/node_modules/@noble/hashes/esm/pbkdf2.js +0 -97
  486. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +0 -1
  487. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +0 -13
  488. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +0 -1
  489. package/node_modules/@noble/hashes/esm/ripemd160.js +0 -13
  490. package/node_modules/@noble/hashes/esm/ripemd160.js.map +0 -1
  491. package/node_modules/@noble/hashes/esm/scrypt.d.ts +0 -34
  492. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +0 -1
  493. package/node_modules/@noble/hashes/esm/scrypt.js +0 -228
  494. package/node_modules/@noble/hashes/esm/scrypt.js.map +0 -1
  495. package/node_modules/@noble/hashes/esm/sha1.d.ts +0 -11
  496. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +0 -1
  497. package/node_modules/@noble/hashes/esm/sha1.js +0 -11
  498. package/node_modules/@noble/hashes/esm/sha1.js.map +0 -1
  499. package/node_modules/@noble/hashes/esm/sha2.d.ts +0 -159
  500. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +0 -1
  501. package/node_modules/@noble/hashes/esm/sha2.js +0 -375
  502. package/node_modules/@noble/hashes/esm/sha2.js.map +0 -1
  503. package/node_modules/@noble/hashes/esm/sha256.d.ts +0 -20
  504. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +0 -1
  505. package/node_modules/@noble/hashes/esm/sha256.js +0 -20
  506. package/node_modules/@noble/hashes/esm/sha256.js.map +0 -1
  507. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +0 -142
  508. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +0 -1
  509. package/node_modules/@noble/hashes/esm/sha3-addons.js +0 -393
  510. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +0 -1
  511. package/node_modules/@noble/hashes/esm/sha3.d.ts +0 -53
  512. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +0 -1
  513. package/node_modules/@noble/hashes/esm/sha3.js +0 -234
  514. package/node_modules/@noble/hashes/esm/sha3.js.map +0 -1
  515. package/node_modules/@noble/hashes/esm/sha512.d.ts +0 -26
  516. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +0 -1
  517. package/node_modules/@noble/hashes/esm/sha512.js +0 -26
  518. package/node_modules/@noble/hashes/esm/sha512.js.map +0 -1
  519. package/node_modules/@noble/hashes/esm/utils.d.ts +0 -161
  520. package/node_modules/@noble/hashes/esm/utils.d.ts.map +0 -1
  521. package/node_modules/@noble/hashes/esm/utils.js +0 -281
  522. package/node_modules/@noble/hashes/esm/utils.js.map +0 -1
  523. package/node_modules/@noble/hashes/hkdf.d.ts +0 -36
  524. package/node_modules/@noble/hashes/hkdf.d.ts.map +0 -1
  525. package/node_modules/@noble/hashes/hkdf.js +0 -88
  526. package/node_modules/@noble/hashes/hkdf.js.map +0 -1
  527. package/node_modules/@noble/hashes/hmac.d.ts +0 -35
  528. package/node_modules/@noble/hashes/hmac.d.ts.map +0 -1
  529. package/node_modules/@noble/hashes/hmac.js +0 -91
  530. package/node_modules/@noble/hashes/hmac.js.map +0 -1
  531. package/node_modules/@noble/hashes/index.d.ts +0 -1
  532. package/node_modules/@noble/hashes/index.d.ts.map +0 -1
  533. package/node_modules/@noble/hashes/index.js +0 -33
  534. package/node_modules/@noble/hashes/index.js.map +0 -1
  535. package/node_modules/@noble/hashes/legacy.d.ts +0 -71
  536. package/node_modules/@noble/hashes/legacy.d.ts.map +0 -1
  537. package/node_modules/@noble/hashes/legacy.js +0 -287
  538. package/node_modules/@noble/hashes/legacy.js.map +0 -1
  539. package/node_modules/@noble/hashes/package.json +0 -266
  540. package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -23
  541. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +0 -1
  542. package/node_modules/@noble/hashes/pbkdf2.js +0 -101
  543. package/node_modules/@noble/hashes/pbkdf2.js.map +0 -1
  544. package/node_modules/@noble/hashes/ripemd160.d.ts +0 -13
  545. package/node_modules/@noble/hashes/ripemd160.d.ts.map +0 -1
  546. package/node_modules/@noble/hashes/ripemd160.js +0 -16
  547. package/node_modules/@noble/hashes/ripemd160.js.map +0 -1
  548. package/node_modules/@noble/hashes/scrypt.d.ts +0 -34
  549. package/node_modules/@noble/hashes/scrypt.d.ts.map +0 -1
  550. package/node_modules/@noble/hashes/scrypt.js +0 -232
  551. package/node_modules/@noble/hashes/scrypt.js.map +0 -1
  552. package/node_modules/@noble/hashes/sha1.d.ts +0 -11
  553. package/node_modules/@noble/hashes/sha1.d.ts.map +0 -1
  554. package/node_modules/@noble/hashes/sha1.js +0 -14
  555. package/node_modules/@noble/hashes/sha1.js.map +0 -1
  556. package/node_modules/@noble/hashes/sha2.d.ts +0 -159
  557. package/node_modules/@noble/hashes/sha2.d.ts.map +0 -1
  558. package/node_modules/@noble/hashes/sha2.js +0 -384
  559. package/node_modules/@noble/hashes/sha2.js.map +0 -1
  560. package/node_modules/@noble/hashes/sha256.d.ts +0 -20
  561. package/node_modules/@noble/hashes/sha256.d.ts.map +0 -1
  562. package/node_modules/@noble/hashes/sha256.js +0 -23
  563. package/node_modules/@noble/hashes/sha256.js.map +0 -1
  564. package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -142
  565. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +0 -1
  566. package/node_modules/@noble/hashes/sha3-addons.js +0 -402
  567. package/node_modules/@noble/hashes/sha3-addons.js.map +0 -1
  568. package/node_modules/@noble/hashes/sha3.d.ts +0 -53
  569. package/node_modules/@noble/hashes/sha3.d.ts.map +0 -1
  570. package/node_modules/@noble/hashes/sha3.js +0 -239
  571. package/node_modules/@noble/hashes/sha3.js.map +0 -1
  572. package/node_modules/@noble/hashes/sha512.d.ts +0 -26
  573. package/node_modules/@noble/hashes/sha512.d.ts.map +0 -1
  574. package/node_modules/@noble/hashes/sha512.js +0 -29
  575. package/node_modules/@noble/hashes/sha512.js.map +0 -1
  576. package/node_modules/@noble/hashes/src/_assert.ts +0 -22
  577. package/node_modules/@noble/hashes/src/_blake.ts +0 -50
  578. package/node_modules/@noble/hashes/src/_md.ts +0 -176
  579. package/node_modules/@noble/hashes/src/_u64.ts +0 -91
  580. package/node_modules/@noble/hashes/src/argon2.ts +0 -497
  581. package/node_modules/@noble/hashes/src/blake1.ts +0 -534
  582. package/node_modules/@noble/hashes/src/blake2.ts +0 -486
  583. package/node_modules/@noble/hashes/src/blake2b.ts +0 -10
  584. package/node_modules/@noble/hashes/src/blake2s.ts +0 -20
  585. package/node_modules/@noble/hashes/src/blake3.ts +0 -272
  586. package/node_modules/@noble/hashes/src/crypto.ts +0 -9
  587. package/node_modules/@noble/hashes/src/cryptoNode.ts +0 -15
  588. package/node_modules/@noble/hashes/src/eskdf.ts +0 -187
  589. package/node_modules/@noble/hashes/src/hkdf.ts +0 -88
  590. package/node_modules/@noble/hashes/src/hmac.ts +0 -94
  591. package/node_modules/@noble/hashes/src/index.ts +0 -31
  592. package/node_modules/@noble/hashes/src/legacy.ts +0 -293
  593. package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -122
  594. package/node_modules/@noble/hashes/src/ripemd160.ts +0 -12
  595. package/node_modules/@noble/hashes/src/scrypt.ts +0 -257
  596. package/node_modules/@noble/hashes/src/sha1.ts +0 -10
  597. package/node_modules/@noble/hashes/src/sha2.ts +0 -402
  598. package/node_modules/@noble/hashes/src/sha256.ts +0 -24
  599. package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -499
  600. package/node_modules/@noble/hashes/src/sha3.ts +0 -258
  601. package/node_modules/@noble/hashes/src/sha512.ts +0 -34
  602. package/node_modules/@noble/hashes/src/utils.ts +0 -395
  603. package/node_modules/@noble/hashes/utils.d.ts +0 -161
  604. package/node_modules/@noble/hashes/utils.d.ts.map +0 -1
  605. package/node_modules/@noble/hashes/utils.js +0 -313
  606. package/node_modules/@noble/hashes/utils.js.map +0 -1
@@ -1,78 +1,17 @@
1
- // ../mmt-alta-parse/dist/index.js
1
+ // ../mmt-base/dist/mmtp.js
2
2
  var MMTP_HEADER_SIZE = 12;
3
3
  var MPU_HEADER_SIZE = 8;
4
- var MPU_LENGTH_FIELD_SIZE = 2;
5
- var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
6
- var TIMED_MFU_DU_HEADER_SIZE = 14;
7
- var NONTIMED_MFU_DU_HEADER_SIZE = 4;
8
- var PACKET_TYPE_MPU = 0;
9
- var MMTP_EXT_TYPE_ALTA_AUTH = 61601;
10
- var MMTP_EXT_TRAILER_HEADER_SIZE = 4;
11
- function extractAltaTrailer(mmtpPacket, options) {
12
- if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
13
- return null;
14
- const packetType = mmtpPacket[1] & 63;
15
- if (packetType !== PACKET_TYPE_MPU)
16
- return null;
17
- const fecType = mmtpPacket[0] >> 3 & 3;
18
- let headerLength = MMTP_HEADER_SIZE;
19
- if (fecType < 2 && (mmtpPacket[0] & 32) !== 0)
20
- headerLength += 4;
21
- if (fecType < 2 && (mmtpPacket[0] & 2) !== 0) {
22
- if (headerLength + 4 > mmtpPacket.length)
23
- return null;
24
- const extensionLength = new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + headerLength + 2, 2).getUint16(0, false);
25
- headerLength += 4 + extensionLength;
26
- }
27
- if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length)
28
- return null;
29
- const payload = mmtpPacket.subarray(headerLength);
30
- const payloadLength = payload[0] << 8 | payload[1];
31
- const flagsByte = payload[2];
32
- const fragmentType = flagsByte >> 4 & 15;
33
- const timed = (flagsByte >> 3 & 1) === 1;
34
- const fragmentationIndicator = flagsByte >> 1 & 3;
35
- if (fragmentType !== 2)
36
- return null;
37
- const dataUnitHeaderSize = fragmentationIndicator <= 1 ? timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE : 0;
38
- if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize)
39
- return null;
40
- if (fecType > 1)
41
- return null;
42
- const hasFecId = !options?.skipFecId && fecType === 1;
43
- const payloadEnd = hasFecId ? payload.length - 4 : payload.length;
44
- const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength;
45
- if (declaredEnd > payloadEnd)
46
- return null;
47
- if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd)
48
- return null;
49
- const extType = payload[declaredEnd] << 8 | payload[declaredEnd + 1];
50
- if (extType !== MMTP_EXT_TYPE_ALTA_AUTH)
51
- return null;
52
- const extLen = payload[declaredEnd + 2] << 8 | payload[declaredEnd + 3];
53
- const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE;
54
- if (authStart + extLen !== payloadEnd)
55
- return null;
56
- return {
57
- auth: payload.subarray(authStart, payloadEnd),
58
- signedEnd: headerLength + declaredEnd
59
- };
60
- }
61
-
62
- // ../mmt-base/dist/mmtp.js
63
- var MMTP_HEADER_SIZE2 = 12;
64
- var MPU_HEADER_SIZE2 = 8;
65
4
  var MFU_DU_HEADER_SIZE_TIMED = 14;
66
5
  var MFU_DU_HEADER_SIZE_NONTIMED = 4;
67
- var MPU_LENGTH_FIELD_SIZE2 = 2;
68
- var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
6
+ var MPU_LENGTH_FIELD_SIZE = 2;
7
+ var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
69
8
  var SOURCE_FEC_PAYLOAD_ID_SIZE = 4;
70
- var PACKET_TYPE_MPU2 = 0;
9
+ var PACKET_TYPE_MPU = 0;
71
10
  var PACKET_TYPE_REPAIR = 3;
72
11
  var MMTP_SIGNALING_PACKET_ID = 0;
73
12
  var MMTP_PACKET_ID_MAX = 65535;
74
13
  function parseMMTPHeader(buffer) {
75
- if (buffer.length < MMTP_HEADER_SIZE2) {
14
+ if (buffer.length < MMTP_HEADER_SIZE) {
76
15
  throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
77
16
  }
78
17
  const view2 = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -128,12 +67,12 @@ function parseMMTPHeader(buffer) {
128
67
  };
129
68
  }
130
69
  function readMMTPPacketId(buffer) {
131
- if (buffer.length < MMTP_HEADER_SIZE2)
70
+ if (buffer.length < MMTP_HEADER_SIZE)
132
71
  return null;
133
72
  const flags = buffer[0];
134
73
  if ((flags >> 6 & 3) !== 0)
135
74
  return null;
136
- let offset = MMTP_HEADER_SIZE2;
75
+ let offset = MMTP_HEADER_SIZE;
137
76
  if ((flags & 32) !== 0) {
138
77
  if (offset + 4 > buffer.length)
139
78
  return null;
@@ -149,7 +88,7 @@ function readMMTPPacketId(buffer) {
149
88
  return buffer[2] << 8 | buffer[3];
150
89
  }
151
90
  function parseMPUHeader(payload) {
152
- if (payload.length < MPU_HEADER_SIZE2) {
91
+ if (payload.length < MPU_HEADER_SIZE) {
153
92
  throw new Error(`Payload too small for MPU header: ${payload.length} bytes`);
154
93
  }
155
94
  const view2 = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
@@ -182,15 +121,15 @@ function parseTimedMfuDuHeader(payload, offset) {
182
121
  };
183
122
  }
184
123
  function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
185
- if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE2) {
124
+ if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {
186
125
  return {
187
126
  rejection: {
188
127
  gate: "mpu-payload-length-below-header-remainder",
189
- detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE2}`
128
+ detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`
190
129
  }
191
130
  };
192
131
  }
193
- const declaredEnd = MPU_LENGTH_FIELD_SIZE2 + mpu.payloadLength;
132
+ const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength;
194
133
  if (declaredEnd > payload.length) {
195
134
  return {
196
135
  rejection: {
@@ -201,7 +140,7 @@ function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
201
140
  }
202
141
  const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1;
203
142
  const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED;
204
- const mfuOffset = MPU_HEADER_SIZE2 + (hasHeader ? mfuHeaderSize : 0);
143
+ const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0);
205
144
  if (mfuOffset >= declaredEnd) {
206
145
  return {
207
146
  rejection: {
@@ -218,44 +157,46 @@ function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
218
157
  }
219
158
  };
220
159
  }
221
- function isEmptyOrExactAltaTrailer(payload, start, end) {
160
+ var LEGACY_TRAILER_EXT_TYPE = 61601;
161
+ var LEGACY_TRAILER_HEADER_SIZE = 4;
162
+ function isEmptyOrExactLegacyTrailer(payload, start, end) {
222
163
  if (start === end)
223
164
  return true;
224
- if (start < 0 || end > payload.length || start + MMTP_EXT_TRAILER_HEADER_SIZE > end) {
165
+ if (start < 0 || end > payload.length || start + LEGACY_TRAILER_HEADER_SIZE > end) {
225
166
  return false;
226
167
  }
227
168
  const view2 = new DataView(payload.buffer, payload.byteOffset + start, end - start);
228
- if (view2.getUint16(0, false) !== MMTP_EXT_TYPE_ALTA_AUTH)
169
+ if (view2.getUint16(0, false) !== LEGACY_TRAILER_EXT_TYPE)
229
170
  return false;
230
171
  const authLength = view2.getUint16(2, false);
231
- return start + MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end;
172
+ return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
232
173
  }
233
174
  function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
234
175
  if (declaredEnd > payload.length)
235
176
  return null;
236
177
  if (options?.skipFecId) {
237
- return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
178
+ return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
238
179
  }
239
180
  if (fecType === 1) {
240
181
  const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
241
- if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {
182
+ if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
242
183
  return null;
243
184
  }
244
185
  return { hasFecId: true };
245
186
  }
246
187
  if (fecType !== 0)
247
188
  return null;
248
- if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {
189
+ if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
249
190
  return { hasFecId: false };
250
191
  }
251
192
  return null;
252
193
  }
253
194
  function planMfuExtraction(mmtpPacket, options, parsedHeader) {
254
- if (mmtpPacket.length < MMTP_HEADER_SIZE2 + MPU_HEADER_SIZE2) {
195
+ if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
255
196
  return {
256
197
  rejection: {
257
198
  gate: "packet-shorter-than-mmtp-plus-mpu-header",
258
- detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE2 + MPU_HEADER_SIZE2}`
199
+ detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`
259
200
  }
260
201
  };
261
202
  }
@@ -274,7 +215,7 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
274
215
  };
275
216
  }
276
217
  }
277
- if (header.packetType !== PACKET_TYPE_MPU2) {
218
+ if (header.packetType !== PACKET_TYPE_MPU) {
278
219
  return {
279
220
  rejection: {
280
221
  gate: "not-an-mpu-packet",
@@ -283,11 +224,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
283
224
  };
284
225
  }
285
226
  const payload = mmtpPacket.subarray(header.headerLength);
286
- if (payload.length < MPU_HEADER_SIZE2) {
227
+ if (payload.length < MPU_HEADER_SIZE) {
287
228
  return {
288
229
  rejection: {
289
230
  gate: "payload-shorter-than-mpu-header",
290
- detail: `${payload.length} bytes < ${MPU_HEADER_SIZE2}`
231
+ detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`
291
232
  }
292
233
  };
293
234
  }
@@ -318,7 +259,7 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
318
259
  return {
319
260
  rejection: {
320
261
  gate: "declared-trailers-unresolved",
321
- detail: `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty nor an exact ALTA trailer (fecType=${header.fecType})`
262
+ detail: `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty nor an exact legacy authenticator trailer (fecType=${header.fecType})`
322
263
  }
323
264
  };
324
265
  }
@@ -334,8 +275,8 @@ function extractPlannedMfu(mmtpPacket, result) {
334
275
  const mfuDuHeaderPresent = layout.hasMfuDuHeader;
335
276
  const mfuOffset = layout.mfuOffset;
336
277
  const dataEnd = layout.dataEnd;
337
- const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE2) : null;
338
- const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE2, 4).getUint32(0, false) : null;
278
+ const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
279
+ const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
339
280
  const data = payload.subarray(mfuOffset, dataEnd);
340
281
  const fecPayloadId = trailerLayout.hasFecId ? {
341
282
  ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, SOURCE_FEC_PAYLOAD_ID_SIZE).getUint32(0, false)
@@ -343,17 +284,17 @@ function extractPlannedMfu(mmtpPacket, result) {
343
284
  return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
344
285
  }
345
286
  function sourceFecPayloadIdOf(mmtpPacket, header) {
346
- if (mmtpPacket.length < MMTP_HEADER_SIZE2 + MPU_HEADER_SIZE2)
287
+ if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
347
288
  return null;
348
- if (header.packetType !== PACKET_TYPE_MPU2)
289
+ if (header.packetType !== PACKET_TYPE_MPU)
349
290
  return null;
350
291
  const payload = mmtpPacket.subarray(header.headerLength);
351
- if (payload.length < MPU_HEADER_SIZE2)
292
+ if (payload.length < MPU_HEADER_SIZE)
352
293
  return null;
353
294
  const payloadLength = payload[0] << 8 | payload[1];
354
- if (payloadLength < MPU_HEADER_REMAINDER_SIZE2)
295
+ if (payloadLength < MPU_HEADER_REMAINDER_SIZE)
355
296
  return null;
356
- const declaredEnd = MPU_LENGTH_FIELD_SIZE2 + payloadLength;
297
+ const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength;
357
298
  if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId)
358
299
  return null;
359
300
  const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
@@ -361,7 +302,6 @@ function sourceFecPayloadIdOf(mmtpPacket, header) {
361
302
  ssId: (mmtpPacket[off] << 24 | mmtpPacket[off + 1] << 16 | mmtpPacket[off + 2] << 8 | mmtpPacket[off + 3]) >>> 0
362
303
  };
363
304
  }
364
- var extractAltaTrailer2 = extractAltaTrailer;
365
305
  var REPAIR_FEC_PID_SIZE = 13;
366
306
  function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
367
307
  let header;
@@ -441,49 +381,21 @@ function serial32Precedes(a, b) {
441
381
  const distance = serial32Distance(a, b);
442
382
  return distance !== 0 && distance < SERIAL32_HALF;
443
383
  }
444
- function kStrideBlockStart(sbn, k) {
445
- assertUint32(sbn, "sbn");
446
- if (!Number.isInteger(k) || k < 1 || k >= SERIAL32_HALF) {
447
- throw new RangeError(`k must be a positive integer below 2^31 (got ${k})`);
448
- }
449
- const high = Math.floor(sbn / 65536);
450
- const low = sbn % 65536;
451
- return (high * k % SERIAL32_SPAN * 65536 + low * k) % SERIAL32_SPAN;
452
- }
453
- function kStrideBlockPrecedes(a, b, k) {
454
- return serial32Precedes(kStrideBlockStart(a, k), kStrideBlockStart(b, k));
455
- }
456
- function kStrideBlocksBehind(a, b, k) {
457
- const from = kStrideBlockStart(a, k);
458
- const to = kStrideBlockStart(b, k);
459
- if (!serial32Precedes(from, to))
460
- return 0;
461
- return Math.ceil(serial32Distance(from, to) / k);
462
- }
463
- function isKStrideBlock(sbn, k) {
464
- return Number.isInteger(sbn) && sbn >= 0 && sbn < SERIAL32_SPAN && Number.isInteger(k) && k >= 1 && k < SERIAL32_HALF;
465
- }
466
- function kStrideNextBlock(sbn, k) {
467
- assertUint32(sbn, "sbn");
468
- kStrideBlockStart(sbn, k);
469
- return (sbn + 1) * k >= SERIAL32_SPAN ? 0 : sbn + 1;
470
- }
471
384
 
472
385
  // ../mmt-base/dist/fec-block-profile.js
473
386
  var FEC_BLOCK_PROFILES = Object.freeze([
474
- "k-padded-v1",
475
387
  "iso-ssbg1-v1"
476
388
  ]);
477
389
  function isFecBlockProfile(value) {
478
- return value === "k-padded-v1" || value === "iso-ssbg1-v1";
390
+ return value === "iso-ssbg1-v1";
479
391
  }
480
392
  function parseFecBlockProfile(value, where) {
481
393
  if (isFecBlockProfile(value))
482
394
  return value;
483
395
  if (value === void 0) {
484
- throw new Error(`${where} is required: name the FEC block profile ("k-padded-v1" or "iso-ssbg1-v1")`);
396
+ throw new Error(`${where} is required: name the FEC block profile ("iso-ssbg1-v1")`);
485
397
  }
486
- throw new Error(`${where} must be "k-padded-v1" or "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
398
+ throw new Error(`${where} must be "iso-ssbg1-v1" (got ${JSON.stringify(value)})`);
487
399
  }
488
400
  var SSBG1_LENGTH_FIELD_BYTES = 2;
489
401
  function ssbg1MaxPacketLength(symbolSize) {
@@ -508,20 +420,22 @@ function isoBlocksBehindLowerBound(a, b, kMax) {
508
420
  // src/track-session.ts
509
421
  import { fecBlockBudgetMs } from "@blockcast/mmt-fec";
510
422
  function computeFecDeliveryWindowMs(config) {
511
- const fecInterleaveMs = config.interleaveDepthMs;
512
423
  const fecK = config.sourceSymbols;
513
- if (!Number.isFinite(fecInterleaveMs) || fecInterleaveMs <= 0) {
424
+ if (!Number.isFinite(fecK) || fecK <= 0) {
514
425
  throw new Error(
515
- `[TrackSession] fecInterleaveMs=${fecInterleaveMs}. Derive a positive FEC interleave window from the stream catalog.`
426
+ `[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
516
427
  );
517
428
  }
518
- if (!Number.isFinite(fecK) || fecK <= 0) {
429
+ return fecSpanDeliveryWindowMs(config.interleaveDepthMs, config.jitterBufferMs);
430
+ }
431
+ function fecSpanDeliveryWindowMs(interleaveDepthMs, jitterBufferMs) {
432
+ if (!Number.isFinite(interleaveDepthMs) || interleaveDepthMs <= 0) {
519
433
  throw new Error(
520
- `[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
434
+ `[TrackSession] fecInterleaveMs=${interleaveDepthMs}. Derive a positive FEC interleave window from the stream catalog.`
521
435
  );
522
436
  }
523
- if (config.jitterBufferMs === void 0) return Math.ceil(fecInterleaveMs);
524
- return fecBlockBudgetMs(fecInterleaveMs, config.jitterBufferMs);
437
+ if (jitterBufferMs === void 0) return Math.ceil(interleaveDepthMs);
438
+ return fecBlockBudgetMs(interleaveDepthMs, jitterBufferMs);
525
439
  }
526
440
 
527
441
  // src/delivery-gap.ts
@@ -613,7 +527,9 @@ var DeliveryGapEstimator = class {
613
527
  import {
614
528
  deriveFecInterleaveDepth as deriveFecInterleaveDepth2,
615
529
  deriveFecRetentionBlocks,
616
- tryDeriveFecRetentionBlocks
530
+ tryDeriveFecRetentionBlocks,
531
+ LOCAL_FEC_RETENTION_BLOCKS,
532
+ LOCAL_FEC_TIMEOUT_MS
617
533
  } from "@blockcast/mmt-fec";
618
534
 
619
535
  // src/iso-block-locator.ts
@@ -847,7 +763,7 @@ var IsoBlockLocator = class _IsoBlockLocator {
847
763
  };
848
764
 
849
765
  // src/fec-state.ts
850
- var MIN_INTERLEAVE_DEPTH_MS = 33;
766
+ var MIN_INTERLEAVE_DEPTH_MS = 1;
851
767
  var FEC_INACTIVE_REASONS = Object.freeze([
852
768
  "no-catalog-fec",
853
769
  "reliable-moq",
@@ -858,6 +774,15 @@ var INACTIVE = Object.freeze({
858
774
  "reliable-moq": Object.freeze({ state: "inactive", reason: "reliable-moq" }),
859
775
  "no-multicast-bridge": Object.freeze({ state: "inactive", reason: "no-multicast-bridge" })
860
776
  });
777
+ function recordOf(value) {
778
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
779
+ }
780
+ function isoBlockSpanUsable(fec) {
781
+ const block = recordOf(fec);
782
+ if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1") return true;
783
+ const ms = block["interleaveDepthMs"];
784
+ return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
785
+ }
861
786
 
862
787
  // src/block-policy.ts
863
788
  import { deriveFecInterleaveDepth } from "@blockcast/mmt-fec";
@@ -1366,7 +1291,6 @@ var FECManager = class _FECManager {
1366
1291
  constructor() {
1367
1292
  // Per-track WASM RaptorQ decoders, keyed by physical stream identity.
1368
1293
  this.#fecDecoders = /* @__PURE__ */ new Map();
1369
- /** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */
1370
1294
  // Per-track Worker-backed FEC decoders, keyed by physical stream identity.
1371
1295
  // Worker-backed tracks use async decode — recovered frames arrive via onFrame callback,
1372
1296
  // not synchronous return from feedSourceSymbol/feedRepairSymbol.
@@ -1381,10 +1305,9 @@ var FECManager = class _FECManager {
1381
1305
  // source/repair symbols do not split across two RaptorQ states.
1382
1306
  this.#syncDecoderBlockOwners = /* @__PURE__ */ new Map();
1383
1307
  this.#decoderBlockOwners = /* @__PURE__ */ new WeakMap();
1384
- this.#fecK = 4;
1385
1308
  this.#fecSymbolSize = 0;
1386
- this.#fecTimeoutMs = 500;
1387
- // default 500ms; refined by setFramerate() when fps known
1309
+ this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
1310
+ // local default; refined by setFramerate() when fps known
1388
1311
  this.#trackConfigs = /* @__PURE__ */ new Map();
1389
1312
  this.#fecStats = newFecCounters();
1390
1313
  this.#fecTrackStats = /* @__PURE__ */ new Map();
@@ -1424,14 +1347,17 @@ var FECManager = class _FECManager {
1424
1347
  this.#blockSourceEsis = /* @__PURE__ */ new Map();
1425
1348
  // Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
1426
1349
  // first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
1427
- // media units, usually before it holds K symbols, and fec-raptorq
1428
- // force_complete() encodes it as a K-symbol RaptorQ block whose ESIs
1429
- // SSB_length..K-1 are zero padding that is never sent. Such a block is clean
1430
- // at SSB_length source arrivals and recoverable from SSB_length received
1350
+ // media units, usually before it holds K symbols; under iso-ssbg1-v1
1351
+ // (ssbg_mode1) that block is K' = SSB_length symbols, its repair ESIs start
1352
+ // at SSB_length, and K is only the maximum. Such a block is clean at
1353
+ // SSB_length source arrivals and recoverable from SSB_length received
1431
1354
  // symbols; waiting for K fails every one of them.
1432
1355
  this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
1433
1356
  this.#ssbLengthConflictCount = 0;
1434
1357
  this.#blockRepairRsIds = /* @__PURE__ */ new Map();
1358
+ // RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
1359
+ // symbols its repair group carries. Same lifecycle as #blockRepairRsIds.
1360
+ this.#blockRsbLengths = /* @__PURE__ */ new Map();
1435
1361
  // Blocks already recovered — prevents late source symbols from restarting timeouts.
1436
1362
  // WASM removes blocks after recovery; late symbols create empty blocks returning null,
1437
1363
  // which would falsely start new timeouts without this guard.
@@ -1544,14 +1470,16 @@ var FECManager = class _FECManager {
1544
1470
  this.#resolvedBlockClasses = /* @__PURE__ */ new Map();
1545
1471
  // Blocks already reported through onBlockRepaired (once per block).
1546
1472
  this.#repairedBlocks = /* @__PURE__ */ new Set();
1547
- // Blocks created without any symbol because every symbol vanished.
1473
+ // Blocks created before any symbol of theirs was tracked: the repair-less
1474
+ // spans placeIsoRepair materializes (onBlockVanished).
1548
1475
  this.#materializedBlocks = /* @__PURE__ */ new Set();
1549
1476
  // packetIds whose ordered-relay horizon became underivable and fell back to
1550
1477
  // the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once
1551
1478
  // per stream. Not only throws: the ordinary out-of-range case is detected by
1552
1479
  // arithmetic and never throws at all.
1553
1480
  this.#orderedHorizonWarnedFor = /* @__PURE__ */ new Set();
1554
- // Highest SBN seen per stream and when, for vanished-block detection.
1481
+ // Newest block start (SS_Start) seen per stream and when: the frontier for
1482
+ // source-discontinuity detection and isoSpanResolution.
1555
1483
  this.#highestSbn = /* @__PURE__ */ new Map();
1556
1484
  // iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a
1557
1485
  // block only once a repair symbol stated the block's (SS_Start, SSB_length).
@@ -1645,7 +1573,10 @@ var FECManager = class _FECManager {
1645
1573
  #releasedWorkerClients;
1646
1574
  #syncDecoderBlockOwners;
1647
1575
  #decoderBlockOwners;
1648
- #fecK;
1576
+ // The stream-global K a configure() call stated, reported as fecStats.k.
1577
+ // It never resolves a packetId: a packetId has only its own track config
1578
+ // (BLO-45953), and K does not feed the delivery-window math.
1579
+ #streamK;
1649
1580
  #fecSymbolSize;
1650
1581
  #fecTimeoutMs;
1651
1582
  #trackConfigs;
@@ -1667,6 +1598,7 @@ var FECManager = class _FECManager {
1667
1598
  #blockAuthoredLengths;
1668
1599
  #ssbLengthConflictCount;
1669
1600
  #blockRepairRsIds;
1601
+ #blockRsbLengths;
1670
1602
  #completedBlocks;
1671
1603
  #terminalExpiryMs;
1672
1604
  #terminalQueues;
@@ -1807,7 +1739,8 @@ var FECManager = class _FECManager {
1807
1739
  this.#completedBlocks.add(key);
1808
1740
  if (clean) this.#cleanlyCompletedBlocks.add(key);
1809
1741
  this.#indexBlockKey(key, sbn, packetId);
1810
- const expiresAtMs = nowMs + this.terminalRetentionMsForPacket(packetId);
1742
+ const config = this.#trackConfig(packetId);
1743
+ const expiresAtMs = nowMs + config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
1811
1744
  this.#terminalExpiryMs.set(key, expiresAtMs);
1812
1745
  let queue = this.#terminalQueues.get(packetId);
1813
1746
  if (!queue) {
@@ -1843,8 +1776,9 @@ var FECManager = class _FECManager {
1843
1776
  * retention the Worker and the WASM decoder are configured with, because it
1844
1777
  * comes from the same function they do (`deriveFecRetentionBlocks`, over the
1845
1778
  * block's delivery window and its span). The decoder retires every block
1846
- * more than this many SBNs behind the newest one it saw (`cleanup_by_sbn`),
1847
- * so settling earlier than this would decide a block the decoder would still
1779
+ * more than this many blocks behind the newest SS_Start it saw, counted
1780
+ * from below by `isoBlocksBehindLowerBound` (`cleanup_by_ss_start`), so
1781
+ * settling earlier than this would decide a block the decoder would still
1848
1782
  * have recovered.
1849
1783
  *
1850
1784
  * This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA
@@ -1857,6 +1791,14 @@ var FECManager = class _FECManager {
1857
1791
  * derivation; do not re-derive a horizon at either call site.
1858
1792
  */
1859
1793
  #orderedHorizonBlocksFor(packetId, config) {
1794
+ if (config.interleaveDepthMs === 0) {
1795
+ if (Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
1796
+ return LOCAL_FEC_RETENTION_BLOCKS;
1797
+ }
1798
+ throw new Error(
1799
+ `[FECManager] packetId=${packetId}: local FEC cleanup requires a positive timeout (got ${config.timeoutMs})`
1800
+ );
1801
+ }
1860
1802
  if (!_FECManager.#hasOrderedGeometry(config)) {
1861
1803
  throw new Error(
1862
1804
  `[FECManager] packetId=${packetId}: ordered-relay settlement derives the decoder's interleave horizon from the catalog interleaveDepthMs (>= ${MIN_INTERLEAVE_DEPTH_MS}ms), the media-unit rate and the block delivery window; got interleaveDepthMs=${config.interleaveDepthMs} fps=${String(config.fps)} timeoutMs=${config.timeoutMs}.`
@@ -1870,7 +1812,7 @@ var FECManager = class _FECManager {
1870
1812
  * two can never disagree about which streams settle by stream order.
1871
1813
  */
1872
1814
  static #hasOrderedGeometry(config) {
1873
- return config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS && // `fps` is no longer an input to the horizon, but it still gates WHICH
1815
+ return _FECManager.#hasCatalogTiming(config) && // `fps` is no longer an input to the horizon, but it still gates WHICH
1874
1816
  // streams settle by stream order at all: a track with no usable
1875
1817
  // media-unit rate fell back to datagram semantics before this and must
1876
1818
  // keep doing so. Removing this widens ordered-relay settlement to streams
@@ -1910,6 +1852,9 @@ var FECManager = class _FECManager {
1910
1852
  * argument: narrower IS the failing direction.
1911
1853
  */
1912
1854
  #derivableOrderedHorizon(config, packetId) {
1855
+ if (config.interleaveDepthMs === 0) {
1856
+ return Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? LOCAL_FEC_RETENTION_BLOCKS : void 0;
1857
+ }
1913
1858
  if (!_FECManager.#hasOrderedGeometry(config)) return void 0;
1914
1859
  try {
1915
1860
  const blocks = tryDeriveFecRetentionBlocks(config.timeoutMs, config.interleaveDepthMs);
@@ -1942,19 +1887,20 @@ var FECManager = class _FECManager {
1942
1887
  /**
1943
1888
  * The stream's interleave horizon, or undefined when its geometry cannot
1944
1889
  * define one. Two different streams reach that case, with different causes:
1945
- * a packetId that was never configured, and so resolves to the manager
1946
- * defaults (explicitly configured tracks are refused an underivable geometry
1890
+ * a packetId that was never configured, which has no geometry at all
1891
+ * (explicitly configured tracks are refused an underivable geometry
1947
1892
  * at config time — #upsertTrackConfig, setMulticastSymbolDelivery); or a
1948
1893
  * configured track whose timeoutMs was later widened past the 255-block
1949
1894
  * ceiling without re-validating, which #derivableOrderedHorizon catches
1950
1895
  * rather than throwing from the packet-feed path. Either way the stream
1951
- * keeps datagram semantics instead of throwing. The manager defaults can
1896
+ * keeps datagram semantics instead of throwing. A track's configuration can
1952
1897
  * also change under a stream that already holds blocks, so a caller cannot
1953
1898
  * assume a horizon that was defined when a block was held is still defined
1954
1899
  * (see #advanceOrderedFrontier).
1955
1900
  */
1956
1901
  #orderedHorizonBlocks(packetId) {
1957
- return this.#derivableOrderedHorizon(this.#resolveTrackConfig(packetId), packetId);
1902
+ const config = this.#trackConfigs.get(packetId);
1903
+ return config === void 0 ? void 0 : this.#derivableOrderedHorizon(config, packetId);
1958
1904
  }
1959
1905
  /** True while a block of an ordered-relay stream can still receive symbols. */
1960
1906
  #withinOrderedHorizon(key) {
@@ -1988,7 +1934,7 @@ var FECManager = class _FECManager {
1988
1934
  */
1989
1935
  #advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName) {
1990
1936
  const previous = this.#orderedFrontiers.get(streamKey);
1991
- const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn, packetId) ? sbn : previous;
1937
+ const frontier = previous === void 0 || this.#blockPrecedes(previous, sbn) ? sbn : previous;
1992
1938
  if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier);
1993
1939
  const pending = this.#orderedPendingBlocks.get(streamKey);
1994
1940
  if (!pending) return;
@@ -2023,7 +1969,7 @@ var FECManager = class _FECManager {
2023
1969
  if (this.#completedBlocks.has(key) || this.#pendingWorkerCleanups.has(key) || this.#repairRequestedBlocks.has(key)) return;
2024
1970
  const authoredLength = this.#blockAuthoredLengths.get(key);
2025
1971
  const received = (this.#blockSourceCounts.get(key) ?? 0) + (this.#blockRepairRsIds.get(key)?.size ?? 0);
2026
- if (this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
1972
+ if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && (authoredLength === void 0 || received < authoredLength)) {
2027
1973
  this.#requestRepair(sbn, packetId, catalogTrackName, true);
2028
1974
  return;
2029
1975
  }
@@ -2179,7 +2125,7 @@ var FECManager = class _FECManager {
2179
2125
  }
2180
2126
  #requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
2181
2127
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
2182
- if (this.#completedBlocks.has(key) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
2128
+ if (this.#completedBlocks.has(key) || !this.#hasCatalogTimingForPacket(packetId) || !this.onRepairNeeded || this.#repairRequestedBlocks.has(key)) return;
2183
2129
  const deadline = this.#repairDeadlines.get(key);
2184
2130
  if (deadline?.handle != null) clearTimeout(deadline.handle);
2185
2131
  this.#repairDeadlines.set(key, null);
@@ -2259,6 +2205,7 @@ var FECManager = class _FECManager {
2259
2205
  }
2260
2206
  /** A block's live class without creating classifier state for the stream. */
2261
2207
  #peekClass(sbn, packetId, catalogTrackName) {
2208
+ if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
2262
2209
  const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName));
2263
2210
  if (classifier) return classifier.classOf(sbn);
2264
2211
  return this.#geometryFor(packetId, catalogTrackName).trackKind === "audio" ? "audio" : "unknown";
@@ -2280,8 +2227,8 @@ var FECManager = class _FECManager {
2280
2227
  // Deliberately one block tighter than deriveFecRetentionBlocks over the same
2281
2228
  // inputs (that function's `+ 1`). Different question: the retention horizon
2282
2229
  // asks how long the decoder still ACCEPTS symbols for a block, this asks how
2283
- // far back a block that vanished entirely may still be repairable (see
2284
- // #noteSbnSeen).
2230
+ // many blocks a stream can live behind (or ahead of) its frontier before a
2231
+ // source symbol there is a discontinuity rather than loss (see #noteSbnSeen).
2285
2232
  // The extra block of retention is slack for a block still being fed; a block
2286
2233
  // nothing has been heard from does not get it. Not the units defect — but it
2287
2234
  // is the next place the two can drift, so change both or neither.
@@ -2290,27 +2237,28 @@ var FECManager = class _FECManager {
2290
2237
  return Math.ceil(config.timeoutMs / config.interleaveDepthMs);
2291
2238
  }
2292
2239
  /**
2293
- * Record that a symbol for block `sbn` arrived and materialise any blocks
2294
- * that vanished entirely between the stream's highest SBN and this one.
2240
+ * Record that a symbol for block `sbn` (the block's SS_Start) arrived and
2241
+ * advance the stream's frontier, the newest block start seen.
2295
2242
  *
2296
- * The newest ceil(timeoutMs / interleaveDepthMs) vanished blocks may still
2297
- * be repairable, so they get the normal repair and fail deadlines, armed
2298
- * from now. Older ones are already past their fail bound and fail now, as
2299
- * class `unknown`. A SOURCE symbol whose SBN goes back further than one
2300
- * live window, or jumps further than the stream could have produced since
2301
- * its last symbol, is a source discontinuity, not loss. A repair symbol can
2302
- * only advance the frontier: a D-28 fetch legitimately answers for an old
2303
- * block, so it never signals a discontinuity.
2243
+ * A SOURCE symbol whose block goes back further than one live window, or
2244
+ * jumps further than the stream could have produced since its last symbol,
2245
+ * is a source discontinuity, not loss. A repair symbol can only advance the
2246
+ * frontier: a D-28 fetch legitimately answers for an old block, so it never
2247
+ * signals a discontinuity.
2304
2248
  *
2305
2249
  * The producer closes a block at its Dth media-unit boundary OR when K
2306
- * source symbols filled it, so one interleave span can start several
2307
- * blocks (every large keyframe does). What bounds a forward jump is
2308
- * therefore packet evidence, not a block rate: with the MMTP
2309
- * packet_sequence_number (contiguous per packet_id, ISO/IEC 23008-1) of
2310
- * both source symbols, every block strictly between them holds at least one
2311
- * of the packets sent strictly between them, and K-closes can add at most
2312
- * one block per K of those packets on top of the D-closes elapsed time
2313
- * allows. Without it, only the D-close rate is known.
2250
+ * source symbols filled it, so one interleave span can start several blocks
2251
+ * (every large keyframe does). What bounds a forward jump is therefore
2252
+ * packet evidence, not a block rate: SS_ID advances by one per FEC source
2253
+ * packet (ISO/IEC 23008-1 C.5.2) and the MMTP packet_sequence_number by one
2254
+ * per packet of the packet_id, so with both source symbols'
2255
+ * packet_sequence_number a block start can move at most the packets sent
2256
+ * between them plus the anchor's own offset into its block (< K). Without
2257
+ * it, only the D-close rate is known.
2258
+ *
2259
+ * Vanished blocks are not enumerated here: their starts are unknown until a
2260
+ * repair states them. The SS_IDs between two stated blocks become one
2261
+ * repair-less span when the later block's repair arrives (placeIsoRepair).
2314
2262
  */
2315
2263
  #noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber) {
2316
2264
  const streamKey = this.#streamKey(packetId, catalogTrackName);
@@ -2319,7 +2267,7 @@ var FECManager = class _FECManager {
2319
2267
  const anchor = this.#sourceAnchors.get(streamKey);
2320
2268
  const psn = symbol === "source" && packetSequenceNumber !== void 0 && Number.isSafeInteger(packetSequenceNumber) && packetSequenceNumber >= 0 && packetSequenceNumber <= 4294967295 ? packetSequenceNumber : void 0;
2321
2269
  const anchorHere = () => {
2322
- if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn, packetId))) {
2270
+ if (psn !== void 0 && (anchor === void 0 || !this.#blockPrecedes(sbn, anchor.sbn))) {
2323
2271
  this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now });
2324
2272
  }
2325
2273
  };
@@ -2335,9 +2283,9 @@ var FECManager = class _FECManager {
2335
2283
  anchorHere();
2336
2284
  return;
2337
2285
  }
2338
- const config = this.#resolveTrackConfig(packetId);
2286
+ const config = this.#trackConfig(packetId);
2339
2287
  const window2 = this.#liveWindowBlocks(config);
2340
- if (!this.#blockPrecedes(previous.sbn, sbn, packetId)) {
2288
+ if (!this.#blockPrecedes(previous.sbn, sbn)) {
2341
2289
  if (symbol === "source" && window2 !== null && this.#blocksBehind(sbn, previous.sbn, packetId) > window2) {
2342
2290
  discontinuity();
2343
2291
  return;
@@ -2348,15 +2296,9 @@ var FECManager = class _FECManager {
2348
2296
  const missing = this.#blocksBehind(previous.sbn, sbn, packetId) - 1;
2349
2297
  let plausible = true;
2350
2298
  if (window2 !== null) {
2351
- if (this.#isIso(packetId) && psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
2299
+ if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn) && psn !== anchor.psn) {
2352
2300
  const sent = psn - anchor.psn >>> 0;
2353
2301
  plausible = sent <= 2147483647 && serial32Distance(anchor.sbn, sbn) <= sent + config.k;
2354
- } else if (psn !== void 0 && anchor !== void 0 && this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
2355
- const sent = psn - anchor.psn >>> 0;
2356
- const between = this.#blocksBehind(anchor.sbn, sbn, packetId) - 1;
2357
- const dCloses = window2 + Math.ceil((now - anchor.atMs) / config.interleaveDepthMs);
2358
- const kCloses = config.k > 0 ? Math.ceil(sent / config.k) : sent;
2359
- plausible = sent <= 2147483647 && between <= sent - 1 && between <= dCloses + kCloses;
2360
2302
  } else {
2361
2303
  plausible = missing <= window2 + Math.ceil((now - previous.atMs) / config.interleaveDepthMs);
2362
2304
  }
@@ -2368,48 +2310,31 @@ var FECManager = class _FECManager {
2368
2310
  }
2369
2311
  this.#highestSbn.set(streamKey, { sbn, atMs: now });
2370
2312
  anchorHere();
2371
- if (this.#isIso(packetId)) return;
2372
- if (missing <= 0 || window2 === null) return;
2373
- let vanished = previous.sbn;
2374
- for (let i = 0; i < missing; i++) {
2375
- vanished = this.#nextBlock(vanished, packetId);
2376
- this.#materializeBlock(
2377
- vanished,
2378
- packetId,
2379
- catalogTrackName,
2380
- this.#blocksBehind(vanished, sbn, packetId) <= window2
2381
- );
2382
- }
2383
2313
  }
2384
2314
  /*
2385
- * Block order for one stream (BLO-35597 iso:5). SS_ID is a 32-bit serial
2386
- * number (ISO/IEC 23008-1 C.5.2), so blocks are ordered by RFC 1982
2387
- * comparison of their first SS_ID (sbn * K mod 2^32). That is identical to
2388
- * comparing SBNs with < for every stream that has not wrapped SS_ID; only
2389
- * the order across the wrap changes. A block outside K-stride geometry has
2390
- * no SS_ID position, so it keeps the SBN order it always had.
2315
+ * Block order for one stream (BLO-35597 iso:5). A block is keyed by its
2316
+ * SS_Start and SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2), so
2317
+ * blocks are ordered by RFC 1982 comparison of their starts. Only configured
2318
+ * streams hold blocks: every entry point refuses an unconfigured packetId.
2391
2319
  */
2392
- #blockPrecedes(a, b, packetId) {
2393
- if (this.#isIso(packetId)) return serial32Precedes(a, b);
2394
- const k = this.kForPacket(packetId);
2395
- return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlockPrecedes(a, b, k) : a < b;
2320
+ #blockPrecedes(a, b) {
2321
+ return serial32Precedes(a, b);
2396
2322
  }
2397
- /** Blocks `a` lies behind `b` (0 when it does not precede it). */
2323
+ /**
2324
+ * Blocks `a` lies behind `b` (0 when it does not precede it). Blocks vary
2325
+ * in length (K' = SSB_length <= K), so only a lower bound is known: the one
2326
+ * metric the decoder and the worker retire by too.
2327
+ */
2398
2328
  #blocksBehind(a, b, packetId) {
2399
- if (this.#isIso(packetId)) return isoBlocksBehindLowerBound(a, b, this.kForPacket(packetId));
2400
- const k = this.kForPacket(packetId);
2401
- return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlocksBehind(a, b, k) : Math.max(0, b - a);
2402
- }
2403
- /** The block after `sbn`, stepping to block 0 where SS_ID wraps. */
2404
- #nextBlock(sbn, packetId) {
2405
- const k = this.kForPacket(packetId);
2406
- return isKStrideBlock(sbn, k) ? kStrideNextBlock(sbn, k) : sbn + 1;
2329
+ return isoBlocksBehindLowerBound(a, b, this.#trackConfig(packetId).k);
2407
2330
  }
2408
2331
  #materializeBlock(sbn, packetId, catalogTrackName, repairable) {
2409
2332
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
2410
2333
  if (this.#blockResolutionFor(key) !== "untracked") return;
2411
2334
  this.#materializedBlocks.add(key);
2412
- this.#classifierFor(packetId, catalogTrackName).touch(sbn);
2335
+ if (this.#hasCatalogTimingForPacket(packetId)) {
2336
+ this.#classifierFor(packetId, catalogTrackName).touch(sbn);
2337
+ }
2413
2338
  this.onBlockVanished?.(sbn, packetId, catalogTrackName);
2414
2339
  if (repairable) {
2415
2340
  this.noteBlockActivity(sbn, packetId, catalogTrackName, true);
@@ -2448,21 +2373,39 @@ var FECManager = class _FECManager {
2448
2373
  });
2449
2374
  if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32);
2450
2375
  }
2451
- #defaultTrackConfig() {
2452
- return {
2453
- k: this.#fecK,
2454
- symbolSize: this.#fecSymbolSize,
2455
- interleaveDepthMs: this.#fecInterleaveDepthMs,
2456
- fps: this.#fecFps,
2457
- timeoutMs: this.#fecTimeoutMs
2458
- };
2376
+ /**
2377
+ * A configured packetId's track config. There is no manager-wide default
2378
+ * (BLO-45953): an unconfigured packetId does no FEC work, so every caller
2379
+ * here is reached only behind a configuration gate and this throw marks a
2380
+ * broken invariant, never a packet-path outcome.
2381
+ */
2382
+ #trackConfig(packetId) {
2383
+ const config = this.#trackConfigs.get(packetId);
2384
+ if (config === void 0) {
2385
+ throw new Error(
2386
+ `[FECManager] packetId=${packetId} was never configured; configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first`
2387
+ );
2388
+ }
2389
+ return config;
2459
2390
  }
2460
- #resolveTrackConfig(packetId) {
2461
- return this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig();
2391
+ /**
2392
+ * Whether this track has the catalog's elapsed-time FEC policy. A zero span
2393
+ * is a deliberate no-deadline signal; it still leaves the wire geometry
2394
+ * usable for in-band decode, but it cannot drive classification, repair
2395
+ * fetches or layer adaptation.
2396
+ */
2397
+ static #hasCatalogTiming(config) {
2398
+ return Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS;
2399
+ }
2400
+ #hasCatalogTimingForPacket(packetId) {
2401
+ const config = this.#trackConfigs.get(packetId);
2402
+ return config !== void 0 && _FECManager.#hasCatalogTiming(config);
2462
2403
  }
2463
2404
  #computeTimeout(config) {
2464
- if (config.timeoutMs && config.timeoutMs > 0) return config.timeoutMs;
2465
- if (config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
2405
+ if (typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
2406
+ return config.timeoutMs;
2407
+ }
2408
+ if (Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
2466
2409
  return Math.max(
2467
2410
  this.#fecTimeoutMs,
2468
2411
  computeFecDeliveryWindowMs({
@@ -2487,14 +2430,17 @@ var FECManager = class _FECManager {
2487
2430
  */
2488
2431
  #refreshDeliveryGapWindow() {
2489
2432
  let windowMs;
2490
- const consider = (geometry) => {
2433
+ const consider = (geometry, packetId) => {
2434
+ if (!this.#hasCatalogTimingForPacket(packetId)) return;
2491
2435
  if (geometry?.trackKind !== "video" || geometry.mode !== "classifying") return;
2492
2436
  const { ticks, timescale } = geometry.keyframeInterval;
2493
2437
  const ms = ticks / timescale * 1e3;
2494
2438
  if (Number.isFinite(ms) && ms > 0 && (windowMs === void 0 || ms > windowMs)) windowMs = ms;
2495
2439
  };
2496
- for (const geometry of this.#streamGeometries.values()) consider(geometry);
2497
- for (const config of this.#trackConfigs.values()) consider(config.geometry);
2440
+ for (const [streamKey, geometry] of this.#streamGeometries) {
2441
+ consider(geometry, this.#parseStreamKey(streamKey).packetId);
2442
+ }
2443
+ for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId);
2498
2444
  this.#deliveryGaps.setWindowMs(windowMs);
2499
2445
  }
2500
2446
  /**
@@ -2522,6 +2468,7 @@ var FECManager = class _FECManager {
2522
2468
  return Math.max(config.timeoutMs, Math.ceil(config.interleaveDepthMs + gapMs));
2523
2469
  }
2524
2470
  #repairDeadlineMs(config, waitMs = this.#multicastWaitMs(config)) {
2471
+ if (!_FECManager.#hasCatalogTiming(config)) return waitMs;
2525
2472
  const halfFailBoundMs = Math.ceil(waitMs / 2);
2526
2473
  if (config.k > 0 && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS && config.fps !== void 0 && Number.isFinite(config.fps) && config.fps > 0) {
2527
2474
  const mediaUnitMs = 1e3 / config.fps;
@@ -2536,10 +2483,12 @@ var FECManager = class _FECManager {
2536
2483
  }
2537
2484
  /**
2538
2485
  * One block lifetime: the inactivity fail bound plus the D-28 repair grace,
2539
- * at the widest wait the catalog allows (#maxMulticastWaitMs). Everything
2540
- * derived from it (terminal retention, the release gate's hold ceiling, the
2541
- * duplicate ledgers, the owed-cleanup bound) is compared across time, so it
2542
- * must not follow the live measurement.
2486
+ * at the widest wait the catalog allows (#maxMulticastWaitMs). For a track
2487
+ * without a catalog span this is receiver-local cleanup time only; no repair
2488
+ * grace is added. Everything derived from it (terminal retention, the
2489
+ * release gate's hold ceiling, the duplicate ledgers, the owed-cleanup
2490
+ * bound) is compared across time, so it must not follow the live
2491
+ * measurement.
2543
2492
  */
2544
2493
  #blockLifetimeMs(config) {
2545
2494
  const waitMs = this.#maxMulticastWaitMs(config);
@@ -2555,6 +2504,7 @@ var FECManager = class _FECManager {
2555
2504
  return Math.min(waitMs + this.#repairGraceMs(config, waitMs), this.#blockLifetimeMs(config));
2556
2505
  }
2557
2506
  #repairGraceMs(config, waitMs) {
2507
+ if (!_FECManager.#hasCatalogTiming(config)) return 0;
2558
2508
  return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs));
2559
2509
  }
2560
2510
  #armBlockTimeout(sbn, packetId, catalogTrackName, delayMs) {
@@ -2641,51 +2591,72 @@ var FECManager = class _FECManager {
2641
2591
  * stream; every other field stays per packetId.
2642
2592
  */
2643
2593
  #upsertTrackConfig(packetId, config, catalogTrackName) {
2644
- const previous = this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig();
2594
+ const previousTrack = this.#trackConfigs.get(packetId);
2595
+ const k = config.k ?? previousTrack?.k;
2596
+ if (k === void 0 || !Number.isSafeInteger(k) || k <= 0) {
2597
+ throw new Error(
2598
+ `[FECManager] packetId=${packetId}: fec.k=${String(k)} must be a positive integer; configure the track from its catalog`
2599
+ );
2600
+ }
2601
+ const previous = previousTrack ?? {};
2645
2602
  const streamScopedGeometry = catalogTrackName !== void 0 && config.geometry !== void 0 ? config.geometry : void 0;
2646
2603
  if (streamScopedGeometry !== void 0) {
2647
2604
  const { geometry: _streamGeometry, ...packetLevel } = config;
2648
2605
  config = packetLevel;
2649
2606
  }
2650
2607
  const timingChanged = config.k !== void 0 || config.interleaveDepthMs !== void 0 || config.fps !== void 0;
2608
+ if (config.timeoutMs !== void 0 && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
2609
+ throw new Error(
2610
+ `[FECManager] packetId=${packetId}: timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
2611
+ );
2612
+ }
2651
2613
  if (config.blockProfile !== void 0) {
2652
2614
  parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`);
2653
2615
  }
2654
2616
  const blockProfile = config.blockProfile ?? previous.blockProfile;
2655
2617
  if (blockProfile === void 0) {
2656
2618
  throw new Error(
2657
- `[FECManager] packetId=${packetId}: fec.blockProfile is required ("k-padded-v1" or "iso-ssbg1-v1"); configure the track from its catalog`
2619
+ `[FECManager] packetId=${packetId}: fec.blockProfile is required ("iso-ssbg1-v1"); configure the track from its catalog`
2658
2620
  );
2659
2621
  }
2660
- if (blockProfile === "iso-ssbg1-v1") {
2661
- const ownInterleaveDepthMs = config.interleaveDepthMs ?? this.#trackConfigs.get(packetId)?.interleaveDepthMs;
2662
- if (ownInterleaveDepthMs === void 0 || !Number.isFinite(ownInterleaveDepthMs) || ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
2663
- throw new Error(
2664
- `[FECManager] packetId=${packetId}: iso-ssbg1-v1 requires the catalog fec.interleaveDepthMs (>= ${MIN_INTERLEAVE_DEPTH_MS} ms; got ${String(ownInterleaveDepthMs)}): it bounds how long block location keeps stated blocks and held sources`
2665
- );
2666
- }
2622
+ const ownInterleaveDepthMs = config.interleaveDepthMs ?? previousTrack?.interleaveDepthMs ?? 0;
2623
+ if (!Number.isSafeInteger(ownInterleaveDepthMs) || ownInterleaveDepthMs !== 0 && ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
2624
+ throw new Error(
2625
+ `[FECManager] packetId=${packetId}: fec.interleaveDepthMs must be absent, 0, or a finite value >= ${MIN_INTERLEAVE_DEPTH_MS} ms (got ${String(ownInterleaveDepthMs)})`
2626
+ );
2667
2627
  }
2628
+ const hasCatalogTiming = _FECManager.#hasCatalogTiming({ interleaveDepthMs: ownInterleaveDepthMs });
2668
2629
  const next = {
2669
2630
  blockProfile,
2670
- k: config.k ?? previous.k,
2671
- symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize : previous.symbolSize,
2672
- interleaveDepthMs: config.interleaveDepthMs ?? previous.interleaveDepthMs,
2673
- fps: config.fps ?? previous.fps,
2674
- timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),
2631
+ k,
2632
+ symbolSize: config.symbolSize != null && config.symbolSize > 0 ? config.symbolSize : previousTrack?.symbolSize ?? 0,
2633
+ interleaveDepthMs: ownInterleaveDepthMs,
2634
+ fps: config.fps ?? previous.fps ?? this.#fecFps,
2635
+ timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs ?? 0),
2675
2636
  ...(config.trackKind ?? previous.trackKind) === void 0 ? {} : { trackKind: config.trackKind ?? previous.trackKind },
2676
2637
  ...(config.codec ?? previous.codec) === void 0 ? {} : { codec: config.codec ?? previous.codec },
2677
2638
  ...(config.geometry ?? previous.geometry) === void 0 ? {} : { geometry: config.geometry ?? previous.geometry }
2678
2639
  };
2679
- next.timeoutMs = this.#computeTimeout(next);
2640
+ if (!hasCatalogTiming) {
2641
+ next.timeoutMs = config.timeoutMs !== void 0 && config.timeoutMs > 0 ? config.timeoutMs : previousTrack !== void 0 && previousTrack.interleaveDepthMs === 0 ? previousTrack.timeoutMs : LOCAL_FEC_TIMEOUT_MS;
2642
+ } else {
2643
+ next.timeoutMs = this.#computeTimeout(next);
2644
+ }
2680
2645
  if (this.#multicastSymbolDelivery === "ordered-relay") {
2681
2646
  this.#orderedHorizonBlocksFor(packetId, next);
2682
2647
  }
2683
- if (previous.blockProfile !== void 0 && (previous.blockProfile !== blockProfile || blockProfile === "iso-ssbg1-v1" && previous.k !== next.k)) {
2648
+ const timingPolicyChanged = _FECManager.#hasCatalogTiming({ interleaveDepthMs: previous.interleaveDepthMs ?? 0 }) !== hasCatalogTiming;
2649
+ if (timingPolicyChanged && !hasCatalogTiming) {
2650
+ for (const key of [...this.#repairDeadlines.keys()]) {
2651
+ if (this.#belongsToPacket(key, packetId)) this.#clearRepairDeadline(key);
2652
+ }
2653
+ }
2654
+ if (previous.blockProfile !== void 0 && previous.k !== next.k) {
2684
2655
  this.#dropIsoLocators(packetId);
2685
2656
  }
2686
2657
  this.#trackConfigs.set(packetId, next);
2687
2658
  this.#refreshDeliveryGapWindow();
2688
- let policyChanged = config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
2659
+ let policyChanged = timingPolicyChanged || config.geometry !== void 0 && JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry) || config.trackKind !== void 0 && config.trackKind !== previous.trackKind;
2689
2660
  if (streamScopedGeometry !== void 0) {
2690
2661
  const streamKey = this.#streamKey(packetId, catalogTrackName);
2691
2662
  if (JSON.stringify(streamScopedGeometry) !== JSON.stringify(this.#streamGeometries.get(streamKey))) {
@@ -2700,20 +2671,14 @@ var FECManager = class _FECManager {
2700
2671
  if (!this.#belongsToPacket(key, packetId)) continue;
2701
2672
  const stream = this.#parseStreamKey(key);
2702
2673
  const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName);
2703
- if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
2674
+ if (hasCatalogTiming && JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue;
2704
2675
  this.#classifiers.delete(key);
2676
+ if (!hasCatalogTiming) continue;
2705
2677
  this.#applyClassifierChanges(stream.packetId, stream.catalogTrackName, classifier.retire());
2706
2678
  }
2707
2679
  }
2708
2680
  return next;
2709
2681
  }
2710
- #normalizeSymbolData(packetId, data) {
2711
- const symbolSize = this.symbolSizeForPacket(packetId);
2712
- if (symbolSize <= 0 || data.byteLength === symbolSize) return data;
2713
- const normalized = new Uint8Array(symbolSize);
2714
- normalized.set(data.subarray(0, symbolSize));
2715
- return normalized;
2716
- }
2717
2682
  #blockKeysFor(sbn, packetId, catalogTrackName) {
2718
2683
  if (packetId != null && catalogTrackName !== void 0) {
2719
2684
  return [this.#blockKey(sbn, packetId, catalogTrackName)];
@@ -2774,7 +2739,7 @@ var FECManager = class _FECManager {
2774
2739
  ...stats,
2775
2740
  packetId,
2776
2741
  blocksComplete: trackTotal,
2777
- k: this.#resolveTrackConfig(packetId).k,
2742
+ k: this.#trackConfigs.get(packetId)?.k,
2778
2743
  recoveryRate: trackAffected > 0 ? stats.blocksRecovered / trackAffected * 100 : 100,
2779
2744
  packetLossRate: trackTotal > 0 ? trackAffected / trackTotal * 100 : 0
2780
2745
  };
@@ -2786,7 +2751,7 @@ var FECManager = class _FECManager {
2786
2751
  (block) => block.multicastSymbols > 0 && block.moqSymbols > 0
2787
2752
  ).length,
2788
2753
  recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),
2789
- k: this.#fecK,
2754
+ k: this.#streamK,
2790
2755
  // True recovery rate: of blocks that HAD loss, how many were recovered?
2791
2756
  recoveryRate: affected > 0 ? s.blocksRecovered / affected * 100 : 100,
2792
2757
  packetLossRate: total > 0 ? affected / total * 100 : 0,
@@ -2799,7 +2764,6 @@ var FECManager = class _FECManager {
2799
2764
  },
2800
2765
  blocks: stats.blocks.map((block) => ({ ...block })),
2801
2766
  decoder: stats.decoder ? { ...stats.decoder } : void 0,
2802
- altaFailedDetails: stats.altaFailedDetails?.map((detail) => ({ ...detail })),
2803
2767
  packetId
2804
2768
  })),
2805
2769
  repairSymbolSizeMismatches: this.#repairSymbolSizeMismatchCount,
@@ -2906,6 +2870,7 @@ var FECManager = class _FECManager {
2906
2870
  resetMap(this.#blockSourceEsis);
2907
2871
  resetMap(this.#blockAuthoredLengths);
2908
2872
  resetMap(this.#blockRepairRsIds);
2873
+ resetMap(this.#blockRsbLengths);
2909
2874
  resetMap(this.#blockPathCounts);
2910
2875
  resetMap(this.#blockAnchorsMs);
2911
2876
  resetMap(this.#blockCatalogTrackNames);
@@ -3023,6 +2988,7 @@ var FECManager = class _FECManager {
3023
2988
  dropMap(this.#blockSourceEsis);
3024
2989
  dropMap(this.#blockAuthoredLengths);
3025
2990
  dropMap(this.#blockRepairRsIds);
2991
+ dropMap(this.#blockRsbLengths);
3026
2992
  dropMap(this.#blockPathCounts);
3027
2993
  dropMap(this.#blockAnchorsMs);
3028
2994
  dropMap(this.#blockCatalogTrackNames);
@@ -3081,15 +3047,20 @@ var FECManager = class _FECManager {
3081
3047
  }
3082
3048
  }
3083
3049
  }
3084
- /** Record a transport symbol and reject later duplicates of the same packetId/SBN/ESI. */
3050
+ /**
3051
+ * Record a transport symbol and reject later duplicates of the same
3052
+ * packetId/SBN/ESI. `sbn` is the block's SS_Start. A packetId with no
3053
+ * configured block profile has no block geometry: its symbol is refused
3054
+ * (false) and no block state is created for it.
3055
+ */
3085
3056
  feedSymbolFromSource(packetId, sbn, esi, _data, source, catalogTrackName) {
3057
+ if (!this.#hasBlockProfile(packetId)) return false;
3086
3058
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
3087
3059
  const streamKey = this.#streamKey(packetId, catalogTrackName);
3088
3060
  if (source === "multicast" && this.#multicastSymbolDelivery === "ordered-relay") {
3089
3061
  this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName);
3090
3062
  }
3091
- const k = this.kForPacket(packetId);
3092
- const sourceSymbols = this.#isIso(packetId) ? this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? k : k;
3063
+ const sourceSymbols = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? this.#trackConfig(packetId).k;
3093
3064
  const isSourceSymbol = esi >= 0 && esi < sourceSymbols;
3094
3065
  const isStartupPartial = this.#startupPartialBlocks.has(blockKey);
3095
3066
  const isReadyMulticastRepair = this.#startupRequireRepairReady && source === "multicast" && !isSourceSymbol && this.#startupRepairReadyPackets.has(streamKey);
@@ -3207,14 +3178,14 @@ var FECManager = class _FECManager {
3207
3178
  for (const [key] of this.#activeBlocks) {
3208
3179
  const { packetId, sbn } = this.#parseBlockKey(key);
3209
3180
  const count = this.#blockSourceCounts.get(key) ?? 0;
3210
- const k = this.#blockAuthoredLengths.get(key) ?? this.kForPacket(packetId);
3181
+ const k = this.#blockAuthoredLengths.get(key) ?? this.#trackConfig(packetId).k;
3211
3182
  blocks.push({ sbn, packetId, state: "collecting", sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });
3212
3183
  }
3213
3184
  const completed = [...this.#completedBlocks].slice(-16);
3214
3185
  for (const key of completed) {
3215
3186
  const { packetId, sbn } = this.#parseBlockKey(key);
3216
3187
  if (!this.#activeBlocks.has(key)) {
3217
- const k = this.kForPacket(packetId);
3188
+ const k = this.#trackConfig(packetId).k;
3218
3189
  blocks.push({ sbn, packetId, state: "complete", sourceCount: k, k, progress: 1 });
3219
3190
  }
3220
3191
  }
@@ -3225,25 +3196,23 @@ var FECManager = class _FECManager {
3225
3196
  get wasmDecoder() {
3226
3197
  return this.#fecDecoders.values().next().value?.client ?? null;
3227
3198
  }
3228
- get k() {
3229
- return this.#fecK;
3230
- }
3199
+ /** A configured packetId's K; undefined for an unconfigured one (no FEC work). */
3231
3200
  kForPacket(packetId) {
3232
- return this.#resolveTrackConfig(packetId).k;
3201
+ return this.#trackConfigs.get(packetId)?.k;
3233
3202
  }
3234
3203
  /** FEC symbol size in bytes. */
3235
3204
  get symbolSize() {
3236
3205
  return this.#fecSymbolSize;
3237
3206
  }
3238
3207
  symbolSizeForPacket(packetId) {
3239
- return this.#resolveTrackConfig(packetId).symbolSize;
3208
+ return this.#trackConfigs.get(packetId)?.symbolSize;
3240
3209
  }
3241
3210
  /** FEC block span in milliseconds (0 = no interleaving). */
3242
3211
  get interleaveDepthMs() {
3243
3212
  return this.#fecInterleaveDepthMs;
3244
3213
  }
3245
3214
  interleaveDepthMsForPacket(packetId) {
3246
- return this.#resolveTrackConfig(packetId).interleaveDepthMs;
3215
+ return this.#trackConfigs.get(packetId)?.interleaveDepthMs;
3247
3216
  }
3248
3217
  /**
3249
3218
  * Record one multicast MMTP packet arrival (every packet of the leg, FEC
@@ -3261,7 +3230,8 @@ var FECManager = class _FECManager {
3261
3230
  * plus the larger of the catalog tolerance and the measured delivery gap.
3262
3231
  */
3263
3232
  timeoutMsForPacket(packetId) {
3264
- return this.#multicastWaitMs(this.#resolveTrackConfig(packetId));
3233
+ const config = this.#trackConfigs.get(packetId);
3234
+ return config === void 0 ? void 0 : this.#multicastWaitMs(config);
3265
3235
  }
3266
3236
  /**
3267
3237
  * Largest multicast delivery gap measured over the current window (ms), and
@@ -3273,6 +3243,31 @@ var FECManager = class _FECManager {
3273
3243
  windowMs: this.#deliveryGaps.windowMs
3274
3244
  };
3275
3245
  }
3246
+ /**
3247
+ * An open block's accepted sources, distinct repair RS_IDs, authored K'
3248
+ * (SSB_length) and RSB_length (each undefined until a repair stated it).
3249
+ * Undefined for a block that is not open.
3250
+ */
3251
+ blockSymbolCounts(sbn, packetId, catalogTrackName) {
3252
+ const key = this.#blockKey(sbn, packetId, catalogTrackName);
3253
+ if (!this.#activeBlocks.has(key)) return void 0;
3254
+ return {
3255
+ sources: this.#blockSourceCounts.get(key) ?? 0,
3256
+ repairs: this.#blockRepairRsIds.get(key)?.size ?? 0,
3257
+ authoredLength: this.#blockAuthoredLengths.get(key),
3258
+ repairGroupLength: this.#blockRsbLengths.get(key)
3259
+ };
3260
+ }
3261
+ /**
3262
+ * Record a block's RSB_length from one of its repair symbols (the router
3263
+ * parses it from the Repair FEC Payload ID). The largest stated value wins.
3264
+ */
3265
+ noteRepairGroupLength(sbn, packetId, rsbLength, catalogTrackName) {
3266
+ if (!Number.isSafeInteger(rsbLength) || rsbLength <= 0) return;
3267
+ const key = this.#blockKey(sbn, packetId, catalogTrackName);
3268
+ if (!this.#activeBlocks.has(key)) return;
3269
+ if ((this.#blockRsbLengths.get(key) ?? 0) < rsbLength) this.#blockRsbLengths.set(key, rsbLength);
3270
+ }
3276
3271
  /**
3277
3272
  * Receiver-local time of a block's first accepted symbol, or undefined for
3278
3273
  * a block no symbol reached (one materialized because a later SBN arrived).
@@ -3317,27 +3312,31 @@ var FECManager = class _FECManager {
3317
3312
  }
3318
3313
  /** Resolved per-track D-28 repair request deadline in milliseconds. */
3319
3314
  repairDeadlineMsForPacket(packetId) {
3320
- return this.#repairDeadlineMs(this.#resolveTrackConfig(packetId));
3315
+ const config = this.#trackConfigs.get(packetId);
3316
+ return config === void 0 ? void 0 : this.#repairDeadlineMs(config);
3321
3317
  }
3322
3318
  /**
3323
3319
  * One block lifetime: the longest a block stays open after its last symbol
3324
3320
  * (inactivity fail bound plus D-28 repair grace).
3325
3321
  */
3326
3322
  blockLifetimeMsForPacket(packetId) {
3327
- return this.#blockLifetimeMs(this.#resolveTrackConfig(packetId));
3323
+ const config = this.#trackConfigs.get(packetId);
3324
+ return config === void 0 ? void 0 : this.#blockLifetimeMs(config);
3328
3325
  }
3329
3326
  /**
3330
3327
  * How long a terminal (clean, recovered or failed) block stays authoritative,
3331
3328
  * for late symbols and for the in-order release gate. It is also the gate's
3332
3329
  * hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as
3333
3330
  * untracked, which the gate counts as lost, so no frame may still be waiting
3334
- * on a block that can retire. The window is one interleave depth for a gap's
3335
- * last block to finish arriving, one block lifetime to fail it, and one more
3336
- * for an owed Worker cleanup that can keep a failing block pending. Every
3337
- * term comes from the track's catalog geometry; there is no separate cap.
3331
+ * on a block that can retire. With a positive catalog span the window is one
3332
+ * interleave depth for a gap's last block to finish arriving, one block
3333
+ * lifetime to fail it, and one more for an owed Worker cleanup. With an
3334
+ * absent/zero span it is a receiver-local cleanup bound only; it creates no
3335
+ * catalog FEC deadline.
3338
3336
  */
3339
3337
  terminalRetentionMsForPacket(packetId) {
3340
- const config = this.#resolveTrackConfig(packetId);
3338
+ const config = this.#trackConfigs.get(packetId);
3339
+ if (config === void 0) return void 0;
3341
3340
  return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
3342
3341
  }
3343
3342
  /** Remaining fetch budget for a specific repair-requested block. */
@@ -3348,7 +3347,8 @@ var FECManager = class _FECManager {
3348
3347
  if (deadlineMs !== void 0) {
3349
3348
  return Math.max(1, Math.ceil(deadlineMs - performance.now()));
3350
3349
  }
3351
- const trackConfig = this.#resolveTrackConfig(packetId);
3350
+ const trackConfig = this.#trackConfigs.get(packetId);
3351
+ if (trackConfig === void 0) return void 0;
3352
3352
  return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig));
3353
3353
  }
3354
3354
  /** True once configureTrack/setFecDecoder/setFecWorker configured this packetId. */
@@ -3359,14 +3359,19 @@ var FECManager = class _FECManager {
3359
3359
  blockProfileForPacket(packetId) {
3360
3360
  return this.#trackConfigs.get(packetId)?.blockProfile;
3361
3361
  }
3362
- #isIso(packetId) {
3363
- return this.#trackConfigs.get(packetId)?.blockProfile === "iso-ssbg1-v1";
3362
+ /**
3363
+ * Whether a packetId has a configured block geometry. iso-ssbg1-v1 is the
3364
+ * only profile, so every configured track has it; a packetId never
3365
+ * configured (or released) has none and gets no FEC block tracking.
3366
+ */
3367
+ #hasBlockProfile(packetId) {
3368
+ return this.#trackConfigs.get(packetId)?.blockProfile !== void 0;
3364
3369
  }
3365
3370
  #isoLocatorFor(packetId, catalogTrackName) {
3366
3371
  const streamKey = this.#streamKey(packetId, catalogTrackName);
3367
3372
  let locator = this.#isoLocators.get(streamKey);
3368
3373
  if (!locator) {
3369
- locator = new IsoBlockLocator(this.kForPacket(packetId), this.#isoRetentionBlocks(packetId));
3374
+ locator = new IsoBlockLocator(this.#trackConfig(packetId).k, this.#isoRetentionBlocks(packetId));
3370
3375
  this.#isoLocators.set(streamKey, locator);
3371
3376
  }
3372
3377
  return locator;
@@ -3397,12 +3402,14 @@ var FECManager = class _FECManager {
3397
3402
  */
3398
3403
  #isoRetentionBlocks(packetId) {
3399
3404
  const config = this.#trackConfigs.get(packetId);
3400
- if (config?.blockProfile !== "iso-ssbg1-v1" || !(config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS)) {
3405
+ if (config?.blockProfile === void 0) {
3401
3406
  throw new Error(
3402
- `[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block span to derive block-location retention from`
3407
+ `[FECManager] packetId=${packetId} has no configured iso-ssbg1-v1 block profile`
3403
3408
  );
3404
3409
  }
3405
- const blocks = Math.ceil(this.terminalRetentionMsForPacket(packetId) / config.interleaveDepthMs) + 1;
3410
+ if (!_FECManager.#hasCatalogTiming(config)) return LOCAL_FEC_RETENTION_BLOCKS;
3411
+ const terminalRetentionMs = config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config);
3412
+ const blocks = Math.ceil(terminalRetentionMs / config.interleaveDepthMs) + 1;
3406
3413
  if (!Number.isSafeInteger(blocks) || blocks <= 0) {
3407
3414
  throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`);
3408
3415
  }
@@ -3410,7 +3417,8 @@ var FECManager = class _FECManager {
3410
3417
  }
3411
3418
  /** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */
3412
3419
  isoSourceFits(packetId, packetLength) {
3413
- return ssbg1FitsSymbol(packetLength, this.symbolSizeForPacket(packetId));
3420
+ const symbolSize = this.#trackConfigs.get(packetId)?.symbolSize;
3421
+ return symbolSize !== void 0 && ssbg1FitsSymbol(packetLength, symbolSize);
3414
3422
  }
3415
3423
  /**
3416
3424
  * Locate an iso-ssbg1-v1 source by range membership in a block a repair
@@ -3471,11 +3479,11 @@ var FECManager = class _FECManager {
3471
3479
  * that could hold them is pending or not yet stated by a repair, else the
3472
3480
  * starts of the blocks that failed or vanished.
3473
3481
  *
3474
- * `untracked` reads exactly as in the k-padded-v1 release gate
3475
- * (MmtpRouter #createFecReleaseGate): a block holding either released
3476
- * frame's SS_ID (`fromSsId` or `toSsId`) delivered that frame, so untracked
3477
- * there means it resolved and retired; a block starting after the stream's frontier (`highestSbn`,
3478
- * an SS_Start here) may still be heard from; a block at or behind the
3482
+ * `untracked` reads as the release gate reads it (MmtpRouter
3483
+ * #createFecReleaseGate): a block holding either released frame's SS_ID
3484
+ * (`fromSsId` or `toSsId`) delivered that frame, so untracked there means it
3485
+ * resolved and retired; a block starting after the stream's frontier
3486
+ * (`highestSbn`, an SS_Start) may still be heard from; a block at or behind the
3479
3487
  * frontier that no symbol of this manager ever reached vanished, and its
3480
3488
  * media is lost.
3481
3489
  */
@@ -3543,6 +3551,7 @@ var FECManager = class _FECManager {
3543
3551
  }
3544
3552
  /** Current classifier mode of a stream. */
3545
3553
  classifierModeFor(packetId, catalogTrackName) {
3554
+ if (!this.#hasCatalogTimingForPacket(packetId)) return { mode: "all-protected", reason: null };
3546
3555
  const classifier = this.#classifierFor(packetId, catalogTrackName);
3547
3556
  return { mode: classifier.mode, reason: classifier.reason };
3548
3557
  }
@@ -3552,10 +3561,12 @@ var FECManager = class _FECManager {
3552
3561
  * it does not. A stream with no classifier yet has shown nothing against it.
3553
3562
  */
3554
3563
  mpuBoundsRapsFor(packetId, catalogTrackName) {
3564
+ if (!this.#hasCatalogTimingForPacket(packetId)) return false;
3555
3565
  return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true;
3556
3566
  }
3557
3567
  /** A block's class now, or frozen at its resolution. */
3558
3568
  blockClassFor(sbn, packetId, catalogTrackName) {
3569
+ if (!this.#hasCatalogTimingForPacket(packetId)) return "unknown";
3559
3570
  const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName));
3560
3571
  if (frozen !== void 0) return frozen;
3561
3572
  return this.#peekClass(sbn, packetId, catalogTrackName);
@@ -3577,7 +3588,7 @@ var FECManager = class _FECManager {
3577
3588
  * invariant (consecutive MPUs exactly one keyframe interval apart).
3578
3589
  */
3579
3590
  noteReleasedRap(packetId, catalogTrackName, mpu, presentationTime, mediaTimescale) {
3580
- if (!this.#trackConfigs.has(packetId)) return;
3591
+ if (!this.#trackConfigs.has(packetId) || !this.#hasCatalogTimingForPacket(packetId)) return;
3581
3592
  this.#applyClassifierChanges(
3582
3593
  packetId,
3583
3594
  catalogTrackName,
@@ -3593,27 +3604,34 @@ var FECManager = class _FECManager {
3593
3604
  // -----------------------------------------------------------------------
3594
3605
  /** Configure FEC parameters. */
3595
3606
  configure(config) {
3596
- if (config.k != null) this.#fecK = config.k;
3607
+ if (config.interleaveDepthMs != null && (!Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < 0)) {
3608
+ throw new Error(
3609
+ `[FECManager] interleaveDepthMs=${config.interleaveDepthMs} must be a non-negative safe integer in milliseconds`
3610
+ );
3611
+ }
3612
+ if (config.timeoutMs != null && (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)) {
3613
+ throw new Error(
3614
+ `[FECManager] timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`
3615
+ );
3616
+ }
3617
+ if (config.k != null && (!Number.isSafeInteger(config.k) || config.k <= 0)) {
3618
+ throw new Error(`[FECManager] k=${config.k} must be a positive integer`);
3619
+ }
3620
+ if (config.k != null) this.#streamK = config.k;
3597
3621
  if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize;
3598
3622
  if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs;
3599
3623
  if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {
3600
3624
  this.#fecFps = config.fps;
3601
3625
  }
3602
- if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
3603
- console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (\xA75.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`);
3604
- }
3605
3626
  if (config.timeoutMs != null && config.timeoutMs > 0) {
3606
3627
  this.#fecTimeoutMs = config.timeoutMs;
3607
- } else if (config.interleaveDepthMs != null && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
3608
- this.#fecTimeoutMs = computeFecDeliveryWindowMs({
3609
- interleaveDepthMs: config.interleaveDepthMs,
3610
- sourceSymbols: this.#fecK,
3611
- symbolSize: this.#fecSymbolSize,
3612
- mediaUnitsPerSecond: config.fps
3613
- });
3628
+ } else if (config.interleaveDepthMs === 0) {
3629
+ this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS;
3630
+ } else if (config.interleaveDepthMs != null && Number.isSafeInteger(config.interleaveDepthMs) && config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
3631
+ this.#fecTimeoutMs = fecSpanDeliveryWindowMs(config.interleaveDepthMs);
3614
3632
  }
3615
3633
  for (const [packetId, track] of this.#trackConfigs) {
3616
- if (track.timeoutMs < this.#fecTimeoutMs) {
3634
+ if (_FECManager.#hasCatalogTiming(track) && track.timeoutMs < this.#fecTimeoutMs) {
3617
3635
  this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs });
3618
3636
  }
3619
3637
  }
@@ -3672,7 +3690,7 @@ var FECManager = class _FECManager {
3672
3690
  );
3673
3691
  }
3674
3692
  if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName);
3675
- const workerK = this.#resolveTrackConfig(packetId).k;
3693
+ const workerK = this.#trackConfig(packetId).k;
3676
3694
  this.#workerClients.set(streamKey, client);
3677
3695
  this.#releasedWorkerClients.delete(client);
3678
3696
  client.onFrame((data, meta) => {
@@ -3852,7 +3870,7 @@ var FECManager = class _FECManager {
3852
3870
  return;
3853
3871
  }
3854
3872
  this.#settleOwedCleanup(key, "cleanup-ack-timeout");
3855
- }, Math.ceil(this.#liveBlockLifetimeMs(this.#resolveTrackConfig(packetId))))
3873
+ }, Math.ceil(this.#liveBlockLifetimeMs(this.#trackConfig(packetId))))
3856
3874
  };
3857
3875
  this.#pendingWorkerCleanupBounds.set(key, bound);
3858
3876
  }
@@ -3893,16 +3911,11 @@ var FECManager = class _FECManager {
3893
3911
  if (this.#fpsDetected || fps <= 0) return;
3894
3912
  this.#fpsDetected = true;
3895
3913
  this.#fecFps = fps;
3896
- if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
3897
- this.#fecTimeoutMs = computeFecDeliveryWindowMs({
3898
- interleaveDepthMs: this.#fecInterleaveDepthMs,
3899
- sourceSymbols: this.#fecK,
3900
- symbolSize: this.#fecSymbolSize,
3901
- mediaUnitsPerSecond: fps
3902
- });
3914
+ if (Number.isSafeInteger(this.#fecInterleaveDepthMs) && this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
3915
+ this.#fecTimeoutMs = fecSpanDeliveryWindowMs(this.#fecInterleaveDepthMs);
3903
3916
  }
3904
3917
  for (const [packetId, config] of this.#trackConfigs) {
3905
- if (config.fps !== void 0 || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
3918
+ if (config.fps !== void 0 || !Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue;
3906
3919
  this.#trackConfigs.set(packetId, {
3907
3920
  ...config,
3908
3921
  fps,
@@ -3931,51 +3944,33 @@ var FECManager = class _FECManager {
3931
3944
  * Caller is responsible for routing recovered data and updating
3932
3945
  * block completion state via `markBlockCompleted()`.
3933
3946
  */
3934
- feedSourceSymbol(packetId, ssId, symbolData, alta, catalogTrackName) {
3947
+ feedSourceSymbol(packetId, ssId, symbolData, catalogTrackName) {
3935
3948
  this.#fecStats.sourcePackets++;
3936
3949
  this.#statsForPacket(packetId).sourcePackets++;
3937
- let sbn;
3938
- let fecData;
3939
- if (this.#isIso(packetId)) {
3940
- const locator = this.#isoLocatorFor(packetId, catalogTrackName);
3941
- const located = locator.locate(ssId);
3942
- if (located === null) {
3943
- throw new Error(
3944
- `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
3945
- );
3946
- }
3947
- locator.observe(ssId);
3948
- if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {
3949
- this.#isoSourceOversizeCount++;
3950
- return null;
3951
- }
3952
- sbn = located.block.start;
3953
- fecData = symbolData;
3954
- } else {
3955
- fecData = this.#normalizeSymbolData(packetId, symbolData);
3956
- sbn = Math.floor(ssId / this.kForPacket(packetId));
3950
+ if (!this.#hasBlockProfile(packetId)) return null;
3951
+ const locator = this.#isoLocatorFor(packetId, catalogTrackName);
3952
+ const located = locator.locate(ssId);
3953
+ if (located === null) {
3954
+ throw new Error(
3955
+ `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; hold it until a repair places it (placeIsoRepair)`
3956
+ );
3957
3957
  }
3958
+ locator.observe(ssId);
3959
+ if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) {
3960
+ this.#isoSourceOversizeCount++;
3961
+ return null;
3962
+ }
3963
+ const sbn = located.block.start;
3958
3964
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
3959
3965
  const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey);
3960
3966
  const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName);
3961
3967
  if (workerClient && !syncBlockOwner) {
3962
3968
  if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null;
3963
- const buf = fecData !== symbolData ? fecData.buffer : fecData.buffer.slice(
3964
- fecData.byteOffset,
3965
- fecData.byteOffset + fecData.byteLength
3969
+ const buf = symbolData.buffer.slice(
3970
+ symbolData.byteOffset,
3971
+ symbolData.byteOffset + symbolData.byteLength
3966
3972
  );
3967
- if (alta) {
3968
- const authBuf = alta.auth.buffer.slice(
3969
- alta.auth.byteOffset,
3970
- alta.auth.byteOffset + alta.auth.byteLength
3971
- );
3972
- workerClient.feedSource(ssId, buf, performance.now(), {
3973
- auth: authBuf,
3974
- payloadEnd: alta.payloadEnd
3975
- });
3976
- } else {
3977
- workerClient.feedSource(ssId, buf, performance.now());
3978
- }
3973
+ workerClient.feedSource(ssId, buf, performance.now());
3979
3974
  return null;
3980
3975
  }
3981
3976
  const entry = this.#decoderForPacket(packetId, catalogTrackName);
@@ -3985,7 +3980,7 @@ var FECManager = class _FECManager {
3985
3980
  this.#syncDecoderBlockOwners.set(blockKey, client);
3986
3981
  let recovered = null;
3987
3982
  try {
3988
- recovered = client.add_source(ssId, fecData, performance.now());
3983
+ recovered = client.add_source(ssId, symbolData, performance.now());
3989
3984
  } catch {
3990
3985
  }
3991
3986
  this.#retireIsoUnplaced(client, packetId, catalogTrackName);
@@ -4004,7 +3999,8 @@ var FECManager = class _FECManager {
4004
3999
  feedRepairSymbol(packetId, ssStart, ssbLength, rsId, repairData, catalogTrackName) {
4005
4000
  this.#fecStats.repairPackets++;
4006
4001
  this.#statsForPacket(packetId).repairPackets++;
4007
- const symbolSize = this.symbolSizeForPacket(packetId);
4002
+ if (!this.#hasBlockProfile(packetId)) return null;
4003
+ const symbolSize = this.#trackConfig(packetId).symbolSize;
4008
4004
  if (symbolSize > 0 && repairData.byteLength !== symbolSize) {
4009
4005
  this.#repairSymbolSizeMismatchCount++;
4010
4006
  if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {
@@ -4015,18 +4011,12 @@ var FECManager = class _FECManager {
4015
4011
  return null;
4016
4012
  }
4017
4013
  const fecData = repairData;
4018
- const k = this.kForPacket(packetId);
4019
- let sbn;
4020
- if (this.#isIso(packetId)) {
4021
- const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart);
4022
- if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
4023
- return null;
4024
- }
4025
- sbn = ssStart;
4026
- } else {
4027
- if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
4028
- sbn = Math.floor(ssStart / k);
4014
+ const k = this.#trackConfig(packetId).k;
4015
+ const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart);
4016
+ if (ssbLength <= 0 || ssbLength > k || block === void 0 || block.span || block.length !== ssbLength) {
4017
+ return null;
4029
4018
  }
4019
+ const sbn = ssStart;
4030
4020
  this.#noteSbnSeen(sbn, packetId, catalogTrackName, "repair");
4031
4021
  this.noteBlockActivity(sbn, packetId, catalogTrackName);
4032
4022
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName);
@@ -4064,8 +4054,9 @@ var FECManager = class _FECManager {
4064
4054
  * Record a block's authored SSB_length from a repair symbol. Returns false,
4065
4055
  * leaving the block unchanged, when the symbol contradicts the block: a
4066
4056
  * different SSB_length than an earlier repair symbol declared is dropped,
4067
- * and an SSB_length that places an already-received source ESI inside the
4068
- * zero padding fails the block, since its geometry can no longer be trusted.
4057
+ * and an SSB_length that places an already-received source ESI at or past
4058
+ * the block's end fails the block, since its geometry can no longer be
4059
+ * trusted.
4069
4060
  */
4070
4061
  #noteAuthoredLength(blockKey, sbn, packetId, ssbLength, catalogTrackName) {
4071
4062
  const known = this.#blockAuthoredLengths.get(blockKey);
@@ -4132,7 +4123,7 @@ var FECManager = class _FECManager {
4132
4123
  * a sweep tied to expiry alone would never run there.
4133
4124
  */
4134
4125
  #retireIsoUnplaced(decoder, packetId, catalogTrackName) {
4135
- if (!this.#isIso(packetId)) return;
4126
+ if (!this.#hasBlockProfile(packetId)) return;
4136
4127
  const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest;
4137
4128
  if (newest === void 0) return;
4138
4129
  try {
@@ -4177,11 +4168,13 @@ var FECManager = class _FECManager {
4177
4168
  this.#failBlock(sbn, packetId, catalogTrackName);
4178
4169
  return;
4179
4170
  }
4180
- this.#applyClassifierChanges(
4181
- packetId,
4182
- catalogTrackName,
4183
- this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence)
4184
- );
4171
+ if (_FECManager.#hasCatalogTiming(trackConfig)) {
4172
+ this.#applyClassifierChanges(
4173
+ packetId,
4174
+ catalogTrackName,
4175
+ this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence)
4176
+ );
4177
+ }
4185
4178
  const count = (this.#blockSourceCounts.get(key) ?? 0) + 1;
4186
4179
  this.#blockSourceCounts.set(key, count);
4187
4180
  if (count === 1) this.#indexBlockKey(key, sbn, packetId);
@@ -4214,6 +4207,7 @@ var FECManager = class _FECManager {
4214
4207
  this.#blockSourceEsis.delete(key);
4215
4208
  this.#blockAuthoredLengths.delete(key);
4216
4209
  this.#blockRepairRsIds.delete(key);
4210
+ this.#blockRsbLengths.delete(key);
4217
4211
  this.#markTerminal(key, sbn, packetId, true);
4218
4212
  this.#markPathBlockComplete(sbn, packetId, catalogTrackName);
4219
4213
  if (catalogTrackName === void 0) {
@@ -4225,6 +4219,7 @@ var FECManager = class _FECManager {
4225
4219
  }
4226
4220
  /** Refresh deadlines after accepted block activity. */
4227
4221
  noteBlockActivity(sbn, packetId, catalogTrackName, sourceProgress = false) {
4222
+ if (!this.#trackConfigs.has(packetId)) return;
4228
4223
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
4229
4224
  if (this.#completedBlocks.has(key)) return;
4230
4225
  if (this.#pendingWorkerCleanups.has(key)) return;
@@ -4234,13 +4229,13 @@ var FECManager = class _FECManager {
4234
4229
  this.#holdForOrderedSettlement(key, sbn, packetId, catalogTrackName);
4235
4230
  return;
4236
4231
  }
4237
- const trackConfig = this.#resolveTrackConfig(packetId);
4232
+ const trackConfig = this.#trackConfig(packetId);
4238
4233
  const timeoutMs = this.#multicastWaitMs(trackConfig);
4239
4234
  if (!(timeoutMs > 0)) {
4240
4235
  throw new Error(`[FECManager] packetId=${packetId} has no positive fail bound (timeoutMs=${timeoutMs})`);
4241
4236
  }
4242
4237
  const repairDeadline = this.#repairDeadlines.get(key);
4243
- if (this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
4238
+ if (this.#hasCatalogTimingForPacket(packetId) && this.onRepairNeeded && !repairRequested && (repairDeadline === void 0 || sourceProgress)) {
4244
4239
  this.#armRepairDeadline(
4245
4240
  key,
4246
4241
  sbn,
@@ -4264,6 +4259,7 @@ var FECManager = class _FECManager {
4264
4259
  this.#blockSourceEsis.delete(key);
4265
4260
  this.#blockAuthoredLengths.delete(key);
4266
4261
  this.#blockRepairRsIds.delete(key);
4262
+ this.#blockRsbLengths.delete(key);
4267
4263
  this.#syncDecoderBlockOwners.delete(key);
4268
4264
  this.#markTerminal(key, sbn, packetId, false);
4269
4265
  this.#failedBlocks.add(key);
@@ -4301,6 +4297,7 @@ var FECManager = class _FECManager {
4301
4297
  this.#blockSourceEsis.delete(key);
4302
4298
  this.#blockAuthoredLengths.delete(key);
4303
4299
  this.#blockRepairRsIds.delete(key);
4300
+ this.#blockRsbLengths.delete(key);
4304
4301
  this.#clearBlockTimer(key);
4305
4302
  this.onBlockResolved?.(block.sbn, block.packetId, block.catalogTrackName);
4306
4303
  }
@@ -4310,6 +4307,7 @@ var FECManager = class _FECManager {
4310
4307
  }
4311
4308
  /** Mark a block as completed externally (e.g., after routing recovered data). */
4312
4309
  markBlockCompleted(sbn, packetId, catalogTrackName) {
4310
+ if (packetId != null && !this.#trackConfigs.has(packetId)) return;
4313
4311
  for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {
4314
4312
  this.#resolveCompleted(key);
4315
4313
  }
@@ -4390,6 +4388,7 @@ var FECManager = class _FECManager {
4390
4388
  this.#blockSourceEsis.clear();
4391
4389
  this.#blockAuthoredLengths.clear();
4392
4390
  this.#blockRepairRsIds.clear();
4391
+ this.#blockRsbLengths.clear();
4393
4392
  this.#completedBlocks.clear();
4394
4393
  this.#cleanlyCompletedBlocks.clear();
4395
4394
  this.#repairRequestedBlocks.clear();
@@ -8526,7 +8525,6 @@ var CmafBlockAssembler = class {
8526
8525
  failedBlocks: 0
8527
8526
  };
8528
8527
  this.#config = {
8529
- k: config.k,
8530
8528
  timescale: config.timescale ?? 1e6,
8531
8529
  trackId: config.trackId ?? 1,
8532
8530
  trackType: config.trackType,
@@ -10627,6 +10625,42 @@ var FecReleaseSequencer = class {
10627
10625
  for (const stream of this.#streams.values()) total += stream.buf.length;
10628
10626
  return total;
10629
10627
  }
10628
+ #deferDepth = 0;
10629
+ #deferredStreams = /* @__PURE__ */ new Set();
10630
+ /**
10631
+ * Run `work` with the drains its pushes would run deferred to its end. A
10632
+ * recovered FEC block is routed one frame at a time, and its block is
10633
+ * already resolved when the first frame is pushed: draining on that push
10634
+ * would release past the gap before the block's other recovered frames
10635
+ * arrive (deciding a conceal or stall for media that is about to land),
10636
+ * and those frames would then be dropped as stale. Nests.
10637
+ */
10638
+ defer(work) {
10639
+ this.#deferDepth++;
10640
+ let threw = true;
10641
+ try {
10642
+ work();
10643
+ threw = false;
10644
+ } finally {
10645
+ this.#deferDepth--;
10646
+ if (this.#deferDepth === 0 && this.#deferredStreams.size > 0) {
10647
+ const streams = [...this.#deferredStreams];
10648
+ this.#deferredStreams.clear();
10649
+ if (!threw) {
10650
+ for (const stream of streams) this.#drain(stream);
10651
+ } else {
10652
+ try {
10653
+ for (const stream of streams) this.#drain(stream);
10654
+ } catch {
10655
+ }
10656
+ }
10657
+ }
10658
+ }
10659
+ }
10660
+ #drainUnlessDeferred(stream) {
10661
+ if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
10662
+ else this.#drain(stream);
10663
+ }
10630
10664
  push(streamKey, data, meta, seq) {
10631
10665
  const frame = { data, meta: { ...meta }, seq, arrivedAtMs: performance.now() };
10632
10666
  const stream = this.#streamFor(streamKey, meta);
@@ -10648,7 +10682,7 @@ var FecReleaseSequencer = class {
10648
10682
  return;
10649
10683
  }
10650
10684
  this.#release(stream, frame, void 0);
10651
- this.#drain(stream);
10685
+ this.#drainUnlessDeferred(stream);
10652
10686
  return;
10653
10687
  }
10654
10688
  let lo = 0;
@@ -10659,7 +10693,23 @@ var FecReleaseSequencer = class {
10659
10693
  else hi = mid;
10660
10694
  }
10661
10695
  stream.buf.splice(lo, 0, frame);
10662
- this.#drain(stream);
10696
+ this.#drainUnlessDeferred(stream);
10697
+ }
10698
+ /**
10699
+ * Arrival time of the earliest frame a stream of `packetId` holds behind a
10700
+ * gap (the gate forces it out `holdLimitMs` after that), or undefined when
10701
+ * none is held. Without `catalogTrackName`, every stream of the packetId.
10702
+ */
10703
+ earliestHeldArrivalMs(packetId, catalogTrackName) {
10704
+ let earliest;
10705
+ for (const stream of this.#streams.values()) {
10706
+ if (stream.packetId !== packetId) continue;
10707
+ if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
10708
+ for (const frame of stream.buf) {
10709
+ if (earliest === void 0 || frame.arrivedAtMs < earliest) earliest = frame.arrivedAtMs;
10710
+ }
10711
+ }
10712
+ return earliest;
10663
10713
  }
10664
10714
  /** Re-evaluate held gaps after FEC state changed. */
10665
10715
  poke(streamKey) {
@@ -10712,6 +10762,21 @@ var FecReleaseSequencer = class {
10712
10762
  this.#streams.delete(key);
10713
10763
  }
10714
10764
  }
10765
+ /**
10766
+ * The packet's FEC ended, so no block will resolve a held gap: release its
10767
+ * frames in order, each gap stalled as a discontinuity, and forget its
10768
+ * streams, so FEC configured on it again starts from a fresh one.
10769
+ */
10770
+ endPacket(packetId) {
10771
+ for (const [key, stream] of this.#streams) {
10772
+ if (stream.packetId !== packetId) continue;
10773
+ while (stream.buf.length > 0 && stream.last !== null) {
10774
+ stream.discontinuity = true;
10775
+ this.#drain(stream);
10776
+ }
10777
+ this.#streams.delete(key);
10778
+ }
10779
+ }
10715
10780
  discardPacket(packetId) {
10716
10781
  for (const [key, stream] of this.#streams) {
10717
10782
  if (stream.packetId !== packetId) continue;
@@ -11080,6 +11145,22 @@ var MmtpContainerParser = class _MmtpContainerParser {
11080
11145
  }
11081
11146
  ) : null;
11082
11147
  }
11148
+ /**
11149
+ * Run `work` with the FEC release gate's drains deferred to its end, so
11150
+ * every frame it pushes is in place before any held gap is decided (see
11151
+ * FecReleaseSequencer.defer).
11152
+ */
11153
+ deferFecRelease(work) {
11154
+ if (!this.#fecSequencer) {
11155
+ work();
11156
+ return;
11157
+ }
11158
+ this.#fecSequencer.defer(work);
11159
+ }
11160
+ /** See FecReleaseSequencer.earliestHeldArrivalMs. */
11161
+ fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
11162
+ return this.#fecSequencer?.earliestHeldArrivalMs(packetId, catalogTrackName);
11163
+ }
11083
11164
  /** Re-evaluate held gaps after FEC block state changed. */
11084
11165
  pokeFecRelease(packetId, catalogTrackName) {
11085
11166
  if (!this.#fecSequencer) return;
@@ -11094,6 +11175,13 @@ var MmtpContainerParser = class _MmtpContainerParser {
11094
11175
  markFecDiscontinuity(packetId, catalogTrackName) {
11095
11176
  this.#fecSequencer?.markDiscontinuity(this.#streamKey(packetId, catalogTrackName));
11096
11177
  }
11178
+ /**
11179
+ * The FEC of a packet ID ended: release the frames the FEC release gate
11180
+ * holds for it in order, each gap stalled, and forget its gated streams.
11181
+ */
11182
+ endFecRelease(packetId) {
11183
+ this.#fecSequencer?.endPacket(packetId);
11184
+ }
11097
11185
  /**
11098
11186
  * MPU and data-unit identity of one MMTP packet, parsed in place (no copy,
11099
11187
  * no reassembly). Null for non-MPU packets and packets that do not parse.
@@ -11274,7 +11362,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
11274
11362
  const parsed = preParsed !== void 0 && preParsed.skipFecPayloadId === (options?.skipFecPayloadId === true) ? preParsed : this.parsePacket(packet, options);
11275
11363
  if (!parsed) return this.#drop("header-parse-failed");
11276
11364
  const info = parsed.info;
11277
- if (info.packetType !== PACKET_TYPE_MPU2) {
11365
+ if (info.packetType !== PACKET_TYPE_MPU) {
11278
11366
  return {
11279
11367
  kind: info.isRepair ? "repair" : "non-media",
11280
11368
  packet: info,
@@ -11283,10 +11371,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
11283
11371
  }
11284
11372
  if (info.fecPayloadId !== null && this.#sourceSymbolCb) {
11285
11373
  const rawSymbol = packet.subarray(0, packet.length - 4);
11286
- const altaTrailer = extractAltaTrailer2(packet);
11287
- const alta = altaTrailer ? { auth: altaTrailer.auth, payloadEnd: altaTrailer.signedEnd } : void 0;
11288
11374
  try {
11289
- this.#sourceSymbolCb(info.packetId, info.fecPayloadId.ssId, rawSymbol, alta);
11375
+ this.#sourceSymbolCb(info.packetId, info.fecPayloadId.ssId, rawSymbol);
11290
11376
  } catch {
11291
11377
  }
11292
11378
  }
@@ -12411,6 +12497,8 @@ var MmtpRouter = class {
12411
12497
  // ── Keyframe loss detection state ───────────────────────────────
12412
12498
  /** Catalog-derived keyframe loss timeout (ms), or a reader evaluated at each arm. null = not configured (warns on video track registration; Phase-13 keyframe repair stays off). */
12413
12499
  #keyframeLostTimeoutMs;
12500
+ /** packetIds already warned for running without Phase 13 (non-FEC video only). */
12501
+ #warnedNoKeyframeLostTimeout = /* @__PURE__ */ new Set();
12414
12502
  /** Why the last reader evaluation left the timer unarmed; null while arming. Dedups the warning. */
12415
12503
  #keyframeLostUnarmedReason = null;
12416
12504
  /** Active keyframe loss timer handle. Single timer per video track — cleared on each rapFlag and on dispose(). */
@@ -12426,6 +12514,8 @@ var MmtpRouter = class {
12426
12514
  */
12427
12515
  #onPublisherRestart;
12428
12516
  #publisherRestarts = 0;
12517
+ /** Inits delivered with no onTrackInit handler for their packetId (MOQT-01). */
12518
+ #unmappedInitDeliveries = 0;
12429
12519
  // FEC Payload IDs left unread because their (owner) packetId had no
12430
12520
  // configured fec.blockProfile (see #noteUnreadableFecPayloadId).
12431
12521
  #fecPayloadIdsWithoutProfile = 0;
@@ -12622,15 +12712,24 @@ var MmtpRouter = class {
12622
12712
  return NO_FEC_GEOMETRY_HORIZON;
12623
12713
  }
12624
12714
  const blockSpanMs = fec.interleaveDepthMsForPacket(packetId);
12715
+ const blockLifetimeMs = fec.blockLifetimeMsForPacket(packetId);
12716
+ if (blockSpanMs === void 0 || blockLifetimeMs === void 0) return NO_FEC_GEOMETRY_HORIZON;
12625
12717
  if (blockSpanMs === 0) return NO_BLOCK_SPAN_HORIZON;
12626
12718
  return {
12627
12719
  kind: "fec-horizon",
12628
- retentionMs: duplicateRetentionMs({
12629
- blockSpanMs,
12630
- blockLifetimeMs: fec.blockLifetimeMsForPacket(packetId)
12631
- })
12720
+ retentionMs: duplicateRetentionMs({ blockSpanMs, blockLifetimeMs })
12632
12721
  };
12633
12722
  }
12723
+ /**
12724
+ * Init segments delivered for a packetId with no `onTrackInit` handler
12725
+ * since construction, whether the deprecated aggregate `onInitSegment`
12726
+ * callback then received them or nothing did. A supported deployment reads
12727
+ * 0 here; a non-zero value is the evidence that blocks removing the
12728
+ * aggregate callback (MOQT-01, BLO-21181).
12729
+ */
12730
+ get unmappedInitDeliveryCount() {
12731
+ return this.#unmappedInitDeliveries;
12732
+ }
12634
12733
  /** Publisher restarts detected on the multicast leg since construction. */
12635
12734
  get publisherRestartCount() {
12636
12735
  return this.#publisherRestarts;
@@ -12718,7 +12817,7 @@ var MmtpRouter = class {
12718
12817
  /** Update MFU reassembly timeout. Source and FEC-recovery paths share the
12719
12818
  * parser's single reassembler (BLO-8151), so one update covers both. */
12720
12819
  setMfuTimeoutMs(ms) {
12721
- if (ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
12820
+ if (ms !== null && ms <= 0) throw new Error("[MmtpRouter] mfuTimeoutMs must be > 0");
12722
12821
  this.#parser.setMfuTimeoutMs(ms);
12723
12822
  }
12724
12823
  /** Update parser frame reordering for unordered multicast delivery.
@@ -12729,15 +12828,14 @@ var MmtpRouter = class {
12729
12828
  }
12730
12829
  /** Register MMTP packetId → track type mapping.
12731
12830
  * Call BEFORE packets arrive to ensure correct routing from the first packet.
12732
- * If keyframeLostTimeoutMs is not provided, Phase 13 keyframe repair is
12733
- * disabled (video still routes/decodes normally). framerate is OPTIONAL per
12734
- * draft-ietf-moq-catalogformat-01 §3.2.23, so we can't require the timeout. */
12831
+ * Without keyframeLostTimeoutMs a track WITHOUT FEC has no Phase 13
12832
+ * keyframe repair (video still routes/decodes normally); that is warned at
12833
+ * its first keyframe, once the FEC configuration is known (see
12834
+ * #noteEmittedKeyframe). Nothing is warned here: an FEC track never uses
12835
+ * the timer, and at registration the FEC configuration may not be applied
12836
+ * yet. framerate is OPTIONAL per draft-ietf-moq-catalogformat-01 §3.2.23,
12837
+ * so we can't require the timeout. */
12735
12838
  setPacketIdType(packetId, trackType) {
12736
- if (trackType === "video" && this.#keyframeLostTimeoutMs === null) {
12737
- console.warn(
12738
- "[MmtpRouter] keyframeLostTimeoutMs not configured \u2014 Phase 13 keyframe repair disabled for this track. Set it via setKeyframeLostTimeoutMs() or provide in constructor config to enable: Math.ceil(groupDurationMs * KEYFRAME_LOST_TIMEOUT_SLACK)."
12739
- );
12740
- }
12741
12839
  if (trackType === "video") this.#videoPacketIds.add(packetId);
12742
12840
  else this.#audioPacketIds.add(packetId);
12743
12841
  const codec = this.#trackCodecs.get(packetId) ?? "";
@@ -12788,7 +12886,15 @@ var MmtpRouter = class {
12788
12886
  onTrackPacket(packetId, cb) {
12789
12887
  this.#trackPacketCallbacks.set(packetId, cb);
12790
12888
  }
12791
- /** Register an init segment callback (ftyp+moov from MMTP MPU metadata). */
12889
+ /**
12890
+ * Register an init segment callback (ftyp+moov from MMTP MPU metadata).
12891
+ * Receives an init only for a packetId with no `onTrackInit` handler.
12892
+ *
12893
+ * @deprecated Use onTrackInit(packetId, cb). Still delivered as a fallback;
12894
+ * removed in the 1.0.0 major, no earlier than one minor release after this
12895
+ * deprecation reaches the `latest` dist-tag (BLO-21181). Count fallback
12896
+ * deliveries with `unmappedInitDeliveryCount`.
12897
+ */
12792
12898
  onInitSegment(cb) {
12793
12899
  this.#onInitSegment = cb;
12794
12900
  }
@@ -12802,17 +12908,16 @@ var MmtpRouter = class {
12802
12908
  * When active, source frames are buffered per FEC block and emitted as
12803
12909
  * multi-sample CMAF segments instead of per-frame callbacks. */
12804
12910
  enableMseAssembler(packetId, config, catalogTrackName) {
12805
- const k = this.#kForPacket(packetId);
12806
12911
  const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);
12807
12912
  const previous = this.#mseAssemblers.get(assemblerKey);
12808
12913
  if (previous) {
12809
12914
  previous.flush();
12810
12915
  previous.dispose();
12811
12916
  }
12812
- const assembler = new CmafBlockAssembler({ ...config, k });
12917
+ const assembler = new CmafBlockAssembler(config);
12813
12918
  this.#mseAssemblers.set(assemblerKey, assembler);
12814
12919
  console.log(
12815
- `[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (K=${k}, ${config.trackType})`
12920
+ `[MmtpRouter] MSE assembler enabled for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${config.trackType})`
12816
12921
  );
12817
12922
  return assembler;
12818
12923
  }
@@ -12921,6 +13026,21 @@ var MmtpRouter = class {
12921
13026
  }
12922
13027
  this.#fecMappersByPacketId.set(packetId, mapper);
12923
13028
  }
13029
+ /**
13030
+ * Release the router side of a packetId whose FEC is ending: drop its block
13031
+ * mapper and end its FEC release gating. Frames the gate holds are released
13032
+ * in order, each gap stalled (no block will resolve it), not stranded. Call
13033
+ * before FECManager.releasePacket, while the gate can still read its blocks.
13034
+ */
13035
+ releaseFecPacket(packetId) {
13036
+ this.#fecMappersByPacketId.delete(packetId);
13037
+ this.#withDeferredFecPokes(() => {
13038
+ this.#parser.endFecRelease(packetId);
13039
+ for (const [key, poke] of this.#pendingFecPokes) {
13040
+ if (poke.packetId === packetId) this.#pendingFecPokes.delete(key);
13041
+ }
13042
+ });
13043
+ }
12924
13044
  registerManifestTrack(context) {
12925
13045
  if (!this.#manifestVerifier) throw new Error("manifest verifier is not configured");
12926
13046
  this.#manifestVerifier.registerTrack(context);
@@ -13040,87 +13160,21 @@ var MmtpRouter = class {
13040
13160
  this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
13041
13161
  return;
13042
13162
  }
13043
- if (this.#isIsoPacket(ownerPacketId)) {
13044
- this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
13045
- return;
13046
- }
13047
- const repairSbn = this.#deriveRepairSbn(parsed.ssStart, parsed.ssbLength, ownerPacketId);
13048
- if (repairSbn === null) return;
13049
- const repairEsi = this.#kForPacket(ownerPacketId) + parsed.rsId;
13050
- if (!this.#feedFecTransportSymbol(
13051
- ownerPacketId,
13052
- repairSbn,
13053
- repairEsi,
13054
- parsed.symbolData,
13055
- "moq",
13056
- catalogTrackName
13057
- )) return;
13058
- const recovered = catalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
13059
- ownerPacketId,
13060
- parsed.ssStart,
13061
- parsed.ssbLength,
13062
- parsed.rsId,
13063
- parsed.symbolData
13064
- ) : this.#fecManager.feedRepairSymbol(
13065
- ownerPacketId,
13066
- parsed.ssStart,
13067
- parsed.ssbLength,
13068
- parsed.rsId,
13069
- parsed.symbolData,
13070
- catalogTrackName
13071
- );
13072
- if (recovered) {
13073
- const alreadyCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
13074
- if (catalogTrackName === void 0) {
13075
- this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
13076
- } else {
13077
- this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId, catalogTrackName);
13078
- }
13079
- if (!alreadyCompleted) {
13080
- this.#fecManager.countRecovery(ownerPacketId, repairSbn, catalogTrackName);
13081
- if (catalogTrackName === void 0) {
13082
- this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
13083
- } else {
13084
- this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId, catalogTrackName);
13085
- }
13086
- this.#routeRecoveredBlock(
13087
- repairSbn,
13088
- recovered,
13089
- ownerPacketId,
13090
- catalogTrackName
13091
- );
13092
- }
13093
- }
13163
+ this.#handleIsoRepair(ownerPacketId, catalogTrackName, parsed, "moq");
13094
13164
  }
13095
13165
  }
13096
- // ── SBN/ESI derivation ──────────────────────────────────────────
13097
- #kForPacket(packetId) {
13098
- if (packetId != null)
13099
- return this.#fecManager?.kForPacket?.(packetId) ?? this.#fecMappersByPacketId.get(packetId)?.k ?? this.#fecManager?.k ?? 4;
13100
- return this.#defaultFecMapper?.k ?? this.#fecManager?.k ?? 4;
13101
- }
13166
+ // ── Packet FEC geometry and block profile ───────────────────────
13102
13167
  #symbolSizeForPacket(packetId) {
13103
- if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? this.#fecManager?.symbolSize ?? 0;
13168
+ if (packetId != null) return this.#fecManager?.symbolSizeForPacket?.(packetId) ?? 0;
13104
13169
  return this.#fecManager?.symbolSize ?? 0;
13105
13170
  }
13106
- /** Derive SBN/ESI from SS_ID using packet-scoped FEC geometry. */
13107
- #deriveSbnEsi(ssId, packetId) {
13108
- const mapper = packetId == null ? this.#defaultFecMapper : this.#fecMappersByPacketId.get(packetId) ?? null;
13109
- if (mapper) return mapper.fromSsId(ssId);
13110
- const k = this.#kForPacket(packetId);
13111
- return { sbn: Math.floor(ssId / k), esi: ssId % k };
13112
- }
13113
- #deriveRepairSbn(ssStart, ssbLength, packetId) {
13114
- const k = this.#kForPacket(packetId);
13115
- if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null;
13116
- return Math.floor(ssStart / k);
13117
- }
13118
13171
  /**
13119
13172
  * The configured block profile of a packetId, or undefined while it is
13120
13173
  * unconfigured. With an FEC manager its per-packet configuration is the
13121
13174
  * only source. A router without one still recovers symbols through its
13122
13175
  * registered mapper, built with the catalog's explicit profile: the
13123
- * packet's own mapper, else the default one (the #deriveSbnEsi rule).
13176
+ * packet's own mapper, else the default one. A NullBlockMapper (a stream
13177
+ * without an FEC instance) states none.
13124
13178
  */
13125
13179
  #configuredBlockProfile(packetId) {
13126
13180
  if (this.#fecManager) return this.#fecManager.blockProfileForPacket(packetId);
@@ -13128,9 +13182,10 @@ var MmtpRouter = class {
13128
13182
  return mapper?.blockProfile ?? void 0;
13129
13183
  }
13130
13184
  /**
13131
- * The block profile an FEC coordinate of `packetId` is read under. Throws
13132
- * while none is configured: the two profiles carry identical wire tuples
13133
- * with different block math, so there is no default to fall back to.
13185
+ * The block profile an FEC coordinate of `packetId` is read under:
13186
+ * `iso-ssbg1-v1`, the only one (`k-padded-v1` is retired and refused).
13187
+ * Throws while none is configured: such a packet has no FEC geometry, and
13188
+ * nothing is inferred from the wire or defaulted.
13134
13189
  * Packet ingress leaves such a packet's FEC Payload ID unread first
13135
13190
  * (#noteUnreadableFecPayloadId); a throw here is a wiring fault on a path
13136
13191
  * that cannot hold an unconfigured packet.
@@ -13144,8 +13199,16 @@ var MmtpRouter = class {
13144
13199
  }
13145
13200
  return profile;
13146
13201
  }
13147
- #isIsoPacket(packetId) {
13148
- return this.#blockProfileFor(packetId) === "iso-ssbg1-v1";
13202
+ /**
13203
+ * The block coordinates (SS_Start, ESI) of an FEC source: range membership
13204
+ * in a block a repair already stated (ISO/IEC 23008-1 C.5.3). Null while no
13205
+ * repair has stated its block, while its packetId has no configured block
13206
+ * profile, and without an FEC manager: SS_ID alone locates no block, so
13207
+ * nothing is derived from it.
13208
+ */
13209
+ #locateFecSource(packetId, catalogTrackName, ssId) {
13210
+ if (this.#configuredBlockProfile(packetId) === void 0) return null;
13211
+ return this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId) ?? null;
13149
13212
  }
13150
13213
  /**
13151
13214
  * Explicit refusal to read an FEC Payload ID: its (owner) packetId has no
@@ -13164,21 +13227,20 @@ var MmtpRouter = class {
13164
13227
  }
13165
13228
  }
13166
13229
  /**
13167
- * The release-gate anchor of a recovered symbol: its block (SBN) under
13168
- * k-padded-v1, its own SS_ID under iso-ssbg1-v1, where a frame is gated by
13169
- * the SS_IDs between it and the last released frame.
13230
+ * The release-gate anchor of a recovered symbol: its own SS_ID, SS_Start +
13231
+ * ESI modulo 2^32 (C.5.2), where a frame is gated by the SS_IDs between it
13232
+ * and the last released frame.
13170
13233
  */
13171
- #fecAnchorForRecovered(ownerPacketId, blockId, index) {
13172
- return ownerPacketId !== void 0 && this.#isIsoPacket(ownerPacketId) ? serial32Add(blockId, index) : blockId;
13234
+ #fecAnchorForRecovered(blockStart, index) {
13235
+ return serial32Add(blockStart, index);
13173
13236
  }
13174
13237
  /**
13175
13238
  * One iso-ssbg1-v1 repair symbol (ISO/IEC 23008-1 C.5.3). It states the
13176
13239
  * block `[SS_Start, SS_Start + SSB_length)`; repair ESI = SSB_length +
13177
13240
  * RS_ID (K' = SSB_length). The held sources it placed (and those of a
13178
13241
  * repair-less span in front of it) are fed as first-arrival sources, then
13179
- * the repair, the order they had on the wire, so a loss-free block is clean
13180
- * on its repair exactly as under k-padded-v1. A repair contradicting a known
13181
- * block is dropped.
13242
+ * the repair, the order they had on the wire, so a loss-free block completes
13243
+ * clean on its repair. A repair contradicting a known block is dropped.
13182
13244
  */
13183
13245
  #handleIsoRepair(ownerPacketId, catalogTrackName, repair, source) {
13184
13246
  const fec = this.#fecManager;
@@ -13204,6 +13266,7 @@ var MmtpRouter = class {
13204
13266
  catalogTrackName
13205
13267
  )) return;
13206
13268
  fec.noteBlockActivity?.(blockStart, ownerPacketId, catalogTrackName);
13269
+ fec.noteRepairGroupLength?.(blockStart, ownerPacketId, repair.rsbLength, catalogTrackName);
13207
13270
  const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
13208
13271
  const recovered = fec.feedRepairSymbol(
13209
13272
  ownerPacketId,
@@ -13245,7 +13308,7 @@ var MmtpRouter = class {
13245
13308
  }
13246
13309
  if (payload.oversized) continue;
13247
13310
  const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
13248
- const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, void 0, catalogTrackName);
13311
+ const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
13249
13312
  if (!recovered || alreadyCompleted) continue;
13250
13313
  fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
13251
13314
  fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
@@ -13270,9 +13333,10 @@ var MmtpRouter = class {
13270
13333
  return null;
13271
13334
  }
13272
13335
  /** Route a recovered FEC block through the parser + dedup pipeline.
13273
- * Sync WASM decoders return K fixed-width source symbols concatenated. */
13336
+ * Sync WASM decoders return the block's K' = SSB_length fixed-width
13337
+ * ssbg_mode1 source symbols concatenated. */
13274
13338
  routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
13275
- this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
13339
+ this.#withDeferredFecPokes(() => this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName));
13276
13340
  }
13277
13341
  /** Route a single recovered source symbol through the parser + dedup pipeline.
13278
13342
  * Worker-backed decoders emit one padded source symbol per frame event. */
@@ -13686,8 +13750,16 @@ var MmtpRouter = class {
13686
13750
  #noteEmittedKeyframe(packetId, keyframe) {
13687
13751
  if (!keyframe) return;
13688
13752
  if (!this.#videoPacketIds.has(packetId)) return;
13689
- if (this.#keyframeLostTimeoutMs === null) return;
13690
13753
  if (this.#isFecTrack(packetId)) return;
13754
+ if (this.#keyframeLostTimeoutMs === null) {
13755
+ if (!this.#warnedNoKeyframeLostTimeout.has(packetId)) {
13756
+ this.#warnedNoKeyframeLostTimeout.add(packetId);
13757
+ console.warn(
13758
+ `[MmtpRouter] keyframeLostTimeoutMs not configured \u2014 Phase 13 keyframe repair disabled for packetId=${packetId}, a video track without FEC. Set it via setKeyframeLostTimeoutMs() or provide in constructor config to enable.`
13759
+ );
13760
+ }
13761
+ return;
13762
+ }
13691
13763
  this.#lastKeyframeGroupId++;
13692
13764
  this.#armKeyframeLostTimer();
13693
13765
  }
@@ -13699,7 +13771,7 @@ var MmtpRouter = class {
13699
13771
  #withDeferredFecPokes(work) {
13700
13772
  this.#fecPokeDepth++;
13701
13773
  try {
13702
- work();
13774
+ this.#parser.deferFecRelease(work);
13703
13775
  } finally {
13704
13776
  this.#fecPokeDepth--;
13705
13777
  if (this.#fecPokeDepth === 0 && this.#pendingFecPokes.size > 0 && !this.#disposed) {
@@ -13711,6 +13783,13 @@ var MmtpRouter = class {
13711
13783
  }
13712
13784
  }
13713
13785
  }
13786
+ /**
13787
+ * Arrival time of the earliest frame the FEC release gate holds for this
13788
+ * stream (see FecReleaseSequencer.earliestHeldArrivalMs), or undefined.
13789
+ */
13790
+ fecReleaseEarliestHeldAtMs(packetId, catalogTrackName) {
13791
+ return this.#parser.fecReleaseEarliestHeldAtMs(packetId, catalogTrackName);
13792
+ }
13714
13793
  #requestFecPoke(packetId, catalogTrackName) {
13715
13794
  if (this.#disposed) return;
13716
13795
  if (this.#fecPokeDepth > 0) {
@@ -13738,29 +13817,19 @@ var MmtpRouter = class {
13738
13817
  resolvedLoss: (packetId, catalogTrackName, fromSbn, toSbn, releasedSbn) => {
13739
13818
  const fec = this.#fecManager;
13740
13819
  if (!fec?.blockResolution) return [];
13741
- if (this.#isIsoPacket(packetId)) {
13742
- const head = fromSbn === releasedSbn ? toSbn : fromSbn;
13743
- return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
13744
- }
13745
- const highest = fec.highestSbn?.(packetId, catalogTrackName);
13746
- const lost = [];
13747
- for (let sbn = fromSbn; sbn <= toSbn; sbn++) {
13748
- const resolution = fec.blockResolution(sbn, packetId, catalogTrackName);
13749
- if (resolution === "pending") return null;
13750
- if (resolution === "failed") {
13751
- lost.push(sbn);
13752
- } else if (resolution === "untracked") {
13753
- if (sbn === releasedSbn) continue;
13754
- if (highest === void 0 || sbn > highest) return null;
13755
- lost.push(sbn);
13756
- }
13757
- }
13758
- return lost;
13820
+ const head = fromSbn === releasedSbn ? toSbn : fromSbn;
13821
+ return fec.isoSpanResolution?.(packetId, catalogTrackName, releasedSbn, head) ?? null;
13759
13822
  },
13760
13823
  // The retention window itself: a frame is never still held when a block
13761
13824
  // it waits on retires. Only consulted while resolvedLoss holds a gap,
13762
13825
  // which needs blockResolution, so the check above guarantees the method.
13763
- holdLimitMs: (packetId) => this.#fecManager?.terminalRetentionMsForPacket?.(packetId) ?? Infinity,
13826
+ // A packetId the manager no longer has configured (released) has no
13827
+ // block left to wait on: release its frames now (BLO-45953).
13828
+ holdLimitMs: (packetId) => {
13829
+ const fec = this.#fecManager;
13830
+ if (!fec?.terminalRetentionMsForPacket) return Infinity;
13831
+ return fec.terminalRetentionMsForPacket(packetId) ?? 0;
13832
+ },
13764
13833
  decide: (before, after, lostBlocks, discontinuity) => {
13765
13834
  const decision = this.#decideGap(before, after, lostBlocks, discontinuity);
13766
13835
  this.#onFrameGap?.(decision);
@@ -13822,9 +13891,10 @@ var MmtpRouter = class {
13822
13891
  }
13823
13892
  /**
13824
13893
  * The most media units a gap between two released frames can hold: the
13825
- * FEC blocks from `before`'s SBN to `after`'s, times D, the media units one
13826
- * block touches (ceil(interleaveDepthMs / unit duration), §5.1). Null when
13827
- * either SBN, the unit duration or the catalog interleave is unknown.
13894
+ * FEC blocks between `before`'s anchor and `after`'s, times D, the media
13895
+ * units one block touches (ceil(interleaveDepthMs / unit duration), §5.1).
13896
+ * Null when either anchor, the unit duration or the catalog interleave is
13897
+ * unknown.
13828
13898
  */
13829
13899
  #maxMissingUnits(before, after) {
13830
13900
  const from = before.fecSbn;
@@ -13837,7 +13907,7 @@ var MmtpRouter = class {
13837
13907
  const denominator = BigInt(timing.durationTicks) * 1000n;
13838
13908
  const unitsPerBlock = (numerator + denominator - 1n) / denominator;
13839
13909
  const blocks = BigInt(
13840
- (this.#isIsoPacket(after.packetId) ? serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from) : Math.abs(to - from)) + 1
13910
+ (serial32Precedes(from, to) ? serial32Distance(from, to) : serial32Distance(to, from)) + 1
13841
13911
  );
13842
13912
  const bound = unitsPerBlock * blocks;
13843
13913
  return bound > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(bound);
@@ -13887,8 +13957,9 @@ var MmtpRouter = class {
13887
13957
  const cb = this.#trackInitCallbacks.get(packetId);
13888
13958
  if (cb) {
13889
13959
  cb(data);
13890
- } else if (this.#onInitSegment) {
13891
- this.#onInitSegment(data);
13960
+ } else {
13961
+ this.#unmappedInitDeliveries++;
13962
+ this.#onInitSegment?.(data);
13892
13963
  }
13893
13964
  console.log(
13894
13965
  `[MmtpRouter] Init delivered for packetId=${packetId}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`} (${data.byteLength}B)`
@@ -13930,9 +14001,9 @@ var MmtpRouter = class {
13930
14001
  if (!info.isRepair && this.#manifestVerifier && !manifestAdmitted) {
13931
14002
  let identity = manifestIdentity;
13932
14003
  if (!identity && info.fecPayloadId && !fecUnreadable && (this.#resolveManifestIdentityForRoute || this.#resolveManifestIdentity)) {
13933
- const coordinates = this.#deriveSbnEsi(info.fecPayloadId.ssId, info.packetId);
13934
14004
  const catalogTrackName = catalogTrackNameForPacket(ingress, info.packetId);
13935
- identity = (this.#resolveManifestIdentityForRoute ? this.#resolveManifestIdentityForRoute({
14005
+ const coordinates = this.#locateFecSource(info.packetId, catalogTrackName, info.fecPayloadId.ssId);
14006
+ identity = coordinates === null ? void 0 : (this.#resolveManifestIdentityForRoute ? this.#resolveManifestIdentityForRoute({
13936
14007
  packetId: info.packetId,
13937
14008
  sbn: coordinates.sbn,
13938
14009
  esi: coordinates.esi,
@@ -13984,79 +14055,7 @@ var MmtpRouter = class {
13984
14055
  this.#noteUnreadableFecPayloadId(ownerPacketId, "repair");
13985
14056
  return;
13986
14057
  }
13987
- if (this.#isIsoPacket(ownerPacketId)) {
13988
- this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
13989
- return;
13990
- }
13991
- const repairSbn = this.#deriveRepairSbn(repair.ssStart, repair.ssbLength, ownerPacketId);
13992
- if (repairSbn === null) return;
13993
- const repairEsi = this.#kForPacket(ownerPacketId) + repair.rsId;
13994
- if (!this.#feedFecTransportSymbol(
13995
- ownerPacketId,
13996
- repairSbn,
13997
- repairEsi,
13998
- repair.symbolData,
13999
- source,
14000
- catalogTrackNameForPacket(ingress, ownerPacketId)
14001
- )) return;
14002
- const repairCatalogTrackName = catalogTrackNameForPacket(ingress, ownerPacketId);
14003
- if (repairCatalogTrackName === void 0) {
14004
- this.#fecManager.noteBlockActivity?.(repairSbn, ownerPacketId);
14005
- } else {
14006
- this.#fecManager.noteBlockActivity?.(
14007
- repairSbn,
14008
- ownerPacketId,
14009
- repairCatalogTrackName
14010
- );
14011
- }
14012
- const recovered = repairCatalogTrackName === void 0 ? this.#fecManager.feedRepairSymbol(
14013
- ownerPacketId,
14014
- repair.ssStart,
14015
- repair.ssbLength,
14016
- repair.rsId,
14017
- repair.symbolData
14018
- ) : this.#fecManager.feedRepairSymbol(
14019
- ownerPacketId,
14020
- repair.ssStart,
14021
- repair.ssbLength,
14022
- repair.rsId,
14023
- repair.symbolData,
14024
- repairCatalogTrackName
14025
- );
14026
- if (recovered) {
14027
- const alreadyCompleted = repairCatalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(repairSbn, ownerPacketId) : this.#fecManager.isBlockCompleted(
14028
- repairSbn,
14029
- ownerPacketId,
14030
- repairCatalogTrackName
14031
- );
14032
- if (repairCatalogTrackName === void 0) {
14033
- this.#fecManager.cancelBlockTimeout(repairSbn, ownerPacketId);
14034
- } else {
14035
- this.#fecManager.cancelBlockTimeout(
14036
- repairSbn,
14037
- ownerPacketId,
14038
- repairCatalogTrackName
14039
- );
14040
- }
14041
- if (!alreadyCompleted) {
14042
- this.#fecManager.countRecovery(ownerPacketId, repairSbn, repairCatalogTrackName);
14043
- if (repairCatalogTrackName === void 0) {
14044
- this.#fecManager.markBlockCompleted(repairSbn, ownerPacketId);
14045
- } else {
14046
- this.#fecManager.markBlockCompleted(
14047
- repairSbn,
14048
- ownerPacketId,
14049
- repairCatalogTrackName
14050
- );
14051
- }
14052
- this.#routeRecoveredBlock(
14053
- repairSbn,
14054
- recovered,
14055
- ownerPacketId,
14056
- catalogTrackNameForPacket(ingress, ownerPacketId)
14057
- );
14058
- }
14059
- }
14058
+ this.#handleIsoRepair(ownerPacketId, catalogTrackNameForPacket(ingress, ownerPacketId), repair, source);
14060
14059
  }
14061
14060
  } else {
14062
14061
  if (this.#classifyPacketId(info.packetId) === null) return;
@@ -14080,8 +14079,8 @@ var MmtpRouter = class {
14080
14079
  }
14081
14080
  const duplicates = this.#stream(info.packetId, catalogTrackName).duplicates;
14082
14081
  const fecPayloadId = fecUnreadable ? null : info.fecPayloadId;
14083
- const isoSource = this.#fecManager !== null && fecPayloadId != null && this.#isIsoPacket(info.packetId);
14084
- const coords = this.#fecManager && fecPayloadId ? isoSource ? this.#fecManager.locateIsoSource?.(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? void 0 : this.#deriveSbnEsi(fecPayloadId.ssId, info.packetId) : void 0;
14082
+ const fecSource = this.#fecManager !== null && fecPayloadId != null;
14083
+ const coords = fecSource && fecPayloadId ? this.#locateFecSource(info.packetId, catalogTrackName, fecPayloadId.ssId) ?? void 0 : void 0;
14085
14084
  duplicates.advance(coords?.sbn);
14086
14085
  if (source === "multicast" && !overlapCopy && !releasedFromQuarantine && catalogTrackName !== void 0 && duplicates.predatesHorizon(info.sequenceNumber, fecPayloadId?.ssId)) {
14087
14086
  this.#publisherRestarts++;
@@ -14096,9 +14095,8 @@ var MmtpRouter = class {
14096
14095
  }
14097
14096
  if (this.#fecManager && fecPayloadId && coords) {
14098
14097
  const fecId = fecPayloadId;
14099
- const symbolSize = this.#symbolSizeForPacket(info.packetId);
14100
14098
  const rawSymbol = packet.subarray(0, packet.length - 4);
14101
- const oversized = isoSource ? !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false) : symbolSize > 0 && rawSymbol.length > symbolSize;
14099
+ const oversized = !(this.#fecManager.isoSourceFits?.(info.packetId, rawSymbol.length) ?? false);
14102
14100
  let overlapBlockCompleted = false;
14103
14101
  if (overlapCopy) {
14104
14102
  overlapBlockCompleted = catalogTrackName === void 0 ? this.#fecManager.isBlockCompleted(coords.sbn, info.packetId) : this.#fecManager.isBlockCompleted(
@@ -14133,7 +14131,7 @@ var MmtpRouter = class {
14133
14131
  }
14134
14132
  parseSourceSymbol = (overlapCopy ? !overlapBlockCompleted : sourceSymbolAccepted) && !duplicates.parsedSymbols.has(fecId.ssId);
14135
14133
  fecInfo = { sbn: coords.sbn, esi: coords.esi, oversized };
14136
- } else if (isoSource && this.#fecManager && fecPayloadId) {
14134
+ } else if (fecSource && this.#fecManager && fecPayloadId) {
14137
14135
  const ssId = fecPayloadId.ssId;
14138
14136
  if (overlapCopy) {
14139
14137
  parseSourceSymbol = !duplicates.parsedSymbols.has(ssId);
@@ -14167,12 +14165,13 @@ var MmtpRouter = class {
14167
14165
  this.#feedViaParser(packet, info, fecInfo, {
14168
14166
  source,
14169
14167
  catalogTrackName,
14170
- ...isoSource && info.fecPayloadId ? { fecSbn: info.fecPayloadId.ssId } : fecInfo === void 0 ? {} : { fecSbn: fecInfo.sbn }
14168
+ // An FEC source frame is anchored by its own SS_ID (C.5.2).
14169
+ ...fecSource && fecPayloadId ? { fecSbn: fecPayloadId.ssId } : {}
14171
14170
  }, parsed);
14172
14171
  }
14173
14172
  if (this.#classifyPacketId(info.packetId) === null) return;
14174
- if (this.#fecManager && fecPayloadId && sourceSymbolAccepted && sourceSymbolTrackable) {
14175
- const { sbn, esi } = isoSource && coords ? coords : this.#deriveSbnEsi(fecPayloadId.ssId, info.packetId);
14173
+ if (this.#fecManager && fecPayloadId && coords && sourceSymbolAccepted && sourceSymbolTrackable) {
14174
+ const { sbn, esi } = coords;
14176
14175
  if (this.#classifyPacketId(info.packetId) !== null && this.#fecManager.isTrackConfigured?.(info.packetId) !== false) {
14177
14176
  this.#fecManager.trackBlock(
14178
14177
  sbn,
@@ -14193,13 +14192,11 @@ var MmtpRouter = class {
14193
14192
  const recovered = catalogTrackName === void 0 ? this.#fecManager.feedSourceSymbol(
14194
14193
  info.packetId,
14195
14194
  sourceFec.ssId,
14196
- sourceFec.symbolData,
14197
- void 0
14195
+ sourceFec.symbolData
14198
14196
  ) : this.#fecManager.feedSourceSymbol(
14199
14197
  info.packetId,
14200
14198
  sourceFec.ssId,
14201
14199
  sourceFec.symbolData,
14202
- void 0,
14203
14200
  catalogTrackName
14204
14201
  );
14205
14202
  if (recovered) {
@@ -14606,7 +14603,6 @@ var MmtpRouter = class {
14606
14603
  // ── Private: FEC recovery routing ───────────────────────────────
14607
14604
  /** Route WASM-recovered block through appropriate path based on container kind. */
14608
14605
  #routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName) {
14609
- const k = this.#kForPacket(ownerPacketId);
14610
14606
  const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
14611
14607
  if (ownerPacketId === void 0) {
14612
14608
  throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
@@ -14619,7 +14615,6 @@ var MmtpRouter = class {
14619
14615
  }
14620
14616
  const symbolSize = packetSymbolSize;
14621
14617
  const result = processFecBlock(recovered, {
14622
- ...blockProfile === "k-padded-v1" ? { k } : {},
14623
14618
  symbolSize,
14624
14619
  ownerPacketId,
14625
14620
  blockProfile
@@ -14699,8 +14694,7 @@ var MmtpRouter = class {
14699
14694
  const admission = verifier.admitObject(
14700
14695
  {
14701
14696
  ...identity,
14702
- packetId: ownerPacketId,
14703
- ...ownerPacketId !== void 0 && this.#isIsoPacket(ownerPacketId) ? {} : { ssId: blockId * this.#kForPacket(ownerPacketId) + symbol.index }
14697
+ packetId: ownerPacketId
14704
14698
  },
14705
14699
  symbol.rawSymbol,
14706
14700
  () => {
@@ -14803,7 +14797,7 @@ var MmtpRouter = class {
14803
14797
  rapFlag: sym.fragment.rapFlag,
14804
14798
  recovered: true,
14805
14799
  deliveryProvenance,
14806
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
14800
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
14807
14801
  ...catalogTrackName === void 0 ? {} : { catalogTrackName }
14808
14802
  });
14809
14803
  continue;
@@ -14915,7 +14909,7 @@ var MmtpRouter = class {
14915
14909
  rapFlag: assembled.rapFlag,
14916
14910
  recovered: true,
14917
14911
  ...assembled.deliveryProvenance === void 0 ? {} : { deliveryProvenance: assembled.deliveryProvenance },
14918
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
14912
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
14919
14913
  ...recoveredCatalogTrackName === void 0 ? {} : { catalogTrackName: recoveredCatalogTrackName }
14920
14914
  });
14921
14915
  continue;
@@ -15052,7 +15046,7 @@ var MmtpRouter = class {
15052
15046
  rapFlag: sym.fragment.rapFlag,
15053
15047
  recovered: true,
15054
15048
  deliveryProvenance,
15055
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
15049
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
15056
15050
  ...catalogTrackName === void 0 ? {} : { catalogTrackName }
15057
15051
  });
15058
15052
  } else {
@@ -15131,7 +15125,7 @@ var MmtpRouter = class {
15131
15125
  rapFlag: assembled.rapFlag,
15132
15126
  recovered: true,
15133
15127
  deliveryProvenance,
15134
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
15128
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
15135
15129
  ...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
15136
15130
  });
15137
15131
  }
@@ -15166,7 +15160,7 @@ var MmtpRouter = class {
15166
15160
  ...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
15167
15161
  recovered: true,
15168
15162
  deliveryProvenance,
15169
- fecSbn: this.#fecAnchorForRecovered(ownerPacketId, blockId, sym.index),
15163
+ fecSbn: this.#fecAnchorForRecovered(blockId, sym.index),
15170
15164
  ...catalogTrackName === void 0 ? {} : { catalogTrackName }
15171
15165
  });
15172
15166
  continue;
@@ -15217,22 +15211,17 @@ var MoqFecAlgorithm;
15217
15211
  MoqFecAlgorithm2[MoqFecAlgorithm2["RaptorQ"] = 2] = "RaptorQ";
15218
15212
  })(MoqFecAlgorithm || (MoqFecAlgorithm = {}));
15219
15213
 
15220
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-alta-parse/dist/index.js
15221
- var MPU_HEADER_SIZE3 = 8;
15222
- var MPU_LENGTH_FIELD_SIZE3 = 2;
15223
- var MPU_HEADER_REMAINDER_SIZE3 = MPU_HEADER_SIZE3 - MPU_LENGTH_FIELD_SIZE3;
15224
-
15225
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/mmtp.js
15226
- var MPU_HEADER_SIZE4 = 8;
15227
- var MPU_LENGTH_FIELD_SIZE4 = 2;
15228
- var MPU_HEADER_REMAINDER_SIZE4 = MPU_HEADER_SIZE4 - MPU_LENGTH_FIELD_SIZE4;
15214
+ // node_modules/@blockcast/mmt-base/dist/mmtp.js
15215
+ var MPU_HEADER_SIZE2 = 8;
15216
+ var MPU_LENGTH_FIELD_SIZE2 = 2;
15217
+ var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
15229
15218
  var MMTP_SIGNALING_PACKET_ID2 = 0;
15230
15219
  var MMTP_PACKET_ID_MAX2 = 65535;
15231
15220
 
15232
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/net-constants.js
15221
+ // node_modules/@blockcast/mmt-base/dist/net-constants.js
15233
15222
  var DEFAULT_AMT_RELAY_PORT2 = 2268;
15234
15223
 
15235
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/ntp-short.js
15224
+ // node_modules/@blockcast/mmt-base/dist/ntp-short.js
15236
15225
  var NTP_SHORT_BITS2 = 32;
15237
15226
  var NTP_SHORT_MODULUS2 = 2 ** NTP_SHORT_BITS2;
15238
15227
  var NTP_SHORT_HALF_RANGE2 = 2 ** (NTP_SHORT_BITS2 - 1);
@@ -15253,9 +15242,8 @@ function compareNtpShort(a, b) {
15253
15242
  return "unordered";
15254
15243
  }
15255
15244
 
15256
- // node_modules/@blockcast/mmt-transport/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js
15245
+ // node_modules/@blockcast/mmt-base/dist/fec-block-profile.js
15257
15246
  var FEC_BLOCK_PROFILES2 = Object.freeze([
15258
- "k-padded-v1",
15259
15247
  "iso-ssbg1-v1"
15260
15248
  ]);
15261
15249
 
@@ -15647,10 +15635,12 @@ var MulticastOrchestrator = class {
15647
15635
  }
15648
15636
  onGoodput(sample) {
15649
15637
  const bitrate = sample.bitrate;
15650
- if (!this.#current || this.options.adaptive === false)
15638
+ if (!this.#current)
15651
15639
  return void 0;
15652
15640
  if (this.#state === "joining")
15653
15641
  return this.poll();
15642
+ if (this.options.adaptive === false)
15643
+ return void 0;
15654
15644
  if (bitrate === void 0 || !Number.isFinite(bitrate))
15655
15645
  return void 0;
15656
15646
  this.#estimate = bitrate;
@@ -15717,6 +15707,27 @@ var MulticastOrchestrator = class {
15717
15707
  this.#favored = void 0;
15718
15708
  return { kind: "join-target", current: this.#current, target: desired };
15719
15709
  }
15710
+ /**
15711
+ * Join `target` because the viewer selected it, not because goodput or
15712
+ * headroom ranked it. It runs whether or not the orchestrator adapts, and
15713
+ * takes the path an adaptive join takes: commit on the target's RAP at or
15714
+ * beyond the high-water mark, abandon at the join deadline. While it is
15715
+ * pending nothing re-ranks the ladder; goodput only advances the deadline.
15716
+ *
15717
+ * Returns undefined, changing nothing, while another join is pending (cancel
15718
+ * it first), when `target` is the current tier, or when it carries no media.
15719
+ */
15720
+ select(target) {
15721
+ if (!this.#current || this.#state === "joining" || !carriesMedia(target))
15722
+ return void 0;
15723
+ if (endpointKey(target) === endpointKey(this.#current))
15724
+ return void 0;
15725
+ this.#state = "joining";
15726
+ this.#target = target;
15727
+ this.#joinStartedAt = this.#now();
15728
+ this.#favored = void 0;
15729
+ return { kind: "join-target", current: this.#current, target };
15730
+ }
15720
15731
  /**
15721
15732
  * Feed a packet from the active tier (to advance the high-water mark) or
15722
15733
  * from the in-flight target (to find a committable RAP).
@@ -16756,7 +16767,7 @@ var MulticastTransport = class _MulticastTransport {
16756
16767
  // inside the window, instead of a clear+set pair on every packet.
16757
16768
  #watchdog = null;
16758
16769
  #lastPacketAtMs = 0;
16759
- #failbackWindowMs = null;
16770
+ #failbackWindowMs;
16760
16771
  #onTakeover = null;
16761
16772
  #onFailback = null;
16762
16773
  // Data-plane-live edge: the first packet this join accepted. Everything
@@ -17061,14 +17072,20 @@ var MulticastTransport = class _MulticastTransport {
17061
17072
  return this.#dataPlaneLive;
17062
17073
  }
17063
17074
  setFailbackWindowMs(windowMs) {
17064
- if (!Number.isFinite(windowMs) || windowMs <= 0) {
17075
+ if (windowMs !== null && (!Number.isFinite(windowMs) || windowMs <= 0)) {
17065
17076
  throw new Error(
17066
17077
  `[MulticastTransport] failbackWindowMs=${windowMs}. Derive a positive failback window from the stream catalog.`
17067
17078
  );
17068
17079
  }
17069
17080
  this.#failbackWindowMs = windowMs;
17070
- if (this.#watchdog !== null) {
17071
- clearTimeout(this.#watchdog);
17081
+ if (windowMs === null) {
17082
+ if (this.#watchdog !== null) clearTimeout(this.#watchdog);
17083
+ this.#watchdog = null;
17084
+ return;
17085
+ }
17086
+ if (this.#watchdog !== null) clearTimeout(this.#watchdog);
17087
+ this.#watchdog = null;
17088
+ if (this.#dataPlaneLive && this.#onFailback) {
17072
17089
  const silentMs = performance.now() - this.#lastPacketAtMs;
17073
17090
  this.#armWatchdog(Math.max(0, Math.ceil(windowMs - silentMs)));
17074
17091
  }
@@ -17394,7 +17411,8 @@ var MulticastTransport = class _MulticastTransport {
17394
17411
  }
17395
17412
  if (this.#watchdog !== null) return;
17396
17413
  if (!this.#onFailback) return;
17397
- if (this.#failbackWindowMs === null) {
17414
+ if (this.#failbackWindowMs === null) return;
17415
+ if (this.#failbackWindowMs === void 0) {
17398
17416
  throw new Error(
17399
17417
  "[MulticastTransport] Failback watchdog requires a catalog-derived window"
17400
17418
  );
@@ -17404,7 +17422,7 @@ var MulticastTransport = class _MulticastTransport {
17404
17422
  #armWatchdog(delayMs) {
17405
17423
  this.#watchdog = setTimeout(() => {
17406
17424
  this.#watchdog = null;
17407
- if (this.#disposed || !this.#onFailback || this.#failbackWindowMs === null) return;
17425
+ if (this.#disposed || !this.#onFailback || this.#failbackWindowMs == null) return;
17408
17426
  const silentMs = performance.now() - this.#lastPacketAtMs;
17409
17427
  if (silentMs < this.#failbackWindowMs) {
17410
17428
  this.#armWatchdog(Math.ceil(this.#failbackWindowMs - silentMs));
@@ -17438,6 +17456,35 @@ var BASE64URL_DECODE_TABLE = (() => {
17438
17456
  return table;
17439
17457
  })();
17440
17458
 
17459
+ // src/repair-layers.ts
17460
+ var RepairContractError = class extends Error {
17461
+ constructor(code, path, message) {
17462
+ super(`[repair-layers:${code}] ${path}: ${message}`);
17463
+ this.code = code;
17464
+ this.path = path;
17465
+ this.name = "RepairContractError";
17466
+ }
17467
+ };
17468
+ var throwRepairContractError = (code, path, message) => {
17469
+ throw new RepairContractError(code, path, message);
17470
+ };
17471
+ function readFecBlockProfile(value, path, declaresFecInstance, fail) {
17472
+ if (value === void 0 && !declaresFecInstance) return void 0;
17473
+ if (isFecBlockProfile(value)) return value;
17474
+ if (typeof value === "string" && value.length > 0) {
17475
+ return fail(
17476
+ "track_fec_block_profile_unimplemented",
17477
+ path,
17478
+ `${JSON.stringify(value)} is not an implemented FEC block profile; only "iso-ssbg1-v1" is implemented`
17479
+ );
17480
+ }
17481
+ try {
17482
+ return parseFecBlockProfile(value, "blockProfile");
17483
+ } catch (error) {
17484
+ return fail("track_fec_block_profile", path, error.message);
17485
+ }
17486
+ }
17487
+
17441
17488
  // src/carp-catalog.ts
17442
17489
  var VALID_ROLE_SUFFIXES = /* @__PURE__ */ new Set(["base", "delta", "repair"]);
17443
17490
  function parseTrackRole(trackName) {
@@ -17477,12 +17524,49 @@ function hasSwitchingSetRole(names) {
17477
17524
  return suffix === "base" || suffix === "delta";
17478
17525
  });
17479
17526
  }
17480
- function isFecCatalogConfig(obj) {
17527
+ function isFecCatalogGeometry(obj) {
17481
17528
  if (typeof obj !== "object" || obj === null) return false;
17482
17529
  const o = obj;
17483
17530
  const algorithm = o["algorithm"];
17484
17531
  const symbolSize = o["symbolSize"];
17485
- return typeof algorithm === "string" && (algorithm === "none" || algorithm === "raptorq" || algorithm === "reed-solomon") && typeof o["sourceSymbols"] === "number" && o["sourceSymbols"] >= 1 && typeof o["repairSymbols"] === "number" && o["repairSymbols"] >= 1 && typeof symbolSize === "number" && symbolSize >= 1 && (algorithm !== "raptorq" || symbolSize % 8 === 0) && typeof o["repairTrack"] === "string" && o["repairTrack"].length > 0 && (isFecBlockProfile(o["blockProfile"]) || algorithm === "none" && o["blockProfile"] === void 0);
17532
+ return typeof algorithm === "string" && (algorithm === "none" || algorithm === "raptorq" || algorithm === "reed-solomon") && typeof o["sourceSymbols"] === "number" && o["sourceSymbols"] >= 1 && typeof o["repairSymbols"] === "number" && o["repairSymbols"] >= 1 && typeof symbolSize === "number" && symbolSize >= 1 && (algorithm !== "raptorq" || symbolSize % 8 === 0) && typeof o["repairTrack"] === "string" && o["repairTrack"].length > 0;
17533
+ }
17534
+ function isFecCatalogConfig(obj) {
17535
+ if (!isFecCatalogGeometry(obj)) return false;
17536
+ const o = obj;
17537
+ return isFecBlockProfile(o["blockProfile"]) || o["algorithm"] === "none" && o["blockProfile"] === void 0;
17538
+ }
17539
+
17540
+ // src/catalog-fec-params.ts
17541
+ import { catalogFecInterleaveMs } from "@blockcast/mmt-fec";
17542
+ var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
17543
+ function recordOf2(value) {
17544
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
17545
+ }
17546
+ function blockProfileUnavailable(value, path, declaresFecInstance) {
17547
+ try {
17548
+ readFecBlockProfile(value, path, declaresFecInstance, throwRepairContractError);
17549
+ return void 0;
17550
+ } catch (error) {
17551
+ if (!(error instanceof RepairContractError) || error.code !== "track_fec_block_profile" && error.code !== "track_fec_block_profile_unimplemented") {
17552
+ throw error;
17553
+ }
17554
+ return Object.freeze({ code: error.code, path: error.path, message: error.message });
17555
+ }
17556
+ }
17557
+ function isoSpanUnavailable(fec, path, declaresFecInstance) {
17558
+ if (!declaresFecInstance || isoBlockSpanUsable(fec)) return void 0;
17559
+ return Object.freeze({
17560
+ code: "track_fec",
17561
+ path,
17562
+ message: `[repair-layers:track_fec] ${path}: must be a non-negative safe integer in milliseconds`
17563
+ });
17564
+ }
17565
+ function catalogFecUnavailable(fec, fecPath) {
17566
+ const block = recordOf2(fec);
17567
+ if (block === void 0) return void 0;
17568
+ const declaresFecInstance = block["algorithm"] !== "none";
17569
+ return blockProfileUnavailable(block["blockProfile"], `${fecPath}.blockProfile`, declaresFecInstance) ?? isoSpanUnavailable(block, `${fecPath}.interleaveDepthMs`, declaresFecInstance);
17486
17570
  }
17487
17571
 
17488
17572
  // src/multicast-manager.ts
@@ -17516,6 +17600,13 @@ function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
17516
17600
  deltaRepairsPreempted: 0,
17517
17601
  deltaRepairsDeferred: 0,
17518
17602
  repairsSkipped: 0,
17603
+ repairsSkippedRttUnmeasured: 0,
17604
+ repairsSkippedRttExceedsBudget: 0,
17605
+ repairsSkippedPresentationDelayUnavailable: 0,
17606
+ lastRepairSkip: null,
17607
+ keyframeRepairFetchesSent: 0,
17608
+ keyframeBudgetHolds: 0,
17609
+ keyframeBudgetHoldMs: 0,
17519
17610
  deltaBlocksFailed: 0,
17520
17611
  audioBlocksRecoveredInband: 0,
17521
17612
  audioBlocksRecoveredQuic: 0,
@@ -17728,7 +17819,10 @@ function projectFecDiagnosticBlock(block) {
17728
17819
  lastSymbolTs: block.lastSymbolTs,
17729
17820
  ...block.completedTs === void 0 ? {} : { completedTs: block.completedTs },
17730
17821
  deadlineTs: block.deadlineTs,
17731
- altaVerified: block.altaVerified,
17822
+ // ponytail: the per-packet authenticator is gone, so this is always 0. It
17823
+ // stays only because pim-multicast-gateway's normalizeSubscriptionDiagnostics
17824
+ // still requires a number here; drop it once that validator stops asking.
17825
+ altaVerified: 0,
17732
17826
  relayBlockSigValid: block.relayBlockSigValid
17733
17827
  };
17734
17828
  }
@@ -17748,7 +17842,6 @@ function projectFecDiagnosticConfig(config) {
17748
17842
  interleaveMs: config.interleaveMs,
17749
17843
  fecMode: config.fecMode,
17750
17844
  ...config.repairTrack === void 0 ? {} : { repairTrack: config.repairTrack },
17751
- altaEnabled: config.altaEnabled,
17752
17845
  relayBlockSigEnabled: config.relayBlockSigEnabled
17753
17846
  };
17754
17847
  }
@@ -17795,9 +17888,8 @@ function repairTrackNamesBySource(tracks) {
17795
17888
  bySource.set(source, names);
17796
17889
  };
17797
17890
  for (const track of tracks) {
17798
- if (track.fec !== void 0 && isFecCatalogConfig(track.fec) && track.fec.repairTrack) {
17799
- add(track.name, track.fec.repairTrack);
17800
- }
17891
+ const repairTrack = typeof track.fec === "object" && track.fec !== null ? track.fec.repairTrack : void 0;
17892
+ if (typeof repairTrack === "string") add(track.name, repairTrack);
17801
17893
  if (track.packaging === "fec-repair" && Array.isArray(track.depends) && track.depends.length === 1) {
17802
17894
  add(track.depends[0], track.name);
17803
17895
  }
@@ -17807,6 +17899,7 @@ function repairTrackNamesBySource(tracks) {
17807
17899
  var MulticastManager = class {
17808
17900
  #fec;
17809
17901
  #router;
17902
+ #hostMfuTimeoutMs;
17810
17903
  #transport;
17811
17904
  #reportedFecDiagnosticsFailure = false;
17812
17905
  // ── Stream-global FEC timing reduction ──
@@ -17905,6 +17998,7 @@ var MulticastManager = class {
17905
17998
  this.#emitRepairLifecycle(decision);
17906
17999
  }
17907
18000
  });
18001
+ this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
17908
18002
  this.#transport = new MulticastTransport((packet, ingress) => {
17909
18003
  this.#router.feedPacket(packet, "multicast", void 0, ingress);
17910
18004
  });
@@ -18121,7 +18215,6 @@ var MulticastManager = class {
18121
18215
  ...this.#streamFecBaseline ? [this.#streamFecBaseline] : [],
18122
18216
  ...this.#trackFecTiming.values()
18123
18217
  ];
18124
- if (contributions.length === 0) return;
18125
18218
  let deliveryWindowMs;
18126
18219
  let minimumFailbackWindowMs;
18127
18220
  let mfuTimeoutMs;
@@ -18153,15 +18246,9 @@ var MulticastManager = class {
18153
18246
  }
18154
18247
  }
18155
18248
  const failbackWindowMs = deliveryWindowMs === void 0 ? minimumFailbackWindowMs : Math.max(deliveryWindowMs, minimumFailbackWindowMs ?? 0);
18156
- if (failbackWindowMs !== void 0) {
18157
- this.#transport.setFailbackWindowMs(failbackWindowMs);
18158
- }
18159
- if (mfuTimeoutMs !== void 0) {
18160
- this.#router.setMfuTimeoutMs(mfuTimeoutMs);
18161
- }
18162
- if (reorderWindowFrames !== void 0) {
18163
- this.#router.setReorderWindow(reorderWindowFrames);
18164
- }
18249
+ this.#transport.setFailbackWindowMs(failbackWindowMs ?? null);
18250
+ this.#router.setMfuTimeoutMs(mfuTimeoutMs ?? this.#hostMfuTimeoutMs);
18251
+ this.#router.setReorderWindow(reorderWindowFrames ?? 0);
18165
18252
  }
18166
18253
  /**
18167
18254
  * Configure one track's FEC geometry, block mapper, and block-inactivity
@@ -18188,9 +18275,9 @@ var MulticastManager = class {
18188
18275
  `[MulticastManager] configureTrackFec(${packetId}) catalogTrackName must be non-empty when given`
18189
18276
  );
18190
18277
  }
18191
- if (config.fecK === void 0 || config.fecInterleaveDepthMs === void 0) {
18278
+ if (config.fecK === void 0) {
18192
18279
  throw new Error(
18193
- `[MulticastManager] configureTrackFec(${packetId}) requires fecK and fecInterleaveDepthMs. Derive the per-track FEC geometry from the stream catalog.`
18280
+ `[MulticastManager] configureTrackFec(${packetId}) requires fecK. Derive the per-track FEC geometry from the stream catalog.`
18194
18281
  );
18195
18282
  }
18196
18283
  const blockProfile = parseFecBlockProfile(
@@ -18198,7 +18285,10 @@ var MulticastManager = class {
18198
18285
  `[MulticastManager] configureTrackFec(${packetId}) fecBlockProfile`
18199
18286
  );
18200
18287
  assertPositiveGeometry("fecK", config.fecK);
18201
- assertPositiveGeometry("fecInterleaveDepthMs", config.fecInterleaveDepthMs);
18288
+ const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
18289
+ if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
18290
+ throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
18291
+ }
18202
18292
  assertPositiveGeometry("fecSymbolSize", config.fecSymbolSize);
18203
18293
  assertPositiveGeometry("fps", config.fps);
18204
18294
  assertPositiveGeometry("bandwidth", config.bandwidth);
@@ -18214,8 +18304,8 @@ var MulticastManager = class {
18214
18304
  "fecReorderToleranceMs",
18215
18305
  config.fecReorderToleranceMs
18216
18306
  );
18217
- const deliveryWindowMs = config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
18218
- interleaveDepthMs: config.fecInterleaveDepthMs,
18307
+ const deliveryWindowMs = interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
18308
+ interleaveDepthMs,
18219
18309
  sourceSymbols: config.fecK,
18220
18310
  repairSymbols: config.fecRepairSymbols,
18221
18311
  symbolSize: config.fecSymbolSize,
@@ -18224,10 +18314,10 @@ var MulticastManager = class {
18224
18314
  jitterBufferMs: config.fecReorderToleranceMs
18225
18315
  }) : void 0;
18226
18316
  const trackKind = config.trackKind ?? this.#router.trackKindFor(packetId);
18227
- const geometry = trackKind === void 0 ? void 0 : deriveBlockClassifierGeometry({
18317
+ const geometry = trackKind === void 0 || interleaveDepthMs === 0 ? void 0 : deriveBlockClassifierGeometry({
18228
18318
  trackKind,
18229
18319
  framerate: config.fps,
18230
- interleaveDepthMs: config.fecInterleaveDepthMs,
18320
+ interleaveDepthMs,
18231
18321
  timescale: config.timescale,
18232
18322
  keyframeIntervalTicks: config.keyframeIntervalTicks,
18233
18323
  keyframeIntervalMs: config.keyframeIntervalMs
@@ -18236,7 +18326,7 @@ var MulticastManager = class {
18236
18326
  blockProfile,
18237
18327
  k: config.fecK,
18238
18328
  symbolSize: config.fecSymbolSize,
18239
- interleaveDepthMs: config.fecInterleaveDepthMs,
18329
+ interleaveDepthMs,
18240
18330
  fps: config.fps,
18241
18331
  timeoutMs: deliveryWindowMs,
18242
18332
  ...trackKind === void 0 ? {} : { trackKind },
@@ -18247,7 +18337,7 @@ var MulticastManager = class {
18247
18337
  this.#configureTrackMapper(packetId, fecConfig);
18248
18338
  this.#trackFecTiming.set(packetId, {
18249
18339
  deliveryWindowMs,
18250
- interleaveDepthMs: config.fecInterleaveDepthMs,
18340
+ interleaveDepthMs,
18251
18341
  fps: config.fps,
18252
18342
  k: config.fecK,
18253
18343
  repairSymbols: config.fecRepairSymbols,
@@ -18256,6 +18346,10 @@ var MulticastManager = class {
18256
18346
  this.#applyStreamGlobalFecTiming();
18257
18347
  }
18258
18348
  configureFec(config) {
18349
+ const interleaveDepthMs = config.fecInterleaveDepthMs ?? 0;
18350
+ if (!Number.isSafeInteger(interleaveDepthMs) || interleaveDepthMs < 0) {
18351
+ throw new Error("[MulticastManager] fecInterleaveDepthMs must be a non-negative safe integer");
18352
+ }
18259
18353
  const minimumFailbackWindowMs = config.minimumFailbackWindowMs;
18260
18354
  if (minimumFailbackWindowMs !== void 0 && (!Number.isFinite(minimumFailbackWindowMs) || minimumFailbackWindowMs <= 0)) {
18261
18355
  throw new Error(
@@ -18267,7 +18361,7 @@ var MulticastManager = class {
18267
18361
  config.fecReorderToleranceMs
18268
18362
  );
18269
18363
  const deliveryWindowMs = config.fecK && config.fecK > 0 && config.fecInterleaveDepthMs && config.fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS ? computeFecDeliveryWindowMs({
18270
- interleaveDepthMs: config.fecInterleaveDepthMs,
18364
+ interleaveDepthMs,
18271
18365
  sourceSymbols: config.fecK,
18272
18366
  repairSymbols: config.fecRepairSymbols,
18273
18367
  symbolSize: config.fecSymbolSize,
@@ -18278,13 +18372,13 @@ var MulticastManager = class {
18278
18372
  this.#fec.configure({
18279
18373
  k: config.fecK,
18280
18374
  symbolSize: config.fecSymbolSize,
18281
- interleaveDepthMs: config.fecInterleaveDepthMs,
18375
+ interleaveDepthMs,
18282
18376
  fps: config.fps,
18283
18377
  timeoutMs: deliveryWindowMs
18284
18378
  });
18285
18379
  this.#streamFecBaseline = {
18286
18380
  deliveryWindowMs,
18287
- interleaveDepthMs: config.fecInterleaveDepthMs,
18381
+ interleaveDepthMs,
18288
18382
  fps: config.fps,
18289
18383
  k: config.fecK,
18290
18384
  repairSymbols: config.fecRepairSymbols,
@@ -18333,6 +18427,7 @@ var MulticastManager = class {
18333
18427
  }
18334
18428
  setMfuTimeoutMs(ms) {
18335
18429
  this.#router.setMfuTimeoutMs(ms);
18430
+ this.#hostMfuTimeoutMs = this.#router.reassembler.timeoutMs;
18336
18431
  }
18337
18432
  setTrackCodec(packetId, codec) {
18338
18433
  this.#router.setTrackCodec(packetId, codec);
@@ -18355,6 +18450,7 @@ var MulticastManager = class {
18355
18450
  onTrackPacket(packetId, cb) {
18356
18451
  this.#router.onTrackPacket(packetId, cb);
18357
18452
  }
18453
+ /** @deprecated Use onTrackInit(packetId, cb); see MmtpRouter.onInitSegment. */
18358
18454
  onInitSegment(cb) {
18359
18455
  this.#router.onInitSegment(cb);
18360
18456
  }
@@ -18427,7 +18523,7 @@ var MulticastManager = class {
18427
18523
  )
18428
18524
  )
18429
18525
  };
18430
- const amtRelay = config.amtRelay ?? config.relayAddress;
18526
+ const amtRelay = config.amtRelay;
18431
18527
  const transportConfig = {
18432
18528
  sourceAddress: config.sourceAddress,
18433
18529
  groupAddress: config.groupAddress,
@@ -18440,9 +18536,8 @@ var MulticastManager = class {
18440
18536
  // endpoint-config.ts) deliberately has no implicit relay port — it throws
18441
18537
  // and `MulticastTransport.join` resolves false — so the default is
18442
18538
  // resolved HERE and arrives there explicit, exactly as the two catalog
18443
- // readers do it. This is the last of the three readers: the `relayAddress`
18444
- // half cannot supply a port at all (the pre-migration wire shape has no
18445
- // port field), and `resolveNetworkSource` emits the same port-less shape.
18539
+ // readers do it. This is the last of the three readers: `resolveNetworkSource`
18540
+ // emits a port-less relay shape.
18446
18541
  // Keyed on the resolved relay, so a DRIAD entry — which pins no relay —
18447
18542
  // never has a port manufactured for it. Blank counts as no relay, the
18448
18543
  // same test `catalogAmtNetworkSource` applies (it trims, then skips) and
@@ -18666,7 +18761,10 @@ var MulticastManager = class {
18666
18761
  console.warn("[MulticastManager] rendition switch onCancel failed", error);
18667
18762
  }
18668
18763
  }
18669
- /** Release what the manager and FEC hold for a stream whose packet ID the router retired. */
18764
+ /**
18765
+ * Release what the manager and FEC hold for a stream whose packet ID the
18766
+ * router retired, or whose FEC a catalog reconfiguration no longer configures.
18767
+ */
18670
18768
  #releaseRetiredStream(stream) {
18671
18769
  for (const observer of this.#trackResetObservers) {
18672
18770
  try {
@@ -18685,7 +18783,10 @@ var MulticastManager = class {
18685
18783
  for (const [key, stats] of [...this.#blockPolicyStats]) {
18686
18784
  if (stats.packetId === stream.packetId) this.#blockPolicyStats.delete(key);
18687
18785
  }
18688
- this.#trackFecTiming.delete(stream.packetId);
18786
+ if (this.#trackFecTiming.delete(stream.packetId)) {
18787
+ this.#applyStreamGlobalFecTiming();
18788
+ }
18789
+ this.#multiTrackFecTracks.delete(stream.packetId);
18689
18790
  this.#fec.releasePacket(stream.packetId);
18690
18791
  }
18691
18792
  /** Feed a raw packet from an external source (AMT tunnel, ASM subscription).
@@ -18730,12 +18831,53 @@ var MulticastManager = class {
18730
18831
  * D-17: Builds altGroup → track-name index for getSwitchingSet().
18731
18832
  * D-26: Registers packetId → TrackRole on MmtpRouter for block-complete fanout.
18732
18833
  *
18834
+ * FEC availability is per track (draft-ramadan-moq-fec §5.1). A media track
18835
+ * whose `fec` names no implemented `blockProfile` (only `iso-ssbg1-v1` is
18836
+ * implemented; the retired `k-padded-v1` is not), or an `iso-ssbg1-v1` span
18837
+ * it cannot use, is consumed without FEC: it gets no decoder, and none of
18838
+ * its repair tracks (layer 0 by `fec.repairTrack`, further layers and the
18839
+ * overlay by `depends`) is given an owner, so the router drops their packets
18840
+ * before any decoder. Those legs keep the `repair` role and their owner's
18841
+ * switching set. Neither the call nor any other track's FEC fails on that
18842
+ * account; `trackFecUnavailable(name)` reports the reason. FEC a previous
18843
+ * call configured on a packetId this call does not is released
18844
+ * (FECManager.releasePacket); frames its release gate held are delivered in
18845
+ * order, each gap stalled. FEC wired by hand (setFecDecoder) is left alone.
18846
+ * Geometry a decoder could not configure (K, P, T, `repairTrack`,
18847
+ * algorithm) still throws.
18848
+ *
18733
18849
  * @param catalog - Multi-track catalog (tracks array with name, altGroup, fec, packetId).
18734
18850
  * @param wasmDecoderSource - One WASM FEC decoder for a single FEC stream, or
18735
18851
  * a factory returning a distinct decoder per stream.
18736
18852
  */
18737
18853
  configureMultiTrack(catalog, wasmDecoderSource) {
18738
18854
  const repairNamesBySource = repairTrackNamesBySource(catalog.tracks);
18855
+ const declaredRepairNames = /* @__PURE__ */ new Set();
18856
+ for (const names of repairNamesBySource.values()) {
18857
+ for (const name of names) declaredRepairNames.add(name);
18858
+ }
18859
+ const fecUnavailableByTrack = /* @__PURE__ */ new Map();
18860
+ for (let i = 0; i < catalog.tracks.length; i++) {
18861
+ const track = catalog.tracks[i];
18862
+ if (track.fec === void 0 || track.packaging === "fec-repair" || declaredRepairNames.has(track.name)) {
18863
+ continue;
18864
+ }
18865
+ const fecUnavailable = catalogFecUnavailable(track.fec, `$.tracks[${i}].fec`);
18866
+ if (fecUnavailable === void 0) continue;
18867
+ fecUnavailableByTrack.set(track.name, fecUnavailable);
18868
+ const layerZero = track.fec.repairTrack;
18869
+ if (typeof layerZero === "string" && layerZero.length > 0) {
18870
+ const names = repairNamesBySource.get(track.name) ?? /* @__PURE__ */ new Set();
18871
+ names.add(layerZero);
18872
+ repairNamesBySource.set(track.name, names);
18873
+ }
18874
+ }
18875
+ const unownedRepairNames = /* @__PURE__ */ new Set();
18876
+ for (const sourceName of fecUnavailableByTrack.keys()) {
18877
+ for (const repairName of repairNamesBySource.get(sourceName) ?? []) {
18878
+ unownedRepairNames.add(repairName);
18879
+ }
18880
+ }
18739
18881
  const repairOwnerGroup = /* @__PURE__ */ new Map();
18740
18882
  const repairOwnerAltGroup = /* @__PURE__ */ new Map();
18741
18883
  for (const track of catalog.tracks) {
@@ -18788,11 +18930,13 @@ var MulticastManager = class {
18788
18930
  for (let i = 0; i < catalog.tracks.length; i++) {
18789
18931
  const track = catalog.tracks[i];
18790
18932
  if (resolvedRoles[i] === "repair" || track.fec === void 0) continue;
18791
- if (!isFecCatalogConfig(track.fec)) {
18933
+ const unavailable = fecUnavailableByTrack.has(track.name);
18934
+ if (!(unavailable ? isFecCatalogGeometry(track.fec) : isFecCatalogConfig(track.fec))) {
18792
18935
  throw new Error(
18793
18936
  `[MulticastManager] invalid fec config for track '${track.name}'`
18794
18937
  );
18795
18938
  }
18939
+ if (unavailable) continue;
18796
18940
  if (track.packetId !== void 0) fecTrackIndexes.push(i);
18797
18941
  }
18798
18942
  const fecDecoders = /* @__PURE__ */ new Map();
@@ -18821,6 +18965,20 @@ var MulticastManager = class {
18821
18965
  this.#switchingSets.clear();
18822
18966
  this.#repairPacketIds.clear();
18823
18967
  this.#router.clearRepairPacketIdOwners();
18968
+ const fecPacketIds = new Set([...fecDecoders.keys()].map((i) => catalog.tracks[i].packetId));
18969
+ for (const [packetId, catalogTrackName] of this.#multiTrackFecTracks) {
18970
+ if (fecPacketIds.has(packetId)) continue;
18971
+ if (!this.#fec.isTrackConfigured(packetId)) {
18972
+ this.#multiTrackFecTracks.delete(packetId);
18973
+ continue;
18974
+ }
18975
+ this.#router.releaseFecPacket(packetId);
18976
+ this.#releaseRetiredStream({ packetId, catalogTrackName });
18977
+ }
18978
+ this.#fecUnavailableByTrack.clear();
18979
+ for (const [name, reason] of fecUnavailableByTrack) {
18980
+ this.#fecUnavailableByTrack.set(name, reason);
18981
+ }
18824
18982
  const catalogTracksByName = new Map(
18825
18983
  catalog.tracks.map((track) => [track.name, track])
18826
18984
  );
@@ -18842,10 +19000,11 @@ var MulticastManager = class {
18842
19000
  }
18843
19001
  continue;
18844
19002
  }
18845
- if (track.fec !== void 0) {
19003
+ if (track.fec !== void 0 && !fecUnavailableByTrack.has(track.name)) {
18846
19004
  const fecCfg = track.fec;
18847
19005
  if (track.packetId !== void 0) {
18848
19006
  for (const repairName of repairNamesBySource.get(track.name) ?? []) {
19007
+ if (unownedRepairNames.has(repairName)) continue;
18849
19008
  const repairTrack = catalogTracksByName.get(repairName);
18850
19009
  if (repairTrack?.packetId !== void 0) {
18851
19010
  this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
@@ -18866,17 +19025,18 @@ var MulticastManager = class {
18866
19025
  blockProfile,
18867
19026
  k: fecCfg.sourceSymbols,
18868
19027
  symbolSize: fecCfg.symbolSize,
18869
- interleaveDepthMs: fecCfg.interleaveDepthMs,
19028
+ interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
18870
19029
  fps: track.framerate,
18871
19030
  trackKind: "video"
18872
19031
  },
18873
19032
  track.name
18874
19033
  );
19034
+ this.#multiTrackFecTracks.set(track.packetId, track.name);
18875
19035
  this.#configureTrackMapper(track.packetId, {
18876
19036
  blockProfile,
18877
19037
  k: fecCfg.sourceSymbols,
18878
19038
  symbolSize: fecCfg.symbolSize,
18879
- interleaveDepthMs: fecCfg.interleaveDepthMs,
19039
+ interleaveDepthMs: fecCfg.interleaveDepthMs ?? 0,
18880
19040
  fps: track.framerate
18881
19041
  });
18882
19042
  this.#router.setPacketIdType(track.packetId, "video");
@@ -18884,6 +19044,24 @@ var MulticastManager = class {
18884
19044
  }
18885
19045
  }
18886
19046
  }
19047
+ /**
19048
+ * Why `configureMultiTrack` consumes the named catalog track without FEC
19049
+ * although its `fec` block declares one (draft-ramadan-moq-fec §5.1): no
19050
+ * implemented `blockProfile` (`track_fec_block_profile_unimplemented`, e.g.
19051
+ * the retired `k-padded-v1`; `track_fec_block_profile` when absent), or an
19052
+ * invalid `iso-ssbg1-v1` span (`track_fec`). Absent/zero spans keep in-band
19053
+ * decoding enabled. The same reason the hang
19054
+ * resolver reports (`catalogFecUnavailable`). Undefined for every other
19055
+ * track, and for every track before the first call; each call replaces the
19056
+ * previous call's answers.
19057
+ */
19058
+ trackFecUnavailable(trackName) {
19059
+ return this.#fecUnavailableByTrack.get(trackName);
19060
+ }
19061
+ /** Per-track FEC availability from the last `configureMultiTrack` call. */
19062
+ #fecUnavailableByTrack = /* @__PURE__ */ new Map();
19063
+ /** packetId → catalog track name of the FEC the last `configureMultiTrack` installed. */
19064
+ #multiTrackFecTracks = /* @__PURE__ */ new Map();
18887
19065
  /**
18888
19066
  * Return the track names belonging to the given altGroup switching set.
18889
19067
  * D-17: altGroup groups base+delta+repair into one switching set.
@@ -18931,7 +19109,17 @@ var MulticastManager = class {
18931
19109
  }
18932
19110
  /** Record a repair request the scheduler sent no FETCH for. */
18933
19111
  noteRepairSkipped(reason, measurement, sbn, packetId, catalogTrackName) {
18934
- this.#statsFor(packetId, catalogTrackName).repairsSkipped++;
19112
+ const stats = this.#statsFor(packetId, catalogTrackName);
19113
+ stats.repairsSkipped++;
19114
+ if (reason === "rtt-unmeasured") stats.repairsSkippedRttUnmeasured++;
19115
+ else if (reason === "rtt-exceeds-budget") stats.repairsSkippedRttExceedsBudget++;
19116
+ else stats.repairsSkippedPresentationDelayUnavailable++;
19117
+ stats.lastRepairSkip = {
19118
+ reason,
19119
+ rttMs: measurement.rttMs,
19120
+ budgetMs: measurement.budgetMs,
19121
+ priority: measurement.priority ?? null
19122
+ };
18935
19123
  this.#emitRepairLifecycle({
18936
19124
  version: 2,
18937
19125
  type: "repair-skipped",
@@ -18941,6 +19129,24 @@ var MulticastManager = class {
18941
19129
  budgetMs: measurement.budgetMs
18942
19130
  });
18943
19131
  }
19132
+ /**
19133
+ * Arrival time of the earliest frame the in-order release gate holds for
19134
+ * the stream: the gate releases it as a discontinuity once
19135
+ * `terminalRetentionMsForPacket` has passed since then.
19136
+ */
19137
+ earliestGatedFrameAtMs(packetId, catalogTrackName) {
19138
+ return this.#router.fecReleaseEarliestHeldAtMs(packetId, catalogTrackName);
19139
+ }
19140
+ /** Record a keyframe late-repair budget the scheduler granted a block. */
19141
+ noteKeyframeBudgetGranted(extensionMs, _sbn, packetId, catalogTrackName) {
19142
+ const stats = this.#statsFor(packetId, catalogTrackName);
19143
+ stats.keyframeBudgetHolds++;
19144
+ stats.keyframeBudgetHoldMs += extensionMs;
19145
+ }
19146
+ /** Record a repair FETCH the scheduler admitted and sent. */
19147
+ noteRepairFetchSent(priority, _sbn, packetId, catalogTrackName) {
19148
+ if (priority === 0) this.#statsFor(packetId, catalogTrackName).keyframeRepairFetchesSent++;
19149
+ }
18944
19150
  /**
18945
19151
  * Record a repair-scheduler decision about a delta block: preempted for
18946
19152
  * keyframe/audio work, or never dispatched before its block resolved.
@@ -18968,7 +19174,10 @@ var MulticastManager = class {
18968
19174
  const out = {};
18969
19175
  for (const [key, stats] of this.#blockPolicyStats) {
18970
19176
  if (stats.epoch !== this.#fec.epochFor(stats.packetId)) continue;
18971
- out[key] = { ...stats };
19177
+ out[key] = {
19178
+ ...stats,
19179
+ lastRepairSkip: stats.lastRepairSkip === null ? null : { ...stats.lastRepairSkip }
19180
+ };
18972
19181
  }
18973
19182
  return out;
18974
19183
  }