@videojs/spf 10.0.0-beta.31 → 10.0.0-beta.32

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 (520) hide show
  1. package/dist/default/core/actors/create-machine-actor.js +23 -25
  2. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  3. package/dist/default/core/actors/create-transition-actor.js +7 -9
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  5. package/dist/default/core/composition/create-composition.js +26 -29
  6. package/dist/default/core/composition/create-composition.js.map +1 -1
  7. package/dist/default/core/composition/share-signals.js +12 -17
  8. package/dist/default/core/composition/share-signals.js.map +1 -1
  9. package/dist/default/core/machine.js +3 -4
  10. package/dist/default/core/machine.js.map +1 -1
  11. package/dist/default/core/reactors/create-machine-reactor.js +20 -19
  12. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  13. package/dist/default/core/signals/effect.js +24 -4
  14. package/dist/default/core/signals/effect.js.map +1 -1
  15. package/dist/default/core/signals/primitives.js +10 -14
  16. package/dist/default/core/signals/primitives.js.map +1 -1
  17. package/dist/default/core/signals/when.js +6 -8
  18. package/dist/default/core/signals/when.js.map +1 -1
  19. package/dist/default/core/tasks/delayed-reschedule.js +7 -11
  20. package/dist/default/core/tasks/delayed-reschedule.js.map +1 -1
  21. package/dist/default/core/tasks/task.js +59 -82
  22. package/dist/default/core/tasks/task.js.map +1 -1
  23. package/dist/default/hls.js +3 -1
  24. package/dist/default/index.js +2 -3
  25. package/dist/default/index.js.map +1 -1
  26. package/dist/default/media/abr/quality-selection.js +3 -7
  27. package/dist/default/media/abr/quality-selection.js.map +1 -1
  28. package/dist/default/media/buffer/back-buffer.js +15 -19
  29. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  30. package/dist/default/media/buffer/forward-buffer.js +32 -41
  31. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  32. package/dist/default/media/dom/capabilities.js +26 -37
  33. package/dist/default/media/dom/capabilities.js.map +1 -1
  34. package/dist/default/media/dom/mse/append-segment.js +3 -5
  35. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  36. package/dist/default/media/dom/mse/buffer-flusher.js +5 -5
  37. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  38. package/dist/default/media/dom/mse/duration.js +23 -31
  39. package/dist/default/media/dom/mse/duration.js.map +1 -1
  40. package/dist/default/media/dom/mse/end-of-stream.js +3 -6
  41. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  42. package/dist/default/media/dom/mse/mediasource-setup.js +65 -81
  43. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  44. package/dist/default/media/dom/screen.js +37 -54
  45. package/dist/default/media/dom/screen.js.map +1 -1
  46. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  47. package/dist/default/media/dom/text/text-track-slots.js +15 -23
  48. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  49. package/dist/default/media/errors.js +25 -34
  50. package/dist/default/media/errors.js.map +1 -1
  51. package/dist/default/media/hls/parse-attributes.js +9 -26
  52. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  53. package/dist/default/media/hls/parse-media-playlist.js +25 -33
  54. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  55. package/dist/default/media/hls/parse-multivariant.js +3 -3
  56. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  57. package/dist/default/media/hls/reload-policy.js +19 -27
  58. package/dist/default/media/hls/reload-policy.js.map +1 -1
  59. package/dist/default/media/hls/resolve-url.js +1 -3
  60. package/dist/default/media/hls/resolve-url.js.map +1 -1
  61. package/dist/default/media/live-window.js +9 -13
  62. package/dist/default/media/live-window.js.map +1 -1
  63. package/dist/default/media/media-tracks/media-tracks.js +12 -17
  64. package/dist/default/media/media-tracks/media-tracks.js.map +1 -1
  65. package/dist/default/media/mp4/box.js +4 -5
  66. package/dist/default/media/mp4/box.js.map +1 -1
  67. package/dist/default/media/mp4/timestamp-origin.js +30 -42
  68. package/dist/default/media/mp4/timestamp-origin.js.map +1 -1
  69. package/dist/default/media/primitives/resolution.js +5 -8
  70. package/dist/default/media/primitives/resolution.js.map +1 -1
  71. package/dist/default/media/primitives/select-tracks.js +24 -34
  72. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  73. package/dist/default/media/text/parse-vtt-timestamp-map.js +6 -8
  74. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -1
  75. package/dist/default/media/text/resolve-vtt-metadata.js +8 -9
  76. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -1
  77. package/dist/default/media/types/index.js +10 -18
  78. package/dist/default/media/types/index.js.map +1 -1
  79. package/dist/default/media/utils/cdn.js +10 -16
  80. package/dist/default/media/utils/cdn.js.map +1 -1
  81. package/dist/default/media/utils/preload.js +4 -6
  82. package/dist/default/media/utils/preload.js.map +1 -1
  83. package/dist/default/media/utils/track-selection.js +10 -14
  84. package/dist/default/media/utils/track-selection.js.map +1 -1
  85. package/dist/default/media/utils/tracks.js +56 -54
  86. package/dist/default/media/utils/tracks.js.map +1 -1
  87. package/dist/default/network/bandwidth-estimator.js +18 -19
  88. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  89. package/dist/default/network/chunked-stream-iterable.js +4 -6
  90. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  91. package/dist/default/network/ewma.js +22 -25
  92. package/dist/default/network/ewma.js.map +1 -1
  93. package/dist/default/network/fetch.js +30 -37
  94. package/dist/default/network/fetch.js.map +1 -1
  95. package/dist/default/playback/actors/dom/segment-loader.js +28 -44
  96. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  97. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  98. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  99. package/dist/default/playback/actors/text-track-segment-loader.js +24 -35
  100. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  101. package/dist/default/playback/adapters/hls-audio/adapter.js +23 -31
  102. package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -1
  103. package/dist/default/playback/adapters/hls-audio/media.js.map +1 -1
  104. package/dist/default/playback/adapters/hls-background-video/adapter.js +50 -70
  105. package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -1
  106. package/dist/default/playback/adapters/hls-background-video/host.js +12 -16
  107. package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -1
  108. package/dist/default/playback/adapters/hls-background-video/media.js +5 -8
  109. package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -1
  110. package/dist/default/playback/adapters/hls-video/adapter.js +43 -60
  111. package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -1
  112. package/dist/default/playback/adapters/hls-video/error-surface.js +13 -18
  113. package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -1
  114. package/dist/default/playback/adapters/hls-video/media-tracks.js +4 -5
  115. package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -1
  116. package/dist/default/playback/adapters/hls-video/media.js.map +1 -1
  117. package/dist/default/playback/adapters/mux-audio/media.js +8 -12
  118. package/dist/default/playback/adapters/mux-audio/media.js.map +1 -1
  119. package/dist/default/playback/adapters/mux-video/adapter.js +25 -33
  120. package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -1
  121. package/dist/default/playback/adapters/mux-video/media.js +7 -9
  122. package/dist/default/playback/adapters/mux-video/media.js.map +1 -1
  123. package/dist/default/playback/behaviors/calculate-presentation-duration.js +5 -8
  124. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  125. package/dist/default/playback/behaviors/collect-errors.js +39 -52
  126. package/dist/default/playback/behaviors/collect-errors.js.map +1 -1
  127. package/dist/default/playback/behaviors/derive-cdn-priority.js +16 -23
  128. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
  129. package/dist/default/playback/behaviors/dom/airplay.js +38 -61
  130. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -1
  131. package/dist/default/playback/behaviors/dom/apply-start-position.js +23 -35
  132. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -1
  133. package/dist/default/playback/behaviors/dom/end-of-stream.js +11 -16
  134. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  135. package/dist/default/playback/behaviors/dom/load-segments.js +27 -39
  136. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  137. package/dist/default/playback/behaviors/dom/recover-end-stall.js +21 -26
  138. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -1
  139. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +41 -60
  140. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -1
  141. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +50 -74
  142. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  143. package/dist/default/playback/behaviors/dom/setup-mediasource.js +29 -43
  144. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  145. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  146. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +3 -4
  147. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -1
  148. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +30 -45
  149. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  150. package/dist/default/playback/behaviors/dom/track-current-time.js +9 -11
  151. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  152. package/dist/default/playback/behaviors/dom/track-load-triggers.js +24 -32
  153. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  154. package/dist/default/playback/behaviors/dom/track-playback-rate.js +6 -8
  155. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  156. package/dist/default/playback/behaviors/dom/track-player-resolution.js +10 -14
  157. package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -1
  158. package/dist/default/playback/behaviors/dom/track-screen-resolution.js +8 -9
  159. package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -1
  160. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +28 -43
  161. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  162. package/dist/default/playback/behaviors/establish-start-media-time.js +32 -42
  163. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -1
  164. package/dist/default/playback/behaviors/resolve-presentation.js +13 -22
  165. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  166. package/dist/default/playback/behaviors/resolve-track.js +5 -12
  167. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  168. package/dist/default/playback/behaviors/select-tracks.js +77 -104
  169. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  170. package/dist/default/playback/behaviors/setup-failover-monitor.js +11 -16
  171. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
  172. package/dist/default/playback/behaviors/sync-preload.js +9 -13
  173. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  174. package/dist/default/playback/behaviors/track-switching.js +189 -173
  175. package/dist/default/playback/behaviors/track-switching.js.map +1 -1
  176. package/dist/default/playback/engines/hls/engine-audio-only.js +20 -24
  177. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
  178. package/dist/default/playback/engines/hls/engine-background-video.js +22 -27
  179. package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -1
  180. package/dist/default/playback/engines/hls/engine.js +22 -25
  181. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  182. package/dist/default/playback/primitives/error-messages.js +16 -25
  183. package/dist/default/playback/primitives/error-messages.js.map +1 -1
  184. package/dist/default/playback/primitives/failover-fetch.js +9 -14
  185. package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
  186. package/dist/default/playback/primitives/head-peek.js +7 -10
  187. package/dist/default/playback/primitives/head-peek.js.map +1 -1
  188. package/dist/default/playback/primitives/live-window.js +12 -15
  189. package/dist/default/playback/primitives/live-window.js.map +1 -1
  190. package/dist/default/playback/primitives/relocation-pipelines.js +34 -46
  191. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -1
  192. package/dist/default/playback/primitives/report-track-conditions.js +26 -38
  193. package/dist/default/playback/primitives/report-track-conditions.js.map +1 -1
  194. package/dist/default/playback/primitives/segment-load-pipeline.js +15 -15
  195. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -1
  196. package/dist/default/playback/primitives/selection-rules.js +65 -36
  197. package/dist/default/playback/primitives/selection-rules.js.map +1 -1
  198. package/dist/default/playback/primitives/text-segment-load-pipeline.js +4 -5
  199. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -1
  200. package/dist/default/playback/primitives/track-types.js +10 -18
  201. package/dist/default/playback/primitives/track-types.js.map +1 -1
  202. package/dist/dev/core/actors/actor.d.ts +4 -4
  203. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  204. package/dist/dev/core/actors/create-machine-actor.d.ts +38 -47
  205. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  206. package/dist/dev/core/actors/create-machine-actor.js +23 -25
  207. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  208. package/dist/dev/core/actors/create-transition-actor.d.ts +11 -15
  209. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  210. package/dist/dev/core/actors/create-transition-actor.js +7 -9
  211. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  212. package/dist/dev/core/composition/create-composition.d.ts +70 -102
  213. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  214. package/dist/dev/core/composition/create-composition.js +26 -29
  215. package/dist/dev/core/composition/create-composition.js.map +1 -1
  216. package/dist/dev/core/composition/share-signals.d.ts +17 -24
  217. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  218. package/dist/dev/core/composition/share-signals.js +12 -17
  219. package/dist/dev/core/composition/share-signals.js.map +1 -1
  220. package/dist/dev/core/machine.d.ts +4 -4
  221. package/dist/dev/core/machine.js +3 -4
  222. package/dist/dev/core/machine.js.map +1 -1
  223. package/dist/dev/core/reactors/create-machine-reactor.d.ts +33 -39
  224. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  225. package/dist/dev/core/reactors/create-machine-reactor.js +20 -19
  226. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  227. package/dist/dev/core/signals/effect.d.ts +3 -4
  228. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  229. package/dist/dev/core/signals/effect.js +24 -4
  230. package/dist/dev/core/signals/effect.js.map +1 -1
  231. package/dist/dev/core/signals/primitives.d.ts +10 -12
  232. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  233. package/dist/dev/core/signals/primitives.js +10 -14
  234. package/dist/dev/core/signals/primitives.js.map +1 -1
  235. package/dist/dev/core/signals/when.js +6 -8
  236. package/dist/dev/core/signals/when.js.map +1 -1
  237. package/dist/dev/core/tasks/delayed-reschedule.js +7 -11
  238. package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -1
  239. package/dist/dev/core/tasks/task.d.ts +51 -73
  240. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  241. package/dist/dev/core/tasks/task.js +59 -82
  242. package/dist/dev/core/tasks/task.js.map +1 -1
  243. package/dist/dev/hls.d.ts +3 -1
  244. package/dist/dev/hls.js +3 -1
  245. package/dist/dev/index.d.ts +2 -3
  246. package/dist/dev/index.d.ts.map +1 -1
  247. package/dist/dev/index.js +2 -3
  248. package/dist/dev/index.js.map +1 -1
  249. package/dist/dev/media/abr/quality-selection.d.ts +7 -10
  250. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  251. package/dist/dev/media/abr/quality-selection.js +3 -7
  252. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  253. package/dist/dev/media/buffer/back-buffer.d.ts +2 -7
  254. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  255. package/dist/dev/media/buffer/back-buffer.js +15 -19
  256. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  257. package/dist/dev/media/buffer/forward-buffer.d.ts +2 -7
  258. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  259. package/dist/dev/media/buffer/forward-buffer.js +32 -41
  260. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  261. package/dist/dev/media/dom/capabilities.js +26 -37
  262. package/dist/dev/media/dom/capabilities.js.map +1 -1
  263. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -5
  264. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  265. package/dist/dev/media/dom/mse/append-segment.js +3 -5
  266. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  267. package/dist/dev/media/dom/mse/buffer-flusher.d.ts +5 -5
  268. package/dist/dev/media/dom/mse/buffer-flusher.js +5 -5
  269. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  270. package/dist/dev/media/dom/mse/duration.js +23 -31
  271. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  272. package/dist/dev/media/dom/mse/end-of-stream.js +3 -6
  273. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  274. package/dist/dev/media/dom/mse/mediasource-setup.js +65 -81
  275. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  276. package/dist/dev/media/dom/screen.d.ts.map +1 -1
  277. package/dist/dev/media/dom/screen.js +37 -54
  278. package/dist/dev/media/dom/screen.js.map +1 -1
  279. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  280. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  281. package/dist/dev/media/dom/text/text-track-slots.d.ts +10 -16
  282. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  283. package/dist/dev/media/dom/text/text-track-slots.js +15 -23
  284. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  285. package/dist/dev/media/errors.d.ts +32 -46
  286. package/dist/dev/media/errors.d.ts.map +1 -1
  287. package/dist/dev/media/errors.js +25 -34
  288. package/dist/dev/media/errors.js.map +1 -1
  289. package/dist/dev/media/hls/parse-attributes.js +9 -26
  290. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  291. package/dist/dev/media/hls/parse-media-playlist.js +25 -33
  292. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  293. package/dist/dev/media/hls/parse-multivariant.js +3 -3
  294. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  295. package/dist/dev/media/hls/reload-policy.js +19 -27
  296. package/dist/dev/media/hls/reload-policy.js.map +1 -1
  297. package/dist/dev/media/hls/resolve-url.js +1 -3
  298. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  299. package/dist/dev/media/live-window.js +9 -13
  300. package/dist/dev/media/live-window.js.map +1 -1
  301. package/dist/dev/media/media-tracks/media-tracks.d.ts +8 -13
  302. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -1
  303. package/dist/dev/media/media-tracks/media-tracks.js +12 -17
  304. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -1
  305. package/dist/dev/media/mp4/box.js +4 -5
  306. package/dist/dev/media/mp4/box.js.map +1 -1
  307. package/dist/dev/media/mp4/timestamp-origin.js +30 -42
  308. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -1
  309. package/dist/dev/media/primitives/resolution.d.ts +6 -8
  310. package/dist/dev/media/primitives/resolution.d.ts.map +1 -1
  311. package/dist/dev/media/primitives/resolution.js +5 -8
  312. package/dist/dev/media/primitives/resolution.js.map +1 -1
  313. package/dist/dev/media/primitives/select-tracks.js +24 -34
  314. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  315. package/dist/dev/media/text/parse-vtt-timestamp-map.js +6 -8
  316. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -1
  317. package/dist/dev/media/text/resolve-vtt-metadata.js +8 -9
  318. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -1
  319. package/dist/dev/media/types/index.d.ts +106 -177
  320. package/dist/dev/media/types/index.d.ts.map +1 -1
  321. package/dist/dev/media/types/index.js +10 -18
  322. package/dist/dev/media/types/index.js.map +1 -1
  323. package/dist/dev/media/utils/cdn.d.ts +4 -6
  324. package/dist/dev/media/utils/cdn.d.ts.map +1 -1
  325. package/dist/dev/media/utils/cdn.js +10 -16
  326. package/dist/dev/media/utils/cdn.js.map +1 -1
  327. package/dist/dev/media/utils/preload.js +4 -6
  328. package/dist/dev/media/utils/preload.js.map +1 -1
  329. package/dist/dev/media/utils/track-selection.js +10 -14
  330. package/dist/dev/media/utils/track-selection.js.map +1 -1
  331. package/dist/dev/media/utils/tracks.js +56 -54
  332. package/dist/dev/media/utils/tracks.js.map +1 -1
  333. package/dist/dev/network/bandwidth-estimator.d.ts +7 -9
  334. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  335. package/dist/dev/network/bandwidth-estimator.js +18 -19
  336. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  337. package/dist/dev/network/chunked-stream-iterable.js +4 -6
  338. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  339. package/dist/dev/network/ewma.js +22 -25
  340. package/dist/dev/network/ewma.js.map +1 -1
  341. package/dist/dev/network/fetch.js +30 -37
  342. package/dist/dev/network/fetch.js.map +1 -1
  343. package/dist/dev/playback/actors/dom/segment-loader.d.ts +7 -11
  344. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  345. package/dist/dev/playback/actors/dom/segment-loader.js +28 -44
  346. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  347. package/dist/dev/playback/actors/dom/source-buffer.d.ts +5 -7
  348. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  349. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  350. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  351. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +10 -16
  352. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  353. package/dist/dev/playback/actors/text-track-segment-loader.js +24 -35
  354. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  355. package/dist/dev/playback/actors/text-tracks.d.ts +5 -8
  356. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  357. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +11 -14
  358. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -1
  359. package/dist/dev/playback/adapters/hls-audio/adapter.js +23 -31
  360. package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -1
  361. package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -1
  362. package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -1
  363. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +28 -39
  364. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -1
  365. package/dist/dev/playback/adapters/hls-background-video/adapter.js +50 -70
  366. package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -1
  367. package/dist/dev/playback/adapters/hls-background-video/host.d.ts +12 -16
  368. package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -1
  369. package/dist/dev/playback/adapters/hls-background-video/host.js +12 -16
  370. package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -1
  371. package/dist/dev/playback/adapters/hls-background-video/media.d.ts +5 -8
  372. package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -1
  373. package/dist/dev/playback/adapters/hls-background-video/media.js +5 -8
  374. package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -1
  375. package/dist/dev/playback/adapters/hls-video/adapter.d.ts +12 -14
  376. package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -1
  377. package/dist/dev/playback/adapters/hls-video/adapter.js +43 -60
  378. package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -1
  379. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +4 -5
  380. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -1
  381. package/dist/dev/playback/adapters/hls-video/error-surface.js +13 -18
  382. package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -1
  383. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +4 -5
  384. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -1
  385. package/dist/dev/playback/adapters/hls-video/media-tracks.js +4 -5
  386. package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -1
  387. package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -1
  388. package/dist/dev/playback/adapters/hls-video/media.js.map +1 -1
  389. package/dist/dev/playback/adapters/mux-audio/media.d.ts +8 -12
  390. package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -1
  391. package/dist/dev/playback/adapters/mux-audio/media.js +8 -12
  392. package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -1
  393. package/dist/dev/playback/adapters/mux-video/adapter.d.ts +9 -11
  394. package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -1
  395. package/dist/dev/playback/adapters/mux-video/adapter.js +25 -33
  396. package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -1
  397. package/dist/dev/playback/adapters/mux-video/media.d.ts +7 -9
  398. package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -1
  399. package/dist/dev/playback/adapters/mux-video/media.js +7 -9
  400. package/dist/dev/playback/adapters/mux-video/media.js.map +1 -1
  401. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +5 -8
  402. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  403. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +5 -8
  404. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  405. package/dist/dev/playback/behaviors/collect-errors.d.ts +2 -0
  406. package/dist/dev/playback/behaviors/collect-errors.js +39 -52
  407. package/dist/dev/playback/behaviors/collect-errors.js.map +1 -1
  408. package/dist/dev/playback/behaviors/derive-cdn-priority.js +16 -23
  409. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
  410. package/dist/dev/playback/behaviors/dom/airplay.js +38 -61
  411. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -1
  412. package/dist/dev/playback/behaviors/dom/apply-start-position.js +23 -35
  413. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -1
  414. package/dist/dev/playback/behaviors/dom/end-of-stream.js +11 -16
  415. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  416. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +5 -8
  417. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  418. package/dist/dev/playback/behaviors/dom/load-segments.js +27 -39
  419. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  420. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +21 -26
  421. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -1
  422. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +41 -60
  423. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -1
  424. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +50 -74
  425. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  426. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +29 -43
  427. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  428. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +3 -3
  429. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  430. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  431. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +3 -4
  432. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -1
  433. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +30 -45
  434. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  435. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +2 -2
  436. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  437. package/dist/dev/playback/behaviors/dom/track-current-time.js +9 -11
  438. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  439. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +6 -12
  440. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  441. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +24 -32
  442. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  443. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +2 -2
  444. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  445. package/dist/dev/playback/behaviors/dom/track-playback-rate.js +6 -8
  446. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  447. package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -1
  448. package/dist/dev/playback/behaviors/dom/track-player-resolution.js +10 -14
  449. package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -1
  450. package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +8 -9
  451. package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -1
  452. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +28 -43
  453. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  454. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +15 -19
  455. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -1
  456. package/dist/dev/playback/behaviors/establish-start-media-time.js +32 -42
  457. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -1
  458. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  459. package/dist/dev/playback/behaviors/resolve-presentation.js +13 -22
  460. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  461. package/dist/dev/playback/behaviors/resolve-track.js +5 -12
  462. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  463. package/dist/dev/playback/behaviors/select-tracks.d.ts +28 -39
  464. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
  465. package/dist/dev/playback/behaviors/select-tracks.js +77 -104
  466. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  467. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +1 -4
  468. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
  469. package/dist/dev/playback/behaviors/setup-failover-monitor.js +11 -16
  470. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
  471. package/dist/dev/playback/behaviors/sync-preload.js +9 -13
  472. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  473. package/dist/dev/playback/behaviors/track-switching.d.ts +110 -0
  474. package/dist/dev/playback/behaviors/track-switching.d.ts.map +1 -0
  475. package/dist/dev/playback/behaviors/track-switching.js +189 -173
  476. package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
  477. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +59 -72
  478. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
  479. package/dist/dev/playback/engines/hls/engine-audio-only.js +20 -24
  480. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
  481. package/dist/dev/playback/engines/hls/engine-background-video.d.ts +51 -78
  482. package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -1
  483. package/dist/dev/playback/engines/hls/engine-background-video.js +22 -27
  484. package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -1
  485. package/dist/dev/playback/engines/hls/engine.d.ts +115 -155
  486. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  487. package/dist/dev/playback/engines/hls/engine.js +22 -25
  488. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  489. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +3 -3
  490. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -1
  491. package/dist/dev/playback/primitives/error-messages.js +16 -25
  492. package/dist/dev/playback/primitives/error-messages.js.map +1 -1
  493. package/dist/dev/playback/primitives/failover-fetch.js +9 -14
  494. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
  495. package/dist/dev/playback/primitives/head-peek.js +7 -10
  496. package/dist/dev/playback/primitives/head-peek.js.map +1 -1
  497. package/dist/dev/playback/primitives/live-window.js +12 -15
  498. package/dist/dev/playback/primitives/live-window.js.map +1 -1
  499. package/dist/dev/playback/primitives/relocation-pipelines.js +34 -46
  500. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -1
  501. package/dist/dev/playback/primitives/report-track-conditions.d.ts +1 -4
  502. package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -1
  503. package/dist/dev/playback/primitives/report-track-conditions.js +26 -38
  504. package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -1
  505. package/dist/dev/playback/primitives/segment-load-pipeline.js +15 -15
  506. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -1
  507. package/dist/dev/playback/primitives/selection-rules.d.ts +45 -17
  508. package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -1
  509. package/dist/dev/playback/primitives/selection-rules.js +65 -36
  510. package/dist/dev/playback/primitives/selection-rules.js.map +1 -1
  511. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +6 -9
  512. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -1
  513. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +20 -29
  514. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -1
  515. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +4 -5
  516. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -1
  517. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -1
  518. package/dist/dev/playback/primitives/track-types.js +10 -18
  519. package/dist/dev/playback/primitives/track-types.js.map +1 -1
  520. package/package.json +25 -28
