@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
@@ -6,6 +6,8 @@ import { setupTextTrackActors } from "../../behaviors/dom/setup-text-track-actor
6
6
  import { trackCurrentTime } from "../../behaviors/dom/track-current-time.js";
7
7
  import { trackLoadTriggers } from "../../behaviors/dom/track-load-triggers.js";
8
8
  import { deriveSharedMinStartMediaTime, establishStartMediaTime, gateFirstParseOnAnchor } from "../../behaviors/establish-start-media-time.js";
9
+ import { collectErrors } from "../../behaviors/collect-errors.js";
10
+ import { switchAudioTrack, switchTextTrack, switchVideoTrack } from "../../behaviors/track-switching.js";
9
11
  import { delayedReschedule } from "../../../core/tasks/delayed-reschedule.js";
10
12
  import { attachMediaSourceAsSourceElement } from "../../../media/dom/mse/mediasource-setup.js";
11
13
  import { canPlayTrack } from "../../../media/dom/capabilities.js";
@@ -14,7 +16,6 @@ import { parseMultivariantPlaylist } from "../../../media/hls/parse-multivariant
14
16
  import { mediaPlaylistReloadDelay, resolveLiveLatency } from "../../../media/hls/reload-policy.js";
15
17
  import { getResolvedSelectedTrackDuration } from "../../../media/utils/track-selection.js";
16
18
  import { calculatePresentationDuration } from "../../behaviors/calculate-presentation-duration.js";
17
- import { collectErrors } from "../../behaviors/collect-errors.js";
18
19
  import { deriveCdnPriority } from "../../behaviors/derive-cdn-priority.js";
19
20
  import { setupAirPlay } from "../../behaviors/dom/airplay.js";
20
21
  import { applyStartPosition } from "../../behaviors/dom/apply-start-position.js";
@@ -31,17 +32,14 @@ import { resolvePresentation } from "../../behaviors/resolve-presentation.js";
31
32
  import { resolveAudioTrack, resolveTextTrack, resolveVideoTrack } from "../../behaviors/resolve-track.js";
32
33
  import { setupFailoverMonitor } from "../../behaviors/setup-failover-monitor.js";
33
34
  import { syncPreload } from "../../behaviors/sync-preload.js";
34
- import { switchAudioTrack, switchTextTrack, switchVideoTrack } from "../../behaviors/track-switching.js";
35
35
  import { relocatingTextPipelines, relocationPipelinesFor } from "../../primitives/relocation-pipelines.js";
36
36
  import { reportUnsupportedTrackConditions } from "../../primitives/report-track-conditions.js";
37
37
  //#region src/playback/engines/hls/engine.ts
38
38
  /**
39
- * Generic `shareSignals` instantiated against the HLS engine's full state
40
- * and context captures composition signal refs into the consumer's
41
- * `onSignalsReady` callback at setup time, and materializes input slots that no
42
- * composed behavior produces: `user*TrackSelection` (track-switching only reads
43
- * them). `failedCdns` is owned by `setupFailoverMonitor`, so it's already
44
- * materialized and reachable on the `onSignalsReady` refs without being listed
39
+ * Generic `shareSignals` instantiated against the HLS engine's full state and context — captures composition signal
40
+ * refs into the consumer's `onSignalsReady` callback at setup time, and materializes input slots that no composed
41
+ * behavior produces: `user*TrackSelection` (track-switching only reads them). `failedCdns` is owned by
42
+ * `setupFailoverMonitor`, so it's already materialized and reachable on the `onSignalsReady` refs without being listed
45
43
  * here.
46
44
  */