@@ -25,10 +25,9 @@ function writeContainer(slot, trackType, patch) {
25
25
  }));
26
26
  }
27
27
  /**
28
- * Resolve once `read()` returns a number. Shared by the A/V stamp (waits for the
29
- * `derive`d origin — immediate for per-type, the shared-`min` barrier for coordinated)
30
- * and the text step (waits for the primary A/V origin). No bound: for fMP4 the origin
31
- * always establishes.
28
+ * Resolve once `read()` returns a number. Shared by the A/V stamp (waits for the `derive`d origin — immediate for
29
+ * per-type, the shared-`min` barrier for coordinated) and the text step (waits for the primary A/V origin). No bound:
30
+ * for fMP4 the origin always establishes.
32
31
  */
33
32
  function awaitDefined(read) {
34
33
  return new Promise((resolve) => {
@@ -43,20 +42,18 @@ function awaitDefined(read) {
43
42
  });
44
43
  }
45
44
  /**
46
- * Relocation pipelines for one track type — a plain config `messagePipelines`.
47
- * Keyed by **track type** (`'video'` / `'audio'`), so ABR rungs of a type share the
48
- * origin (discover skips once the type's value is present). The steps read/write
49
- * `state.mediaContainerData[trackType]` via their call-time `deps`; the stamp applies
50
- * the same `derive` seam the reactor uses (pass the composition's resolved
51
- * `deriveStartMediaTime` so the buffer offset and the model's `startMediaTime` agree).
45
+ * Relocation pipelines for one track type — a plain config `messagePipelines`. Keyed by **track type** (`'video'` /
46
+ * `'audio'`), so ABR rungs of a type share the origin (discover skips once the type's value is present). The steps
47
+ * read/write `state.mediaContainerData[trackType]` via their call-time `deps`; the stamp applies the same `derive` seam
48
+ * the reactor uses (pass the composition's resolved `deriveStartMediaTime` so the buffer offset and the model's
49
+ * `startMediaTime` agree).
52
50
  */
53
51
  function relocationPipelinesFor(trackType, derive) {
54
52
  const handlerType = trackType === "video" ? "vide" : "soun";
55
53
  /**
56
- * Init step: head-peek the buffered media track's `track_id` + `mdhd` timescale into
57
- * `mediaContainerData[trackType]`. Matching by handler (`vide`/`soun`) skips a muxed
58
- * `clcp` caption track, and the `track_id` lets `readSegmentOrigin` read *this*
59
- * track's `tfdt` rather than the first `traf` in the segment.
54
+ * Init step: head-peek the buffered media track's `track_id` + `mdhd` timescale into `mediaContainerData[trackType]`.
55
+ * Matching by handler (`vide`/`soun`) skips a muxed `clcp` caption track, and the `track_id` lets `readSegmentOrigin`
56
+ * read _this_ track's `tfdt` rather than the first `traf` in the segment.
60
57
  */
61
58
  const readInitTrackInfo = async (frame, _signal, deps) => {
62
59
  const { op } = frame;
@@ -74,12 +71,10 @@ function relocationPipelinesFor(trackType, derive) {
74
71
  });
75
72
  };
76
73
  /**
77
- * Media-segment step: head-peek the `tfdt` baseMediaDecodeTime of the media track's
78
- * `traf` (matched by the `track_id` discovered from the init), recording the
79
- * segment's 0-based `startTime` with it the origin is `bmdt/ts segmentStartTime`,
80
- * so the first *loaded* segment need not be the 0th. Without a discovered `track_id`
81
- * (non-fMP4 / mock init) there's no media track to relocate, so the step no-ops and
82
- * the append stays native.
74
+ * Media-segment step: head-peek the `tfdt` baseMediaDecodeTime of the media track's `traf` (matched by the `track_id`
75
+ * discovered from the init), recording the segment's 0-based `startTime` with it the origin is `bmdt/ts −
76
+ * segmentStartTime`, so the first _loaded_ segment need not be the 0th. Without a discovered `track_id` (non-fMP4 /
77
+ * mock init) there's no media track to relocate, so the step no-ops and the append stays native.
83
78
  */
84
79
  const readSegmentOrigin = async (frame, _signal, deps) => {
85
80
  const { op } = frame;
@@ -101,15 +96,13 @@ function relocationPipelinesFor(trackType, derive) {
101
96
  });
102
97
  };