47
45
  const shareSignals = makeShareSignals([
@@ -53,28 +51,27 @@ const shareSignals = makeShareSignals([
53
51
  /**
54
52
  * Create an HLS playback engine.
55
53
  *
56
- * Composes SPF behaviors into a reactive pipeline for HLS playback over MSE:
57
- * manifest resolution, track selection, ABR, segment loading, and
58
- * end-of-stream coordination.
54
+ * Composes SPF behaviors into a reactive pipeline for HLS playback over MSE: manifest resolution, track selection, ABR,
55
+ * segment loading, and end-of-stream coordination.
59
56
  *
60
57
  * @example
61
- * ```ts
62
- * let signals: HlsVideoEngineSignals;
63
- * const engine = createHlsVideoEngine({
64
- * initialBandwidth: 2_000_000,
65
- * preferredAudioLanguage: 'en',
66
- * onSignalsReady: (refs) => {
67
- * signals = refs;
68
- * },
69
- * });
58
+ * ```ts
59
+ * let signals: HlsVideoEngineSignals;
60
+ * const engine = createHlsVideoEngine({
61
+ * initialBandwidth: 2_000_000,
62
+ * preferredAudioLanguage: 'en',
63
+ * onSignalsReady: (refs) => {
64
+ * signals = refs;
65
+ * },
66
+ * });
70
67
  *
71
- * signals.context.mediaElement.set(videoEl);
72
- * signals.state.presentation.set({ url: 'https://example.com/stream.m3u8' });
68
+ * signals.context.mediaElement.set(videoEl);
69
+ * signals.state.presentation.set({ url: 'https://example.com/stream.m3u8' });
73
70
  *
74
- * videoEl.play();
71
+ * videoEl.play();
75
72
  *
76
- * await engine.destroy();
77
- * ```
73
+ * await engine.destroy();
74
+ * ```;
78
75
  */
79
76
  function createHlsVideoEngine(config = {}) {
80
77
  const deriveStartMediaTime = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","names":[],"sources":["../../../../../src/playback/engines/hls/engine.ts"],"sourcesContent":["import {\n type Composition,\n type ContextSignals,\n createComposition,\n type StateSignals,\n} from '../../../core/composition/create-composition';\nimport { makeShareSignals, type ShareSignalsConfig } from '../../../core/composition/share-signals';\nimport { delayedReschedule } from '../../../core/tasks/delayed-reschedule';\nimport type { Reschedule } from '../../../core/tasks/task';\nimport type { QualityConfig } from '../../../media/abr/quality-selection';\nimport type { BackBufferConfig } from '../../../media/buffer/back-buffer';\nimport type { ForwardBufferConfig } from '../../../media/buffer/forward-buffer';\nimport { canPlayTrack } from '../../../media/dom/capabilities';\nimport { attachMediaSourceAsSourceElement } from '../../../media/dom/mse/mediasource-setup';\nimport { resolveVttSegment } from '../../../media/dom/text/resolve-vtt-segment';\nimport {\n addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia,\n} from '../../../media/dom/text/text-track-slots';\nimport type { SvtaError } from '../../../media/errors';\nimport { parseMultivariantPlaylist } from '../../../media/hls/parse-multivariant';\nimport { mediaPlaylistReloadDelay, resolveLiveLatency } from '../../../media/hls/reload-policy';\nimport type {\n AudioTrack,\n CanPlayTrack,\n MaybeResolvedPresentation,\n MediaContainerData,\n ResolvedTrack,\n TextTrack,\n VideoTrack,\n} from '../../../media/types';\nimport type { GetCdnId } from '../../../media/utils/cdn';\nimport { getResolvedSelectedTrackDuration } from '../../../media/utils/track-selection';\nimport type { BandwidthConfig, BandwidthState } from '../../../network/bandwidth-estimator';\nimport type { SegmentLoaderActor } from '../../actors/dom/segment-loader';\nimport type { SourceBufferActor } from '../../actors/dom/source-buffer';\nimport type { TextTracksActor } from '../../actors/dom/text-tracks';\nimport type { TextTrackSegmentLoaderActor } from '../../actors/text-track-segment-loader';\nimport {\n calculatePresentationDuration,\n type PresentationDurationResolver,\n} from '../../behaviors/calculate-presentation-duration';\nimport { collectErrors } from '../../behaviors/collect-errors';\nimport { deriveCdnPriority } from '../../behaviors/derive-cdn-priority';\nimport { setupAirPlay } from '../../behaviors/dom/airplay';\nimport { applyStartPosition } from '../../behaviors/dom/apply-start-position';\nimport { endOfStream } from '../../behaviors/dom/end-of-stream';\nimport { loadAudioSegments, loadTextTrackSegments, loadVideoSegments } from '../../behaviors/dom/load-segments';\nimport { recoverEndStall } from '../../behaviors/dom/recover-end-stall';\nimport { seekToLiveEdge } from '../../behaviors/dom/seek-to-live-edge';\nimport { setupAudioBufferActors, setupVideoBufferActors } from '../../behaviors/dom/setup-buffer-actors';\nimport { setupMediaSource } from '../../behaviors/dom/setup-mediasource';\nimport { setupTextTrackActors } from '../../behaviors/dom/setup-text-track-actors';\nimport { syncLiveSeekableRange } from '../../behaviors/dom/sync-live-seekable-range';\nimport { syncTextTracks } from '../../behaviors/dom/sync-text-tracks';\nimport { trackCurrentTime } from '../../behaviors/dom/track-current-time';\nimport { trackLoadTriggers } from '../../behaviors/dom/track-load-triggers';\nimport { type PlayerResolution, trackPlayerResolution } from '../../behaviors/dom/track-player-resolution';\nimport { updateMediaSourceDuration } from '../../behaviors/dom/update-mediasource-duration';\n// Non-zero-PTS relocation (spike): remove this import, the composed reactor, the\n// `video/audio/textMessagePipelines` finalConfig entries, the `mediaContainerData`\n// state slot, and the `deriveStartMediaTime` config field to drop relocation entirely\n// (text then falls back to the plain `resolveVttSegment` resolver).\nimport {\n type DeriveStartMediaTime,\n deriveSharedMinStartMediaTime,\n establishStartMediaTime,\n gateFirstParseOnAnchor,\n} from '../../behaviors/establish-start-media-time';\nimport { type ParsePresentation, resolvePresentation } from '../../behaviors/resolve-presentation';\nimport { resolveAudioTrack, resolveTextTrack, resolveVideoTrack } from '../../behaviors/resolve-track';\nimport { type FailoverMonitorConfig, setupFailoverMonitor } from '../../behaviors/setup-failover-monitor';\nimport { syncPreload } from '../../behaviors/sync-preload';\nimport { switchAudioTrack, switchTextTrack, switchVideoTrack } from '../../behaviors/track-switching';\nimport { relocatingTextPipelines, relocationPipelinesFor } from '../../primitives/relocation-pipelines';\nimport {\n type ReportUnsupportedTrackConditions,\n reportUnsupportedTrackConditions,\n} from '../../primitives/report-track-conditions';\nimport type { TextTrackSegmentResolver } from '../../primitives/text-segment-load-pipeline';\n\n// ============================================================================\n// HLS Engine State & Context\n// ============================================================================\n\n/**\n * State shape for the HLS playback engine.\n *\n * This is the union of all state required by the behaviors composed into\n * the HLS engine. Each behavior declares its own state interface; this\n * type satisfies all of them.\n */\nexport interface HlsVideoEngineState {\n /**\n * The presentation being played. A caller writes `{ url }`;\n * `resolvePresentation` parses the manifest and populates the rest.\n */\n presentation?: MaybeResolvedPresentation;\n preload?: 'auto' | 'metadata' | 'none';\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n bandwidthState?: BandwidthState;\n // Non-zero-PTS relocation (spike): transient per-track container data owned by\n // `establishStartMediaTime`. Remove with the composed reactor.\n mediaContainerData?: Record<string, MediaContainerData>;\n userVideoTrackSelection?: Partial<VideoTrack>;\n /**\n * Consumer-driven constraint narrowing the audio candidate set. Sibling\n * of `userVideoTrackSelection`. Partial-track shape — `{ language: 'es' }`,\n * `{ id: 'audio-en' }`, etc. `selectAudioTrack` reads this and re-picks\n * when it changes. Multi-language-audio Tier 2 programmatic-write path.\n */\n userAudioTrackSelection?: Partial<AudioTrack>;\n /**\n * Consumer-driven *intent* for text selection, resolved into\n * `selectedTextTrackId` by `switchTextTrack`. A language-based partial\n * (`{ language: 'es' }`) selects captions, `'off'` disables them, and absence\n * means auto (the engine's `preferredSubtitleLanguage` / DEFAULT-track policy).\n * Also the write path for the DOM caption UI (via `syncTextTracks`); unlike the\n * resolved id it persists across source changes (sticky preference).\n */\n userTextTrackSelection?: Partial<TextTrack> | 'off';\n /**\n * The CDNs the source is served from (track-URL origins), in manifest\n * priority order — most-preferred first (mirrors HLS content steering's\n * `PATHWAY-PRIORITY`). Owned by `deriveCdnPriority`, read by\n * `track-switching`'s `preferActiveCdn` scope, which narrows to the\n * highest-priority CDN with surviving tracks so video / audio / text stay on\n * one host. Only meaningful for redundant-stream sources; a single-CDN source\n * has one entry.\n */\n cdnPriority?: string[];\n /**\n * CDN ids (origins) currently in failover cooldown — written by the CDN\n * monitor when a host fails too often, read by `track-switching`'s\n * `excludeFailedCdns` hard constraint, which prunes their tracks so the\n * active-CDN scope falls to the next CDN in `cdnPriority`. Empty / absent\n * means all CDNs are eligible.\n */\n failedCdns?: string[];\n /**\n * Conditions reported during playback, in the order encountered — appended by\n * whichever behavior detects one (`emitError`), owned and cleared per source by\n * `collectErrors`. Carries no severity: which of these is fatal is decided\n * above the engine, at the adapter. See\n * `internal/design/spf/features/errors.md`.\n */\n errors?: SvtaError[];\n currentTime?: number;\n /**\n * The player element's rendered pixel dimensions, or `undefined` where there\n * is nothing to measure. Written by `trackPlayerResolution`, read by the\n * `playerResolutionCap` selection rule — which treats `undefined` as\n * \"don't cap\".\n */\n playerResolution?: PlayerResolution;\n loadActivated?: boolean;\n /**\n * One-shot command: start the current source at this position\n * (presentation-timeline seconds). Written by consumers or by\n * `setupAirPlay`'s session-end snapshot; consumed (cleared) by\n * `applyStartPosition` once the element seeks. See\n * `behaviors/dom/apply-start-position.ts`.\n */\n startPosition?: number;\n /**\n * Intent-level loading policy: initiate no new loading work while `true`.\n * Written by `setupAirPlay` (the only behavior declaring the key) while a\n * remote-playback session owns presentation; observed by the\n * `loadXSegments` dispatchers (park in `'dormant'`) and by\n * `setupMediaSource` (a pending rebuild waits). See\n * `SegmentLoadingState['loadingSuspended']`.\n */\n loadingSuspended?: boolean;\n /**\n * Author intent for the AirPlay/remote-playback picker, written by the media\n * adapter's `disableRemotePlayback` IDL property. `true` is an explicit\n * opt-out: `setupAirPlay` reads it at attach and sets nothing up, leaving the\n * element's remote playback disabled. Distinct from the underlying\n * `<video>.disableRemotePlayback`, which stays programmatically managed\n * (ManagedMediaSource / AirPlay).\n */\n disableRemotePlayback?: boolean;\n}\n\n/**\n * Context shape for the HLS playback engine.\n *\n * Platform objects and actor references managed by HLS behaviors.\n */\nexport interface HlsVideoEngineContext {\n mediaElement?: HTMLMediaElement | undefined;\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n textTracksActor?: TextTracksActor;\n textTrackSegmentLoaderActor?: TextTrackSegmentLoaderActor;\n}\n\n/**\n * The composition signal refs handed to `onSignalsReady` callers — the\n * canonical way to drive the engine externally (writes) or observe its\n * state (reads) without touching `composition.state` / `composition.context`\n * directly.\n */\nexport type HlsVideoEngineSignals = {\n state: StateSignals<HlsVideoEngineState>;\n context: ContextSignals<HlsVideoEngineContext>;\n};\n\n/**\n * Configuration for the HLS playback engine.\n *\n * Each option is consumed by the appropriate behavior — the engine itself\n * has no config beyond what its behaviors read.\n */\nexport interface HlsVideoEngineConfig extends ShareSignalsConfig<HlsVideoEngineState, HlsVideoEngineContext> {\n /**\n * Bandwidth estimate in bps to use before enough samples have been\n * collected. Default: `DEFAULT_INITIAL_BANDWIDTH` (5 Mbps).\n */\n initialBandwidth?: number;\n /**\n * Codec capability probe injected into `track-switching`'s\n * `excludeUnplayableTracks` constraint — drops renditions the environment\n * can't decode before selection. Defaults to the `MediaSource.isTypeSupported`\n * -backed `canPlayTrack`; supply your own to override (e.g. force-exclude a\n * codec).\n */\n canPlayTrack?: CanPlayTrack;\n /**\n * Conditions reported about each rendition as it resolves — the *causes* behind\n * a later verdict, and the copy a verdict reuses when they agree. Defaults to\n * {@link reportUnsupportedTrackConditions}, which reports non-fMP4 containers\n * and encryption; supply your own to report a different set (a provider that\n * never ships MPEG-TS can drop that check) or `() => []` to report nothing.\n */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n preferredAudioLanguage?: string;\n preferredSubtitleLanguage?: string;\n includeForcedTracks?: boolean;\n enableDefaultTrack?: boolean;\n /**\n * Resolver that turns a text-track segment fetch into VTT cues.\n * Defaults to the DOM-bound `resolveVttSegment` resolver, which uses an\n * offscreen `<track>` element to parse WebVTT.\n */\n resolveTextTrackSegment?: TextTrackSegmentResolver<VTTCue>;\n /**\n * Resolver for `presentation.duration`. Defaults to picking the first\n * resolved selected track's duration (video preferred, audio fallback) —\n * appropriate for VoD and audio-only. Live engines should supply a\n * resolver that returns `Number.POSITIVE_INFINITY` once the presentation\n * is established as live; downstream `updateMediaSourceDuration` propagates\n * that value to `mediaSource.duration` per the MSE spec.\n */\n resolveDuration?: PresentationDurationResolver;\n /**\n * Manifest parser handed to `resolvePresentation`. Defaults to the HLS\n * multivariant-playlist parser; supply your own for alternate format\n * support without forking the engine.\n */\n parsePresentation?: ParsePresentation;\n /**\n * Allocate SPF-owned text-track slots on the media element. Defaults to\n * the standard `<track>`-element implementation in\n * `media/dom/text/text-track-slots`.\n */\n addSubtitlesTracksToMedia?: typeof addSubtitlesTracksToMedia;\n /**\n * Return the SPF-owned subtitle/caption `TextTrack` currently in showing\n * mode. Defaults to the standard selector-based implementation in\n * `media/dom/text/text-track-slots`.\n */\n getShowingSubtitlesTrackFromMedia?: typeof getShowingSubtitlesTrackFromMedia;\n /**\n * Evict all SPF-owned text-track slots from the media element. Defaults to\n * the standard selector-based implementation in\n * `media/dom/text/text-track-slots`.\n */\n removeAllSubtitlesTracksFromMedia?: typeof removeAllSubtitlesTracksFromMedia;\n /**\n * Forward-buffer tuning. `bufferDuration` controls how far ahead of the\n * playhead segments are loaded (and where forward-flush kicks in).\n * Defaults: see `DEFAULT_FORWARD_BUFFER_CONFIG` (30 seconds). Threaded to\n * segment-loader actors (v/a + text) at construction time and to\n * `loadXSegments` dispatchers for the load-message range.\n */\n forwardBuffer?: Partial<ForwardBufferConfig>;\n /**\n * Back-buffer tuning. `keepSegments` controls how many segments stay\n * behind the playhead before eviction. Defaults: see\n * `DEFAULT_BACK_BUFFER_CONFIG` (2 segments). Threaded to the v/a\n * segment-loader actor only (text tracks don't use back-buffer eviction).\n */\n backBuffer?: Partial<BackBufferConfig>;\n /**\n * Bandwidth-estimator tuning. Overrides any field of `BandwidthConfig`\n * (`fastHalfLife`, `slowHalfLife`, `minTotalBytes`, `minBytes`,\n * `minDuration`). `bandwidth.minTotalBytes` supersedes the flat\n * `minTotalBytes` field above. Defaults: see `DEFAULT_BANDWIDTH_CONFIG`.\n */\n bandwidth?: Partial<BandwidthConfig>;\n /**\n * Quality-selection tuning. `safetyMargin` is the bandwidth-headroom\n * multiplier used by `selectQuality`; `upgradeMargin` is the hysteresis\n * ratio gating ABR upgrades. Defaults: `DEFAULT_QUALITY_CONFIG` (0.85 / 1.15).\n */\n quality?: Partial<QualityConfig>;\n /**\n * Whether video renditions are capped to the player element's rendered size.\n * Read by `trackPlayerResolution`; `false` measures nothing, which leaves the\n * `playerResolutionCap` rule inert. Defaults to `true`.\n */\n capRenditionToPlayerSize?: boolean;\n /**\n * Whether `state.playerResolution` is reported in device pixels. Read by\n * `trackPlayerResolution`; defaults to `true`.\n */\n useDevicePixelRatio?: boolean;\n /**\n * Multi-CDN failover monitor tuning. `cooldownMs` is how long a CDN stays\n * excluded after a failed fetch trips it. Defaults:\n * `DEFAULT_FAILOVER_MONITOR_CONFIG` (300s). Only meaningful for redundant-stream\n * sources.\n */\n failover?: Partial<FailoverMonitorConfig>;\n /**\n * How to derive a CDN grouping key from a track URL — used to build\n * `cdnPriority`, to record the failover trip in `failedCdns`, and by the\n * track-switching CDN scope + failover constraint. One function, read by all of\n * them, so the keys stay comparable. Defaults to the URL origin; override to\n * key on something else (e.g. Mux's `cdn=` query param).\n */\n getCdnId?: GetCdnId;\n /**\n * Non-zero-PTS relocation (spike): the reduce seam consumed by the\n * `establishStartMediaTime` reactor. Defaults to per-track own origin (Tier 1);\n * a Tier-2 variant returns the shared `min` across selected A/V. Relocation is\n * composed into the standard engine below — see the marked block — so this only\n * needs setting to swap the tier policy. See\n * `internal/design/spf/presentation-timeline-model.md`.\n */\n deriveStartMediaTime?: DeriveStartMediaTime;\n /**\n * Proximity window (seconds) for the `recoverEndStall` behavior — how close the\n * playhead must be to the reachable buffered end for a `waiting` to be treated as the\n * end-of-stream freeze and nudged to `ended`. Defaults to `0.2`. See\n * `behaviors/dom/recover-end-stall`.\n */\n endStallNudgeWindow?: number;\n /**\n * Live media-playlist re-run policy for the resolve* loaders' `RecurringRunner`:\n * returns a promise that resolves when the playlist should reload, or `null` to\n * stop. Defaults to `mediaPlaylistReloadDelay` (target-duration cadence, half on\n * an unchanged window, stop on `#EXT-X-ENDLIST`) composed with a cancellable\n * `sleep`. Inert for VoD (a complete playlist stops it after the first resolve).\n * Override to tune live reload timing.\n */\n reschedule?: Reschedule<ResolvedTrack>;\n}\n\n// ============================================================================\n// HLS Playback Engine\n// ============================================================================\n\n/**\n * Generic `shareSignals` instantiated against the HLS engine's full state\n * and context — captures composition signal refs into the consumer's\n * `onSignalsReady` callback at setup time, and materializes input slots that no\n * composed behavior produces: `user*TrackSelection` (track-switching only reads\n * them). `failedCdns` is owned by `setupFailoverMonitor`, so it's already\n * materialized and reachable on the `onSignalsReady` refs without being listed\n * here.\n */\nconst shareSignals = makeShareSignals<HlsVideoEngineState, HlsVideoEngineContext>([\n 'userVideoTrackSelection',\n 'userAudioTrackSelection',\n 'userTextTrackSelection',\n 'disableRemotePlayback',\n]);\n\n/**\n * Create an HLS playback engine.\n *\n * Composes SPF behaviors into a reactive pipeline for HLS playback over MSE:\n * manifest resolution, track selection, ABR, segment loading, and\n * end-of-stream coordination.\n *\n * @example\n * ```ts\n * let signals: HlsVideoEngineSignals;\n * const engine = createHlsVideoEngine({\n * initialBandwidth: 2_000_000,\n * preferredAudioLanguage: 'en',\n * onSignalsReady: (refs) => {\n * signals = refs;\n * },\n * });\n *\n * signals.context.mediaElement.set(videoEl);\n * signals.state.presentation.set({ url: 'https://example.com/stream.m3u8' });\n *\n * videoEl.play();\n *\n * await engine.destroy();\n * ```\n */\nexport function createHlsVideoEngine(\n config: HlsVideoEngineConfig = {}\n): Composition<HlsVideoEngineState, HlsVideoEngineContext> {\n // Non-zero-PTS relocation (spike): resolve the coordination seam once so the reactor\n // (model `startMediaTime`) and the loader stamps (buffer `timestampOffset`) apply the\n // SAME derive. Default is shared-`min` across selected A/V (subsumes per-type).\n const deriveStartMediaTime = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;\n const finalConfig = {\n ...config,\n deriveStartMediaTime,\n // Baked (not user-overridable): this engine composes `setupAirPlay`,\n // whose native fallback `<source>` requires the MSE attachment to keep\n // sibling source alternatives part of resource selection.\n attachMediaSource: attachMediaSourceAsSourceElement,\n canPlayTrack: config.canPlayTrack ?? canPlayTrack,\n reportUnsupportedTrackConditions: config.reportUnsupportedTrackConditions ?? reportUnsupportedTrackConditions,\n resolveTextTrackSegment: config.resolveTextTrackSegment ?? resolveVttSegment,\n // Non-zero-PTS relocation (spike): the text pipeline rebases cues onto the\n // relocated 0-based timeline. Remove `textMessagePipelines` to drop text relocation.\n textMessagePipelines: relocatingTextPipelines,\n resolveDuration: config.resolveDuration ?? getResolvedSelectedTrackDuration,\n parsePresentation: config.parsePresentation ?? parseMultivariantPlaylist,\n addSubtitlesTracksToMedia: config.addSubtitlesTracksToMedia ?? addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia: config.getShowingSubtitlesTrackFromMedia ?? getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia: config.removeAllSubtitlesTracksFromMedia ?? removeAllSubtitlesTracksFromMedia,\n // Non-zero-PTS relocation (spike): the discover/stamp steps `establishStartMediaTime`\n // pairs with. They apply the same `deriveStartMediaTime` seam as the reactor. Remove\n // these two lines with the reactor.\n videoMessagePipelines: relocationPipelinesFor('video', deriveStartMediaTime),\n audioMessagePipelines: relocationPipelinesFor('audio', deriveStartMediaTime),\n // Live-anchor establishment order: each non-reference track's first parse\n // waits for the reference track to settle the wall-clock anchor question\n // (see `gate-first-parse.ts`); pairs with the reactor's anchor stamp.\n gateFirstParse: gateFirstParseOnAnchor,\n // Format-neutral live-latency seam for `seekToLiveEdge` — the HLS resolver\n // (HOLD-BACK); a DASH engine would inject `suggestedPresentationDelay`.\n resolveLiveLatency,\n // The resolve* loaders' RecurringRunner re-runs on this `reschedule`: the pure\n // target-duration cadence, start-anchored + made awaitable by `delayedReschedule`.\n // Inert for VoD (the cadence returns null once a playlist is complete), so it\n // composes always.\n reschedule: config.reschedule ?? delayedReschedule(mediaPlaylistReloadDelay),\n };\n\n return createComposition(\n [\n syncPreload,\n trackLoadTriggers,\n resolvePresentation,\n\n // Session-level CDN priority for redundant-stream sources. Owns\n // `cdnPriority`; `track-switching`'s preferActiveCdn scope reads it so\n // every type stays on one CDN. No-op for single-CDN sources.\n //\n // Placed before switch* so `cdnPriority` is set before the first pick —\n // but this ordering is only *mildly* load-bearing, not required for\n // correctness. Selection is reactive: a late `cdnPriority` re-fires the\n // pick and converges on the same result (see the late-arrival test in\n // track-switching.test.ts). Order affects only a transient, and only for\n // an *asymmetric* manifest (a type listing a non-primary CDN first):\n // composing this after switch* would let that type fire one wasted\n // media-playlist fetch to the wrong CDN before correcting. Symmetric\n // redundant streams (the norm) never hit it — the first-listed CDN is\n // already the primary we'd pick anyway.\n deriveCdnPriority,\n\n // CDN failover cooldown: owns the expiry half of failover — watches\n // `failedCdns` (tripped directly by track resolution on a failed\n // media-playlist fetch) and removes each CDN once its cooldown lapses.\n setupFailoverMonitor,\n\n // Owns `errors` and its per-source lifecycle. Composed before the\n // behaviors that report into it so the slot exists when they first run;\n // reporting no-ops if it isn't composed at all.\n collectErrors,\n\n // Resolve selected tracks (fetch media playlists). Composed before the\n // switch* slot owners; selection is reactive, so a resolve* re-fires once\n // its switch* sets the id (same convergence for all three types).\n resolveVideoTrack,\n resolveAudioTrack,\n resolveTextTrack,\n\n // Presentation duration\n calculatePresentationDuration,\n\n // MSE setup. Video cluster is registered first so that, when both\n // per-type variants flip to `'buffer-ready'` on the shared gate's\n // monitor evaluation, `addSourceBuffer(video)` runs before\n // `addSourceBuffer(audio)` — see the Firefox `mozHasAudio` invariant\n // in setup-buffer-actors.ts.\n setupMediaSource,\n updateMediaSourceDuration,\n\n // ── Non-zero-PTS relocation (spike) ──────────────────────────────────\n // Establishes per-track `startMediaTime` and publishes the relocating\n // segment-loader pipelines to context. MUST precede `setup*BufferActors`\n // so the pipelines are published before the loaders read them. Remove this\n // one line (+ the import, the `mediaContainerData`/`*MessagePipelines`\n // slots including `textMessagePipelines`, and the `deriveStartMediaTime`\n // config) to drop relocation and test the Tier-0 baseline / bundle size.\n establishStartMediaTime,\n // ─────────────────────────────────────────────────────────────────────\n\n setupVideoBufferActors,\n setupAudioBufferActors,\n\n // AirPlay/MSE bridge (WebKit only; no-op elsewhere).\n setupAirPlay,\n\n // Playback tracking\n trackCurrentTime,\n // After trackCurrentTime: the one-shot currentTime seed must land after\n // the mirror's attach-time sync (see apply-start-position.ts).\n applyStartPosition,\n\n // Ordering isn't load-bearing — selection is reactive, so a measurement\n // that lands after the first pick just re-fires it.\n trackPlayerResolution,\n switchVideoTrack,\n switchAudioTrack,\n // Mid-stream audio-buffer flush on language switch is handled in\n // `segment-loader`'s `planTasks` (predicate: language differs from\n // the previously-buffered track) — not in switchAudioTrack itself.\n\n // Text selection: resolves `userTextTrackSelection` intent (incl. 'off',\n // or the configured preferred-language / DEFAULT-track policy) against the\n // failed-CDN-pruned, active-CDN-scoped text renditions. Optional selection\n // (captions are opt-in), so it can resolve to none.\n switchTextTrack,\n\n // Segment loading\n loadVideoSegments,\n loadAudioSegments,\n\n // Live: declare the seekable window, then command the live-edge start\n // position + keep the playhead in-window. No-op for complete playlists\n // (VoD / ended). `seekToLiveEdge` commands `state.startPosition`;\n // `applyStartPosition` (composed above) performs the seek.\n syncLiveSeekableRange,\n seekToLiveEdge,\n\n // End of stream coordination\n endOfStream,\n // Force native `ended` when Chrome freezes the playhead a few frames short of a\n // skewed-A/V end after `endOfStream` (audio-clock stall). Inert otherwise.\n recoverEndStall,\n\n // Text tracks\n syncTextTracks,\n setupTextTrackActors,\n loadTextTrackSegments,\n\n // Behavior whose sole purpose is to use a callback to allow for signal writing from the outside (e.g. an adapter)\n // NOTE: While not required, adding at the end since behaviors are setup in order, so this increases the likelihood\n // that initial signal setup will have occurred before shareSignals' callback is invoked. (CJP)\n shareSignals,\n ],\n {\n config: finalConfig,\n // Seed bandwidthState so switchVideoTrack fires on initial subscribe\n // with the `initialBandwidth` fallback rather than waiting for the\n // first chunk. The empty sample buffer means `getBandwidthEstimate`\n // returns the configured initial bandwidth until real samples land.\n initialState: {\n bandwidthState: {\n fastEstimate: 0,\n fastTotalWeight: 0,\n slowEstimate: 0,\n slowTotalWeight: 0,\n bytesSampled: 0,\n },\n },\n }\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2XA,MAAM,eAAe,iBAA6D;CAChF;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BD,SAAgB,qBACd,SAA+B,CAAC,GACyB;CAIzD,MAAM,uBAAuB,OAAO,wBAAwB;CAC5D,MAAM,cAAc;EAClB,GAAG;EACH;EAIA,mBAAmB;EACnB,cAAc,OAAO,gBAAgB;EACrC,kCAAkC,OAAO,oCAAoC;EAC7E,yBAAyB,OAAO,2BAA2B;EAG3D,sBAAsB;EACtB,iBAAiB,OAAO,mBAAmB;EAC3C,mBAAmB,OAAO,qBAAqB;EAC/C,2BAA2B,OAAO,6BAA6B;EAC/D,mCAAmC,OAAO,qCAAqC;EAC/E,mCAAmC,OAAO,qCAAqC;EAI/E,uBAAuB,uBAAuB,SAAS,oBAAoB;EAC3E,uBAAuB,uBAAuB,SAAS,oBAAoB;EAI3E,gBAAgB;EAGhB;EAKA,YAAY,OAAO,cAAc,kBAAkB,wBAAwB;CAC7E;CAEA,OAAO,kBACL;EACE;EACA;EACA;EAgBA;EAKA;EAKA;EAKA;EACA;EACA;EAGA;EAOA;EACA;EASA;EAGA;EACA;EAGA;EAGA;EAGA;EAIA;EACA;EACA;EASA;EAGA;EACA;EAMA;EACA;EAGA;EAGA;EAGA;EACA;EACA;EAKA;CACF,GACA;EACE,QAAQ;EAKR,cAAc,EACZ,gBAAgB;GACd,cAAc;GACd,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,cAAc;EAChB,EACF;CACF,CACF;AACF"}
1
+ {"version":3,"file":"engine.js","names":[],"sources":["../../../../../src/playback/engines/hls/engine.ts"],"sourcesContent":["import {\n type Composition,\n type ContextSignals,\n createComposition,\n type StateSignals,\n} from '../../../core/composition/create-composition';\nimport { makeShareSignals, type ShareSignalsConfig } from '../../../core/composition/share-signals';\nimport { delayedReschedule } from '../../../core/tasks/delayed-reschedule';\nimport type { Reschedule } from '../../../core/tasks/task';\nimport type { QualityConfig } from '../../../media/abr/quality-selection';\nimport type { BackBufferConfig } from '../../../media/buffer/back-buffer';\nimport type { ForwardBufferConfig } from '../../../media/buffer/forward-buffer';\nimport { canPlayTrack } from '../../../media/dom/capabilities';\nimport { attachMediaSourceAsSourceElement } from '../../../media/dom/mse/mediasource-setup';\nimport { resolveVttSegment } from '../../../media/dom/text/resolve-vtt-segment';\nimport {\n addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia,\n} from '../../../media/dom/text/text-track-slots';\nimport type { SvtaError } from '../../../media/errors';\nimport { parseMultivariantPlaylist } from '../../../media/hls/parse-multivariant';\nimport { mediaPlaylistReloadDelay, resolveLiveLatency } from '../../../media/hls/reload-policy';\nimport type {\n AudioTrack,\n CanPlayTrack,\n MaybeResolvedPresentation,\n MediaContainerData,\n ResolvedTrack,\n TextTrack,\n VideoTrack,\n} from '../../../media/types';\nimport type { GetCdnId } from '../../../media/utils/cdn';\nimport { getResolvedSelectedTrackDuration } from '../../../media/utils/track-selection';\nimport type { BandwidthConfig, BandwidthState } from '../../../network/bandwidth-estimator';\nimport type { SegmentLoaderActor } from '../../actors/dom/segment-loader';\nimport type { SourceBufferActor } from '../../actors/dom/source-buffer';\nimport type { TextTracksActor } from '../../actors/dom/text-tracks';\nimport type { TextTrackSegmentLoaderActor } from '../../actors/text-track-segment-loader';\nimport {\n calculatePresentationDuration,\n type PresentationDurationResolver,\n} from '../../behaviors/calculate-presentation-duration';\nimport { collectErrors } from '../../behaviors/collect-errors';\nimport { deriveCdnPriority } from '../../behaviors/derive-cdn-priority';\nimport { setupAirPlay } from '../../behaviors/dom/airplay';\nimport { applyStartPosition } from '../../behaviors/dom/apply-start-position';\nimport { endOfStream } from '../../behaviors/dom/end-of-stream';\nimport { loadAudioSegments, loadTextTrackSegments, loadVideoSegments } from '../../behaviors/dom/load-segments';\nimport { recoverEndStall } from '../../behaviors/dom/recover-end-stall';\nimport { seekToLiveEdge } from '../../behaviors/dom/seek-to-live-edge';\nimport { setupAudioBufferActors, setupVideoBufferActors } from '../../behaviors/dom/setup-buffer-actors';\nimport { setupMediaSource } from '../../behaviors/dom/setup-mediasource';\nimport { setupTextTrackActors } from '../../behaviors/dom/setup-text-track-actors';\nimport { syncLiveSeekableRange } from '../../behaviors/dom/sync-live-seekable-range';\nimport { syncTextTracks } from '../../behaviors/dom/sync-text-tracks';\nimport { trackCurrentTime } from '../../behaviors/dom/track-current-time';\nimport { trackLoadTriggers } from '../../behaviors/dom/track-load-triggers';\nimport { type PlayerResolution, trackPlayerResolution } from '../../behaviors/dom/track-player-resolution';\nimport { updateMediaSourceDuration } from '../../behaviors/dom/update-mediasource-duration';\n// Non-zero-PTS relocation (spike): remove this import, the composed reactor, the\n// `video/audio/textMessagePipelines` finalConfig entries, the `mediaContainerData`\n// state slot, and the `deriveStartMediaTime` config field to drop relocation entirely\n// (text then falls back to the plain `resolveVttSegment` resolver).\nimport {\n type DeriveStartMediaTime,\n deriveSharedMinStartMediaTime,\n establishStartMediaTime,\n gateFirstParseOnAnchor,\n} from '../../behaviors/establish-start-media-time';\nimport { type ParsePresentation, resolvePresentation } from '../../behaviors/resolve-presentation';\nimport { resolveAudioTrack, resolveTextTrack, resolveVideoTrack } from '../../behaviors/resolve-track';\nimport { type FailoverMonitorConfig, setupFailoverMonitor } from '../../behaviors/setup-failover-monitor';\nimport { syncPreload } from '../../behaviors/sync-preload';\nimport { switchAudioTrack, switchTextTrack, switchVideoTrack } from '../../behaviors/track-switching';\nimport { relocatingTextPipelines, relocationPipelinesFor } from '../../primitives/relocation-pipelines';\nimport {\n type ReportUnsupportedTrackConditions,\n reportUnsupportedTrackConditions,\n} from '../../primitives/report-track-conditions';\nimport type { TextTrackSegmentResolver } from '../../primitives/text-segment-load-pipeline';\n\n// ============================================================================\n// HLS Engine State & Context\n// ============================================================================\n\n/**\n * State shape for the HLS playback engine.\n *\n * This is the union of all state required by the behaviors composed into the HLS engine. Each behavior declares its own\n * state interface; this type satisfies all of them.\n */\nexport interface HlsVideoEngineState {\n /**\n * The presentation being played. A caller writes `{ url }`; `resolvePresentation` parses the manifest and populates\n * the rest.\n */\n presentation?: MaybeResolvedPresentation;\n preload?: 'auto' | 'metadata' | 'none';\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n bandwidthState?: BandwidthState;\n // Non-zero-PTS relocation (spike): transient per-track container data owned by\n // `establishStartMediaTime`. Remove with the composed reactor.\n mediaContainerData?: Record<string, MediaContainerData>;\n userVideoTrackSelection?: Partial<VideoTrack>;\n /**\n * Consumer-driven constraint narrowing the audio candidate set. Sibling of `userVideoTrackSelection`. Partial-track\n * shape — `{ language: 'es' }`, `{ id: 'audio-en' }`, etc. `selectAudioTrack` reads this and re-picks when it\n * changes. Multi-language-audio Tier 2 programmatic-write path.\n */\n userAudioTrackSelection?: Partial<AudioTrack>;\n /**\n * Consumer-driven _intent_ for text selection, resolved into `selectedTextTrackId` by `switchTextTrack`. A\n * language-based partial (`{ language: 'es' }`) selects captions, `'off'` disables them, and absence means auto (the\n * engine's `preferredSubtitleLanguage` / DEFAULT-track policy). Also the write path for the DOM caption UI (via\n * `syncTextTracks`); unlike the resolved id it persists across source changes (sticky preference).\n */\n userTextTrackSelection?: Partial<TextTrack> | 'off';\n /**\n * The CDNs the source is served from (track-URL origins), in manifest priority order — most-preferred first (mirrors\n * HLS content steering's `PATHWAY-PRIORITY`). Owned by `deriveCdnPriority`, read by `track-switching`'s\n * `preferActiveCdn` scope, which narrows to the highest-priority CDN with surviving tracks so video / audio / text\n * stay on one host. Only meaningful for redundant-stream sources; a single-CDN source has one entry.\n */\n cdnPriority?: string[];\n /**\n * CDN ids (origins) currently in failover cooldown — written by the CDN monitor when a host fails too often, read by\n * `track-switching`'s `excludeFailedCdns` hard constraint, which prunes their tracks so the active-CDN scope falls to\n * the next CDN in `cdnPriority`. Empty / absent means all CDNs are eligible.\n */\n failedCdns?: string[];\n /**\n * Conditions reported during playback, in the order encountered — appended by whichever behavior detects one\n * (`emitError`), owned and cleared per source by `collectErrors`. Carries no severity: which of these is fatal is\n * decided above the engine, at the adapter. See `internal/design/spf/features/errors.md`.\n */\n errors?: SvtaError[];\n currentTime?: number;\n /**\n * The player element's rendered pixel dimensions, or `undefined` where there is nothing to measure. Written by\n * `trackPlayerResolution`, read by the `playerResolutionCap` selection rule — which treats `undefined` as \"don't\n * cap\".\n */\n playerResolution?: PlayerResolution;\n loadActivated?: boolean;\n /**\n * One-shot command: start the current source at this position (presentation-timeline seconds). Written by consumers\n * or by `setupAirPlay`'s session-end snapshot; consumed (cleared) by `applyStartPosition` once the element seeks. See\n * `behaviors/dom/apply-start-position.ts`.\n */\n startPosition?: number;\n /**\n * Intent-level loading policy: initiate no new loading work while `true`. Written by `setupAirPlay` (the only\n * behavior declaring the key) while a remote-playback session owns presentation; observed by the `loadXSegments`\n * dispatchers (park in `'dormant'`) and by `setupMediaSource` (a pending rebuild waits). See\n * `SegmentLoadingState['loadingSuspended']`.\n */\n loadingSuspended?: boolean;\n /**\n * Author intent for the AirPlay/remote-playback picker, written by the media adapter's `disableRemotePlayback` IDL\n * property. `true` is an explicit opt-out: `setupAirPlay` reads it at attach and sets nothing up, leaving the\n * element's remote playback disabled. Distinct from the underlying `<video>.disableRemotePlayback`, which stays\n * programmatically managed (ManagedMediaSource / AirPlay).\n */\n disableRemotePlayback?: boolean;\n}\n\n/**\n * Context shape for the HLS playback engine.\n *\n * Platform objects and actor references managed by HLS behaviors.\n */\nexport interface HlsVideoEngineContext {\n mediaElement?: HTMLMediaElement | undefined;\n mediaSource?: MediaSource;\n videoBufferActor?: SourceBufferActor;\n audioBufferActor?: SourceBufferActor;\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n textTracksActor?: TextTracksActor;\n textTrackSegmentLoaderActor?: TextTrackSegmentLoaderActor;\n}\n\n/**\n * The composition signal refs handed to `onSignalsReady` callers — the canonical way to drive the engine externally\n * (writes) or observe its state (reads) without touching `composition.state` / `composition.context` directly.\n */\nexport type HlsVideoEngineSignals = {\n state: StateSignals<HlsVideoEngineState>;\n context: ContextSignals<HlsVideoEngineContext>;\n};\n\n/**\n * Configuration for the HLS playback engine.\n *\n * Each option is consumed by the appropriate behavior — the engine itself has no config beyond what its behaviors read.\n */\nexport interface HlsVideoEngineConfig extends ShareSignalsConfig<HlsVideoEngineState, HlsVideoEngineContext> {\n /**\n * Bandwidth estimate in bps to use before enough samples have been collected. Default: `DEFAULT_INITIAL_BANDWIDTH` (5\n * Mbps).\n */\n initialBandwidth?: number;\n /**\n * Codec capability probe injected into `track-switching`'s `excludeUnplayableTracks` constraint — drops renditions\n * the environment can't decode before selection. Defaults to the `MediaSource.isTypeSupported` -backed\n * `canPlayTrack`; supply your own to override (e.g. force-exclude a codec).\n */\n canPlayTrack?: CanPlayTrack;\n /**\n * Codec families (RFC 6381 4CCs, e.g. `'avc1'` / `'hvc1'` / `'mp4a'`) the initial video/audio picks prefer on a\n * mixed-codec source, read by the `preferCodecFamilies` selection scope. SPF implements no\n * `SourceBuffer.changeType()`, so the initial pick's codec family is sticky for the source's lifetime\n * (`stickToSelectedCodecs`); this decides which family that is. Soft — a source with no preferred-family rendition is\n * unaffected. Defaults to `DEFAULT_PREFERRED_CODECS` (AVC + AAC, the broadest-decode pair); pass `[]` to disable and\n * let ABR pick the initial family (it then still can't leave it mid-stream).\n */\n preferredCodecs?: string[];\n /**\n * Conditions reported about each rendition as it resolves — the _causes_ behind a later verdict, and the copy a\n * verdict reuses when they agree. Defaults to {@link reportUnsupportedTrackConditions}, which reports non-fMP4\n * containers and encryption; supply your own to report a different set (a provider that never ships MPEG-TS can drop\n * that check) or `() => []` to report nothing.\n */\n reportUnsupportedTrackConditions?: ReportUnsupportedTrackConditions;\n preferredAudioLanguage?: string;\n preferredSubtitleLanguage?: string;\n includeForcedTracks?: boolean;\n enableDefaultTrack?: boolean;\n /**\n * Resolver that turns a text-track segment fetch into VTT cues. Defaults to the DOM-bound `resolveVttSegment`\n * resolver, which uses an offscreen `<track>` element to parse WebVTT.\n */\n resolveTextTrackSegment?: TextTrackSegmentResolver<VTTCue>;\n /**\n * Resolver for `presentation.duration`. Defaults to picking the first resolved selected track's duration (video\n * preferred, audio fallback) — appropriate for VoD and audio-only. Live engines should supply a resolver that returns\n * `Number.POSITIVE_INFINITY` once the presentation is established as live; downstream `updateMediaSourceDuration`\n * propagates that value to `mediaSource.duration` per the MSE spec.\n */\n resolveDuration?: PresentationDurationResolver;\n /**\n * Manifest parser handed to `resolvePresentation`. Defaults to the HLS multivariant-playlist parser; supply your own\n * for alternate format support without forking the engine.\n */\n parsePresentation?: ParsePresentation;\n /**\n * Allocate SPF-owned text-track slots on the media element. Defaults to the standard `<track>`-element implementation\n * in `media/dom/text/text-track-slots`.\n */\n addSubtitlesTracksToMedia?: typeof addSubtitlesTracksToMedia;\n /**\n * Return the SPF-owned subtitle/caption `TextTrack` currently in showing mode. Defaults to the standard\n * selector-based implementation in `media/dom/text/text-track-slots`.\n */\n getShowingSubtitlesTrackFromMedia?: typeof getShowingSubtitlesTrackFromMedia;\n /**\n * Evict all SPF-owned text-track slots from the media element. Defaults to the standard selector-based implementation\n * in `media/dom/text/text-track-slots`.\n */\n removeAllSubtitlesTracksFromMedia?: typeof removeAllSubtitlesTracksFromMedia;\n /**\n * Forward-buffer tuning. `bufferDuration` controls how far ahead of the playhead segments are loaded (and where\n * forward-flush kicks in). Defaults: see `DEFAULT_FORWARD_BUFFER_CONFIG` (30 seconds). Threaded to segment-loader\n * actors (v/a + text) at construction time and to `loadXSegments` dispatchers for the load-message range.\n */\n forwardBuffer?: Partial<ForwardBufferConfig>;\n /**\n * Back-buffer tuning. `keepSegments` controls how many segments stay behind the playhead before eviction. Defaults:\n * see `DEFAULT_BACK_BUFFER_CONFIG` (2 segments). Threaded to the v/a segment-loader actor only (text tracks don't use\n * back-buffer eviction).\n */\n backBuffer?: Partial<BackBufferConfig>;\n /**\n * Bandwidth-estimator tuning. Overrides any field of `BandwidthConfig` (`fastHalfLife`, `slowHalfLife`,\n * `minTotalBytes`, `minBytes`, `minDuration`). `bandwidth.minTotalBytes` supersedes the flat `minTotalBytes` field\n * above. Defaults: see `DEFAULT_BANDWIDTH_CONFIG`.\n */\n bandwidth?: Partial<BandwidthConfig>;\n /**\n * Quality-selection tuning. `safetyMargin` is the bandwidth-headroom multiplier used by `selectQuality`;\n * `upgradeMargin` is the hysteresis ratio gating ABR upgrades. Defaults: `DEFAULT_QUALITY_CONFIG` (0.85 / 1.15).\n */\n quality?: Partial<QualityConfig>;\n /**\n * Whether video renditions are capped to the player element's rendered size. Read by `trackPlayerResolution`; `false`\n * measures nothing, which leaves the `playerResolutionCap` rule inert. Defaults to `true`.\n */\n capRenditionToPlayerSize?: boolean;\n /** Whether `state.playerResolution` is reported in device pixels. Read by `trackPlayerResolution`; defaults to `true`. */\n useDevicePixelRatio?: boolean;\n /**\n * Multi-CDN failover monitor tuning. `cooldownMs` is how long a CDN stays excluded after a failed fetch trips it.\n * Defaults: `DEFAULT_FAILOVER_MONITOR_CONFIG` (300s). Only meaningful for redundant-stream sources.\n */\n failover?: Partial<FailoverMonitorConfig>;\n /**\n * How to derive a CDN grouping key from a track URL — used to build `cdnPriority`, to record the failover trip in\n * `failedCdns`, and by the track-switching CDN scope + failover constraint. One function, read by all of them, so the\n * keys stay comparable. Defaults to the URL origin; override to key on something else (e.g. Mux's `cdn=` query\n * param).\n */\n getCdnId?: GetCdnId;\n /**\n * Non-zero-PTS relocation (spike): the reduce seam consumed by the `establishStartMediaTime` reactor. Defaults to\n * per-track own origin (Tier 1); a Tier-2 variant returns the shared `min` across selected A/V. Relocation is\n * composed into the standard engine below — see the marked block — so this only needs setting to swap the tier\n * policy. See `internal/design/spf/presentation-timeline-model.md`.\n */\n deriveStartMediaTime?: DeriveStartMediaTime;\n /**\n * Proximity window (seconds) for the `recoverEndStall` behavior — how close the playhead must be to the reachable\n * buffered end for a `waiting` to be treated as the end-of-stream freeze and nudged to `ended`. Defaults to `0.2`.\n * See `behaviors/dom/recover-end-stall`.\n */\n endStallNudgeWindow?: number;\n /**\n * Live media-playlist re-run policy for the resolve* loaders' `RecurringRunner`: returns a promise that resolves when\n * the playlist should reload, or `null` to stop. Defaults to `mediaPlaylistReloadDelay` (target-duration cadence,\n * half on an unchanged window, stop on `#EXT-X-ENDLIST`) composed with a cancellable `sleep`. Inert for VoD (a\n * complete playlist stops it after the first resolve). Override to tune live reload timing.\n */\n reschedule?: Reschedule<ResolvedTrack>;\n}\n\n// ============================================================================\n// HLS Playback Engine\n// ============================================================================\n\n/**\n * Generic `shareSignals` instantiated against the HLS engine's full state and context — captures composition signal\n * refs into the consumer's `onSignalsReady` callback at setup time, and materializes input slots that no composed\n * behavior produces: `user*TrackSelection` (track-switching only reads them). `failedCdns` is owned by\n * `setupFailoverMonitor`, so it's already materialized and reachable on the `onSignalsReady` refs without being listed\n * here.\n */\nconst shareSignals = makeShareSignals<HlsVideoEngineState, HlsVideoEngineContext>([\n 'userVideoTrackSelection',\n 'userAudioTrackSelection',\n 'userTextTrackSelection',\n 'disableRemotePlayback',\n]);\n\n/**\n * Create an HLS playback engine.\n *\n * Composes SPF behaviors into a reactive pipeline for HLS playback over MSE: manifest resolution, track selection, ABR,\n * segment loading, and end-of-stream coordination.\n *\n * @example\n * ```ts\n * let signals: HlsVideoEngineSignals;\n * const engine = createHlsVideoEngine({\n * initialBandwidth: 2_000_000,\n * preferredAudioLanguage: 'en',\n * onSignalsReady: (refs) => {\n * signals = refs;\n * },\n * });\n *\n * signals.context.mediaElement.set(videoEl);\n * signals.state.presentation.set({ url: 'https://example.com/stream.m3u8' });\n *\n * videoEl.play();\n *\n * await engine.destroy();\n * ```;\n */\nexport function createHlsVideoEngine(\n config: HlsVideoEngineConfig = {}\n): Composition<HlsVideoEngineState, HlsVideoEngineContext> {\n // Non-zero-PTS relocation (spike): resolve the coordination seam once so the reactor\n // (model `startMediaTime`) and the loader stamps (buffer `timestampOffset`) apply the\n // SAME derive. Default is shared-`min` across selected A/V (subsumes per-type).\n const deriveStartMediaTime = config.deriveStartMediaTime ?? deriveSharedMinStartMediaTime;\n const finalConfig = {\n ...config,\n deriveStartMediaTime,\n // Baked (not user-overridable): this engine composes `setupAirPlay`,\n // whose native fallback `<source>` requires the MSE attachment to keep\n // sibling source alternatives part of resource selection.\n attachMediaSource: attachMediaSourceAsSourceElement,\n canPlayTrack: config.canPlayTrack ?? canPlayTrack,\n reportUnsupportedTrackConditions: config.reportUnsupportedTrackConditions ?? reportUnsupportedTrackConditions,\n resolveTextTrackSegment: config.resolveTextTrackSegment ?? resolveVttSegment,\n // Non-zero-PTS relocation (spike): the text pipeline rebases cues onto the\n // relocated 0-based timeline. Remove `textMessagePipelines` to drop text relocation.\n textMessagePipelines: relocatingTextPipelines,\n resolveDuration: config.resolveDuration ?? getResolvedSelectedTrackDuration,\n parsePresentation: config.parsePresentation ?? parseMultivariantPlaylist,\n addSubtitlesTracksToMedia: config.addSubtitlesTracksToMedia ?? addSubtitlesTracksToMedia,\n getShowingSubtitlesTrackFromMedia: config.getShowingSubtitlesTrackFromMedia ?? getShowingSubtitlesTrackFromMedia,\n removeAllSubtitlesTracksFromMedia: config.removeAllSubtitlesTracksFromMedia ?? removeAllSubtitlesTracksFromMedia,\n // Non-zero-PTS relocation (spike): the discover/stamp steps `establishStartMediaTime`\n // pairs with. They apply the same `deriveStartMediaTime` seam as the reactor. Remove\n // these two lines with the reactor.\n videoMessagePipelines: relocationPipelinesFor('video', deriveStartMediaTime),\n audioMessagePipelines: relocationPipelinesFor('audio', deriveStartMediaTime),\n // Live-anchor establishment order: each non-reference track's first parse\n // waits for the reference track to settle the wall-clock anchor question\n // (see `gate-first-parse.ts`); pairs with the reactor's anchor stamp.\n gateFirstParse: gateFirstParseOnAnchor,\n // Format-neutral live-latency seam for `seekToLiveEdge` — the HLS resolver\n // (HOLD-BACK); a DASH engine would inject `suggestedPresentationDelay`.\n resolveLiveLatency,\n // The resolve* loaders' RecurringRunner re-runs on this `reschedule`: the pure\n // target-duration cadence, start-anchored + made awaitable by `delayedReschedule`.\n // Inert for VoD (the cadence returns null once a playlist is complete), so it\n // composes always.\n reschedule: config.reschedule ?? delayedReschedule(mediaPlaylistReloadDelay),\n };\n\n return createComposition(\n [\n syncPreload,\n trackLoadTriggers,\n resolvePresentation,\n\n // Session-level CDN priority for redundant-stream sources. Owns\n // `cdnPriority`; `track-switching`'s preferActiveCdn scope reads it so\n // every type stays on one CDN. No-op for single-CDN sources.\n //\n // Placed before switch* so `cdnPriority` is set before the first pick —\n // but this ordering is only *mildly* load-bearing, not required for\n // correctness. Selection is reactive: a late `cdnPriority` re-fires the\n // pick and converges on the same result (see the late-arrival test in\n // track-switching.test.ts). Order affects only a transient, and only for\n // an *asymmetric* manifest (a type listing a non-primary CDN first):\n // composing this after switch* would let that type fire one wasted\n // media-playlist fetch to the wrong CDN before correcting. Symmetric\n // redundant streams (the norm) never hit it — the first-listed CDN is\n // already the primary we'd pick anyway.\n deriveCdnPriority,\n\n // CDN failover cooldown: owns the expiry half of failover — watches\n // `failedCdns` (tripped directly by track resolution on a failed\n // media-playlist fetch) and removes each CDN once its cooldown lapses.\n setupFailoverMonitor,\n\n // Owns `errors` and its per-source lifecycle. Composed before the\n // behaviors that report into it so the slot exists when they first run;\n // reporting no-ops if it isn't composed at all.\n collectErrors,\n\n // Resolve selected tracks (fetch media playlists). Composed before the\n // switch* slot owners; selection is reactive, so a resolve* re-fires once\n // its switch* sets the id (same convergence for all three types).\n resolveVideoTrack,\n resolveAudioTrack,\n resolveTextTrack,\n\n // Presentation duration\n calculatePresentationDuration,\n\n // MSE setup. Video cluster is registered first so that, when both\n // per-type variants flip to `'buffer-ready'` on the shared gate's\n // monitor evaluation, `addSourceBuffer(video)` runs before\n // `addSourceBuffer(audio)` — see the Firefox `mozHasAudio` invariant\n // in setup-buffer-actors.ts.\n setupMediaSource,\n updateMediaSourceDuration,\n\n // ── Non-zero-PTS relocation (spike) ──────────────────────────────────\n // Establishes per-track `startMediaTime` and publishes the relocating\n // segment-loader pipelines to context. MUST precede `setup*BufferActors`\n // so the pipelines are published before the loaders read them. Remove this\n // one line (+ the import, the `mediaContainerData`/`*MessagePipelines`\n // slots including `textMessagePipelines`, and the `deriveStartMediaTime`\n // config) to drop relocation and test the Tier-0 baseline / bundle size.\n establishStartMediaTime,\n // ─────────────────────────────────────────────────────────────────────\n\n setupVideoBufferActors,\n setupAudioBufferActors,\n\n // AirPlay/MSE bridge (WebKit only; no-op elsewhere).\n setupAirPlay,\n\n // Playback tracking\n trackCurrentTime,\n // After trackCurrentTime: the one-shot currentTime seed must land after\n // the mirror's attach-time sync (see apply-start-position.ts).\n applyStartPosition,\n\n // Ordering isn't load-bearing — selection is reactive, so a measurement\n // that lands after the first pick just re-fires it.\n trackPlayerResolution,\n switchVideoTrack,\n switchAudioTrack,\n // Mid-stream audio-buffer flush on language switch is handled in\n // `segment-loader`'s `planTasks` (predicate: language differs from\n // the previously-buffered track) — not in switchAudioTrack itself.\n\n // Text selection: resolves `userTextTrackSelection` intent (incl. 'off',\n // or the configured preferred-language / DEFAULT-track policy) against the\n // failed-CDN-pruned, active-CDN-scoped text renditions. Optional selection\n // (captions are opt-in), so it can resolve to none.\n switchTextTrack,\n\n // Segment loading\n loadVideoSegments,\n loadAudioSegments,\n\n // Live: declare the seekable window, then command the live-edge start\n // position + keep the playhead in-window. No-op for complete playlists\n // (VoD / ended). `seekToLiveEdge` commands `state.startPosition`;\n // `applyStartPosition` (composed above) performs the seek.\n syncLiveSeekableRange,\n seekToLiveEdge,\n\n // End of stream coordination\n endOfStream,\n // Force native `ended` when Chrome freezes the playhead a few frames short of a\n // skewed-A/V end after `endOfStream` (audio-clock stall). Inert otherwise.\n recoverEndStall,\n\n // Text tracks\n syncTextTracks,\n setupTextTrackActors,\n loadTextTrackSegments,\n\n // Behavior whose sole purpose is to use a callback to allow for signal writing from the outside (e.g. an adapter)\n // NOTE: While not required, adding at the end since behaviors are setup in order, so this increases the likelihood\n // that initial signal setup will have occurred before shareSignals' callback is invoked. (CJP)\n shareSignals,\n ],\n {\n config: finalConfig,\n // Seed bandwidthState so switchVideoTrack fires on initial subscribe\n // with the `initialBandwidth` fallback rather than waiting for the\n // first chunk. The empty sample buffer means `getBandwidthEstimate`\n // returns the configured initial bandwidth until real samples land.\n initialState: {\n bandwidthState: {\n fastEstimate: 0,\n fastTotalWeight: 0,\n slowEstimate: 0,\n slowTotalWeight: 0,\n bytesSampled: 0,\n },\n },\n }\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkVA,MAAM,eAAe,iBAA6D;CAChF;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BD,SAAgB,qBACd,SAA+B,CAAC,GACyB;CAIzD,MAAM,uBAAuB,OAAO,wBAAwB;CAC5D,MAAM,cAAc;EAClB,GAAG;EACH;EAIA,mBAAmB;EACnB,cAAc,OAAO,gBAAgB;EACrC,kCAAkC,OAAO,oCAAoC;EAC7E,yBAAyB,OAAO,2BAA2B;EAG3D,sBAAsB;EACtB,iBAAiB,OAAO,mBAAmB;EAC3C,mBAAmB,OAAO,qBAAqB;EAC/C,2BAA2B,OAAO,6BAA6B;EAC/D,mCAAmC,OAAO,qCAAqC;EAC/E,mCAAmC,OAAO,qCAAqC;EAI/E,uBAAuB,uBAAuB,SAAS,oBAAoB;EAC3E,uBAAuB,uBAAuB,SAAS,oBAAoB;EAI3E,gBAAgB;EAGhB;EAKA,YAAY,OAAO,cAAc,kBAAkB,wBAAwB;CAC7E;CAEA,OAAO,kBACL;EACE;EACA;EACA;EAgBA;EAKA;EAKA;EAKA;EACA;EACA;EAGA;EAOA;EACA;EASA;EAGA;EACA;EAGA;EAGA;EAGA;EAIA;EACA;EACA;EASA;EAGA;EACA;EAMA;EACA;EAGA;EAGA;EAGA;EACA;EACA;EAKA;CACF,GACA;EACE,QAAQ;EAKR,cAAc,EACZ,gBAAgB;GACd,cAAc;GACd,iBAAiB;GACjB,cAAc;GACd,iBAAiB;GACjB,cAAc;EAChB,EACF;CACF,CACF;AACF"}
@@ -2,47 +2,38 @@
2
2
  /**
3
3
  * Developer-facing copy for what this engine reports.
4
4
  *
5
- * Everything here reaches a **console**, never a viewer. Viewer-facing copy for
6
- * a fatal condition is the presentation layer's to compose: the engine reports a
7
- * code, and a consumer maps that code to localized text it owns.
5
+ * Everything here reaches a **console**, never a viewer. Viewer-facing copy for a fatal condition is the presentation
6
+ * layer's to compose: the engine reports a code, and a consumer maps that code to localized text it owns.
8
7
  *
9
- * Plain string constants, one export each. Nothing is parameterized — a message
10
- * that names the engine has to be built at the call site from state the engine
11
- * carries around solely to say it, which is a lot of plumbing for a console
12
- * prefix. Separate exports rather than one object so a composition that logs
13
- * neither notice doesn't carry their bytes.
8
+ * Plain string constants, one export each. Nothing is parameterized — a message that names the engine has to be built
9
+ * at the call site from state the engine carries around solely to say it, which is a lot of plumbing for a console
10
+ * prefix. Separate exports rather than one object so a composition that logs neither notice doesn't carry their bytes.
14
11
  */
15
12
  /**
16
13
  * Logged when a fatal `SVTA_UNSUPPORTED_PLAYBACK_FEATURE` is surfaced.
17
14
  *
18
- * The developer's half of that event; the viewer gets the localized copy the
19
- * code maps to. One sentence is enough because the specificswhich container,
20
- * which rendition, whether it was DRM — stay structured on the reported
15
+ * The developer's half of that event; the viewer gets the localized copy the code maps to. One sentence is enough
16
+ * because the specifics which container, which rendition, whether it was DRM stay structured on the reported
21
17
  * conditions logged beside it.
22
18
  */
23
19
  const UNSUPPORTED_PLAYBACK_FEATURE_MESSAGE = "Can't play this source: it requires an unsupported playback feature.";
24
20
  /**
25
- * Logged for **any** fatal condition, by a composition whose failures don't all
26
- * reduce to a missing feature.
21
+ * Logged for **any** fatal condition, by a composition whose failures don't all reduce to a missing feature.
27
22
  *
28
- * The background variant is the case: it treats a per-rendition cause as fatal on
29
- * its own, so what reaches its surface can be an unsupported container, DRM it
30
- * can't decrypt, or a source carrying no video at all. Naming one feature would be
31
- * wrong for the last of those, so all three are named broadly. Deferring the *why*
32
- * to the conditions instead would promise prose they don't carry — an `SvtaError`
33
- * is a code and its context, and nothing on this path sets `message`.
23
+ * The background variant is the case: it treats a per-rendition cause as fatal on its own, so what reaches its surface
24
+ * can be an unsupported container, DRM it can't decrypt, or a source carrying no video at all. Naming one feature would
25
+ * be wrong for the last of those, so all three are named broadly. Deferring the _why_ to the conditions instead would
26
+ * promise prose they don't carry an `SvtaError` is a code and its context, and nothing on this path sets `message`.
34
27
  */
35
28
  const UNPLAYABLE_SOURCE_MESSAGE = "Can't play this source — unsupported container, encryption, or no usable video track.";
36
29
  /**
37
- * LL-HLS delivery, played as standard live. The parser ignores partial segments
38
- * and the loader fetches whole ones, so latency lands wherever `HOLD-BACK` puts
39
- * it — the stream plays, just not at the latency it was published for.
30
+ * LL-HLS delivery, played as standard live. The parser ignores partial segments and the loader fetches whole ones, so
31
+ * latency lands wherever `HOLD-BACK` puts it — the stream plays, just not at the latency it was published for.
40
32
  */
41
33
  const LOW_LATENCY_UNSUPPORTED_MESSAGE = "Low-Latency HLS is unsupported; playing as standard live at higher latency.";
42
34
  /**
43
- * `#EXT-X-PLAYLIST-TYPE:EVENT` — a growing window, which is how DVR is
44
- * delivered. It plays, but the seekable-range and live-edge handling for it is
45
- * newer and less exercised than sliding-window live.
35
+ * `#EXT-X-PLAYLIST-TYPE:EVENT` — a growing window, which is how DVR is delivered. It plays, but the seekable-range and
36
+ * live-edge handling for it is newer and less exercised than sliding-window live.
46
37
  */
47
38
  const DVR_EXPERIMENTAL_MESSAGE = "DVR support for EVENT playlists is experimental.";
48
39
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"error-messages.js","names":[],"sources":["../../../../src/playback/primitives/error-messages.ts"],"sourcesContent":["/**\n * Developer-facing copy for what this engine reports.\n *\n * Everything here reaches a **console**, never a viewer. Viewer-facing copy for\n * a fatal condition is the presentation layer's to compose: the engine reports a\n * code, and a consumer maps that code to localized text it owns.\n *\n * Plain string constants, one export each. Nothing is parameterized — a message\n * that names the engine has to be built at the call site from state the engine\n * carries around solely to say it, which is a lot of plumbing for a console\n * prefix. Separate exports rather than one object so a composition that logs\n * neither notice doesn't carry their bytes.\n */\n\n/**\n * Logged when a fatal `SVTA_UNSUPPORTED_PLAYBACK_FEATURE` is surfaced.\n *\n * The developer's half of that event; the viewer gets the localized copy the\n * code maps to. One sentence is enough because the specifics — which container,\n * which rendition, whether it was DRM — stay structured on the reported\n * conditions logged beside it.\n */\nexport const UNSUPPORTED_PLAYBACK_FEATURE_MESSAGE =\n \"Can't play this source: it requires an unsupported playback feature.\";\n\n/**\n * Logged for **any** fatal condition, by a composition whose failures don't all\n * reduce to a missing feature.\n *\n * The background variant is the case: it treats a per-rendition cause as fatal on\n * its own, so what reaches its surface can be an unsupported container, DRM it\n * can't decrypt, or a source carrying no video at all. Naming one feature would be\n * wrong for the last of those, so all three are named broadly. Deferring the *why*\n * to the conditions instead would promise prose they don't carry — an `SvtaError`\n * is a code and its context, and nothing on this path sets `message`.\n */\nexport const UNPLAYABLE_SOURCE_MESSAGE =\n \"Can't play this source — unsupported container, encryption, or no usable video track.\";\n\n/**\n * LL-HLS delivery, played as standard live. The parser ignores partial segments\n * and the loader fetches whole ones, so latency lands wherever `HOLD-BACK` puts\n * it — the stream plays, just not at the latency it was published for.\n */\nexport const LOW_LATENCY_UNSUPPORTED_MESSAGE =\n 'Low-Latency HLS is unsupported; playing as standard live at higher latency.';\n\n/**\n * `#EXT-X-PLAYLIST-TYPE:EVENT` — a growing window, which is how DVR is\n * delivered. It plays, but the seekable-range and live-edge handling for it is\n * newer and less exercised than sliding-window live.\n */\nexport const DVR_EXPERIMENTAL_MESSAGE = 'DVR support for EVENT playlists is experimental.';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,uCACX;;;;;;;;;;;;AAaF,MAAa,4BACX;;;;;;AAOF,MAAa,kCACX;;;;;;AAOF,MAAa,2BAA2B"}
1
+ {"version":3,"file":"error-messages.js","names":[],"sources":["../../../../src/playback/primitives/error-messages.ts"],"sourcesContent":["/**\n * Developer-facing copy for what this engine reports.\n *\n * Everything here reaches a **console**, never a viewer. Viewer-facing copy for a fatal condition is the presentation\n * layer's to compose: the engine reports a code, and a consumer maps that code to localized text it owns.\n *\n * Plain string constants, one export each. Nothing is parameterized — a message that names the engine has to be built\n * at the call site from state the engine carries around solely to say it, which is a lot of plumbing for a console\n * prefix. Separate exports rather than one object so a composition that logs neither notice doesn't carry their bytes.\n */\n\n/**\n * Logged when a fatal `SVTA_UNSUPPORTED_PLAYBACK_FEATURE` is surfaced.\n *\n * The developer's half of that event; the viewer gets the localized copy the code maps to. One sentence is enough\n * because the specifics — which container, which rendition, whether it was DRM — stay structured on the reported\n * conditions logged beside it.\n */\nexport const UNSUPPORTED_PLAYBACK_FEATURE_MESSAGE =\n \"Can't play this source: it requires an unsupported playback feature.\";\n\n/**\n * Logged for **any** fatal condition, by a composition whose failures don't all reduce to a missing feature.\n *\n * The background variant is the case: it treats a per-rendition cause as fatal on its own, so what reaches its surface\n * can be an unsupported container, DRM it can't decrypt, or a source carrying no video at all. Naming one feature would\n * be wrong for the last of those, so all three are named broadly. Deferring the _why_ to the conditions instead would\n * promise prose they don't carry — an `SvtaError` is a code and its context, and nothing on this path sets `message`.\n */\nexport const UNPLAYABLE_SOURCE_MESSAGE =\n \"Can't play this source — unsupported container, encryption, or no usable video track.\";\n\n/**\n * LL-HLS delivery, played as standard live. The parser ignores partial segments and the loader fetches whole ones, so\n * latency lands wherever `HOLD-BACK` puts it — the stream plays, just not at the latency it was published for.\n */\nexport const LOW_LATENCY_UNSUPPORTED_MESSAGE =\n 'Low-Latency HLS is unsupported; playing as standard live at higher latency.';\n\n/**\n * `#EXT-X-PLAYLIST-TYPE:EVENT` — a growing window, which is how DVR is delivered. It plays, but the seekable-range and\n * live-edge handling for it is newer and less exercised than sliding-window live.\n */\nexport const DVR_EXPERIMENTAL_MESSAGE = 'DVR support for EVENT playlists is experimental.';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,uCACX;;;;;;;;;AAUF,MAAa,4BACX;;;;;AAMF,MAAa,kCACX;;;;;AAMF,MAAa,2BAA2B"}
@@ -3,22 +3,17 @@ import { findTrackById } from "../../media/utils/tracks.js";
3
3
  import { addFailedCdn, getCdnId } from "../../media/utils/cdn.js";
4
4
  //#region src/playback/primitives/failover-fetch.ts
5
5
  /**
6
- * Decorate a fetch so a failed request trips the **selected track's** CDN into
7
- * `failedCdns`. The decorated fetch's type is preserved, so this wraps both
8
- * `resolve-track`'s playlist `FetchText` and the segment loaders' `FetchBytes`.
6
+ * Decorate a fetch so a failed request trips the **selected track's** CDN into `failedCdns`. The decorated fetch's type
7
+ * is preserved, so this wraps both `resolve-track`'s playlist `FetchText` and the segment loaders' `FetchBytes`.
9
8
  *
10
- * The CDN id comes from the selected track's media-playlist URL, never the
11
- * failed addressable: a segment URL resolves relative to its playlist and, per
12
- * RFC 3986, drops the playlist's query string (`…/r.m3u8?cdn=fastly` `…/0.ts`),
13
- * so a query-keyed `getCdnId` (e.g. Mux's `cdn=`) keyed on it would derive an id
14
- * that never matches the ones `deriveCdnPriority` / track-switching build from
15
- * `track.url`. The in-flight fetch belongs to the selected track a source or
16
- * track switch aborts it, and aborts don't trip — so the selected track is the
17
- * right CDN to fail over. For `resolve-track` the resolving track *is* the
18
- * selected track, so this is identical to keying on its addressable.
9
+ * The CDN id comes from the selected track's media-playlist URL, never the failed addressable: a segment URL resolves
10
+ * relative to its playlist and, per RFC 3986, drops the playlist's query string (`…/r.m3u8?cdn=fastly` → `…/0.ts`), so
11
+ * a query-keyed `getCdnId` (e.g. Mux's `cdn=`) keyed on it would derive an id that never matches the ones
12
+ * `deriveCdnPriority` / track-switching build from `track.url`. The in-flight fetch belongs to the selected track — a
13
+ * source or track switch aborts it, and aborts don't trip — so the selected track is the right CDN to fail over. For
14
+ * `resolve-track` the resolving track _is_ the selected track, so this is identical to keying on its addressable.
19
15
  *
20
- * No-op when no failover monitor is composed (it owns the signal) or the
21
- * selected track can't be located.
16
+ * No-op when no failover monitor is composed (it owns the signal) or the selected track can't be located.
22
17
  */
23
18
  function failoverFetch(baseFetch, state, config) {
24
19
  const getCdnId$1 = config.getCdnId ?? getCdnId;
@@ -1 +1 @@
1
- {"version":3,"file":"failover-fetch.js","names":["getCdnId","defaultGetCdnId"],"sources":["../../../../src/playback/primitives/failover-fetch.ts"],"sourcesContent":["import { type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../media/types';\nimport { addFailedCdn, getCdnId as defaultGetCdnId, type GetCdnId } from '../../media/utils/cdn';\nimport { findTrackById } from '../../media/utils/tracks';\nimport type { FetchOptions, Resource } from '../../network/fetch';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\n/**\n * State a failover-decorated fetch reads: the presentation, the per-type\n * selected-track slot, and the failover monitor's `failedCdns`.\n *\n * `failedCdns` is *optional* — the failover monitor owns that slot, so a\n * behavior's narrow state is assignable here without the behavior declaring it\n * (and the intersection shares keys, so it isn't a weak type). When no monitor\n * is composed the slot is absent and tracking no-ops.\n */\ntype FailoverState<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n failedCdns?: Signal<string[] | undefined>;\n} & { [P in K]: ReadonlySignal<string | undefined> };\n\n/** Any `Resource`-addressable fetch — both `FetchText` and `FetchBytes` qualify. */\ntype FailoverableFetch = (addressable: Resource, options?: FetchOptions) => Promise<unknown>;\n\n/**\n * Decorate a fetch so a failed request trips the **selected track's** CDN into\n * `failedCdns`. The decorated fetch's type is preserved, so this wraps both\n * `resolve-track`'s playlist `FetchText` and the segment loaders' `FetchBytes`.\n *\n * The CDN id comes from the selected track's media-playlist URL, never the\n * failed addressable: a segment URL resolves relative to its playlist and, per\n * RFC 3986, drops the playlist's query string (`…/r.m3u8?cdn=fastly` → `…/0.ts`),\n * so a query-keyed `getCdnId` (e.g. Mux's `cdn=`) keyed on it would derive an id\n * that never matches the ones `deriveCdnPriority` / track-switching build from\n * `track.url`. The in-flight fetch belongs to the selected track — a source or\n * track switch aborts it, and aborts don't trip — so the selected track is the\n * right CDN to fail over. For `resolve-track` the resolving track *is* the\n * selected track, so this is identical to keying on its addressable.\n *\n * No-op when no failover monitor is composed (it owns the signal) or the\n * selected track can't be located.\n */\nexport function failoverFetch<K extends SelectedTrackKey, Fetch extends FailoverableFetch>(\n baseFetch: Fetch,\n state: FailoverState<K>,\n config: { selectedKey: K; getCdnId?: GetCdnId }\n): Fetch {\n const getCdnId = config.getCdnId ?? defaultGetCdnId;\n return (async (addressable: Resource, options?: FetchOptions) => {\n try {\n return await baseFetch(addressable, options);\n } catch (error) {\n if (!options?.signal?.aborted && state.failedCdns) {\n const presentation = state.presentation.get();\n const trackId = state[config.selectedKey].get();\n const track = presentation && trackId ? findTrackById(presentation, trackId) : undefined;\n if (track) update(state.failedCdns, (cdns) => addFailedCdn(cdns, getCdnId(track.url)));\n }\n throw error;\n }\n }) as Fetch;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,cACd,WACA,OACA,QACO;CACP,MAAMA,aAAW,OAAO,YAAYC;CACpC,QAAQ,OAAO,aAAuB,YAA2B;EAC/D,IAAI;GACF,OAAO,MAAM,UAAU,aAAa,OAAO;EAC7C,SAAS,OAAO;GACd,IAAI,CAAC,SAAS,QAAQ,WAAW,MAAM,YAAY;IACjD,MAAM,eAAe,MAAM,aAAa,IAAI;IAC5C,MAAM,UAAU,MAAM,OAAO,YAAY,CAAC,IAAI;IAC9C,MAAM,QAAQ,gBAAgB,UAAU,cAAc,cAAc,OAAO,IAAI,KAAA;IAC/E,IAAI,OAAO,OAAO,MAAM,aAAa,SAAS,aAAa,MAAMD,WAAS,MAAM,GAAG,CAAC,CAAC;GACvF;GACA,MAAM;EACR;CACF;AACF"}
1
+ {"version":3,"file":"failover-fetch.js","names":["getCdnId","defaultGetCdnId"],"sources":["../../../../src/playback/primitives/failover-fetch.ts"],"sourcesContent":["import { type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../media/types';\nimport { addFailedCdn, getCdnId as defaultGetCdnId, type GetCdnId } from '../../media/utils/cdn';\nimport { findTrackById } from '../../media/utils/tracks';\nimport type { FetchOptions, Resource } from '../../network/fetch';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\n/**\n * State a failover-decorated fetch reads: the presentation, the per-type selected-track slot, and the failover\n * monitor's `failedCdns`.\n *\n * `failedCdns` is _optional_ — the failover monitor owns that slot, so a behavior's narrow state is assignable here\n * without the behavior declaring it (and the intersection shares keys, so it isn't a weak type). When no monitor is\n * composed the slot is absent and tracking no-ops.\n */\ntype FailoverState<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n failedCdns?: Signal<string[] | undefined>;\n} & { [P in K]: ReadonlySignal<string | undefined> };\n\n/** Any `Resource`-addressable fetch — both `FetchText` and `FetchBytes` qualify. */\ntype FailoverableFetch = (addressable: Resource, options?: FetchOptions) => Promise<unknown>;\n\n/**\n * Decorate a fetch so a failed request trips the **selected track's** CDN into `failedCdns`. The decorated fetch's type\n * is preserved, so this wraps both `resolve-track`'s playlist `FetchText` and the segment loaders' `FetchBytes`.\n *\n * The CDN id comes from the selected track's media-playlist URL, never the failed addressable: a segment URL resolves\n * relative to its playlist and, per RFC 3986, drops the playlist's query string (`…/r.m3u8?cdn=fastly` → `…/0.ts`), so\n * a query-keyed `getCdnId` (e.g. Mux's `cdn=`) keyed on it would derive an id that never matches the ones\n * `deriveCdnPriority` / track-switching build from `track.url`. The in-flight fetch belongs to the selected track — a\n * source or track switch aborts it, and aborts don't trip — so the selected track is the right CDN to fail over. For\n * `resolve-track` the resolving track _is_ the selected track, so this is identical to keying on its addressable.\n *\n * No-op when no failover monitor is composed (it owns the signal) or the selected track can't be located.\n */\nexport function failoverFetch<K extends SelectedTrackKey, Fetch extends FailoverableFetch>(\n baseFetch: Fetch,\n state: FailoverState<K>,\n config: { selectedKey: K; getCdnId?: GetCdnId }\n): Fetch {\n const getCdnId = config.getCdnId ?? defaultGetCdnId;\n\n return (async (addressable: Resource, options?: FetchOptions) => {\n try {\n return await baseFetch(addressable, options);\n } catch (error) {\n if (!options?.signal?.aborted && state.failedCdns) {\n const presentation = state.presentation.get();\n const trackId = state[config.selectedKey].get();\n const track = presentation && trackId ? findTrackById(presentation, trackId) : undefined;\n\n if (track) update(state.failedCdns, (cdns) => addFailedCdn(cdns, getCdnId(track.url)));\n }\n\n throw error;\n }\n }) as Fetch;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAqCA,SAAgB,cACd,WACA,OACA,QACO;CACP,MAAMA,aAAW,OAAO,YAAYC;CAEpC,QAAQ,OAAO,aAAuB,YAA2B;EAC/D,IAAI;GACF,OAAO,MAAM,UAAU,aAAa,OAAO;EAC7C,SAAS,OAAO;GACd,IAAI,CAAC,SAAS,QAAQ,WAAW,MAAM,YAAY;IACjD,MAAM,eAAe,MAAM,aAAa,IAAI;IAC5C,MAAM,UAAU,MAAM,OAAO,YAAY,CAAC,IAAI;IAC9C,MAAM,QAAQ,gBAAgB,UAAU,cAAc,cAAc,OAAO,IAAI,KAAA;IAE/E,IAAI,OAAO,OAAO,MAAM,aAAa,SAAS,aAAa,MAAMD,WAAS,MAAM,GAAG,CAAC,CAAC;GACvF;GAEA,MAAM;EACR;CACF;AACF"}
@@ -1,10 +1,9 @@
1
1
  //#region src/playback/primitives/head-peek.ts
2
2
  /**
3
- * Eager head-of-stream peek. Pulls chunks from an async byte stream only until
4
- * `tryParse` signals it has read what it needs (or the stream ends), then returns
5
- * a stream that re-emits the pulled head followed by the untouched tail so
6
- * reading a head-of-stream mp4 box (`mdhd` / `tfdt`) doesn't break streaming of
7
- * the append. Once the caller has what it wants, the rest streams as normal.
3
+ * Eager head-of-stream peek. Pulls chunks from an async byte stream only until `tryParse` signals it has read what it
4
+ * needs (or the stream ends), then returns a stream that re-emits the pulled head followed by the untouched tail — so
5
+ * reading a head-of-stream mp4 box (`mdhd` / `tfdt`) doesn't break streaming of the append. Once the caller has what it
6
+ * wants, the rest streams as normal.
8
7
  */
9
8
  function concat(chunks) {
10
9
  if (chunks.length === 1) return chunks[0];
@@ -23,11 +22,9 @@ async function* reassemble(head, tail) {
23
22
  for (let next = await tail.next(); !next.done; next = await tail.next()) yield next.value;
24
23
  }
25
24
  /**
26
- * Pull chunks until `tryParse(accumulatedHead)` returns `true` — it found and read
27
- * its box or the stream ends, then return a stream re-emitting the pulled head
28
- * followed by the untouched tail. `tryParse` is called with the growing head after
29
- * each chunk; it performs the side effect (publishing the parsed value) and returns
30
- * whether it's done.
25
+ * Pull chunks until `tryParse(accumulatedHead)` returns `true` — it found and read its box — or the stream ends, then
26
+ * return a stream re-emitting the pulled head followed by the untouched tail. `tryParse` is called with the growing
27
+ * head after each chunk; it performs the side effect (publishing the parsed value) and returns whether it's done.
31
28
  */
32
29
  async function peekHead(data, tryParse) {
33
30
  const iterator = data[Symbol.asyncIterator]();
@@ -1 +1 @@
1
- {"version":3,"file":"head-peek.js","names":[],"sources":["../../../../src/playback/primitives/head-peek.ts"],"sourcesContent":["/**\n * Eager head-of-stream peek. Pulls chunks from an async byte stream only until\n * `tryParse` signals it has read what it needs (or the stream ends), then returns\n * a stream that re-emits the pulled head followed by the untouched tail — so\n * reading a head-of-stream mp4 box (`mdhd` / `tfdt`) doesn't break streaming of\n * the append. Once the caller has what it wants, the rest streams as normal.\n */\n\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n if (chunks.length === 1) return chunks[0]!;\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n return out;\n}\n\n/** Re-emit the eagerly-pulled head chunks, then stream the untouched tail. */\nasync function* reassemble(head: Uint8Array[], tail: AsyncIterator<Uint8Array>): AsyncIterable<Uint8Array> {\n yield* head;\n for (let next = await tail.next(); !next.done; next = await tail.next()) yield next.value;\n}\n\n/**\n * Pull chunks until `tryParse(accumulatedHead)` returns `true` — it found and read\n * its box — or the stream ends, then return a stream re-emitting the pulled head\n * followed by the untouched tail. `tryParse` is called with the growing head after\n * each chunk; it performs the side effect (publishing the parsed value) and returns\n * whether it's done.\n */\nexport async function peekHead(\n data: AsyncIterable<Uint8Array>,\n tryParse: (bytes: Uint8Array) => boolean\n): Promise<AsyncIterable<Uint8Array>> {\n const iterator = data[Symbol.asyncIterator]();\n const head: Uint8Array[] = [];\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n head.push(next.value);\n if (tryParse(concat(head))) break;\n }\n return reassemble(head, iterator);\n}\n"],"mappings":";;;;;;;;AAQA,SAAS,OAAO,QAAkC;CAChD,IAAI,OAAO,WAAW,GAAG,OAAO,OAAO;CACvC,MAAM,QAAQ,OAAO,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CACrD,MAAM,MAAM,IAAI,WAAW,KAAK;CAChC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,IAAI,OAAO,MAAM;EACrB,UAAU,MAAM;CAClB;CACA,OAAO;AACT;;AAGA,gBAAgB,WAAW,MAAoB,MAA4D;CACzG,OAAO;CACP,KAAK,IAAI,OAAO,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,MAAM,KAAK,KAAK,GAAG,MAAM,KAAK;AACtF;;;;;;;;AASA,eAAsB,SACpB,MACA,UACoC;CACpC,MAAM,WAAW,KAAK,OAAO,cAAc,CAAC;CAC5C,MAAM,OAAqB,CAAC;CAC5B,KAAK,IAAI,OAAO,MAAM,SAAS,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,MAAM,SAAS,KAAK,GAAG;EAC/E,KAAK,KAAK,KAAK,KAAK;EACpB,IAAI,SAAS,OAAO,IAAI,CAAC,GAAG;CAC9B;CACA,OAAO,WAAW,MAAM,QAAQ;AAClC"}
1
+ {"version":3,"file":"head-peek.js","names":[],"sources":["../../../../src/playback/primitives/head-peek.ts"],"sourcesContent":["/**\n * Eager head-of-stream peek. Pulls chunks from an async byte stream only until `tryParse` signals it has read what it\n * needs (or the stream ends), then returns a stream that re-emits the pulled head followed by the untouched tail — so\n * reading a head-of-stream mp4 box (`mdhd` / `tfdt`) doesn't break streaming of the append. Once the caller has what it\n * wants, the rest streams as normal.\n */\n\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n if (chunks.length === 1) return chunks[0]!;\n\n const total = chunks.reduce((n, c) => n + c.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n\n return out;\n}\n\n/** Re-emit the eagerly-pulled head chunks, then stream the untouched tail. */\nasync function* reassemble(head: Uint8Array[], tail: AsyncIterator<Uint8Array>): AsyncIterable<Uint8Array> {\n yield* head;\n\n for (let next = await tail.next(); !next.done; next = await tail.next()) yield next.value;\n}\n\n/**\n * Pull chunks until `tryParse(accumulatedHead)` returns `true` — it found and read its box — or the stream ends, then\n * return a stream re-emitting the pulled head followed by the untouched tail. `tryParse` is called with the growing\n * head after each chunk; it performs the side effect (publishing the parsed value) and returns whether it's done.\n */\nexport async function peekHead(\n data: AsyncIterable<Uint8Array>,\n tryParse: (bytes: Uint8Array) => boolean\n): Promise<AsyncIterable<Uint8Array>> {\n const iterator = data[Symbol.asyncIterator]();\n const head: Uint8Array[] = [];\n\n for (let next = await iterator.next(); !next.done; next = await iterator.next()) {\n head.push(next.value);\n\n if (tryParse(concat(head))) break;\n }\n\n return reassemble(head, iterator);\n}\n"],"mappings":";;;;;;;AAOA,SAAS,OAAO,QAAkC;CAChD,IAAI,OAAO,WAAW,GAAG,OAAO,OAAO;CAEvC,MAAM,QAAQ,OAAO,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;CACrD,MAAM,MAAM,IAAI,WAAW,KAAK;CAChC,IAAI,SAAS;CAEb,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,IAAI,OAAO,MAAM;EACrB,UAAU,MAAM;CAClB;CAEA,OAAO;AACT;;AAGA,gBAAgB,WAAW,MAAoB,MAA4D;CACzG,OAAO;CAEP,KAAK,IAAI,OAAO,MAAM,KAAK,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,MAAM,KAAK,KAAK,GAAG,MAAM,KAAK;AACtF;;;;;;AAOA,eAAsB,SACpB,MACA,UACoC;CACpC,MAAM,WAAW,KAAK,OAAO,cAAc,CAAC;CAC5C,MAAM,OAAqB,CAAC;CAE5B,KAAK,IAAI,OAAO,MAAM,SAAS,KAAK,GAAG,CAAC,KAAK,MAAM,OAAO,MAAM,SAAS,KAAK,GAAG;EAC/E,KAAK,KAAK,KAAK,KAAK;EAEpB,IAAI,SAAS,OAAO,IAAI,CAAC,GAAG;CAC9B;CAEA,OAAO,WAAW,MAAM,QAAQ;AAClC"}
@@ -2,21 +2,19 @@ import { getTracksByType } from "../../media/utils/tracks.js";
2
2
  import { liveWindowFor } from "../../media/live-window.js";
3
3
  //#region src/playback/primitives/live-window.ts
4
4
  /**
5
- * The id of the timeline-bearing track: the selected video track when present,
6
- * else the selected audio track. The single pick both the window derivation and
7
- * the live-latency resolution (`seek-to-live-edge`) share, so they can't drift.
5
+ * The id of the timeline-bearing track: the selected video track when present, else the selected audio track. The
6
+ * single pick both the window derivation and the live-latency resolution (`seek-to-live-edge`) share, so they can't
7
+ * drift.
8
8
  */
9
9
  function liveTrackId(state) {
10
10
  return state.selectedVideoTrackId?.get() ?? state.selectedAudioTrackId?.get();
11
11
  }
12
12
  /**
13
- * One type's window: the selected track's when it's resolved, else any resolved
14
- * track of the type. The fallback keeps the window from blinking to `null`
15
- * mid ABR / user switch all renditions of a type are time-aligned and share
16
- * the anchor, so a deselected track's window may trail live by up to one reload
17
- * during the switch gap (acceptable; the selected track's fresh window resumes
18
- * the moment it resolves). A null blink would flip `seekToLiveEdge` out of
19
- * `live` and stall the seekable-range writer.
13
+ * One type's window: the selected track's when it's resolved, else any resolved track of the type. The fallback keeps
14
+ * the window from blinking to `null` mid ABR / user switch — all renditions of a type are time-aligned and share the
15
+ * anchor, so a deselected track's window may trail live by up to one reload during the switch gap (acceptable; the
16
+ * selected track's fresh window resumes the moment it resolves). A null blink would flip `seekToLiveEdge` out of `live`
17
+ * and stall the seekable-range writer.
20
18
  */
21
19
  function liveWindowForType(presentation, selectedId, type) {
22
20
  if (!presentation || selectedId === void 0) return null;
@@ -43,11 +41,10 @@ function liveWindowFromState(state) {
43
41
  return video ?? audio;
44
42
  }
45
43
  /**
46
- * Resolve the live edge — the window bounds plus the target playhead position —
47
- * from a behavior's setup arguments. Bundles the window geometry and the
48
- * format-specific latency policy (`config.resolveLiveLatency`) so the consuming
49
- * behavior never has to compose them; it just forwards its `{ state, config }`.
50
- * `null` when there is no live edge (VOD / ended / unresolved).
44
+ * Resolve the live edge — the window bounds plus the target playhead position — from a behavior's setup arguments.
45
+ * Bundles the window geometry and the format-specific latency policy (`config.resolveLiveLatency`) so the consuming
46
+ * behavior never has to compose them; it just forwards its `{ state, config }`. `null` when there is no live edge (VOD
47
+ * / ended / unresolved).
51
48
  *
52
49
  * Reads signals lazily — call it inside a reactive context (an effect).
53
50
  */
@@ -1 +1 @@
1
- {"version":3,"file":"live-window.js","names":[],"sources":["../../../../src/playback/primitives/live-window.ts"],"sourcesContent":["/**\n * Resolve the live window from engine state — the single call site the\n * seek-to-live-edge and live-seekable-range behaviors share, so their window\n * derivation can't drift apart.\n *\n * The window is the **intersection over the selected A/V tracks'** windows\n * (`max` of starts, `min` of ends): publication skew between the two playlists\n * shrinks the window to what both can actually serve, and the `max` also clamps\n * the legitimate *negative* early `startTime` of a non-reference track whose\n * window precedes the join point (presentation-0) — see\n * `internal/design/spf/live-presentation-timeline-model.md` (Phase 2 window\n * math). With one selected type (video-only / audio-only) its window stands\n * alone.\n *\n * Reads signals lazily — call it inside a reactive context (an effect) so the\n * read tracks `presentation` + the selected-track ids.\n */\nimport type { ReadonlySignal } from '../../core/signals/primitives';\nimport { type LiveWindow, liveWindowFor } from '../../media/live-window';\nimport type { MaybeResolvedPresentation, TrackType } from '../../media/types';\nimport { getTracksByType } from '../../media/utils/tracks';\n\nexport interface LiveWindowState {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n selectedVideoTrackId?: ReadonlySignal<string | undefined>;\n selectedAudioTrackId?: ReadonlySignal<string | undefined>;\n}\n\n/**\n * The id of the timeline-bearing track: the selected video track when present,\n * else the selected audio track. The single pick both the window derivation and\n * the live-latency resolution (`seek-to-live-edge`) share, so they can't drift.\n */\nexport function liveTrackId(state: LiveWindowState): string | undefined {\n return state.selectedVideoTrackId?.get() ?? state.selectedAudioTrackId?.get();\n}\n\n/**\n * One type's window: the selected track's when it's resolved, else any resolved\n * track of the type. The fallback keeps the window from blinking to `null`\n * mid ABR / user switch — all renditions of a type are time-aligned and share\n * the anchor, so a deselected track's window may trail live by up to one reload\n * during the switch gap (acceptable; the selected track's fresh window resumes\n * the moment it resolves). A null blink would flip `seekToLiveEdge` out of\n * `live` and stall the seekable-range writer.\n */\nfunction liveWindowForType(\n presentation: MaybeResolvedPresentation | undefined,\n selectedId: string | undefined,\n type: TrackType\n): LiveWindow | null {\n if (!presentation || selectedId === undefined) return null;\n const selected = liveWindowFor(presentation, selectedId);\n if (selected) return selected;\n for (const track of getTracksByType(presentation, type)) {\n const window = liveWindowFor(presentation, track.id);\n if (window) return window;\n }\n return null;\n}\n\nexport function liveWindowFromState(state: LiveWindowState): LiveWindow | null {\n const presentation = state.presentation.get();\n const video = liveWindowForType(presentation, state.selectedVideoTrackId?.get(), 'video');\n const audio = liveWindowForType(presentation, state.selectedAudioTrackId?.get(), 'audio');\n if (video && audio) {\n const start = Math.max(video.start, audio.start);\n const end = Math.min(video.end, audio.end);\n // A degenerate intersection (disjoint windows — one playlist stalled a full\n // window behind the other) means there is no position both types can serve:\n // no live edge, rather than an invented one.\n return start < end ? { start, end } : null;\n }\n return video ?? audio;\n}\n\n/**\n * Resolve the target live latency (seconds the playhead should trail the live\n * edge) for the timeline-bearing track. Format-specific — supplied by the engine\n * (HLS: `HOLD-BACK`; DASH would use `suggestedPresentationDelay`) — so the live\n * edge stays format-neutral.\n */\nexport type ResolveLiveLatency = (\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n) => number;\n\n/** The live window plus the target playhead position within it. */\nexport interface LiveEdge extends LiveWindow {\n /** Where to sit near the live edge: `end` − live latency, clamped to `start`. */\n liveEdgeStart: number;\n}\n\n/**\n * Resolve the live edge — the window bounds plus the target playhead position —\n * from a behavior's setup arguments. Bundles the window geometry and the\n * format-specific latency policy (`config.resolveLiveLatency`) so the consuming\n * behavior never has to compose them; it just forwards its `{ state, config }`.\n * `null` when there is no live edge (VOD / ended / unresolved).\n *\n * Reads signals lazily — call it inside a reactive context (an effect).\n */\nexport function getLiveEdge({\n state,\n // context, // not needed yet; in the shape for setup-parity when it is\n config,\n}: {\n state: LiveWindowState;\n config?: { resolveLiveLatency?: ResolveLiveLatency };\n}): LiveEdge | null {\n const window = liveWindowFromState(state);\n if (!window) return null;\n const latency = config?.resolveLiveLatency?.(state.presentation.get(), liveTrackId(state)) ?? 0;\n return { ...window, liveEdgeStart: Math.max(window.start, window.end - latency) };\n}\n"],"mappings":";;;;;;;;AAiCA,SAAgB,YAAY,OAA4C;CACtE,OAAO,MAAM,sBAAsB,IAAI,KAAK,MAAM,sBAAsB,IAAI;AAC9E;;;;;;;;;;AAWA,SAAS,kBACP,cACA,YACA,MACmB;CACnB,IAAI,CAAC,gBAAgB,eAAe,KAAA,GAAW,OAAO;CACtD,MAAM,WAAW,cAAc,cAAc,UAAU;CACvD,IAAI,UAAU,OAAO;CACrB,KAAK,MAAM,SAAS,gBAAgB,cAAc,IAAI,GAAG;EACvD,MAAM,SAAS,cAAc,cAAc,MAAM,EAAE;EACnD,IAAI,QAAQ,OAAO;CACrB;CACA,OAAO;AACT;AAEA,SAAgB,oBAAoB,OAA2C;CAC7E,MAAM,eAAe,MAAM,aAAa,IAAI;CAC5C,MAAM,QAAQ,kBAAkB,cAAc,MAAM,sBAAsB,IAAI,GAAG,OAAO;CACxF,MAAM,QAAQ,kBAAkB,cAAc,MAAM,sBAAsB,IAAI,GAAG,OAAO;CACxF,IAAI,SAAS,OAAO;EAClB,MAAM,QAAQ,KAAK,IAAI,MAAM,OAAO,MAAM,KAAK;EAC/C,MAAM,MAAM,KAAK,IAAI,MAAM,KAAK,MAAM,GAAG;EAIzC,OAAO,QAAQ,MAAM;GAAE;GAAO;EAAI,IAAI;CACxC;CACA,OAAO,SAAS;AAClB;;;;;;;;;;AA4BA,SAAgB,YAAY,EAC1B,OAEA,UAIkB;CAClB,MAAM,SAAS,oBAAoB,KAAK;CACxC,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,UAAU,QAAQ,qBAAqB,MAAM,aAAa,IAAI,GAAG,YAAY,KAAK,CAAC,KAAK;CAC9F,OAAO;EAAE,GAAG;EAAQ,eAAe,KAAK,IAAI,OAAO,OAAO,OAAO,MAAM,OAAO;CAAE;AAClF"}
1
+ {"version":3,"file":"live-window.js","names":[],"sources":["../../../../src/playback/primitives/live-window.ts"],"sourcesContent":["/**\n * Resolve the live window from engine state — the single call site the seek-to-live-edge and live-seekable-range\n * behaviors share, so their window derivation can't drift apart.\n *\n * The window is the **intersection over the selected A/V tracks'** windows (`max` of starts, `min` of ends):\n * publication skew between the two playlists shrinks the window to what both can actually serve, and the `max` also\n * clamps the legitimate _negative_ early `startTime` of a non-reference track whose window precedes the join point\n * (presentation-0) — see `internal/design/spf/live-presentation-timeline-model.md` (Phase 2 window math). With one\n * selected type (video-only / audio-only) its window stands alone.\n *\n * Reads signals lazily — call it inside a reactive context (an effect) so the read tracks `presentation` + the\n * selected-track ids.\n */\nimport type { ReadonlySignal } from '../../core/signals/primitives';\nimport { type LiveWindow, liveWindowFor } from '../../media/live-window';\nimport type { MaybeResolvedPresentation, TrackType } from '../../media/types';\nimport { getTracksByType } from '../../media/utils/tracks';\n\nexport interface LiveWindowState {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n selectedVideoTrackId?: ReadonlySignal<string | undefined>;\n selectedAudioTrackId?: ReadonlySignal<string | undefined>;\n}\n\n/**\n * The id of the timeline-bearing track: the selected video track when present, else the selected audio track. The\n * single pick both the window derivation and the live-latency resolution (`seek-to-live-edge`) share, so they can't\n * drift.\n */\nexport function liveTrackId(state: LiveWindowState): string | undefined {\n return state.selectedVideoTrackId?.get() ?? state.selectedAudioTrackId?.get();\n}\n\n/**\n * One type's window: the selected track's when it's resolved, else any resolved track of the type. The fallback keeps\n * the window from blinking to `null` mid ABR / user switch — all renditions of a type are time-aligned and share the\n * anchor, so a deselected track's window may trail live by up to one reload during the switch gap (acceptable; the\n * selected track's fresh window resumes the moment it resolves). A null blink would flip `seekToLiveEdge` out of `live`\n * and stall the seekable-range writer.\n */\nfunction liveWindowForType(\n presentation: MaybeResolvedPresentation | undefined,\n selectedId: string | undefined,\n type: TrackType\n): LiveWindow | null {\n if (!presentation || selectedId === undefined) return null;\n\n const selected = liveWindowFor(presentation, selectedId);\n if (selected) return selected;\n\n for (const track of getTracksByType(presentation, type)) {\n const window = liveWindowFor(presentation, track.id);\n if (window) return window;\n }\n\n return null;\n}\n\nexport function liveWindowFromState(state: LiveWindowState): LiveWindow | null {\n const presentation = state.presentation.get();\n const video = liveWindowForType(presentation, state.selectedVideoTrackId?.get(), 'video');\n const audio = liveWindowForType(presentation, state.selectedAudioTrackId?.get(), 'audio');\n\n if (video && audio) {\n const start = Math.max(video.start, audio.start);\n const end = Math.min(video.end, audio.end);\n\n // A degenerate intersection (disjoint windows — one playlist stalled a full\n // window behind the other) means there is no position both types can serve:\n // no live edge, rather than an invented one.\n return start < end ? { start, end } : null;\n }\n\n return video ?? audio;\n}\n\n/**\n * Resolve the target live latency (seconds the playhead should trail the live edge) for the timeline-bearing track.\n * Format-specific — supplied by the engine (HLS: `HOLD-BACK`; DASH would use `suggestedPresentationDelay`) — so the\n * live edge stays format-neutral.\n */\nexport type ResolveLiveLatency = (\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n) => number;\n\n/** The live window plus the target playhead position within it. */\nexport interface LiveEdge extends LiveWindow {\n /** Where to sit near the live edge: `end` − live latency, clamped to `start`. */\n liveEdgeStart: number;\n}\n\n/**\n * Resolve the live edge — the window bounds plus the target playhead position from a behavior's setup arguments.\n * Bundles the window geometry and the format-specific latency policy (`config.resolveLiveLatency`) so the consuming\n * behavior never has to compose them; it just forwards its `{ state, config }`. `null` when there is no live edge (VOD\n * / ended / unresolved).\n *\n * Reads signals lazily — call it inside a reactive context (an effect).\n */\nexport function getLiveEdge({\n state,\n // context, // not needed yet; in the shape for setup-parity when it is\n config,\n}: {\n state: LiveWindowState;\n config?: { resolveLiveLatency?: ResolveLiveLatency };\n}): LiveEdge | null {\n const window = liveWindowFromState(state);\n if (!window) return null;\n\n const latency = config?.resolveLiveLatency?.(state.presentation.get(), liveTrackId(state)) ?? 0;\n\n return { ...window, liveEdgeStart: Math.max(window.start, window.end - latency) };\n}\n"],"mappings":";;;;;;;;AA6BA,SAAgB,YAAY,OAA4C;CACtE,OAAO,MAAM,sBAAsB,IAAI,KAAK,MAAM,sBAAsB,IAAI;AAC9E;;;;;;;;AASA,SAAS,kBACP,cACA,YACA,MACmB;CACnB,IAAI,CAAC,gBAAgB,eAAe,KAAA,GAAW,OAAO;CAEtD,MAAM,WAAW,cAAc,cAAc,UAAU;CACvD,IAAI,UAAU,OAAO;CAErB,KAAK,MAAM,SAAS,gBAAgB,cAAc,IAAI,GAAG;EACvD,MAAM,SAAS,cAAc,cAAc,MAAM,EAAE;EACnD,IAAI,QAAQ,OAAO;CACrB;CAEA,OAAO;AACT;AAEA,SAAgB,oBAAoB,OAA2C;CAC7E,MAAM,eAAe,MAAM,aAAa,IAAI;CAC5C,MAAM,QAAQ,kBAAkB,cAAc,MAAM,sBAAsB,IAAI,GAAG,OAAO;CACxF,MAAM,QAAQ,kBAAkB,cAAc,MAAM,sBAAsB,IAAI,GAAG,OAAO;CAExF,IAAI,SAAS,OAAO;EAClB,MAAM,QAAQ,KAAK,IAAI,MAAM,OAAO,MAAM,KAAK;EAC/C,MAAM,MAAM,KAAK,IAAI,MAAM,KAAK,MAAM,GAAG;EAKzC,OAAO,QAAQ,MAAM;GAAE;GAAO;EAAI,IAAI;CACxC;CAEA,OAAO,SAAS;AAClB;;;;;;;;;AA0BA,SAAgB,YAAY,EAC1B,OAEA,UAIkB;CAClB,MAAM,SAAS,oBAAoB,KAAK;CACxC,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,UAAU,QAAQ,qBAAqB,MAAM,aAAa,IAAI,GAAG,YAAY,KAAK,CAAC,KAAK;CAE9F,OAAO;EAAE,GAAG;EAAQ,eAAe,KAAK,IAAI,OAAO,OAAO,OAAO,MAAM,OAAO;CAAE;AAClF"}