103
98
  /**
104
- * Stamp step — tier-agnostic apply. Relocate by the `derive`d `startMediaTime` for
105
- * this type (`offset = −startMediaTime`). Applies the **same** `derive` the reactor
106
- * uses, over the shared `mediaContainerData` slot so the buffer offset matches the
107
- * model's stamped `startMediaTime`, and it's robust to `established` + late tracks
108
- * (the slot persists; the model value may not be re-stamped after the reactor goes
109
- * sticky). Awaited: per-type resolves at once (own origin discovered earlier in this
110
- * pipeline); shared-`min` waits until every selected A/V origin is in — the barrier,
111
- * filled by the other type's discover step. A derived `0` (0-PTS / below threshold)
112
- * leaves the append native — setting `timestampOffset` at all can ripple.
99
+ * Stamp step — tier-agnostic apply. Relocate by the `derive`d `startMediaTime` for this type (`offset =
100
+ * −startMediaTime`). Applies the **same** `derive` the reactor uses, over the shared `mediaContainerData` slot — so
101
+ * the buffer offset matches the model's stamped `startMediaTime`, and it's robust to `established` + late tracks (the
102
+ * slot persists; the model value may not be re-stamped after the reactor goes sticky). Awaited: per-type resolves at
103
+ * once (own origin discovered earlier in this pipeline); shared-`min` waits until every selected A/V origin is in
104
+ * the barrier, filled by the other type's discover step. A derived `0` (0-PTS / below threshold) leaves the append
105
+ * native setting `timestampOffset` at all can ripple.
113
106
  */
114
107
  const stampStartMediaTime = async (frame, signal, deps) => {
115
108
  if (frame.op.type !== "append-segment") return;
@@ -146,12 +139,10 @@ function relocationPipelinesFor(trackType, derive) {
146
139
  });
147
140
  }
148
141
  /**
149
- * Resolve step for the relocation text pipeline. Reuses the injected host resolver
150
- * (the loader's folded `resolveSegment`, via `textStepWiring`) for cues and fetches
151
- * the `X-TIMESTAMP-MAP` header in parallel, stashing it on `frame.metadata` for
152
- * `relocateCuesStep`. Replaces the
153
- * base `resolveCuesStep` (which fetches cues only) — text's native `<track>` parser
154
- * discards the header, so the map needs its own raw-bytes fetch.
142
+ * Resolve step for the relocation text pipeline. Reuses the injected host resolver (the loader's folded
143
+ * `resolveSegment`, via `textStepWiring`) for cues and fetches the `X-TIMESTAMP-MAP` header in parallel, stashing it on
144
+ * `frame.metadata` for `relocateCuesStep`. Replaces the base `resolveCuesStep` (which fetches cues only) — text's
145
+ * native `<track>` parser discards the header, so the map needs its own raw-bytes fetch.
155
146
  */
156
147
  const resolveWithMetadataStep = async (frame, signal, deps) => {
157
148
  const [cues, metadata] = await Promise.all([textStepWiring(deps).resolveSegment(frame.op.segment.url), resolveVttSegmentMetadata(frame.op.segment.url)]);
@@ -160,14 +151,12 @@ const resolveWithMetadataStep = async (frame, signal, deps) => {
160
151
  frame.metadata = metadata;
161
152
  };
162
153
  /**
163
- * Relocate step — shifts each cue onto the 0-based presentation timeline:
164
- * `cueFinal = cueNative startMediaTime`, where `startMediaTime` is the primary
165
- * A/V track's origin (selected **video**, else **audio** the single-anchor rule,
166
- * and defensive like the reactor's optional selection) and `cueNative` folds in the
167
- * `X-TIMESTAMP-MAP` correction (`mpegts/90000 local`) for map-bearing VTT (Apple)
168
- * or is the absolute cue time (no map, e.g. Mux). Text can resolve before A/V
169
- * establishes, so the origin is awaited; fMP4 always establishes it (0-PTS → 0),
170
- * and a text-only source (no A/V selected) simply gets offset 0.
154
+ * Relocate step — shifts each cue onto the 0-based presentation timeline: `cueFinal = cueNative − startMediaTime`,
155
+ * where `startMediaTime` is the primary A/V track's origin (selected **video**, else **audio** — the single-anchor
156
+ * rule, and defensive like the reactor's optional selection) and `cueNative` folds in the `X-TIMESTAMP-MAP` correction
157
+ * (`mpegts/90000 local`) for map-bearing VTT (Apple) or is the absolute cue time (no map, e.g. Mux). Text can resolve
158
+ * before A/V establishes, so the origin is awaited; fMP4 always establishes it (0-PTS 0), and a text-only source (no
159
+ * A/V selected) simply gets offset 0.
171
160
  */
172
161
  const relocateCuesStep = async (frame, signal, deps) => {
173
162
  if (!frame.cues?.length) return;
@@ -188,9 +177,8 @@ const relocateCuesStep = async (frame, signal, deps) => {
188
177
  }
189
178
  };
190
179
  /**
191
- * Relocation text pipeline — the text analog of `relocationPipelinesFor(type)`.
192
- * `resolveWithMetadata` (cues + `X-TIMESTAMP-MAP`) → `relocateCues` (shift by the
193
- * primary A/V origin) → `dispatchCues`.
180
+ * Relocation text pipeline — the text analog of `relocationPipelinesFor(type)`. `resolveWithMetadata` (cues +
181
+ * `X-TIMESTAMP-MAP`) → `relocateCues` (shift by the primary A/V origin) → `dispatchCues`.
194
182
  */
195
183
  const relocatingTextPipelines = () => [
196
184
  resolveWithMetadataStep,
@@ -1 +1 @@
1
- {"version":3,"file":"relocation-pipelines.js","names":[],"sources":["../../../../src/playback/primitives/relocation-pipelines.ts"],"sourcesContent":["/**\n * Non-zero-PTS relocation pipelines — the config-supplied, loader-facing half of the\n * `establishStartMediaTime` behavior (`../behaviors/establish-start-media-time`). The\n * reactor there owns the lifecycle and the `derive` coordination seam; this file supplies\n * the `messagePipelines` the segment/text loaders run to *fill and act on* that behavior's\n * state. It's the relocation analog of `track-switching`'s config-supplied constraint/rule\n * chain — a pluggable strategy handed to the machinery via config, not applied inline —\n * supplied to the loaders rather than applied by the behavior, so the two coordinate\n * through the shared `mediaContainerData` / `startMediaTime` slots alone, never by import.\n *\n * DOM-free: it composes the loader vocabularies (`segment-load-pipeline`,\n * `text-segment-load-pipeline`) through their structural sink seams and shifts cues via\n * the structural `Cue` type, so it names no `SourceBuffer`, `TextTracksActor`, or\n * `VTTCue`. The A/V pipeline is a plain `messagePipelines` array:\n * - `discover` — init `track_id` + `mdhd` timescale for the buffered media track, then\n * that same track's `tfdt` baseMediaDecodeTime, matched by `track_id` so a muxed\n * segment reads the media track's origin, not the first `traf` — writes\n * `state.mediaContainerData`.\n * - `stamp` — reads that track's derived origin back and relocates via\n * `timestampOffset = −startMediaTime`.\n * Steps read composition `state` from their call-time `deps` (no closures, no context).\n *\n * The text half (`relocatingTextPipelines`) is the same idea for the text-segment loader:\n * a `resolveWithMetadata → relocateCues → dispatchCues` pipeline that shifts cues onto the\n * same 0-based timeline, reading the primary A/V track's `startMediaTime` (the reactor's\n * consumed value) via `deps`.\n */\nimport type { StateSignals } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { peek, type Signal, update } from '../../core/signals/primitives';\nimport { findMediaTrack, type MediaHandlerType, readBaseMediaDecodeTime } from '../../media/mp4/timestamp-origin';\nimport { resolveVttSegmentMetadata, type TextSegmentMetadata } from '../../media/text/resolve-vtt-metadata';\nimport type { Cue, MaybeResolvedPresentation, MediaContainerData } from '../../media/types';\nimport { findTrackById, getTracksByType } from '../../media/utils/tracks';\nimport type { DeriveStartMediaTime } from './derive-start-media-time';\nimport { peekHead } from './head-peek';\nimport { dispatchStep, fetchStep, type LoadStep, type MessagePipelines, type StepDeps } from './segment-load-pipeline';\nimport {\n dispatchCuesStep,\n type TextFrame,\n type TextLoadStep,\n type TextStepDeps,\n textStepWiring,\n} from './text-segment-load-pipeline';\n\n/** The composition slots these steps read — the view asserted from the opaque `deps.state`. */\ninterface RelocationSlots {\n presentation?: MaybeResolvedPresentation;\n mediaContainerData?: Record<string, MediaContainerData>;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\ntype ContainerSlot = Signal<Record<string, MediaContainerData> | undefined>;\n\n/** Assert the relocation state view from the opaque step deps — this module knows the slots the composition provides. */\nfunction relocationState(deps: StepDeps): StateSignals<RelocationSlots> {\n return deps.state as unknown as StateSignals<RelocationSlots>;\n}\n\nfunction containerSlot(deps: StepDeps): ContainerSlot {\n return relocationState(deps).mediaContainerData;\n}\n\n/** Synchronous RMW of the per-type entry — disjoint keys across producers, so no lost update. */\nfunction writeContainer(slot: ContainerSlot, trackType: string, patch: Partial<MediaContainerData>): void {\n update(slot, (current) => ({ ...current, [trackType]: { ...current?.[trackType], ...patch } }));\n}\n\n/**\n * Resolve once `read()` returns a number. Shared by the A/V stamp (waits for the\n * `derive`d origin — immediate for per-type, the shared-`min` barrier for coordinated)\n * and the text step (waits for the primary A/V origin). No bound: for fMP4 the origin\n * always establishes.\n */\nfunction awaitDefined(read: () => number | undefined): Promise<number> {\n return new Promise((resolve) => {\n let stop: (() => void) | undefined;\n stop = effect(() => {\n const value = read();\n if (value !== undefined) {\n stop?.();\n resolve(value);\n }\n });\n });\n}\n\n/**\n * Relocation pipelines for one track type — a plain config `messagePipelines`.\n * Keyed by **track type** (`'video'` / `'audio'`), so ABR rungs of a type share the\n * origin (discover skips once the type's value is present). The steps read/write\n * `state.mediaContainerData[trackType]` via their call-time `deps`; the stamp applies\n * the same `derive` seam the reactor uses (pass the composition's resolved\n * `deriveStartMediaTime` so the buffer offset and the model's `startMediaTime` agree).\n */\nexport function relocationPipelinesFor(trackType: 'video' | 'audio', derive: DeriveStartMediaTime): MessagePipelines {\n const handlerType: MediaHandlerType = trackType === 'video' ? 'vide' : 'soun';\n\n /**\n * Init step: head-peek the buffered media track's `track_id` + `mdhd` timescale into\n * `mediaContainerData[trackType]`. Matching by handler (`vide`/`soun`) skips a muxed\n * `clcp` caption track, and the `track_id` lets `readSegmentOrigin` read *this*\n * track's `tfdt` rather than the first `traf` in the segment.\n */\n const readInitTrackInfo: LoadStep = async (frame, _signal, deps) => {\n const { op } = frame;\n if (op.type !== 'append-init' || !frame.data) return;\n const slot = containerSlot(deps);\n if (peek(slot)?.[trackType]?.timescale !== undefined) return; // already have it (any rung of this type)\n frame.data = await peekHead(frame.data, (bytes) => {\n const track = findMediaTrack(bytes, handlerType);\n if (track === undefined) return false;\n writeContainer(slot, trackType, { trackId: track.trackId, timescale: track.timescale });\n return true;\n });\n };\n\n /**\n * Media-segment step: head-peek the `tfdt` baseMediaDecodeTime of the media track's\n * `traf` (matched by the `track_id` discovered from the init), recording the\n * segment's 0-based `startTime` with it — the origin is `bmdt/ts − segmentStartTime`,\n * so the first *loaded* segment need not be the 0th. Without a discovered `track_id`\n * (non-fMP4 / mock init) there's no media track to relocate, so the step no-ops and\n * the append stays native.\n */\n const readSegmentOrigin: LoadStep = async (frame, _signal, deps) => {\n const { op } = frame;\n if (op.type !== 'append-segment' || !frame.data) return;\n const slot = containerSlot(deps);\n const container = peek(slot)?.[trackType];\n if (container?.baseMediaDecodeTime !== undefined) return; // established\n const { trackId } = container ?? {};\n if (trackId === undefined) return; // init didn't identify a media track — nothing to match\n const segmentStartTime = op.meta.startTime;\n frame.data = await peekHead(frame.data, (bytes) => {\n const baseMediaDecodeTime = readBaseMediaDecodeTime(bytes, trackId);\n if (baseMediaDecodeTime === undefined) return false;\n writeContainer(slot, trackType, { baseMediaDecodeTime, segmentStartTime });\n return true;\n });\n };\n\n /**\n * Stamp step — tier-agnostic apply. Relocate by the `derive`d `startMediaTime` for\n * this type (`offset = −startMediaTime`). Applies the **same** `derive` the reactor\n * uses, over the shared `mediaContainerData` slot — so the buffer offset matches the\n * model's stamped `startMediaTime`, and it's robust to `established` + late tracks\n * (the slot persists; the model value may not be re-stamped after the reactor goes\n * sticky). Awaited: per-type resolves at once (own origin discovered earlier in this\n * pipeline); shared-`min` waits until every selected A/V origin is in — the barrier,\n * filled by the other type's discover step. A derived `0` (0-PTS / below threshold)\n * leaves the append native — setting `timestampOffset` at all can ripple.\n */\n const stampStartMediaTime: LoadStep = async (frame, signal, deps) => {\n if (frame.op.type !== 'append-segment') return;\n const state = relocationState(deps);\n // Liveness guard: if THIS type's own origin wasn't discovered — `readSegmentOrigin`\n // ran earlier in this pipeline and found no `tfdt` (mock / TS / containerless) — the\n // source isn't relocatable, so leave the append native and DON'T wait. Only a\n // discoverable type awaits the derived origin (which for shared-`min` legitimately\n // blocks on the other selected type — the barrier).\n const own = peek(state.mediaContainerData)?.[trackType];\n if (own?.timescale === undefined || own.baseMediaDecodeTime === undefined || own.segmentStartTime === undefined) {\n return;\n }\n const startMediaTime = await awaitDefined(() => {\n const containerData = state.mediaContainerData.get();\n if (!containerData) return undefined;\n return derive(containerData, {\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n })[trackType];\n });\n if (signal.aborted || startMediaTime === 0) return;\n frame.meta = { ...(frame.meta ?? frame.op.meta), timestampOffset: -startMediaTime };\n };\n\n return () => ({\n remove: [dispatchStep],\n 'append-init': [fetchStep, readInitTrackInfo, dispatchStep],\n 'append-segment': [fetchStep, readSegmentOrigin, stampStartMediaTime, dispatchStep],\n });\n}\n\n// ============================================================================\n// TEXT (cue relocation)\n// ============================================================================\n\n/**\n * Resolve step for the relocation text pipeline. Reuses the injected host resolver\n * (the loader's folded `resolveSegment`, via `textStepWiring`) for cues and fetches\n * the `X-TIMESTAMP-MAP` header in parallel, stashing it on `frame.metadata` for\n * `relocateCuesStep`. Replaces the\n * base `resolveCuesStep` (which fetches cues only) — text's native `<track>` parser\n * discards the header, so the map needs its own raw-bytes fetch.\n */\nconst resolveWithMetadataStep = async <C extends Cue>(\n frame: TextFrame<C>,\n signal: AbortSignal,\n deps: TextStepDeps\n): Promise<void> => {\n const [cues, metadata] = await Promise.all([\n textStepWiring<C>(deps).resolveSegment(frame.op.segment.url),\n resolveVttSegmentMetadata(frame.op.segment.url),\n ]);\n if (signal.aborted) return;\n frame.cues = cues;\n frame.metadata = metadata;\n};\n\n/**\n * Relocate step — shifts each cue onto the 0-based presentation timeline:\n * `cueFinal = cueNative − startMediaTime`, where `startMediaTime` is the primary\n * A/V track's origin (selected **video**, else **audio** — the single-anchor rule,\n * and defensive like the reactor's optional selection) and `cueNative` folds in the\n * `X-TIMESTAMP-MAP` correction (`mpegts/90000 − local`) for map-bearing VTT (Apple)\n * or is the absolute cue time (no map, e.g. Mux). Text can resolve before A/V\n * establishes, so the origin is awaited; fMP4 always establishes it (0-PTS → 0),\n * and a text-only source (no A/V selected) simply gets offset 0.\n */\nconst relocateCuesStep = async <C extends Cue>(\n frame: TextFrame<C>,\n signal: AbortSignal,\n deps: TextStepDeps\n): Promise<void> => {\n if (!frame.cues?.length) return;\n const state = deps.state as unknown as StateSignals<RelocationSlots>;\n const startMediaTime = await awaitDefined(() => {\n const presentation = state.presentation.get();\n if (!presentation) return undefined;\n const primaryId = state.selectedVideoTrackId.get() ?? state.selectedAudioTrackId.get();\n if (primaryId !== undefined) {\n // A/V selected: use its origin once stamped (undefined until then → keep waiting).\n return findTrackById(presentation, primaryId)?.startMediaTime;\n }\n // No A/V selected. Distinguish \"not selected YET\" from \"text-only source\". If the\n // presentation has A/V tracks, one WILL be selected and stamped — wait for it, or the\n // cues get shifted by the full `mapCorrection` (e.g. +10s on Apple X-TIMESTAMP-MAP\n // sources) when text relocation wins the race against A/V selection. Only a genuinely\n // text-only source (no A/V origin ever coming) legitimately relocates by offset 0.\n const hasAv =\n getTracksByType(presentation, 'video').length > 0 || getTracksByType(presentation, 'audio').length > 0;\n return hasAv ? undefined : 0;\n });\n if (signal.aborted) return;\n const { timestampMap } = (frame.metadata as TextSegmentMetadata | undefined) ?? {};\n const mapCorrection = timestampMap ? timestampMap.mpegts / 90000 - timestampMap.local : 0;\n const delta = mapCorrection - startMediaTime;\n if (delta !== 0) {\n for (const cue of frame.cues) {\n cue.startTime += delta;\n cue.endTime += delta;\n }\n }\n};\n\n/**\n * Relocation text pipeline — the text analog of `relocationPipelinesFor(type)`.\n * `resolveWithMetadata` (cues + `X-TIMESTAMP-MAP`) → `relocateCues` (shift by the\n * primary A/V origin) → `dispatchCues`.\n */\nexport const relocatingTextPipelines = <C extends Cue>(): TextLoadStep<C>[] => [\n resolveWithMetadataStep,\n relocateCuesStep,\n dispatchCuesStep,\n];\n"],"mappings":"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"}
1
+ {"version":3,"file":"relocation-pipelines.js","names":[],"sources":["../../../../src/playback/primitives/relocation-pipelines.ts"],"sourcesContent":["/**\n * Non-zero-PTS relocation pipelines — the config-supplied, loader-facing half of the `establishStartMediaTime` behavior\n * (`../behaviors/establish-start-media-time`). The reactor there owns the lifecycle and the `derive` coordination seam;\n * this file supplies the `messagePipelines` the segment/text loaders run to _fill and act on_ that behavior's state.\n * It's the relocation analog of `track-switching`'s config-supplied constraint/rule chain — a pluggable strategy handed\n * to the machinery via config, not applied inline — supplied to the loaders rather than applied by the behavior, so the\n * two coordinate through the shared `mediaContainerData` / `startMediaTime` slots alone, never by import.\n *\n * DOM-free: it composes the loader vocabularies (`segment-load-pipeline`, `text-segment-load-pipeline`) through their\n * structural sink seams and shifts cues via the structural `Cue` type, so it names no `SourceBuffer`,\n * `TextTracksActor`, or `VTTCue`. The A/V pipeline is a plain `messagePipelines` array:\n *\n * - `discover` — init `track_id` + `mdhd` timescale for the buffered media track, then that same track's `tfdt`\n * baseMediaDecodeTime, matched by `track_id` so a muxed segment reads the media track's origin, not the first `traf`\n * — writes `state.mediaContainerData`.\n * - `stamp` — reads that track's derived origin back and relocates via `timestampOffset = −startMediaTime`. Steps read\n * composition `state` from their call-time `deps` (no closures, no context).\n *\n * The text half (`relocatingTextPipelines`) is the same idea for the text-segment loader: a `resolveWithMetadata →\n * relocateCues → dispatchCues` pipeline that shifts cues onto the same 0-based timeline, reading the primary A/V\n * track's `startMediaTime` (the reactor's consumed value) via `deps`.\n */\nimport type { StateSignals } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { peek, type Signal, update } from '../../core/signals/primitives';\nimport { findMediaTrack, type MediaHandlerType, readBaseMediaDecodeTime } from '../../media/mp4/timestamp-origin';\nimport { resolveVttSegmentMetadata, type TextSegmentMetadata } from '../../media/text/resolve-vtt-metadata';\nimport type { Cue, MaybeResolvedPresentation, MediaContainerData } from '../../media/types';\nimport { findTrackById, getTracksByType } from '../../media/utils/tracks';\nimport type { DeriveStartMediaTime } from './derive-start-media-time';\nimport { peekHead } from './head-peek';\nimport { dispatchStep, fetchStep, type LoadStep, type MessagePipelines, type StepDeps } from './segment-load-pipeline';\nimport {\n dispatchCuesStep,\n type TextFrame,\n type TextLoadStep,\n type TextStepDeps,\n textStepWiring,\n} from './text-segment-load-pipeline';\n\n/** The composition slots these steps read — the view asserted from the opaque `deps.state`. */\ninterface RelocationSlots {\n presentation?: MaybeResolvedPresentation;\n mediaContainerData?: Record<string, MediaContainerData>;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\ntype ContainerSlot = Signal<Record<string, MediaContainerData> | undefined>;\n\n/** Assert the relocation state view from the opaque step deps — this module knows the slots the composition provides. */\nfunction relocationState(deps: StepDeps): StateSignals<RelocationSlots> {\n return deps.state as unknown as StateSignals<RelocationSlots>;\n}\n\nfunction containerSlot(deps: StepDeps): ContainerSlot {\n return relocationState(deps).mediaContainerData;\n}\n\n/** Synchronous RMW of the per-type entry — disjoint keys across producers, so no lost update. */\nfunction writeContainer(slot: ContainerSlot, trackType: string, patch: Partial<MediaContainerData>): void {\n update(slot, (current) => ({ ...current, [trackType]: { ...current?.[trackType], ...patch } }));\n}\n\n/**\n * Resolve once `read()` returns a number. Shared by the A/V stamp (waits for the `derive`d origin — immediate for\n * per-type, the shared-`min` barrier for coordinated) and the text step (waits for the primary A/V origin). No bound:\n * for fMP4 the origin always establishes.\n */\nfunction awaitDefined(read: () => number | undefined): Promise<number> {\n return new Promise((resolve) => {\n let stop: (() => void) | undefined;\n\n stop = effect(() => {\n const value = read();\n\n if (value !== undefined) {\n stop?.();\n resolve(value);\n }\n });\n });\n}\n\n/**\n * Relocation pipelines for one track type — a plain config `messagePipelines`. Keyed by **track type** (`'video'` /\n * `'audio'`), so ABR rungs of a type share the origin (discover skips once the type's value is present). The steps\n * read/write `state.mediaContainerData[trackType]` via their call-time `deps`; the stamp applies the same `derive` seam\n * the reactor uses (pass the composition's resolved `deriveStartMediaTime` so the buffer offset and the model's\n * `startMediaTime` agree).\n */\nexport function relocationPipelinesFor(trackType: 'video' | 'audio', derive: DeriveStartMediaTime): MessagePipelines {\n const handlerType: MediaHandlerType = trackType === 'video' ? 'vide' : 'soun';\n\n /**\n * Init step: head-peek the buffered media track's `track_id` + `mdhd` timescale into `mediaContainerData[trackType]`.\n * Matching by handler (`vide`/`soun`) skips a muxed `clcp` caption track, and the `track_id` lets `readSegmentOrigin`\n * read _this_ track's `tfdt` rather than the first `traf` in the segment.\n */\n const readInitTrackInfo: LoadStep = async (frame, _signal, deps) => {\n const { op } = frame;\n if (op.type !== 'append-init' || !frame.data) return;\n\n const slot = containerSlot(deps);\n if (peek(slot)?.[trackType]?.timescale !== undefined) return; // already have it (any rung of this type)\n\n frame.data = await peekHead(frame.data, (bytes) => {\n const track = findMediaTrack(bytes, handlerType);\n if (track === undefined) return false;\n\n writeContainer(slot, trackType, { trackId: track.trackId, timescale: track.timescale });\n return true;\n });\n };\n\n /**\n * Media-segment step: head-peek the `tfdt` baseMediaDecodeTime of the media track's `traf` (matched by the `track_id`\n * discovered from the init), recording the segment's 0-based `startTime` with it — the origin is `bmdt/ts −\n * segmentStartTime`, so the first _loaded_ segment need not be the 0th. Without a discovered `track_id` (non-fMP4 /\n * mock init) there's no media track to relocate, so the step no-ops and the append stays native.\n */\n const readSegmentOrigin: LoadStep = async (frame, _signal, deps) => {\n const { op } = frame;\n if (op.type !== 'append-segment' || !frame.data) return;\n\n const slot = containerSlot(deps);\n const container = peek(slot)?.[trackType];\n if (container?.baseMediaDecodeTime !== undefined) return; // established\n\n const { trackId } = container ?? {};\n if (trackId === undefined) return; // init didn't identify a media track — nothing to match\n\n const segmentStartTime = op.meta.startTime;\n\n frame.data = await peekHead(frame.data, (bytes) => {\n const baseMediaDecodeTime = readBaseMediaDecodeTime(bytes, trackId);\n if (baseMediaDecodeTime === undefined) return false;\n\n writeContainer(slot, trackType, { baseMediaDecodeTime, segmentStartTime });\n return true;\n });\n };\n\n /**\n * Stamp step — tier-agnostic apply. Relocate by the `derive`d `startMediaTime` for this type (`offset =\n * −startMediaTime`). Applies the **same** `derive` the reactor uses, over the shared `mediaContainerData` slot — so\n * the buffer offset matches the model's stamped `startMediaTime`, and it's robust to `established` + late tracks (the\n * slot persists; the model value may not be re-stamped after the reactor goes sticky). Awaited: per-type resolves at\n * once (own origin discovered earlier in this pipeline); shared-`min` waits until every selected A/V origin is in —\n * the barrier, filled by the other type's discover step. A derived `0` (0-PTS / below threshold) leaves the append\n * native — setting `timestampOffset` at all can ripple.\n */\n const stampStartMediaTime: LoadStep = async (frame, signal, deps) => {\n if (frame.op.type !== 'append-segment') return;\n\n const state = relocationState(deps);\n // Liveness guard: if THIS type's own origin wasn't discovered — `readSegmentOrigin`\n // ran earlier in this pipeline and found no `tfdt` (mock / TS / containerless) — the\n // source isn't relocatable, so leave the append native and DON'T wait. Only a\n // discoverable type awaits the derived origin (which for shared-`min` legitimately\n // blocks on the other selected type — the barrier).\n const own = peek(state.mediaContainerData)?.[trackType];\n\n if (own?.timescale === undefined || own.baseMediaDecodeTime === undefined || own.segmentStartTime === undefined) {\n return;\n }\n\n const startMediaTime = await awaitDefined(() => {\n const containerData = state.mediaContainerData.get();\n if (!containerData) return undefined;\n\n return derive(containerData, {\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n })[trackType];\n });\n if (signal.aborted || startMediaTime === 0) return;\n\n frame.meta = { ...(frame.meta ?? frame.op.meta), timestampOffset: -startMediaTime };\n };\n\n return () => ({\n remove: [dispatchStep],\n 'append-init': [fetchStep, readInitTrackInfo, dispatchStep],\n 'append-segment': [fetchStep, readSegmentOrigin, stampStartMediaTime, dispatchStep],\n });\n}\n\n// ============================================================================\n// TEXT (cue relocation)\n// ============================================================================\n\n/**\n * Resolve step for the relocation text pipeline. Reuses the injected host resolver (the loader's folded\n * `resolveSegment`, via `textStepWiring`) for cues and fetches the `X-TIMESTAMP-MAP` header in parallel, stashing it on\n * `frame.metadata` for `relocateCuesStep`. Replaces the base `resolveCuesStep` (which fetches cues only) — text's\n * native `<track>` parser discards the header, so the map needs its own raw-bytes fetch.\n */\nconst resolveWithMetadataStep = async <C extends Cue>(\n frame: TextFrame<C>,\n signal: AbortSignal,\n deps: TextStepDeps\n): Promise<void> => {\n const [cues, metadata] = await Promise.all([\n textStepWiring<C>(deps).resolveSegment(frame.op.segment.url),\n resolveVttSegmentMetadata(frame.op.segment.url),\n ]);\n\n if (signal.aborted) return;\n\n frame.cues = cues;\n frame.metadata = metadata;\n};\n\n/**\n * Relocate step — shifts each cue onto the 0-based presentation timeline: `cueFinal = cueNative − startMediaTime`,\n * where `startMediaTime` is the primary A/V track's origin (selected **video**, else **audio** — the single-anchor\n * rule, and defensive like the reactor's optional selection) and `cueNative` folds in the `X-TIMESTAMP-MAP` correction\n * (`mpegts/90000 − local`) for map-bearing VTT (Apple) or is the absolute cue time (no map, e.g. Mux). Text can resolve\n * before A/V establishes, so the origin is awaited; fMP4 always establishes it (0-PTS → 0), and a text-only source (no\n * A/V selected) simply gets offset 0.\n */\nconst relocateCuesStep = async <C extends Cue>(\n frame: TextFrame<C>,\n signal: AbortSignal,\n deps: TextStepDeps\n): Promise<void> => {\n if (!frame.cues?.length) return;\n\n const state = deps.state as unknown as StateSignals<RelocationSlots>;\n const startMediaTime = await awaitDefined(() => {\n const presentation = state.presentation.get();\n if (!presentation) return undefined;\n\n const primaryId = state.selectedVideoTrackId.get() ?? state.selectedAudioTrackId.get();\n\n if (primaryId !== undefined) {\n // A/V selected: use its origin once stamped (undefined until then → keep waiting).\n return findTrackById(presentation, primaryId)?.startMediaTime;\n }\n\n // No A/V selected. Distinguish \"not selected YET\" from \"text-only source\". If the\n // presentation has A/V tracks, one WILL be selected and stamped — wait for it, or the\n // cues get shifted by the full `mapCorrection` (e.g. +10s on Apple X-TIMESTAMP-MAP\n // sources) when text relocation wins the race against A/V selection. Only a genuinely\n // text-only source (no A/V origin ever coming) legitimately relocates by offset 0.\n const hasAv =\n getTracksByType(presentation, 'video').length > 0 || getTracksByType(presentation, 'audio').length > 0;\n\n return hasAv ? undefined : 0;\n });\n\n if (signal.aborted) return;\n\n const { timestampMap } = (frame.metadata as TextSegmentMetadata | undefined) ?? {};\n const mapCorrection = timestampMap ? timestampMap.mpegts / 90000 - timestampMap.local : 0;\n const delta = mapCorrection - startMediaTime;\n\n if (delta !== 0) {\n for (const cue of frame.cues) {\n cue.startTime += delta;\n cue.endTime += delta;\n }\n }\n};\n\n/**\n * Relocation text pipeline — the text analog of `relocationPipelinesFor(type)`. `resolveWithMetadata` (cues +\n * `X-TIMESTAMP-MAP`) → `relocateCues` (shift by the primary A/V origin) → `dispatchCues`.\n */\nexport const relocatingTextPipelines = <C extends Cue>(): TextLoadStep<C>[] => [\n resolveWithMetadataStep,\n relocateCuesStep,\n dispatchCuesStep,\n];\n"],"mappings":"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"}
@@ -3,23 +3,18 @@ import { SVTA_UNSUPPORTED_AUDIO_FORMAT, SVTA_UNSUPPORTED_DRM_SYSTEM, SVTA_UNSUPP
3
3
  import { NON_FMP4_CONTAINER_MIMES } from "../../media/hls/parse-media-playlist.js";
4
4
  //#region src/playback/primitives/report-track-conditions.ts
5
5
  /**
6
- * The seam `resolve-track` uses to report conditions discovered in a freshly
7
- * parsed media playlist.
6
+ * The seam `resolve-track` uses to report conditions discovered in a freshly parsed media playlist.
8
7
  *
9
- * Injected rather than baked in so `resolve-track` stays free of format and
10
- * policy knowledge: it parses and commits, and whatever the composition wants
11
- * noticed about the result is this function's business. Which conditions matter,
12
- * and which codes they carry, therefore vary per composition a provider that
13
- * never ships MPEG-TS can drop that check, and future playlist-derived notices
14
- * (LL-HLS or DVR support that's partial rather than absent) attach here without
8
+ * Injected rather than baked in so `resolve-track` stays free of format and policy knowledge: it parses and commits,
9
+ * and whatever the composition wants noticed about the result is this function's business. Which conditions matter, and
10
+ * which codes they carry, therefore vary per composition a provider that never ships MPEG-TS can drop that check, and
11
+ * future playlist-derived notices (LL-HLS or DVR support that's partial rather than absent) attach here without
15
12
  * touching the resolver.
16
13
  *
17
- * Reported **per rendition, as it resolves**, which is why these are causes and
18
- * not verdicts: one rendition being unplayable doesn't make the source
19
- * unplayable. The verdict is `track-switching`'s, which reports
20
- * `SVTA_NO_SUPPORTED_{VIDEO,AUDIO}_TRACK` when a type's candidates actually
21
- * empty. Keeping the two apart is what lets a mixed source — some renditions
22
- * encrypted or MPEG-TS, others playable — log its causes and still play.
14
+ * Reported **per rendition, as it resolves**, which is why these are causes and not verdicts: one rendition being
15
+ * unplayable doesn't make the source unplayable. The verdict is `track-switching`'s, which reports
16
+ * `SVTA_NO_SUPPORTED_{VIDEO,AUDIO}_TRACK` when a type's candidates actually empty. Keeping the two apart is what lets a
17
+ * mixed source — some renditions encrypted or MPEG-TS, others playable log its causes and still play.
23
18
  */
24
19
  /** Unsupported-format code per type; text has none — absent captions aren't a failure. */
25
20
  const UNSUPPORTED_FORMAT_CODE = {
@@ -27,37 +22,30 @@ const UNSUPPORTED_FORMAT_CODE = {
27
22
  audio: SVTA_UNSUPPORTED_AUDIO_FORMAT
28
23
  };
29
24
  /**
30
- * The types a reported cause can legitimately describe: the ones whose candidates
31
- * `canPlayTrack` actually prunes, via `track-switching`'s `excludeUnplayableTracks`
32
- * pre-pass.
25
+ * The types a reported cause can legitimately describe: the ones whose candidates `canPlayTrack` actually prunes, via
26
+ * `track-switching`'s `excludeUnplayableTracks` pre-pass.
33
27
  *
34
- * Text is outside it by design — its switching chain runs failed-CDN constraints
35
- * alone, because an MSE codec probe is the wrong question for a WebVTT rendition
36
- * (see `track-switching.ts`). So nothing prunes a text rendition, no verdict can
37
- * follow from one, and a cause reported against it would be an orphan: the
38
- * adapter's unsupported-feature substitution scans the sequence as a whole, so a
39
- * lone encrypted subtitle would otherwise recode an unrelated verdict — an
40
- * all-CDN cooldown among them — as "this engine can't play the source".
28
+ * Text is outside it by design — its switching chain runs failed-CDN constraints alone, because an MSE codec probe is
29
+ * the wrong question for a WebVTT rendition (see `track-switching.ts`). So nothing prunes a text rendition, no verdict
30
+ * can follow from one, and a cause reported against it would be an orphan: the adapter's unsupported-feature
31
+ * substitution scans the sequence as a whole, so a lone encrypted subtitle would otherwise recode an unrelated verdict
32
+ * an all-CDN cooldown among them as "this engine can't play the source".
41
33
  */
42
34
  const CAPABILITY_PRUNED_TYPES = /* @__PURE__ */ new Set(["video", "audio"]);
43
35
  /**
44
- * The default: report what makes a rendition unplayable *to this engine* — a
45
- * container MSE can't accept, or encryption with no decryption pipeline. Both
46
- * mirror what `canPlayTrack` prunes on, so a reported cause always has a
36
+ * The default: report what makes a rendition unplayable _to this engine_ — a container MSE can't accept, or encryption
37
+ * with no decryption pipeline. Both mirror what `canPlayTrack` prunes on, so a reported cause always has a
47
38
  * corresponding exclusion.
48
39
  *
49
- * A condition carries its code and its context, not copy. `data.mimeType` is
50
- * what preserves the specificity — the format codes cover every non-fMP4
51
- * container, so "which one" lives there for a consumer that wants to say, rather
52
- * than being flattened into an English sentence here.
40
+ * A condition carries its code and its context, not copy. `data.mimeType` is what preserves the specificity — the
41
+ * format codes cover every non-fMP4 container, so "which one" lives there for a consumer that wants to say, rather than
42
+ * being flattened into an English sentence here.
53
43
  *
54
- * Both carry `trackType`, redundantly for the format codes (1004/1005 are
55
- * already per type) but not for 4008, which isn't per type SVTA has one
56
- * content-protection code for both. Nothing branches on the tag: it's diagnostic
57
- * context, reaching a developer through the logged sequence and `error.data`,
58
- * which is why it goes on every condition rather than only where the code can't
59
- * carry it. `trackId` alongside it is what distinguishes two renditions of the
60
- * same type.
44
+ * Both carry `trackType`, redundantly for the format codes (1004/1005 are already per type) but not for 4008, which
45
+ * isn't per type SVTA has one content-protection code for both. Nothing branches on the tag: it's diagnostic context,
46
+ * reaching a developer through the logged sequence and `error.data`, which is why it goes on every condition rather
47
+ * than only where the code can't carry it. `trackId` alongside it is what distinguishes two renditions of the same
48
+ * type.
61
49
  */
62
50
  function reportUnsupportedTrackConditions(track) {
63
51
  if (!CAPABILITY_PRUNED_TYPES.has(track.type)) return [];
@@ -1 +1 @@
1
- {"version":3,"file":"report-track-conditions.js","names":[],"sources":["../../../../src/playback/primitives/report-track-conditions.ts"],"sourcesContent":["/**\n * The seam `resolve-track` uses to report conditions discovered in a freshly\n * parsed media playlist.\n *\n * Injected rather than baked in so `resolve-track` stays free of format and\n * policy knowledge: it parses and commits, and whatever the composition wants\n * noticed about the result is this function's business. Which conditions matter,\n * and which codes they carry, therefore vary per composition — a provider that\n * never ships MPEG-TS can drop that check, and future playlist-derived notices\n * (LL-HLS or DVR support that's partial rather than absent) attach here without\n * touching the resolver.\n *\n * Reported **per rendition, as it resolves**, which is why these are causes and\n * not verdicts: one rendition being unplayable doesn't make the source\n * unplayable. The verdict is `track-switching`'s, which reports\n * `SVTA_NO_SUPPORTED_{VIDEO,AUDIO}_TRACK` when a type's candidates actually\n * empty. Keeping the two apart is what lets a mixed source — some renditions\n * encrypted or MPEG-TS, others playable — log its causes and still play.\n */\nimport {\n SVTA_UNSUPPORTED_AUDIO_FORMAT,\n SVTA_UNSUPPORTED_DRM_SYSTEM,\n SVTA_UNSUPPORTED_VIDEO_FORMAT,\n type SvtaError,\n} from '../../media/errors';\nimport { NON_FMP4_CONTAINER_MIMES } from '../../media/hls/parse-media-playlist';\nimport { getMediaPlaylistMetadata, type ResolvedTrack, type TrackType } from '../../media/types';\n\n/**\n * Conditions worth reporting about a just-resolved track. Return an empty array\n * (or omit the seam) to report nothing.\n */\nexport type ReportUnsupportedTrackConditions = (track: ResolvedTrack) => readonly SvtaError[];\n\n/** Unsupported-format code per type; text has none — absent captions aren't a failure. */\nconst UNSUPPORTED_FORMAT_CODE: Partial<Record<TrackType, number>> = {\n video: SVTA_UNSUPPORTED_VIDEO_FORMAT,\n audio: SVTA_UNSUPPORTED_AUDIO_FORMAT,\n};\n\n/**\n * The types a reported cause can legitimately describe: the ones whose candidates\n * `canPlayTrack` actually prunes, via `track-switching`'s `excludeUnplayableTracks`\n * pre-pass.\n *\n * Text is outside it by design — its switching chain runs failed-CDN constraints\n * alone, because an MSE codec probe is the wrong question for a WebVTT rendition\n * (see `track-switching.ts`). So nothing prunes a text rendition, no verdict can\n * follow from one, and a cause reported against it would be an orphan: the\n * adapter's unsupported-feature substitution scans the sequence as a whole, so a\n * lone encrypted subtitle would otherwise recode an unrelated verdict — an\n * all-CDN cooldown among them — as \"this engine can't play the source\".\n */\nconst CAPABILITY_PRUNED_TYPES: ReadonlySet<TrackType> = new Set<TrackType>(['video', 'audio']);\n\n/**\n * The default: report what makes a rendition unplayable *to this engine* — a\n * container MSE can't accept, or encryption with no decryption pipeline. Both\n * mirror what `canPlayTrack` prunes on, so a reported cause always has a\n * corresponding exclusion.\n *\n * A condition carries its code and its context, not copy. `data.mimeType` is\n * what preserves the specificity — the format codes cover every non-fMP4\n * container, so \"which one\" lives there for a consumer that wants to say, rather\n * than being flattened into an English sentence here.\n *\n * Both carry `trackType`, redundantly for the format codes (1004/1005 are\n * already per type) but not for 4008, which isn't per type — SVTA has one\n * content-protection code for both. Nothing branches on the tag: it's diagnostic\n * context, reaching a developer through the logged sequence and `error.data`,\n * which is why it goes on every condition rather than only where the code can't\n * carry it. `trackId` alongside it is what distinguishes two renditions of the\n * same type.\n */\nexport function reportUnsupportedTrackConditions(track: ResolvedTrack): readonly SvtaError[] {\n if (!CAPABILITY_PRUNED_TYPES.has(track.type)) return [];\n\n const conditions: SvtaError[] = [];\n const data = { trackType: track.type, trackId: track.id };\n\n const formatCode = UNSUPPORTED_FORMAT_CODE[track.type];\n if (formatCode !== undefined && NON_FMP4_CONTAINER_MIMES.has(track.mimeType)) {\n conditions.push({ code: formatCode, data: { ...data, mimeType: track.mimeType } });\n }\n if (getMediaPlaylistMetadata(track)?.encrypted) {\n conditions.push({ code: SVTA_UNSUPPORTED_DRM_SYSTEM, data });\n }\n return conditions;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAM,0BAA8D;CAClE,OAAO;CACP,OAAO;AACT;;;;;;;;;;;;;;AAeA,MAAM,0CAAkD,IAAI,IAAe,CAAC,SAAS,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;AAqB7F,SAAgB,iCAAiC,OAA4C;CAC3F,IAAI,CAAC,wBAAwB,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC;CAEtD,MAAM,aAA0B,CAAC;CACjC,MAAM,OAAO;EAAE,WAAW,MAAM;EAAM,SAAS,MAAM;CAAG;CAExD,MAAM,aAAa,wBAAwB,MAAM;CACjD,IAAI,eAAe,KAAA,KAAa,yBAAyB,IAAI,MAAM,QAAQ,GACzE,WAAW,KAAK;EAAE,MAAM;EAAY,MAAM;GAAE,GAAG;GAAM,UAAU,MAAM;EAAS;CAAE,CAAC;CAEnF,IAAI,yBAAyB,KAAK,CAAC,EAAE,WACnC,WAAW,KAAK;EAAE,MAAM;EAA6B;CAAK,CAAC;CAE7D,OAAO;AACT"}
1
+ {"version":3,"file":"report-track-conditions.js","names":[],"sources":["../../../../src/playback/primitives/report-track-conditions.ts"],"sourcesContent":["/**\n * The seam `resolve-track` uses to report conditions discovered in a freshly parsed media playlist.\n *\n * Injected rather than baked in so `resolve-track` stays free of format and policy knowledge: it parses and commits,\n * and whatever the composition wants noticed about the result is this function's business. Which conditions matter, and\n * which codes they carry, therefore vary per composition — a provider that never ships MPEG-TS can drop that check, and\n * future playlist-derived notices (LL-HLS or DVR support that's partial rather than absent) attach here without\n * touching the resolver.\n *\n * Reported **per rendition, as it resolves**, which is why these are causes and not verdicts: one rendition being\n * unplayable doesn't make the source unplayable. The verdict is `track-switching`'s, which reports\n * `SVTA_NO_SUPPORTED_{VIDEO,AUDIO}_TRACK` when a type's candidates actually empty. Keeping the two apart is what lets a\n * mixed source — some renditions encrypted or MPEG-TS, others playable — log its causes and still play.\n */\nimport {\n SVTA_UNSUPPORTED_AUDIO_FORMAT,\n SVTA_UNSUPPORTED_DRM_SYSTEM,\n SVTA_UNSUPPORTED_VIDEO_FORMAT,\n type SvtaError,\n} from '../../media/errors';\nimport { NON_FMP4_CONTAINER_MIMES } from '../../media/hls/parse-media-playlist';\nimport { getMediaPlaylistMetadata, type ResolvedTrack, type TrackType } from '../../media/types';\n\n/** Conditions worth reporting about a just-resolved track. Return an empty array (or omit the seam) to report nothing. */\nexport type ReportUnsupportedTrackConditions = (track: ResolvedTrack) => readonly SvtaError[];\n\n/** Unsupported-format code per type; text has none — absent captions aren't a failure. */\nconst UNSUPPORTED_FORMAT_CODE: Partial<Record<TrackType, number>> = {\n video: SVTA_UNSUPPORTED_VIDEO_FORMAT,\n audio: SVTA_UNSUPPORTED_AUDIO_FORMAT,\n};\n\n/**\n * The types a reported cause can legitimately describe: the ones whose candidates `canPlayTrack` actually prunes, via\n * `track-switching`'s `excludeUnplayableTracks` pre-pass.\n *\n * Text is outside it by design — its switching chain runs failed-CDN constraints alone, because an MSE codec probe is\n * the wrong question for a WebVTT rendition (see `track-switching.ts`). So nothing prunes a text rendition, no verdict\n * can follow from one, and a cause reported against it would be an orphan: the adapter's unsupported-feature\n * substitution scans the sequence as a whole, so a lone encrypted subtitle would otherwise recode an unrelated verdict\n * — an all-CDN cooldown among them — as \"this engine can't play the source\".\n */\nconst CAPABILITY_PRUNED_TYPES: ReadonlySet<TrackType> = new Set<TrackType>(['video', 'audio']);\n\n/**\n * The default: report what makes a rendition unplayable _to this engine_ — a container MSE can't accept, or encryption\n * with no decryption pipeline. Both mirror what `canPlayTrack` prunes on, so a reported cause always has a\n * corresponding exclusion.\n *\n * A condition carries its code and its context, not copy. `data.mimeType` is what preserves the specificity — the\n * format codes cover every non-fMP4 container, so \"which one\" lives there for a consumer that wants to say, rather than\n * being flattened into an English sentence here.\n *\n * Both carry `trackType`, redundantly for the format codes (1004/1005 are already per type) but not for 4008, which\n * isn't per type — SVTA has one content-protection code for both. Nothing branches on the tag: it's diagnostic context,\n * reaching a developer through the logged sequence and `error.data`, which is why it goes on every condition rather\n * than only where the code can't carry it. `trackId` alongside it is what distinguishes two renditions of the same\n * type.\n */\nexport function reportUnsupportedTrackConditions(track: ResolvedTrack): readonly SvtaError[] {\n if (!CAPABILITY_PRUNED_TYPES.has(track.type)) return [];\n\n const conditions: SvtaError[] = [];\n const data = { trackType: track.type, trackId: track.id };\n\n const formatCode = UNSUPPORTED_FORMAT_CODE[track.type];\n\n if (formatCode !== undefined && NON_FMP4_CONTAINER_MIMES.has(track.mimeType)) {\n conditions.push({ code: formatCode, data: { ...data, mimeType: track.mimeType } });\n }\n\n if (getMediaPlaylistMetadata(track)?.encrypted) {\n conditions.push({ code: SVTA_UNSUPPORTED_DRM_SYSTEM, data });\n }\n\n return conditions;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2BA,MAAM,0BAA8D;CAClE,OAAO;CACP,OAAO;AACT;;;;;;;;;;;AAYA,MAAM,0CAAkD,IAAI,IAAe,CAAC,SAAS,OAAO,CAAC;;;;;;;;;;;;;;;;AAiB7F,SAAgB,iCAAiC,OAA4C;CAC3F,IAAI,CAAC,wBAAwB,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC;CAEtD,MAAM,aAA0B,CAAC;CACjC,MAAM,OAAO;EAAE,WAAW,MAAM;EAAM,SAAS,MAAM;CAAG;CAExD,MAAM,aAAa,wBAAwB,MAAM;CAEjD,IAAI,eAAe,KAAA,KAAa,yBAAyB,IAAI,MAAM,QAAQ,GACzE,WAAW,KAAK;EAAE,MAAM;EAAY,MAAM;GAAE,GAAG;GAAM,UAAU,MAAM;EAAS;CAAE,CAAC;CAGnF,IAAI,yBAAyB,KAAK,CAAC,EAAE,WACnC,WAAW,KAAK;EAAE,MAAM;EAA6B;CAAK,CAAC;CAG7D,OAAO;AACT"}
@@ -1,22 +1,20 @@
1
1
  import { effect } from "../../core/signals/effect.js";
2
2
  //#region src/playback/primitives/segment-load-pipeline.ts
3
3
  /**
4
- * Base-step view of the loader's own wiring. `createSegmentLoaderActor` folds its
5
- * `sourceBufferActor` + `fetch` into the threaded `config` so base steps read them
6
- * from the uniform passthrough present whether the loader runs inside a composition
7
- * or standalone. `config` is loose (`object`), so assert the shape here (one cast, like
8
- * relocation's `containerSlot`).
4
+ * Base-step view of the loader's own wiring. `createSegmentLoaderActor` folds its `sourceBufferActor` + `fetch` into
5
+ * the threaded `config` so base steps read them from the uniform passthrough — present whether the loader runs inside a
6
+ * composition or standalone. `config` is loose (`object`), so assert the shape here (one cast, like relocation's
7
+ * `containerSlot`).
9
8
  */
10
9
  function stepWiring(deps) {
11
10
  return deps.config;
12
11
  }
13
12
  /**
14
- * Resolves when the SourceBufferActor snapshot reaches 'idle'.
15
- * Rejects if the signal is aborted or the actor is destroyed.
13
+ * Resolves when the SourceBufferActor snapshot reaches 'idle'. Rejects if the signal is aborted or the actor is
14
+ * destroyed.
16
15
  *
17
- * Used to sequence SourceBufferActor operations without awaiting send()
18
- * directly — send() is fire-and-forget; callers observe completion via
19
- * state transition.
16
+ * Used to sequence SourceBufferActor operations without awaiting send() directly — send() is fire-and-forget; callers
17
+ * observe completion via state transition.
20
18
  */
21
19
  function waitForIdle(snapshot, signal) {
22
20
  return new Promise((resolve, reject) => {
@@ -47,7 +45,10 @@ function waitForIdle(snapshot, signal) {
47
45
  signal.addEventListener("abort", onAbort, { once: true });
48
46
  });
49
47
  }
50
- /** Build the SourceBuffer message a completed frame dispatches. `fetchStep` always precedes `dispatchStep` in append pipelines, so `data` is set by now. */
48
+ /**
49
+ * Build the SourceBuffer message a completed frame dispatches. `fetchStep` always precedes `dispatchStep` in append
50
+ * pipelines, so `data` is set by now.
51
+ */
51
52
  function toMessage({ op, data, meta }) {
52
53
  switch (op.type) {
53
54
  case "remove": return op;
@@ -64,10 +65,9 @@ function toMessage({ op, data, meta }) {
64
65
  }
65
66
  }
66
67
  /**
67
- * Fetch this op's bytes into the frame. Init segments need the full body
68
- * (`minChunkSize: Infinity`) before appending; media segments stream so chunks
69
- * append as they arrive. Awaiting headers eagerly also starts the HTTP
70
- * connection (and records the fetch in observers like tests).
68
+ * Fetch this op's bytes into the frame. Init segments need the full body (`minChunkSize: Infinity`) before appending;
69
+ * media segments stream so chunks append as they arrive. Awaiting headers eagerly also starts the HTTP connection (and
70
+ * records the fetch in observers like tests).
71
71
  */
72
72
  const fetchStep = async (frame, signal, deps) => {
73
73
  const { op } = frame;
@@ -1 +1 @@
1
- {"version":3,"file":"segment-load-pipeline.js","names":[],"sources":["../../../../src/playback/primitives/segment-load-pipeline.ts"],"sourcesContent":["/**\n * The v/a segment **load pipeline** — the step vocabulary and base steps a segment\n * loader runs, extracted from the loader actor so the \"what\" (the composable steps)\n * is separable from the \"how\" (the actor's scheduling). A pipeline is a per-op list\n * of {@link LoadStep}s the loader drains in order; the default is `fetch → dispatch`,\n * and a composition can insert its own steps between them (e.g. non-zero-PTS\n * relocation) without the loader knowing.\n *\n * The base steps reach their SourceBuffer sink through the structural {@link AppendSink}\n * seam rather than the concrete actor type — the loader folds the real actor into\n * `config` at construction, and it satisfies the seam by structure — so this module\n * carries no dependency on the actor implementation, and no dependency on the DOM.\n */\nimport type { AnySlotMap } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport type { ReadonlySignal } from '../../core/signals/primitives';\nimport type { AddressableObject } from '../../media/types';\nimport type {\n AppendInitMessage,\n AppendSegmentMessage,\n IndividualSourceBufferMessage,\n RemoveMessage,\n} from './source-buffer-messages';\n\n// ============================================================================\n// SINK SEAM\n// ============================================================================\n\n/**\n * The structural view of a SourceBuffer sink the base steps use — just the ops\n * {@link dispatchStep} needs (`send` + a `snapshot` to await idle). The concrete\n * `SourceBufferActor` satisfies it by structure, so the pipeline names no actor.\n */\nexport interface AppendSink {\n send(message: IndividualSourceBufferMessage): void;\n snapshot: ReadonlySignal<{ value: string }>;\n}\n\n// ============================================================================\n// STEP MODEL\n// ============================================================================\n\n/**\n * A LoadTask is the intent to perform one unit of SegmentLoader work.\n * Unlike SourceBufferMessage, fetch-based tasks carry a URL rather than\n * pre-fetched data — the runner fetches and appends them in sequence.\n *\n * Derived from SourceBufferMessage types by removing `data` and adding\n * a fetch URL via AddressableObject.\n *\n * @todo Rename — \"LoadTask\" risks confusion with the `Task` class used for\n * SourceBufferActor scheduling. These are closer to operation descriptors or\n * messages than tasks in that sense.\n */\nexport type LoadTask =\n | (Omit<AppendInitMessage, 'data'> & AddressableObject)\n | (Omit<AppendSegmentMessage, 'data'> & AddressableObject)\n | RemoveMessage;\n\n/**\n * A {@link LoadTask} mid-reassembly into its append message. `LoadTask` is a\n * message with `data` omitted and a URL added; the pipeline reverses that —\n * `fetchStep` produces `data`, `dispatchStep` reassembles the message. `data` is\n * the omitted payload (kept as the fetched stream — the loader never produces the\n * `ArrayBuffer` arm of `SegmentData` — widened back at dispatch). `meta` overrides\n * `op.meta` for `append-segment` (e.g. a stamped `timestampOffset`); an\n * `append-init` dispatches `op.meta` directly.\n */\nexport interface Frame {\n readonly op: LoadTask;\n data?: AsyncIterable<Uint8Array>;\n meta?: AppendSegmentMessage['meta'];\n}\n\n/**\n * One stage of a message pipeline. Mutates the {@link Frame} in place and may be\n * async; the runner checks `signal.aborted` before each step and passes the\n * actor's {@link StepDeps} on every call, so a stateless step (e.g.\n * {@link fetchStep}) is a plain value, and a step that needs composition signals\n * (relocation's discover/stamp) reads them from `deps` at call time.\n */\nexport type LoadStep = (frame: Frame, signal: AbortSignal, deps: StepDeps) => void | Promise<void>;\n\n/**\n * The uniform passthrough handed to every {@link LoadStep} on every call — the\n * composition triple, nothing more. `state`/`context` are the full composition signal\n * maps; `config` is the threaded config with the loader's own wiring folded in (see\n * {@link stepWiring} + `createSegmentLoaderActor`). All three are typed loose: the\n * loader is a conduit and never reads them; a step asserts the slots it knows are\n * present (composition steps read `state`; base steps read the folded wiring off\n * `config`).\n */\nexport interface StepDeps {\n state: AnySlotMap;\n context: AnySlotMap;\n config: object;\n}\n\n/**\n * Base-step view of the loader's own wiring. `createSegmentLoaderActor` folds its\n * `sourceBufferActor` + `fetch` into the threaded `config` so base steps read them\n * from the uniform passthrough — present whether the loader runs inside a composition\n * or standalone. `config` is loose (`object`), so assert the shape here (one cast, like\n * relocation's `containerSlot`).\n */\nfunction stepWiring(deps: StepDeps): { sourceBufferActor: AppendSink; fetch: FetchBytes } {\n return deps.config as { sourceBufferActor: AppendSink; fetch: FetchBytes };\n}\n\n/**\n * Builds the ordered step list for each message type. Called **once per actor**\n * — its role is per-actor instantiation, so stateful steps (relocation's origin\n * discoverer) get fresh state per source reset; deps arrive at step-call time,\n * not here. The default ({@link DEFAULT_MESSAGE_PIPELINES}) is `fetch → dispatch`;\n * a non-zero-PTS composition returns a map that inserts its own discover/stamp\n * steps between them (see `establishStartMediaTime`), so the loader stays oblivious\n * to relocation and the Tier 0 pipeline carries no relocation vocabulary at all.\n */\nexport type MessagePipelines = () => Record<LoadTask['type'], LoadStep[]>;\n\n// ============================================================================\n// BASE STEPS\n// ============================================================================\n\nexport type FetchBytes = (\n addressable: AddressableObject,\n options?: RequestInit & { minChunkSize?: number }\n) => Promise<AsyncIterable<Uint8Array>>;\n\n/**\n * Resolves when the SourceBufferActor snapshot reaches 'idle'.\n * Rejects if the signal is aborted or the actor is destroyed.\n *\n * Used to sequence SourceBufferActor operations without awaiting send()\n * directly — send() is fire-and-forget; callers observe completion via\n * state transition.\n */\nfunction waitForIdle(snapshot: AppendSink['snapshot'], signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (snapshot.get().value === 'idle') {\n resolve();\n return;\n }\n if (snapshot.get().value === 'destroyed') {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n if (signal.aborted) {\n reject(signal.reason);\n return;\n }\n\n let stop: (() => void) | undefined;\n\n const cleanup = (fn: () => void) => {\n stop?.();\n signal.removeEventListener('abort', onAbort);\n fn();\n };\n\n const onAbort = () => cleanup(() => reject(signal.reason));\n\n stop = effect(() => {\n const value = snapshot.get().value;\n if (value === 'idle') cleanup(resolve);\n else if (value === 'destroyed') cleanup(() => reject(new DOMException('Aborted', 'AbortError')));\n });\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** Build the SourceBuffer message a completed frame dispatches. `fetchStep` always precedes `dispatchStep` in append pipelines, so `data` is set by now. */\nfunction toMessage({ op, data, meta }: Frame): IndividualSourceBufferMessage {\n switch (op.type) {\n case 'remove':\n return op;\n case 'append-init':\n return { type: 'append-init', data: data!, meta: op.meta };\n case 'append-segment':\n return { type: 'append-segment', data: data!, meta: meta ?? op.meta };\n }\n}\n\n/**\n * Fetch this op's bytes into the frame. Init segments need the full body\n * (`minChunkSize: Infinity`) before appending; media segments stream so chunks\n * append as they arrive. Awaiting headers eagerly also starts the HTTP\n * connection (and records the fetch in observers like tests).\n */\nexport const fetchStep: LoadStep = async (frame, signal, deps) => {\n const { op } = frame;\n if (op.type === 'remove') return; // fetchStep only appears in append pipelines\n const { fetch } = stepWiring(deps);\n frame.data = await fetch(op, op.type === 'append-init' ? { signal, minChunkSize: Infinity } : { signal });\n};\n\n/** Dispatch the frame's message to the SourceBufferActor and await its return to idle. */\nexport const dispatchStep: LoadStep = async (frame, signal, deps) => {\n const { sourceBufferActor } = stepWiring(deps);\n sourceBufferActor.send(toMessage(frame));\n await waitForIdle(sourceBufferActor.snapshot, signal);\n};\n\n/** Tier 0 default: fetch (for ops that carry bytes) then dispatch. No relocation vocabulary. */\nexport const DEFAULT_MESSAGE_PIPELINES: MessagePipelines = () => ({\n remove: [dispatchStep],\n 'append-init': [fetchStep, dispatchStep],\n 'append-segment': [fetchStep, dispatchStep],\n});\n"],"mappings":";;;;;;;;;AAyGA,SAAS,WAAW,MAAsE;CACxF,OAAO,KAAK;AACd;;;;;;;;;AA8BA,SAAS,YAAY,UAAkC,QAAoC;CACzF,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,SAAS,IAAI,CAAC,CAAC,UAAU,QAAQ;GACnC,QAAQ;GACR;EACF;EACA,IAAI,SAAS,IAAI,CAAC,CAAC,UAAU,aAAa;GACxC,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC;GAChD;EACF;EACA,IAAI,OAAO,SAAS;GAClB,OAAO,OAAO,MAAM;GACpB;EACF;EAEA,IAAI;EAEJ,MAAM,WAAW,OAAmB;GAClC,OAAO;GACP,OAAO,oBAAoB,SAAS,OAAO;GAC3C,GAAG;EACL;EAEA,MAAM,gBAAgB,cAAc,OAAO,OAAO,MAAM,CAAC;EAEzD,OAAO,aAAa;GAClB,MAAM,QAAQ,SAAS,IAAI,CAAC,CAAC;GAC7B,IAAI,UAAU,QAAQ,QAAQ,OAAO;QAChC,IAAI,UAAU,aAAa,cAAc,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC,CAAC;EACjG,CAAC;EAED,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC1D,CAAC;AACH;;AAGA,SAAS,UAAU,EAAE,IAAI,MAAM,QAA8C;CAC3E,QAAQ,GAAG,MAAX;EACE,KAAK,UACH,OAAO;EACT,KAAK,eACH,OAAO;GAAE,MAAM;GAAqB;GAAO,MAAM,GAAG;EAAK;EAC3D,KAAK,kBACH,OAAO;GAAE,MAAM;GAAwB;GAAO,MAAM,QAAQ,GAAG;EAAK;CACxE;AACF;;;;;;;AAQA,MAAa,YAAsB,OAAO,OAAO,QAAQ,SAAS;CAChE,MAAM,EAAE,OAAO;CACf,IAAI,GAAG,SAAS,UAAU;CAC1B,MAAM,EAAE,UAAU,WAAW,IAAI;CACjC,MAAM,OAAO,MAAM,MAAM,IAAI,GAAG,SAAS,gBAAgB;EAAE;EAAQ,cAAc;CAAS,IAAI,EAAE,OAAO,CAAC;AAC1G;;AAGA,MAAa,eAAyB,OAAO,OAAO,QAAQ,SAAS;CACnE,MAAM,EAAE,sBAAsB,WAAW,IAAI;CAC7C,kBAAkB,KAAK,UAAU,KAAK,CAAC;CACvC,MAAM,YAAY,kBAAkB,UAAU,MAAM;AACtD;;AAGA,MAAa,mCAAqD;CAChE,QAAQ,CAAC,YAAY;CACrB,eAAe,CAAC,WAAW,YAAY;CACvC,kBAAkB,CAAC,WAAW,YAAY;AAC5C"}
1
+ {"version":3,"file":"segment-load-pipeline.js","names":[],"sources":["../../../../src/playback/primitives/segment-load-pipeline.ts"],"sourcesContent":["/**\n * The v/a segment **load pipeline** — the step vocabulary and base steps a segment loader runs, extracted from the\n * loader actor so the \"what\" (the composable steps) is separable from the \"how\" (the actor's scheduling). A pipeline is\n * a per-op list of {@link LoadStep}s the loader drains in order; the default is `fetch → dispatch`, and a composition\n * can insert its own steps between them (e.g. non-zero-PTS relocation) without the loader knowing.\n *\n * The base steps reach their SourceBuffer sink through the structural {@link AppendSink} seam rather than the concrete\n * actor type — the loader folds the real actor into `config` at construction, and it satisfies the seam by structure —\n * so this module carries no dependency on the actor implementation, and no dependency on the DOM.\n */\nimport type { AnySlotMap } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport type { ReadonlySignal } from '../../core/signals/primitives';\nimport type { AddressableObject } from '../../media/types';\nimport type {\n AppendInitMessage,\n AppendSegmentMessage,\n IndividualSourceBufferMessage,\n RemoveMessage,\n} from './source-buffer-messages';\n\n// ============================================================================\n// SINK SEAM\n// ============================================================================\n\n/**\n * The structural view of a SourceBuffer sink the base steps use — just the ops {@link dispatchStep} needs (`send` + a\n * `snapshot` to await idle). The concrete `SourceBufferActor` satisfies it by structure, so the pipeline names no\n * actor.\n */\nexport interface AppendSink {\n send(message: IndividualSourceBufferMessage): void;\n snapshot: ReadonlySignal<{ value: string }>;\n}\n\n// ============================================================================\n// STEP MODEL\n// ============================================================================\n\n/**\n * A LoadTask is the intent to perform one unit of SegmentLoader work. Unlike SourceBufferMessage, fetch-based tasks\n * carry a URL rather than pre-fetched data — the runner fetches and appends them in sequence.\n *\n * Derived from SourceBufferMessage types by removing `data` and adding a fetch URL via AddressableObject.\n *\n * @todo Rename — \"LoadTask\" risks confusion with the `Task` class used for SourceBufferActor scheduling. These are\n * closer to operation descriptors or messages than tasks in that sense.\n */\nexport type LoadTask =\n | (Omit<AppendInitMessage, 'data'> & AddressableObject)\n | (Omit<AppendSegmentMessage, 'data'> & AddressableObject)\n | RemoveMessage;\n\n/**\n * A {@link LoadTask} mid-reassembly into its append message. `LoadTask` is a message with `data` omitted and a URL\n * added; the pipeline reverses that — `fetchStep` produces `data`, `dispatchStep` reassembles the message. `data` is\n * the omitted payload (kept as the fetched stream — the loader never produces the `ArrayBuffer` arm of `SegmentData` —\n * widened back at dispatch). `meta` overrides `op.meta` for `append-segment` (e.g. a stamped `timestampOffset`); an\n * `append-init` dispatches `op.meta` directly.\n */\nexport interface Frame {\n readonly op: LoadTask;\n data?: AsyncIterable<Uint8Array>;\n meta?: AppendSegmentMessage['meta'];\n}\n\n/**\n * One stage of a message pipeline. Mutates the {@link Frame} in place and may be async; the runner checks\n * `signal.aborted` before each step and passes the actor's {@link StepDeps} on every call, so a stateless step (e.g.\n * {@link fetchStep}) is a plain value, and a step that needs composition signals (relocation's discover/stamp) reads\n * them from `deps` at call time.\n */\nexport type LoadStep = (frame: Frame, signal: AbortSignal, deps: StepDeps) => void | Promise<void>;\n\n/**\n * The uniform passthrough handed to every {@link LoadStep} on every call — the composition triple, nothing more.\n * `state`/`context` are the full composition signal maps; `config` is the threaded config with the loader's own wiring\n * folded in (see {@link stepWiring} + `createSegmentLoaderActor`). All three are typed loose: the loader is a conduit\n * and never reads them; a step asserts the slots it knows are present (composition steps read `state`; base steps read\n * the folded wiring off `config`).\n */\nexport interface StepDeps {\n state: AnySlotMap;\n context: AnySlotMap;\n config: object;\n}\n\n/**\n * Base-step view of the loader's own wiring. `createSegmentLoaderActor` folds its `sourceBufferActor` + `fetch` into\n * the threaded `config` so base steps read them from the uniform passthrough — present whether the loader runs inside a\n * composition or standalone. `config` is loose (`object`), so assert the shape here (one cast, like relocation's\n * `containerSlot`).\n */\nfunction stepWiring(deps: StepDeps): { sourceBufferActor: AppendSink; fetch: FetchBytes } {\n return deps.config as { sourceBufferActor: AppendSink; fetch: FetchBytes };\n}\n\n/**\n * Builds the ordered step list for each message type. Called **once per actor** — its role is per-actor instantiation,\n * so stateful steps (relocation's origin discoverer) get fresh state per source reset; deps arrive at step-call time,\n * not here. The default ({@link DEFAULT_MESSAGE_PIPELINES}) is `fetch → dispatch`; a non-zero-PTS composition returns a\n * map that inserts its own discover/stamp steps between them (see `establishStartMediaTime`), so the loader stays\n * oblivious to relocation and the Tier 0 pipeline carries no relocation vocabulary at all.\n */\nexport type MessagePipelines = () => Record<LoadTask['type'], LoadStep[]>;\n\n// ============================================================================\n// BASE STEPS\n// ============================================================================\n\nexport type FetchBytes = (\n addressable: AddressableObject,\n options?: RequestInit & { minChunkSize?: number }\n) => Promise<AsyncIterable<Uint8Array>>;\n\n/**\n * Resolves when the SourceBufferActor snapshot reaches 'idle'. Rejects if the signal is aborted or the actor is\n * destroyed.\n *\n * Used to sequence SourceBufferActor operations without awaiting send() directly — send() is fire-and-forget; callers\n * observe completion via state transition.\n */\nfunction waitForIdle(snapshot: AppendSink['snapshot'], signal: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (snapshot.get().value === 'idle') {\n resolve();\n return;\n }\n\n if (snapshot.get().value === 'destroyed') {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n if (signal.aborted) {\n reject(signal.reason);\n return;\n }\n\n let stop: (() => void) | undefined;\n\n const cleanup = (fn: () => void) => {\n stop?.();\n signal.removeEventListener('abort', onAbort);\n fn();\n };\n\n const onAbort = () => cleanup(() => reject(signal.reason));\n\n stop = effect(() => {\n const value = snapshot.get().value;\n\n if (value === 'idle') cleanup(resolve);\n else if (value === 'destroyed') cleanup(() => reject(new DOMException('Aborted', 'AbortError')));\n });\n\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * Build the SourceBuffer message a completed frame dispatches. `fetchStep` always precedes `dispatchStep` in append\n * pipelines, so `data` is set by now.\n */\nfunction toMessage({ op, data, meta }: Frame): IndividualSourceBufferMessage {\n switch (op.type) {\n case 'remove':\n return op;\n case 'append-init':\n return { type: 'append-init', data: data!, meta: op.meta };\n case 'append-segment':\n return { type: 'append-segment', data: data!, meta: meta ?? op.meta };\n }\n}\n\n/**\n * Fetch this op's bytes into the frame. Init segments need the full body (`minChunkSize: Infinity`) before appending;\n * media segments stream so chunks append as they arrive. Awaiting headers eagerly also starts the HTTP connection (and\n * records the fetch in observers like tests).\n */\nexport const fetchStep: LoadStep = async (frame, signal, deps) => {\n const { op } = frame;\n if (op.type === 'remove') return; // fetchStep only appears in append pipelines\n\n const { fetch } = stepWiring(deps);\n\n frame.data = await fetch(op, op.type === 'append-init' ? { signal, minChunkSize: Infinity } : { signal });\n};\n\n/** Dispatch the frame's message to the SourceBufferActor and await its return to idle. */\nexport const dispatchStep: LoadStep = async (frame, signal, deps) => {\n const { sourceBufferActor } = stepWiring(deps);\n\n sourceBufferActor.send(toMessage(frame));\n await waitForIdle(sourceBufferActor.snapshot, signal);\n};\n\n/** Tier 0 default: fetch (for ops that carry bytes) then dispatch. No relocation vocabulary. */\nexport const DEFAULT_MESSAGE_PIPELINES: MessagePipelines = () => ({\n remove: [dispatchStep],\n 'append-init': [fetchStep, dispatchStep],\n 'append-segment': [fetchStep, dispatchStep],\n});\n"],"mappings":";;;;;;;;AA6FA,SAAS,WAAW,MAAsE;CACxF,OAAO,KAAK;AACd;;;;;;;;AA2BA,SAAS,YAAY,UAAkC,QAAoC;CACzF,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,SAAS,IAAI,CAAC,CAAC,UAAU,QAAQ;GACnC,QAAQ;GACR;EACF;EAEA,IAAI,SAAS,IAAI,CAAC,CAAC,UAAU,aAAa;GACxC,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC;GAChD;EACF;EAEA,IAAI,OAAO,SAAS;GAClB,OAAO,OAAO,MAAM;GACpB;EACF;EAEA,IAAI;EAEJ,MAAM,WAAW,OAAmB;GAClC,OAAO;GACP,OAAO,oBAAoB,SAAS,OAAO;GAC3C,GAAG;EACL;EAEA,MAAM,gBAAgB,cAAc,OAAO,OAAO,MAAM,CAAC;EAEzD,OAAO,aAAa;GAClB,MAAM,QAAQ,SAAS,IAAI,CAAC,CAAC;GAE7B,IAAI,UAAU,QAAQ,QAAQ,OAAO;QAChC,IAAI,UAAU,aAAa,cAAc,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC,CAAC;EACjG,CAAC;EAED,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC1D,CAAC;AACH;;;;;AAMA,SAAS,UAAU,EAAE,IAAI,MAAM,QAA8C;CAC3E,QAAQ,GAAG,MAAX;EACE,KAAK,UACH,OAAO;EACT,KAAK,eACH,OAAO;GAAE,MAAM;GAAqB;GAAO,MAAM,GAAG;EAAK;EAC3D,KAAK,kBACH,OAAO;GAAE,MAAM;GAAwB;GAAO,MAAM,QAAQ,GAAG;EAAK;CACxE;AACF;;;;;;AAOA,MAAa,YAAsB,OAAO,OAAO,QAAQ,SAAS;CAChE,MAAM,EAAE,OAAO;CACf,IAAI,GAAG,SAAS,UAAU;CAE1B,MAAM,EAAE,UAAU,WAAW,IAAI;CAEjC,MAAM,OAAO,MAAM,MAAM,IAAI,GAAG,SAAS,gBAAgB;EAAE;EAAQ,cAAc;CAAS,IAAI,EAAE,OAAO,CAAC;AAC1G;;AAGA,MAAa,eAAyB,OAAO,OAAO,QAAQ,SAAS;CACnE,MAAM,EAAE,sBAAsB,WAAW,IAAI;CAE7C,kBAAkB,KAAK,UAAU,KAAK,CAAC;CACvC,MAAM,YAAY,kBAAkB,UAAU,MAAM;AACtD;;AAGA,MAAa,mCAAqD;CAChE,QAAQ,CAAC,YAAY;CACrB,eAAe,CAAC,WAAW,YAAY;CACvC,kBAAkB,CAAC,WAAW,YAAY;AAC5C"}
@@ -1,11 +1,10 @@
1
+ import { getCodecFamilies, getCodecFamily } from "../../media/utils/tracks.js";
1
2
  //#region src/playback/primitives/selection-rules.ts
2
3
  /**
3
- * Apply rules to a candidate list in order; the pick is the first survivor.
4
- * Two responsibilities the rules don't carry: a rule that returns nothing is
5
- * skipped (fall-througha preference never empties the set), and once one
6
- * survivor remains the chain stops (early-bail later rules, including the
7
- * bandwidth ranker, never run, so the effect doesn't subscribe to their
8
- * signals while the choice is fixed).
4
+ * Apply rules to a candidate list in order; the pick is the first survivor. Two responsibilities the rules don't carry:
5
+ * a rule that returns nothing is skipped (fall-through — a preference never empties the set), and once one survivor
6
+ * remains the chain stops (early-baillater rules, including the bandwidth ranker, never run, so the effect doesn't
7
+ * subscribe to their signals while the choice is fixed).
9
8
  *
10
9
  * @param rules - Rules to apply, most authoritative first
11
10
  * @param tracks - Candidate tracks
@@ -23,15 +22,12 @@ function applyRules(rules, tracks, deps) {
23
22
  return current;
24
23
  }
25
24
  /**
26
- * Apply hard constraints to a candidate list — the pre-pass that runs before the
27
- * rule chain. A constraint shares a rule's signature but its exclusion is
28
- * *hard*: it removes the unplayable (a codec the environment can't decode, a CDN
29
- * in failover cooldown) and a removed track is never attempted. Unlike
30
- * `applyRules`, this never skips an empty result and never early-bails every
31
- * constraint always applies, and an empty survivor set is a real outcome
32
- * ("nothing playable here"), not a fall-through. Because each constraint only
33
- * removes, the order they run in can't change which tracks survive — though one
34
- * that also *reports* reads the list at its own position, so placement matters.
25
+ * Apply hard constraints to a candidate list — the pre-pass that runs before the rule chain. A constraint shares a
26
+ * rule's signature but its exclusion is _hard_: it removes the unplayable (a codec the environment can't decode, a CDN
27
+ * in failover cooldown) and a removed track is never attempted. Unlike `applyRules`, this never skips an empty result
28
+ * and never early-bails — every constraint always applies, and an empty survivor set is a real outcome ("nothing
29
+ * playable here"), not a fall-through. Because each constraint only removes, the order they run in can't change which
30
+ * tracks survive though one that also _reports_ reads the list at its own position, so placement matters.
35
31
  *
36
32
  * @param constraints - Constraints to apply, in order
37
33
  * @param tracks - Candidate tracks
@@ -46,41 +42,74 @@ function applyConstraints(constraints, tracks, deps) {
46
42
  /**
47
43
  * Whether two candidate sets hold the same tracks, by id.
48
44
  *
49
- * The `equals` both selection behaviors give their candidate-set `computed`. A
50
- * live playlist refresh swaps in a new presentation object carrying the same
51
- * variants, and a constraint's own inputs can churn without changing which
52
- * tracks survive; in both cases the set is unchanged and the reaction must not
53
- * re-fire. Compares by id rather than array identity for exactly that.
45
+ * The `equals` both selection behaviors give their candidate-set `computed`. A live playlist refresh swaps in a new
46
+ * presentation object carrying the same variants, and a constraint's own inputs can churn without changing which tracks
47
+ * survive; in both cases the set is unchanged and the reaction must not re-fire. Compares by id rather than array
48
+ * identity for exactly that.
54
49
  */
55
50
  function sameCandidateSet(a, b) {
56
51
  return a.length === b.length && a.every((track) => b.some((other) => other.id === track.id));
57
52
  }
58
53
  /**
59
- * Capability constraint — a *hard* filter for the {@link applyConstraints}
60
- * pre-pass. Removes renditions this environment can't decode, probed via the
61
- * injected `canPlayTrack` (codec `MediaSource.isTypeSupported`, plus the
62
- * container and encryption assertions that probe can't make). Constraining here
63
- * before selection means an unplayable variant is pruned upstream and never
64
- * picked, instead of surviving into the pipeline to fail late at
65
- * `createSourceBuffer`. That late throw stays as a defensive structural
66
- * guarantee; with this constraint it should rarely fire.
54
+ * Capability constraint — a _hard_ filter for the {@link applyConstraints} pre-pass. Removes renditions this
55
+ * environment can't decode, probed via the injected `canPlayTrack` (codec → `MediaSource.isTypeSupported`, plus the
56
+ * container and encryption assertions that probe can't make). Constraining here — before selection — means an
57
+ * unplayable variant is pruned upstream and never picked, instead of surviving into the pipeline to fail late at
58
+ * `createSourceBuffer`. That late throw stays as a defensive structural guarantee; with this constraint it should
59
+ * rarely fire.
67
60
  *
68
- * Lives here rather than beside `switchVideoTrack` for the reason this module
69
- * exists: both the re-evaluating variant and the pinned `selectVideoTrack` apply
70
- * it, and reaching it through `behaviors/track-switching.ts` would drag the ABR
61
+ * Lives here rather than beside `switchVideoTrack` for the reason this module exists: both the re-evaluating variant
62
+ * and the pinned `selectVideoTrack` apply it, and reaching it through `behaviors/track-switching.ts` would drag the ABR
71
63
  * path into a composition that deliberately omits it.
72
64
  *
73
- * Passes everything through when there's no probe (a composition that didn't
74
- * wire one, or DOM-free tests). When it prunes *every* track, the empty result is
75
- * preserved (per `applyConstraints`) "nothing playable" which each consuming
76
- * behavior answers by clearing its selection; reporting the verdict is separate.
65
+ * Passes everything through when there's no probe (a composition that didn't wire one, or DOM-free tests). When it
66
+ * prunes _every_ track, the empty result is preserved (per `applyConstraints`) "nothing playable" which each
67
+ * consuming behavior answers by clearing its selection; reporting the verdict is separate.
77
68
  */
78
69
  function excludeUnplayableTracks(tracks, { config }) {
79
70
  const canPlay = config?.canPlayTrack;
80
71
  if (!canPlay) return tracks;
81
72
  return tracks.filter((track) => canPlay(track));
82
73
  }
74
+ /**
75
+ * The codec families {@link preferCodecFamilies} narrows to when the config carries no `preferredCodecs`: AVC (both its
76
+ * 4CCs) and AAC. The default is deliberate rather than neutral — SPF implements no `SourceBuffer.changeType()`, so on a
77
+ * mixed-codec source the _initial_ family choice is the one ABR lives inside for the whole source (see
78
+ * `stickToSelectedCodecs` in `behaviors/track-switching.ts`), and AVC/AAC is the family pair with the broadest decode
79
+ * support. The cost, equally deliberate: on a ladder whose higher rungs are HEVC-only (a common 4K shape), the default
80
+ * caps quality at the top AVC rung — configure `preferredCodecs` (or pass `[]`) to trade the other way.
81
+ */
82
+ const DEFAULT_PREFERRED_CODECS = [
83
+ "avc1",
84
+ "avc3",
85
+ "mp4a"
86
+ ];
87
+ /**
88
+ * Codec-family preference — a _soft_ filter (scope) for a selection-rule chain; the `hevc-variant-selection` concern
89
+ * from `internal/design/spf/track-switching-model.md`. Narrows to the tracks whose codec families are all in
90
+ * `preferredCodecs` (family-compared, so entries may be 4CCs or full codec strings). Soft-filter semantics: a source
91
+ * with no preferred-family rendition (an HEVC-only ladder under the AVC default) is left unnarrowed, and a track
92
+ * without codecs never matches — on a ladder with no codecs anywhere (DOM-free tests) the rule is inert.
93
+ *
94
+ * `preferredCodecs` defaults to {@link DEFAULT_PREFERRED_CODECS}; explicitly pass `[]` to disable the preference while
95
+ * keeping the rule composed.
96
+ *
97
+ * "All families" rather than "any" so a muxed rendition is judged as the unit it is: `hvc1,mp4a` must not count as
98
+ * preferred just because its audio half matches.
99
+ *
100
+ * Lives here rather than beside `switchVideoTrack` for the module's usual reason: the pinned `selectVideoTrack` variant
101
+ * can compose it without dragging the ABR path in.
102
+ */
103
+ function preferCodecFamilies(tracks, { config }) {
104
+ const preferred = config?.preferredCodecs ?? DEFAULT_PREFERRED_CODECS;
105
+ if (!preferred.length) return tracks;
106
+ const preferredFamilies = new Set(preferred.map(getCodecFamily));
107
+ return tracks.filter((track) => {
108
+ const families = getCodecFamilies(track);
109
+ return !!families && families.every((family) => preferredFamilies.has(family));
110
+ });
111
+ }
83
112
  //#endregion
84
- export { applyConstraints, applyRules, excludeUnplayableTracks, sameCandidateSet };
113
+ export { DEFAULT_PREFERRED_CODECS, applyConstraints, applyRules, excludeUnplayableTracks, preferCodecFamilies, sameCandidateSet };
85
114
 
86
115
  //# sourceMappingURL=selection-rules.js.map