@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
@@ -1 +1 @@
1
- {"version":3,"file":"load-segments.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/load-segments.ts"],"sourcesContent":["/**\n * **Per-type segment loading dispatch.** Per available track type (video /\n * audio / text), reads the per-type segment-loader actor from context and\n * sends typed `'load'` messages whenever a meaningful loading condition\n * changes (selected track, current time crossing a segment boundary).\n *\n * Loader-actor lifecycle is owned upstream:\n * - Video: `setupVideoBufferActors`\n * - Audio: `setupAudioBufferActors`\n * - Text: `setupTextTrackActors`\n *\n * This behavior is pure-consumer: it reads `context[loaderKey]` and\n * dispatches typed messages via the variant's per-type `findResolvedTrack`\n * resolver.\n *\n * # Load modes as reactor states\n *\n * Four states encode the load-gating policy directly:\n *\n * - `'preconditions-unmet'` — no loader actor in context, or the selected\n * track hasn't resolved.\n * - `'dormant'` — loading disabled by policy: an observed `loadingSuspended`\n * (highest precedence) or `preload === 'none' && !loadActivated`. Nothing\n * fires; already-queued loader work drains. Auto-resumes into the derived\n * state when the policy lifts.\n * - `'metadata-only'` — `!loadActivated && preload !== 'auto' && preload !== 'none'`.\n * Fires an init-segment-only `load` message **once on entry**. The\n * variant's loader actor decides what to do — v/a's actor fetches the\n * init segment; text's actor no-ops (no init concept).\n * - `'full-range'` — `loadActivated || preload === 'auto'`. Effect re-fires\n * on selected-track change and on segment-boundary crossing.\n *\n * # Per-type parameterization (inference-driven)\n *\n * The helper is generic over `Track` — the resolved-track type. Each\n * variant supplies its own `findResolvedTrack` resolver via config; TS\n * infers `Track` from the resolver's return type. The loader signal's\n * value type is constrained to `SegmentLoaderLike<Track>` — anything with\n * a `send` method accepting the `Track`-parameterized message. Concrete\n * actor types (`SegmentLoaderActor`, `TextTrackSegmentLoaderActor`)\n * satisfy this via function-parameter contravariance: an actor whose\n * `.send` accepts a wider track type is assignable to a slot expecting a\n * narrower track type.\n *\n * No widening of actor message types is needed; no casts inside the\n * helper. Per-variant wiring (right loader paired with right resolver)\n * is enforced at the variant call site.\n */\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport type { Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal } from '../../../core/signals/primitives';\nimport {\n DEFAULT_FORWARD_BUFFER_CONFIG,\n type ForwardBufferConfig,\n segmentStartForTime,\n} from '../../../media/buffer/forward-buffer';\nimport type { MaybeResolvedPresentation, Segment } from '../../../media/types';\nimport { findResolvedAudioTrack, findResolvedTextTrack, findResolvedVideoTrack } from '../../../media/utils/tracks';\nimport type { BufferState, SegmentLoaderActor, SourceBufferState } from '../../actors/dom/segment-loader';\nimport type { TextTrackSegmentLoaderActor } from '../../actors/text-track-segment-loader';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../../primitives/track-types';\n\n// Re-export buffer state types for consumers that import them from this module.\nexport type { BufferState, SourceBufferState };\n\n// ============================================================================\n// STATE & CONTEXT\n// ============================================================================\n\n/** State shape for segment loading. */\nexport interface SegmentLoadingState {\n presentation?: MaybeResolvedPresentation;\n preload?: string;\n /** Current playback position in seconds. Defaults to 0 when undefined. */\n currentTime?: number;\n /** True once a preload-overriding event has fired for the current source. */\n loadActivated?: boolean;\n /**\n * Intent-level policy input: initiate no new loading work while `true`.\n * Read here by the `loadXSegments` dispatchers (park in `'dormant'`,\n * highest precedence) and by `setupMediaSource` (a pending MediaSource\n * rebuild waits — attach runs the element's load algorithm). **Observed,\n * never declared**: no reader lists this key in `stateKeys`, so the slot\n * exists only in compositions where a feature behavior declares and\n * writes it (e.g. `setupAirPlay`, while a remote-playback session owns\n * presentation). An absent slot means never suspended.\n */\n loadingSuspended?: boolean;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n}\n\n/** Context shape for segment loading. Each variant only consumes its own type's loader actor. */\nexport interface SegmentLoadingContext {\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n textTrackSegmentLoaderActor?: TextTrackSegmentLoaderActor;\n}\n\n// ============================================================================\n// REACTOR\n// ============================================================================\n\ntype SegmentLoadingFsmState = 'preconditions-unmet' | 'dormant' | 'metadata-only' | 'full-range';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\ntype SegmentLoaderActorKey = 'videoSegmentLoaderActor' | 'audioSegmentLoaderActor' | 'textTrackSegmentLoaderActor';\n\ntype SegmentLoadingStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<SegmentLoadingState['presentation']>;\n preload: ReadonlySignal<SegmentLoadingState['preload']>;\n currentTime: ReadonlySignal<SegmentLoadingState['currentTime']>;\n loadActivated: ReadonlySignal<SegmentLoadingState['loadActivated']>;\n} & { [P in K]: ReadonlySignal<SegmentLoadingState[P]> };\n\n/** Shared `'load'` message shape parameterized over the resolved track type. */\ninterface LoadMessage<Track> {\n type: 'load';\n track: Track;\n range?: { start: number; end: number };\n}\n\n/** Structural constraint for any segment-loader-style actor. */\ninterface SegmentLoaderLike<Track> {\n send: (message: LoadMessage<Track>) => void;\n}\n\n/**\n * Specialization helper. Generic over `Track` (inferred from\n * `findResolvedTrack`'s return type). The loader value type is\n * constrained to `SegmentLoaderLike<Track>` — concrete actor types\n * (whose `.send` accepts a wider track union) satisfy this via\n * function-parameter contravariance.\n *\n * Tracks that the helper handles must have a `segments` field — used by\n * `segmentBoundarySignal` to compute the load-anchor boundary. Each\n * variant's resolver narrows to the right resolved-track shape.\n */\nfunction setupSegmentLoading<\n K extends SelectedTrackKey,\n L extends SegmentLoaderActorKey,\n Track extends { segments: readonly Segment[] },\n>({\n state,\n context,\n config,\n}: {\n // `loadingSuspended` is observed, never declared (see its state-shape doc):\n // optional here so variant maps — which omit it from their contracts — are\n // assignable as-is, while compositions with a writer expose the live slot.\n state: SegmentLoadingStateMap<K> & { loadingSuspended?: ReadonlySignal<SegmentLoadingState['loadingSuspended']> };\n context: { [P in L]: ReadonlySignal<SegmentLoaderLike<Track> | undefined> };\n config: {\n selectedKey: K;\n loaderKey: L;\n findResolvedTrack: (\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n ) => Track | undefined;\n forwardBuffer?: Partial<ForwardBufferConfig>;\n };\n}): Reactor<SegmentLoadingFsmState | 'destroying' | 'destroyed'> {\n const { selectedKey, loaderKey, findResolvedTrack } = config;\n const bufferDuration = config.forwardBuffer?.bufferDuration ?? DEFAULT_FORWARD_BUFFER_CONFIG.bufferDuration;\n\n const selectedTrack = computed<Track | undefined>(() =>\n findResolvedTrack(state.presentation.get(), state[selectedKey].get())\n );\n\n // Segment-boundary signal — `segmentStartForTime` returns the same number\n // while `currentTime` stays inside one segment, so signal-polyfill's\n // `Object.is` equality on this computed dedups within-segment ticks.\n const segmentBoundarySignal = computed(() => {\n const track = selectedTrack.get();\n if (!track) return undefined;\n return segmentStartForTime(state.currentTime.get() ?? 0, track.segments);\n });\n\n const derivedStateSignal = computed<SegmentLoadingFsmState>(() => {\n // Policy-off wins even over preconditions: a loader arriving while\n // suspended must not dispatch.\n if (state.loadingSuspended?.get()) return 'dormant';\n if (!context[loaderKey].get() || !selectedTrack.get()) return 'preconditions-unmet';\n if (state.loadActivated.get() || state.preload.get() === 'auto') return 'full-range';\n if (state.preload.get() === 'none') return 'dormant';\n return 'metadata-only';\n });\n\n return createMachineReactor<SegmentLoadingFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n dormant: {},\n 'metadata-only': {\n // Fires once on entry. Matches HTMLMediaElement's preload='metadata'\n // semantics — load enough to surface metadata for the entry-time\n // selection. Each variant's loader decides what to do: v/a's actor\n // fetches the init segment; text's actor treats no-range as a\n // no-op. Selection changes while still in `'metadata-only'` are\n // intentionally not followed.\n entry: () => {\n const track = selectedTrack.get()!;\n context[loaderKey].get()!.send({ type: 'load', track });\n },\n },\n 'full-range': {\n // Re-fires on selected-track changes and on segment-boundary\n // crossings. The loader and `currentTime` are peeked — the\n // boundary-dedup signal already handles re-firing policy.\n effects: () => {\n const track = selectedTrack.get()!;\n segmentBoundarySignal.get();\n const currentTime = peek(state.currentTime) ?? 0;\n peek(context[loaderKey])!.send({\n type: 'load',\n track,\n range: { start: currentTime, end: currentTime + bufferDuration },\n });\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_SEGMENT_LOADING_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findResolvedTrack: findResolvedVideoTrack,\n} as const;\n\nconst AUDIO_SEGMENT_LOADING_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findResolvedTrack: findResolvedAudioTrack,\n} as const;\n\nconst TEXT_SEGMENT_LOADING_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findResolvedTrack: findResolvedTextTrack,\n} as const;\n\n// ============================================================================\n// Specialized exports — one per media type\n// ============================================================================\n\nexport const loadVideoSegments = defineBehavior({\n stateKeys: ['presentation', 'preload', 'currentTime', 'loadActivated', 'selectedVideoTrackId'],\n contextKeys: ['videoSegmentLoaderActor'],\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: SegmentLoadingStateMap<'selectedVideoTrackId'>;\n context: {\n videoSegmentLoaderActor: ReadonlySignal<SegmentLoadingContext['videoSegmentLoaderActor']>;\n };\n config?: object;\n }) =>\n setupSegmentLoading({\n state,\n context,\n config: { ...VIDEO_SEGMENT_LOADING_CONFIG, ...config },\n }),\n});\n\nexport const loadAudioSegments = defineBehavior({\n stateKeys: ['presentation', 'preload', 'currentTime', 'loadActivated', 'selectedAudioTrackId'],\n contextKeys: ['audioSegmentLoaderActor'],\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: SegmentLoadingStateMap<'selectedAudioTrackId'>;\n context: {\n audioSegmentLoaderActor: ReadonlySignal<SegmentLoadingContext['audioSegmentLoaderActor']>;\n };\n config?: object;\n }) =>\n setupSegmentLoading({\n state,\n context,\n config: { ...AUDIO_SEGMENT_LOADING_CONFIG, ...config },\n }),\n});\n\nexport const loadTextTrackSegments = defineBehavior({\n stateKeys: ['presentation', 'preload', 'currentTime', 'loadActivated', 'selectedTextTrackId'],\n contextKeys: ['textTrackSegmentLoaderActor'],\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: SegmentLoadingStateMap<'selectedTextTrackId'>;\n context: {\n textTrackSegmentLoaderActor: ReadonlySignal<SegmentLoadingContext['textTrackSegmentLoaderActor']>;\n };\n config?: object;\n }) =>\n setupSegmentLoading({\n state,\n context,\n config: { ...TEXT_SEGMENT_LOADING_CONFIG, ...config },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4IA,SAAS,oBAIP,EACA,OACA,SACA,UAgB+D;CAC/D,MAAM,EAAE,aAAa,WAAW,sBAAsB;CACtD,MAAM,iBAAiB,OAAO,eAAe,kBAAkB,8BAA8B;CAE7F,MAAM,gBAAgB,eACpB,kBAAkB,MAAM,aAAa,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CACtE;CAKA,MAAM,wBAAwB,eAAe;EAC3C,MAAM,QAAQ,cAAc,IAAI;EAChC,IAAI,CAAC,OAAO,OAAO,KAAA;EACnB,OAAO,oBAAoB,MAAM,YAAY,IAAI,KAAK,GAAG,MAAM,QAAQ;CACzE,CAAC;CAED,MAAM,qBAAqB,eAAuC;EAGhE,IAAI,MAAM,kBAAkB,IAAI,GAAG,OAAO;EAC1C,IAAI,CAAC,QAAQ,UAAU,CAAC,IAAI,KAAK,CAAC,cAAc,IAAI,GAAG,OAAO;EAC9D,IAAI,MAAM,cAAc,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO;EACxE,IAAI,MAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO;EAC3C,OAAO;CACT,CAAC;CAED,OAAO,qBAA6C;EAClD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GACxB,SAAS,CAAC;GACV,iBAAiB,EAOf,aAAa;IACX,MAAM,QAAQ,cAAc,IAAI;IAChC,QAAQ,UAAU,CAAC,IAAI,CAAC,CAAE,KAAK;KAAE,MAAM;KAAQ;IAAM,CAAC;GACxD,EACF;GACA,cAAc,EAIZ,eAAe;IACb,MAAM,QAAQ,cAAc,IAAI;IAChC,sBAAsB,IAAI;IAC1B,MAAM,cAAc,KAAK,MAAM,WAAW,KAAK;IAC/C,KAAK,QAAQ,UAAU,CAAC,CAAE,KAAK;KAC7B,MAAM;KACN;KACA,OAAO;MAAE,OAAO;MAAa,KAAK,cAAc;KAAe;IACjE,CAAC;GACH,EACF;EACF;CACF,CAAC;AACH;AAMA,MAAM,+BAA+B;CACnC,GAAG;CACH,mBAAmB;AACrB;AAEA,MAAM,+BAA+B;CACnC,GAAG;CACH,mBAAmB;AACrB;AAEA,MAAM,8BAA8B;CAClC,GAAG;CACH,mBAAmB;AACrB;AAMA,MAAa,oBAAoB,eAAe;CAC9C,WAAW;EAAC;EAAgB;EAAW;EAAe;EAAiB;CAAsB;CAC7F,aAAa,CAAC,yBAAyB;CACvC,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAQV,oBAAoB;EAClB;EACA;EACA,QAAQ;GAAE,GAAG;GAA8B,GAAG;EAAO;CACvD,CAAC;AACL,CAAC;AAED,MAAa,oBAAoB,eAAe;CAC9C,WAAW;EAAC;EAAgB;EAAW;EAAe;EAAiB;CAAsB;CAC7F,aAAa,CAAC,yBAAyB;CACvC,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAQV,oBAAoB;EAClB;EACA;EACA,QAAQ;GAAE,GAAG;GAA8B,GAAG;EAAO;CACvD,CAAC;AACL,CAAC;AAED,MAAa,wBAAwB,eAAe;CAClD,WAAW;EAAC;EAAgB;EAAW;EAAe;EAAiB;CAAqB;CAC5F,aAAa,CAAC,6BAA6B;CAC3C,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAQV,oBAAoB;EAClB;EACA;EACA,QAAQ;GAAE,GAAG;GAA6B,GAAG;EAAO;CACtD,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"load-segments.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/load-segments.ts"],"sourcesContent":["/**\n * **Per-type segment loading dispatch.** Per available track type (video / audio / text), reads the per-type\n * segment-loader actor from context and sends typed `'load'` messages whenever a meaningful loading condition changes\n * (selected track, current time crossing a segment boundary).\n *\n * Loader-actor lifecycle is owned upstream:\n *\n * - Video: `setupVideoBufferActors`\n * - Audio: `setupAudioBufferActors`\n * - Text: `setupTextTrackActors`\n *\n * This behavior is pure-consumer: it reads `context[loaderKey]` and dispatches typed messages via the variant's\n * per-type `findResolvedTrack` resolver.\n *\n * # Load modes as reactor states\n *\n * Four states encode the load-gating policy directly:\n *\n * - `'preconditions-unmet'` — no loader actor in context, or the selected track hasn't resolved.\n * - `'dormant'` — loading disabled by policy: an observed `loadingSuspended` (highest precedence) or `preload === 'none'\n * && !loadActivated`. Nothing fires; already-queued loader work drains. Auto-resumes into the derived state when the\n * policy lifts.\n * - `'metadata-only'` — `!loadActivated && preload !== 'auto' && preload !== 'none'`. Fires an init-segment-only `load`\n * message **once on entry**. The variant's loader actor decides what to do — v/a's actor fetches the init segment;\n * text's actor no-ops (no init concept).\n * - `'full-range'` — `loadActivated || preload === 'auto'`. Effect re-fires on selected-track change and on\n * segment-boundary crossing.\n *\n * # Per-type parameterization (inference-driven)\n *\n * The helper is generic over `Track` — the resolved-track type. Each variant supplies its own `findResolvedTrack`\n * resolver via config; TS infers `Track` from the resolver's return type. The loader signal's value type is constrained\n * to `SegmentLoaderLike<Track>` — anything with a `send` method accepting the `Track`-parameterized message. Concrete\n * actor types (`SegmentLoaderActor`, `TextTrackSegmentLoaderActor`) satisfy this via function-parameter contravariance:\n * an actor whose `.send` accepts a wider track type is assignable to a slot expecting a narrower track type.\n *\n * No widening of actor message types is needed; no casts inside the helper. Per-variant wiring (right loader paired\n * with right resolver) is enforced at the variant call site.\n */\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport type { Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal } from '../../../core/signals/primitives';\nimport {\n DEFAULT_FORWARD_BUFFER_CONFIG,\n type ForwardBufferConfig,\n segmentStartForTime,\n} from '../../../media/buffer/forward-buffer';\nimport type { MaybeResolvedPresentation, Segment } from '../../../media/types';\nimport { findResolvedAudioTrack, findResolvedTextTrack, findResolvedVideoTrack } from '../../../media/utils/tracks';\nimport type { BufferState, SegmentLoaderActor, SourceBufferState } from '../../actors/dom/segment-loader';\nimport type { TextTrackSegmentLoaderActor } from '../../actors/text-track-segment-loader';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../../primitives/track-types';\n\n// Re-export buffer state types for consumers that import them from this module.\nexport type { BufferState, SourceBufferState };\n\n// ============================================================================\n// STATE & CONTEXT\n// ============================================================================\n\n/** State shape for segment loading. */\nexport interface SegmentLoadingState {\n presentation?: MaybeResolvedPresentation;\n preload?: string;\n /** Current playback position in seconds. Defaults to 0 when undefined. */\n currentTime?: number;\n /** True once a preload-overriding event has fired for the current source. */\n loadActivated?: boolean;\n /**\n * Intent-level policy input: initiate no new loading work while `true`. Read here by the `loadXSegments` dispatchers\n * (park in `'dormant'`, highest precedence) and by `setupMediaSource` (a pending MediaSource rebuild waits — attach\n * runs the element's load algorithm). **Observed, never declared**: no reader lists this key in `stateKeys`, so the\n * slot exists only in compositions where a feature behavior declares and writes it (e.g. `setupAirPlay`, while a\n * remote-playback session owns presentation). An absent slot means never suspended.\n */\n loadingSuspended?: boolean;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n}\n\n/** Context shape for segment loading. Each variant only consumes its own type's loader actor. */\nexport interface SegmentLoadingContext {\n videoSegmentLoaderActor?: SegmentLoaderActor;\n audioSegmentLoaderActor?: SegmentLoaderActor;\n textTrackSegmentLoaderActor?: TextTrackSegmentLoaderActor;\n}\n\n// ============================================================================\n// REACTOR\n// ============================================================================\n\ntype SegmentLoadingFsmState = 'preconditions-unmet' | 'dormant' | 'metadata-only' | 'full-range';\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\ntype SegmentLoaderActorKey = 'videoSegmentLoaderActor' | 'audioSegmentLoaderActor' | 'textTrackSegmentLoaderActor';\n\ntype SegmentLoadingStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<SegmentLoadingState['presentation']>;\n preload: ReadonlySignal<SegmentLoadingState['preload']>;\n currentTime: ReadonlySignal<SegmentLoadingState['currentTime']>;\n loadActivated: ReadonlySignal<SegmentLoadingState['loadActivated']>;\n} & { [P in K]: ReadonlySignal<SegmentLoadingState[P]> };\n\n/** Shared `'load'` message shape parameterized over the resolved track type. */\ninterface LoadMessage<Track> {\n type: 'load';\n track: Track;\n range?: { start: number; end: number };\n}\n\n/** Structural constraint for any segment-loader-style actor. */\ninterface SegmentLoaderLike<Track> {\n send: (message: LoadMessage<Track>) => void;\n}\n\n/**\n * Specialization helper. Generic over `Track` (inferred from `findResolvedTrack`'s return type). The loader value type\n * is constrained to `SegmentLoaderLike<Track>` — concrete actor types (whose `.send` accepts a wider track union)\n * satisfy this via function-parameter contravariance.\n *\n * Tracks that the helper handles must have a `segments` field — used by `segmentBoundarySignal` to compute the\n * load-anchor boundary. Each variant's resolver narrows to the right resolved-track shape.\n */\nfunction setupSegmentLoading<\n K extends SelectedTrackKey,\n L extends SegmentLoaderActorKey,\n Track extends { segments: readonly Segment[] },\n>({\n state,\n context,\n config,\n}: {\n // `loadingSuspended` is observed, never declared (see its state-shape doc):\n // optional here so variant maps — which omit it from their contracts — are\n // assignable as-is, while compositions with a writer expose the live slot.\n state: SegmentLoadingStateMap<K> & { loadingSuspended?: ReadonlySignal<SegmentLoadingState['loadingSuspended']> };\n context: { [P in L]: ReadonlySignal<SegmentLoaderLike<Track> | undefined> };\n config: {\n selectedKey: K;\n loaderKey: L;\n findResolvedTrack: (\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n ) => Track | undefined;\n forwardBuffer?: Partial<ForwardBufferConfig>;\n };\n}): Reactor<SegmentLoadingFsmState | 'destroying' | 'destroyed'> {\n const { selectedKey, loaderKey, findResolvedTrack } = config;\n const bufferDuration = config.forwardBuffer?.bufferDuration ?? DEFAULT_FORWARD_BUFFER_CONFIG.bufferDuration;\n\n const selectedTrack = computed<Track | undefined>(() =>\n findResolvedTrack(state.presentation.get(), state[selectedKey].get())\n );\n\n // Segment-boundary signal — `segmentStartForTime` returns the same number\n // while `currentTime` stays inside one segment, so signal-polyfill's\n // `Object.is` equality on this computed dedups within-segment ticks.\n const segmentBoundarySignal = computed(() => {\n const track = selectedTrack.get();\n if (!track) return undefined;\n\n return segmentStartForTime(state.currentTime.get() ?? 0, track.segments);\n });\n\n const derivedStateSignal = computed<SegmentLoadingFsmState>(() => {\n // Policy-off wins even over preconditions: a loader arriving while\n // suspended must not dispatch.\n if (state.loadingSuspended?.get()) return 'dormant';\n\n if (!context[loaderKey].get() || !selectedTrack.get()) return 'preconditions-unmet';\n\n if (state.loadActivated.get() || state.preload.get() === 'auto') return 'full-range';\n\n if (state.preload.get() === 'none') return 'dormant';\n\n return 'metadata-only';\n });\n\n return createMachineReactor<SegmentLoadingFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n dormant: {},\n 'metadata-only': {\n // Fires once on entry. Matches HTMLMediaElement's preload='metadata'\n // semantics — load enough to surface metadata for the entry-time\n // selection. Each variant's loader decides what to do: v/a's actor\n // fetches the init segment; text's actor treats no-range as a\n // no-op. Selection changes while still in `'metadata-only'` are\n // intentionally not followed.\n entry: () => {\n const track = selectedTrack.get()!;\n\n context[loaderKey].get()!.send({ type: 'load', track });\n },\n },\n 'full-range': {\n // Re-fires on selected-track changes and on segment-boundary\n // crossings. The loader and `currentTime` are peeked — the\n // boundary-dedup signal already handles re-firing policy.\n effects: () => {\n const track = selectedTrack.get()!;\n\n segmentBoundarySignal.get();\n const currentTime = peek(state.currentTime) ?? 0;\n\n peek(context[loaderKey])!.send({\n type: 'load',\n track,\n range: { start: currentTime, end: currentTime + bufferDuration },\n });\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_SEGMENT_LOADING_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findResolvedTrack: findResolvedVideoTrack,\n} as const;\n\nconst AUDIO_SEGMENT_LOADING_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findResolvedTrack: findResolvedAudioTrack,\n} as const;\n\nconst TEXT_SEGMENT_LOADING_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findResolvedTrack: findResolvedTextTrack,\n} as const;\n\n// ============================================================================\n// Specialized exports — one per media type\n// ============================================================================\n\nexport const loadVideoSegments = defineBehavior({\n stateKeys: ['presentation', 'preload', 'currentTime', 'loadActivated', 'selectedVideoTrackId'],\n contextKeys: ['videoSegmentLoaderActor'],\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: SegmentLoadingStateMap<'selectedVideoTrackId'>;\n context: {\n videoSegmentLoaderActor: ReadonlySignal<SegmentLoadingContext['videoSegmentLoaderActor']>;\n };\n config?: object;\n }) =>\n setupSegmentLoading({\n state,\n context,\n config: { ...VIDEO_SEGMENT_LOADING_CONFIG, ...config },\n }),\n});\n\nexport const loadAudioSegments = defineBehavior({\n stateKeys: ['presentation', 'preload', 'currentTime', 'loadActivated', 'selectedAudioTrackId'],\n contextKeys: ['audioSegmentLoaderActor'],\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: SegmentLoadingStateMap<'selectedAudioTrackId'>;\n context: {\n audioSegmentLoaderActor: ReadonlySignal<SegmentLoadingContext['audioSegmentLoaderActor']>;\n };\n config?: object;\n }) =>\n setupSegmentLoading({\n state,\n context,\n config: { ...AUDIO_SEGMENT_LOADING_CONFIG, ...config },\n }),\n});\n\nexport const loadTextTrackSegments = defineBehavior({\n stateKeys: ['presentation', 'preload', 'currentTime', 'loadActivated', 'selectedTextTrackId'],\n contextKeys: ['textTrackSegmentLoaderActor'],\n setup: ({\n state,\n context,\n config = {},\n }: {\n state: SegmentLoadingStateMap<'selectedTextTrackId'>;\n context: {\n textTrackSegmentLoaderActor: ReadonlySignal<SegmentLoadingContext['textTrackSegmentLoaderActor']>;\n };\n config?: object;\n }) =>\n setupSegmentLoading({\n state,\n context,\n config: { ...TEXT_SEGMENT_LOADING_CONFIG, ...config },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6HA,SAAS,oBAIP,EACA,OACA,SACA,UAgB+D;CAC/D,MAAM,EAAE,aAAa,WAAW,sBAAsB;CACtD,MAAM,iBAAiB,OAAO,eAAe,kBAAkB,8BAA8B;CAE7F,MAAM,gBAAgB,eACpB,kBAAkB,MAAM,aAAa,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CACtE;CAKA,MAAM,wBAAwB,eAAe;EAC3C,MAAM,QAAQ,cAAc,IAAI;EAChC,IAAI,CAAC,OAAO,OAAO,KAAA;EAEnB,OAAO,oBAAoB,MAAM,YAAY,IAAI,KAAK,GAAG,MAAM,QAAQ;CACzE,CAAC;CAED,MAAM,qBAAqB,eAAuC;EAGhE,IAAI,MAAM,kBAAkB,IAAI,GAAG,OAAO;EAE1C,IAAI,CAAC,QAAQ,UAAU,CAAC,IAAI,KAAK,CAAC,cAAc,IAAI,GAAG,OAAO;EAE9D,IAAI,MAAM,cAAc,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO;EAExE,IAAI,MAAM,QAAQ,IAAI,MAAM,QAAQ,OAAO;EAE3C,OAAO;CACT,CAAC;CAED,OAAO,qBAA6C;EAClD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GACxB,SAAS,CAAC;GACV,iBAAiB,EAOf,aAAa;IACX,MAAM,QAAQ,cAAc,IAAI;IAEhC,QAAQ,UAAU,CAAC,IAAI,CAAC,CAAE,KAAK;KAAE,MAAM;KAAQ;IAAM,CAAC;GACxD,EACF;GACA,cAAc,EAIZ,eAAe;IACb,MAAM,QAAQ,cAAc,IAAI;IAEhC,sBAAsB,IAAI;IAC1B,MAAM,cAAc,KAAK,MAAM,WAAW,KAAK;IAE/C,KAAK,QAAQ,UAAU,CAAC,CAAE,KAAK;KAC7B,MAAM;KACN;KACA,OAAO;MAAE,OAAO;MAAa,KAAK,cAAc;KAAe;IACjE,CAAC;GACH,EACF;EACF;CACF,CAAC;AACH;AAMA,MAAM,+BAA+B;CACnC,GAAG;CACH,mBAAmB;AACrB;AAEA,MAAM,+BAA+B;CACnC,GAAG;CACH,mBAAmB;AACrB;AAEA,MAAM,8BAA8B;CAClC,GAAG;CACH,mBAAmB;AACrB;AAMA,MAAa,oBAAoB,eAAe;CAC9C,WAAW;EAAC;EAAgB;EAAW;EAAe;EAAiB;CAAsB;CAC7F,aAAa,CAAC,yBAAyB;CACvC,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAQV,oBAAoB;EAClB;EACA;EACA,QAAQ;GAAE,GAAG;GAA8B,GAAG;EAAO;CACvD,CAAC;AACL,CAAC;AAED,MAAa,oBAAoB,eAAe;CAC9C,WAAW;EAAC;EAAgB;EAAW;EAAe;EAAiB;CAAsB;CAC7F,aAAa,CAAC,yBAAyB;CACvC,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAQV,oBAAoB;EAClB;EACA;EACA,QAAQ;GAAE,GAAG;GAA8B,GAAG;EAAO;CACvD,CAAC;AACL,CAAC;AAED,MAAa,wBAAwB,eAAe;CAClD,WAAW;EAAC;EAAgB;EAAW;EAAe;EAAiB;CAAqB;CAC5F,aAAa,CAAC,6BAA6B;CAC3C,QAAQ,EACN,OACA,SACA,SAAS,CAAC,QAQV,oBAAoB;EAClB;EACA;EACA,QAAQ;GAAE,GAAG;GAA6B,GAAG;EAAO;CACtD,CAAC;AACL,CAAC"}
@@ -4,41 +4,36 @@ import { getMinBufferedEnd } from "../../../media/dom/mse/duration.js";
4
4
  import { listen } from "@videojs/utils/dom";
5
5
  //#region src/playback/behaviors/dom/recover-end-stall.ts
6
6
  /**
7
- * Recover the end-of-stream stall that Chrome exhibits on skewed A/V. After
8
- * `endOfStream`, when the audio and video tracks end a few ms apart (e.g. a source
9
- * with an A/V PTS skew), Chrome's audio-clock-paced playback freezes the playhead
10
- * ~50–70ms short of the reachable buffered end and never fires `ended` so playback
11
- * hangs at the very end and loop never re-triggers. This behavior watches for the
12
- * `waiting` event that fires at that freeze and, when the MediaSource is `ended` and
13
- * the playhead sits at the reachable buffered end, nudges `currentTime` to `duration`
14
- * to force the native `ended`.
7
+ * Recover the end-of-stream stall that Chrome exhibits on skewed A/V. After `endOfStream`, when the audio and video
8
+ * tracks end a few ms apart (e.g. a source with an A/V PTS skew), Chrome's audio-clock-paced playback freezes the
9
+ * playhead ~50–70ms short of the reachable buffered end and never fires `ended` — so playback hangs at the very end and
10
+ * loop never re-triggers. This behavior watches for the `waiting` event that fires at that freeze and, when the
11
+ * MediaSource is `ended` and the playhead sits at the reachable buffered end, nudges `currentTime` to `duration` to
12
+ * force the native `ended`.
15
13
  *
16
- * **Event-driven, no poll.** `waiting` fires at the instant the playhead stalls
17
- * (measured ~0ms latency), so there's nothing to gain from polling — and polling would
18
- * add its interval + a stall threshold before reacting.
14
+ * **Event-driven, no poll.** `waiting` fires at the instant the playhead stalls (measured ~0ms latency), so there's
15
+ * nothing to gain from polling — and polling would add its interval + a stall threshold before reacting.
19
16
  *
20
- * **Proximity to the *reachable* buffered end** is the discriminator. That end is
21
- * `getMinBufferedEnd(mediaSource.sourceBuffers)` — the `min` of the per-track (video/audio)
22
- * SourceBuffer ends, i.e. the furthest point playback can reach — read from the SourceBuffers
23
- * directly rather than the `mediaElement.buffered` aggregate. Once `endOfStream` is signalled
24
- * that's the true content end. Requiring the playhead within `endStallNudgeWindow` of it
25
- * distinguishes the real end-of-stream freeze from a mid-stream buffer-hole stall (which sits
26
- * far from the buffered end), so we never skip content. The window must exceed the freeze gap;
27
- * too small would miss the stall (a permanent hang), so the default is generous relative to the
28
- * measured gap and is config-tunable for empirical tuning.
17
+ * **Proximity to the _reachable_ buffered end** is the discriminator. That end is
18
+ * `getMinBufferedEnd(mediaSource.sourceBuffers)` — the `min` of the per-track (video/audio) SourceBuffer ends, i.e. the
19
+ * furthest point playback can reach — read from the SourceBuffers directly rather than the `mediaElement.buffered`
20
+ * aggregate. Once `endOfStream` is signalled that's the true content end. Requiring the playhead within
21
+ * `endStallNudgeWindow` of it distinguishes the real end-of-stream freeze from a mid-stream buffer-hole stall (which
22
+ * sits far from the buffered end), so we never skip content. The window must exceed the freeze gap; too small would
23
+ * miss the stall (a permanent hang), so the default is generous relative to the measured gap and is config-tunable for
24
+ * empirical tuning.
29
25
  *
30
- * Inert where it shouldn't act: live (the MediaSource never reaches `ended` while the
31
- * window grows; `duration` is `Infinity`) and streams that end cleanly (no `waiting`).
26
+ * Inert where it shouldn't act: live (the MediaSource never reaches `ended` while the window grows; `duration` is
27
+ * `Infinity`) and streams that end cleanly (no `waiting`).
32
28
  *
33
29
  * See `internal/decisions/spf/end-of-stream-av-skew-recovery.md`.
34
30
  */
35
31
  /** ~2.8× the measured max freeze gap (71ms) — tight, but with headroom against a miss. */
36
32
  const DEFAULT_END_STALL_NUDGE_WINDOW = .2;
37
33
  /**
38
- * Whether a `waiting` should be forced to `ended`: the MediaSource is `ended`, the
39
- * stream is finite (not live), playback is active (not paused/seeking/already-ended),
40
- * and the playhead sits within `nudgeWindow` of the reachable buffered end (so it's the
41
- * true end, not a mid-stream buffer hole). Pure — the behavior supplies the live values.
34
+ * Whether a `waiting` should be forced to `ended`: the MediaSource is `ended`, the stream is finite (not live),
35
+ * playback is active (not paused/seeking/already-ended), and the playhead sits within `nudgeWindow` of the reachable
36
+ * buffered end (so it's the true end, not a mid-stream buffer hole). Pure the behavior supplies the live values.
42
37
  */
43
38
  function shouldForceEnded(input, nudgeWindow) {
44
39
  const { msEnded, durationFinite, paused, seeking, ended, currentTime, bufferedEnd } = input;
@@ -1 +1 @@
1
- {"version":3,"file":"recover-end-stall.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/recover-end-stall.ts"],"sourcesContent":["/**\n * Recover the end-of-stream stall that Chrome exhibits on skewed A/V. After\n * `endOfStream`, when the audio and video tracks end a few ms apart (e.g. a source\n * with an A/V PTS skew), Chrome's audio-clock-paced playback freezes the playhead\n * ~50–70ms short of the reachable buffered end and never fires `ended` — so playback\n * hangs at the very end and loop never re-triggers. This behavior watches for the\n * `waiting` event that fires at that freeze and, when the MediaSource is `ended` and\n * the playhead sits at the reachable buffered end, nudges `currentTime` to `duration`\n * to force the native `ended`.\n *\n * **Event-driven, no poll.** `waiting` fires at the instant the playhead stalls\n * (measured ~0ms latency), so there's nothing to gain from polling — and polling would\n * add its interval + a stall threshold before reacting.\n *\n * **Proximity to the *reachable* buffered end** is the discriminator. That end is\n * `getMinBufferedEnd(mediaSource.sourceBuffers)` — the `min` of the per-track (video/audio)\n * SourceBuffer ends, i.e. the furthest point playback can reach — read from the SourceBuffers\n * directly rather than the `mediaElement.buffered` aggregate. Once `endOfStream` is signalled\n * that's the true content end. Requiring the playhead within `endStallNudgeWindow` of it\n * distinguishes the real end-of-stream freeze from a mid-stream buffer-hole stall (which sits\n * far from the buffered end), so we never skip content. The window must exceed the freeze gap;\n * too small would miss the stall (a permanent hang), so the default is generous relative to the\n * measured gap and is config-tunable for empirical tuning.\n *\n * Inert where it shouldn't act: live (the MediaSource never reaches `ended` while the\n * window grows; `duration` is `Infinity`) and streams that end cleanly (no `waiting`).\n *\n * See `internal/decisions/spf/end-of-stream-av-skew-recovery.md`.\n */\nimport { listen } from '@videojs/utils/dom';\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport { effect } from '../../../core/signals/effect';\nimport type { ReadonlySignal } from '../../../core/signals/primitives';\nimport { getMinBufferedEnd } from '../../../media/dom/mse/duration';\n\nexport interface RecoverEndStallContext {\n mediaElement?: HTMLMediaElement | undefined;\n mediaSource?: MediaSource | undefined;\n}\n\nexport interface RecoverEndStallConfig {\n /**\n * How close (seconds) the playhead must be to the reachable buffered end for a\n * `waiting` to count as the end-of-stream freeze. Must exceed the audio-clock freeze\n * gap (~50–70ms measured on Chrome); too small risks missing the stall (a permanent\n * hang), so keep a margin. Default {@link DEFAULT_END_STALL_NUDGE_WINDOW}.\n */\n endStallNudgeWindow?: number;\n}\n\n/** ~2.8× the measured max freeze gap (71ms) — tight, but with headroom against a miss. */\nexport const DEFAULT_END_STALL_NUDGE_WINDOW = 0.2;\n\n/**\n * Whether a `waiting` should be forced to `ended`: the MediaSource is `ended`, the\n * stream is finite (not live), playback is active (not paused/seeking/already-ended),\n * and the playhead sits within `nudgeWindow` of the reachable buffered end (so it's the\n * true end, not a mid-stream buffer hole). Pure — the behavior supplies the live values.\n */\nexport function shouldForceEnded(\n input: {\n msEnded: boolean;\n durationFinite: boolean;\n paused: boolean;\n seeking: boolean;\n ended: boolean;\n currentTime: number;\n bufferedEnd: number | undefined;\n },\n nudgeWindow: number\n): boolean {\n const { msEnded, durationFinite, paused, seeking, ended, currentTime, bufferedEnd } = input;\n if (!msEnded || !durationFinite || paused || seeking || ended || bufferedEnd === undefined) {\n return false;\n }\n const gap = bufferedEnd - currentTime;\n return gap >= 0 && gap < nudgeWindow;\n}\n\nfunction recoverEndStallSetup({\n context,\n config,\n}: {\n context: {\n mediaElement: ReadonlySignal<RecoverEndStallContext['mediaElement']>;\n mediaSource: ReadonlySignal<RecoverEndStallContext['mediaSource']>;\n };\n config?: RecoverEndStallConfig;\n}): () => void {\n const nudgeWindow = config?.endStallNudgeWindow ?? DEFAULT_END_STALL_NUDGE_WINDOW;\n\n return effect(() => {\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) return;\n\n const onWaiting = () => {\n const mediaSource = context.mediaSource.get();\n const forceEnded = shouldForceEnded(\n {\n msEnded: mediaSource?.readyState === 'ended',\n durationFinite: Number.isFinite(mediaElement.duration),\n paused: mediaElement.paused,\n seeking: mediaElement.seeking,\n ended: mediaElement.ended,\n currentTime: mediaElement.currentTime,\n bufferedEnd: mediaSource ? getMinBufferedEnd(mediaSource.sourceBuffers) : undefined,\n },\n nudgeWindow\n );\n // Nudge to `duration` → native `ended` (the seeking/ended guards above prevent a\n // re-fire while the nudge-seek is in flight, so no latch is needed).\n if (forceEnded) mediaElement.currentTime = mediaElement.duration;\n };\n\n return listen(mediaElement, 'waiting', onWaiting);\n });\n}\n\nexport const recoverEndStall = defineBehavior({\n stateKeys: [] as const,\n contextKeys: ['mediaElement', 'mediaSource'] as const,\n setup: recoverEndStallSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,MAAa,iCAAiC;;;;;;;AAQ9C,SAAgB,iBACd,OASA,aACS;CACT,MAAM,EAAE,SAAS,gBAAgB,QAAQ,SAAS,OAAO,aAAa,gBAAgB;CACtF,IAAI,CAAC,WAAW,CAAC,kBAAkB,UAAU,WAAW,SAAS,gBAAgB,KAAA,GAC/E,OAAO;CAET,MAAM,MAAM,cAAc;CAC1B,OAAO,OAAO,KAAK,MAAM;AAC3B;AAEA,SAAS,qBAAqB,EAC5B,SACA,UAOa;CACb,MAAM,cAAc,QAAQ,uBAAA;CAE5B,OAAO,aAAa;EAClB,MAAM,eAAe,QAAQ,aAAa,IAAI;EAC9C,IAAI,CAAC,cAAc;EAEnB,MAAM,kBAAkB;GACtB,MAAM,cAAc,QAAQ,YAAY,IAAI;GAe5C,IAdmB,iBACjB;IACE,SAAS,aAAa,eAAe;IACrC,gBAAgB,OAAO,SAAS,aAAa,QAAQ;IACrD,QAAQ,aAAa;IACrB,SAAS,aAAa;IACtB,OAAO,aAAa;IACpB,aAAa,aAAa;IAC1B,aAAa,cAAc,kBAAkB,YAAY,aAAa,IAAI,KAAA;GAC5E,GACA,WAIW,GAAG,aAAa,cAAc,aAAa;EAC1D;EAEA,OAAO,OAAO,cAAc,WAAW,SAAS;CAClD,CAAC;AACH;AAEA,MAAa,kBAAkB,eAAe;CAC5C,WAAW,CAAC;CACZ,aAAa,CAAC,gBAAgB,aAAa;CAC3C,OAAO;AACT,CAAC"}
1
+ {"version":3,"file":"recover-end-stall.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/recover-end-stall.ts"],"sourcesContent":["/**\n * Recover the end-of-stream stall that Chrome exhibits on skewed A/V. After `endOfStream`, when the audio and video\n * tracks end a few ms apart (e.g. a source with an A/V PTS skew), Chrome's audio-clock-paced playback freezes the\n * playhead ~50–70ms short of the reachable buffered end and never fires `ended` — so playback hangs at the very end and\n * loop never re-triggers. This behavior watches for the `waiting` event that fires at that freeze and, when the\n * MediaSource is `ended` and the playhead sits at the reachable buffered end, nudges `currentTime` to `duration` to\n * force the native `ended`.\n *\n * **Event-driven, no poll.** `waiting` fires at the instant the playhead stalls (measured ~0ms latency), so there's\n * nothing to gain from polling — and polling would add its interval + a stall threshold before reacting.\n *\n * **Proximity to the _reachable_ buffered end** is the discriminator. That end is\n * `getMinBufferedEnd(mediaSource.sourceBuffers)` — the `min` of the per-track (video/audio) SourceBuffer ends, i.e. the\n * furthest point playback can reach — read from the SourceBuffers directly rather than the `mediaElement.buffered`\n * aggregate. Once `endOfStream` is signalled that's the true content end. Requiring the playhead within\n * `endStallNudgeWindow` of it distinguishes the real end-of-stream freeze from a mid-stream buffer-hole stall (which\n * sits far from the buffered end), so we never skip content. The window must exceed the freeze gap; too small would\n * miss the stall (a permanent hang), so the default is generous relative to the measured gap and is config-tunable for\n * empirical tuning.\n *\n * Inert where it shouldn't act: live (the MediaSource never reaches `ended` while the window grows; `duration` is\n * `Infinity`) and streams that end cleanly (no `waiting`).\n *\n * See `internal/decisions/spf/end-of-stream-av-skew-recovery.md`.\n */\nimport { listen } from '@videojs/utils/dom';\n\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport { effect } from '../../../core/signals/effect';\nimport type { ReadonlySignal } from '../../../core/signals/primitives';\nimport { getMinBufferedEnd } from '../../../media/dom/mse/duration';\n\nexport interface RecoverEndStallContext {\n mediaElement?: HTMLMediaElement | undefined;\n mediaSource?: MediaSource | undefined;\n}\n\nexport interface RecoverEndStallConfig {\n /**\n * How close (seconds) the playhead must be to the reachable buffered end for a `waiting` to count as the\n * end-of-stream freeze. Must exceed the audio-clock freeze gap (~50–70ms measured on Chrome); too small risks missing\n * the stall (a permanent hang), so keep a margin. Default {@link DEFAULT_END_STALL_NUDGE_WINDOW}.\n */\n endStallNudgeWindow?: number;\n}\n\n/** ~2.8× the measured max freeze gap (71ms) — tight, but with headroom against a miss. */\nexport const DEFAULT_END_STALL_NUDGE_WINDOW = 0.2;\n\n/**\n * Whether a `waiting` should be forced to `ended`: the MediaSource is `ended`, the stream is finite (not live),\n * playback is active (not paused/seeking/already-ended), and the playhead sits within `nudgeWindow` of the reachable\n * buffered end (so it's the true end, not a mid-stream buffer hole). Pure — the behavior supplies the live values.\n */\nexport function shouldForceEnded(\n input: {\n msEnded: boolean;\n durationFinite: boolean;\n paused: boolean;\n seeking: boolean;\n ended: boolean;\n currentTime: number;\n bufferedEnd: number | undefined;\n },\n nudgeWindow: number\n): boolean {\n const { msEnded, durationFinite, paused, seeking, ended, currentTime, bufferedEnd } = input;\n if (!msEnded || !durationFinite || paused || seeking || ended || bufferedEnd === undefined) return false;\n\n const gap = bufferedEnd - currentTime;\n\n return gap >= 0 && gap < nudgeWindow;\n}\n\nfunction recoverEndStallSetup({\n context,\n config,\n}: {\n context: {\n mediaElement: ReadonlySignal<RecoverEndStallContext['mediaElement']>;\n mediaSource: ReadonlySignal<RecoverEndStallContext['mediaSource']>;\n };\n config?: RecoverEndStallConfig;\n}): () => void {\n const nudgeWindow = config?.endStallNudgeWindow ?? DEFAULT_END_STALL_NUDGE_WINDOW;\n\n return effect(() => {\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) return;\n\n const onWaiting = () => {\n const mediaSource = context.mediaSource.get();\n const forceEnded = shouldForceEnded(\n {\n msEnded: mediaSource?.readyState === 'ended',\n durationFinite: Number.isFinite(mediaElement.duration),\n paused: mediaElement.paused,\n seeking: mediaElement.seeking,\n ended: mediaElement.ended,\n currentTime: mediaElement.currentTime,\n bufferedEnd: mediaSource ? getMinBufferedEnd(mediaSource.sourceBuffers) : undefined,\n },\n nudgeWindow\n );\n\n // Nudge to `duration` → native `ended` (the seeking/ended guards above prevent a\n // re-fire while the nudge-seek is in flight, so no latch is needed).\n if (forceEnded) mediaElement.currentTime = mediaElement.duration;\n };\n\n return listen(mediaElement, 'waiting', onWaiting);\n });\n}\n\nexport const recoverEndStall = defineBehavior({\n stateKeys: [] as const,\n contextKeys: ['mediaElement', 'mediaSource'] as const,\n setup: recoverEndStallSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAa,iCAAiC;;;;;;AAO9C,SAAgB,iBACd,OASA,aACS;CACT,MAAM,EAAE,SAAS,gBAAgB,QAAQ,SAAS,OAAO,aAAa,gBAAgB;CACtF,IAAI,CAAC,WAAW,CAAC,kBAAkB,UAAU,WAAW,SAAS,gBAAgB,KAAA,GAAW,OAAO;CAEnG,MAAM,MAAM,cAAc;CAE1B,OAAO,OAAO,KAAK,MAAM;AAC3B;AAEA,SAAS,qBAAqB,EAC5B,SACA,UAOa;CACb,MAAM,cAAc,QAAQ,uBAAA;CAE5B,OAAO,aAAa;EAClB,MAAM,eAAe,QAAQ,aAAa,IAAI;EAC9C,IAAI,CAAC,cAAc;EAEnB,MAAM,kBAAkB;GACtB,MAAM,cAAc,QAAQ,YAAY,IAAI;GAgB5C,IAfmB,iBACjB;IACE,SAAS,aAAa,eAAe;IACrC,gBAAgB,OAAO,SAAS,aAAa,QAAQ;IACrD,QAAQ,aAAa;IACrB,SAAS,aAAa;IACtB,OAAO,aAAa;IACpB,aAAa,aAAa;IAC1B,aAAa,cAAc,kBAAkB,YAAY,aAAa,IAAI,KAAA;GAC5E,GACA,WAKW,GAAG,aAAa,cAAc,aAAa;EAC1D;EAEA,OAAO,OAAO,cAAc,WAAW,SAAS;CAClD,CAAC;AACH;AAEA,MAAa,kBAAkB,eAAe;CAC5C,WAAW,CAAC;CACZ,aAAa,CAAC,gBAAgB,aAAa;CAC3C,OAAO;AACT,CAAC"}
@@ -4,76 +4,58 @@ import { getLiveEdge } from "../../primitives/live-window.js";
4
4
  import { listen } from "@videojs/utils/dom";
5
5
  //#region src/playback/behaviors/dom/seek-to-live-edge.ts
6
6
  /**
7
- * Keep the playhead in the live window, via a two-state reactor gated on the
8
- * preconditions for "we know where live is":
7
+ * Keep the playhead in the live window, via a two-state reactor gated on the preconditions for "we know where live is":
9
8
  *
10
- * - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`:
11
- * VOD, ended, or unresolved). Idle.
12
- * - **`live`** preconditions met. `entry` commands `state.startPosition` once
13
- * to the target live latency behind the edge (clamped to the window start) so
14
- * playback begins near the edge and the loader dispatches an in-window range;
9
+ * - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`: VOD, ended, or unresolved). Idle.
10
+ * - **`live`** — preconditions met. `entry` commands `state.startPosition` once to the target live latency behind the
11
+ * edge (clamped to the window start) so playback begins near the edge and the loader dispatches an in-window range;
15
12
  * `effects` runs the window-exit guard.
16
13
  *
17
- * A derivable live edge is itself the establishment gate: segment placement is
18
- * settled at parse time the reference track's local placement *is* the
19
- * presentation timeline, and every other track's first parse is held until the
20
- * anchor is stamped (`resolve-track`'s gate + `establishStartMediaTime`) so
21
- * any window derived from resolved segments is already final, with no separate
22
- * anchor signal to wait on (see
14
+ * A derivable live edge is itself the establishment gate: segment placement is settled at parse time — the reference
15
+ * track's local placement _is_ the presentation timeline, and every other track's first parse is held until the anchor
16
+ * is stamped (`resolve-track`'s gate + `establishStartMediaTime`) so any window derived from resolved segments is
17
+ * already final, with no separate anchor signal to wait on (see
23
18
  * `internal/design/spf/live-presentation-timeline-model.md`).
24
19
  *
25
- * The two pieces split along the axis a future DVR / EVENT mode will care about:
26
- * the **one-time start position** (`entry`) is the *live-specific* behavior — start
27
- * near the edge on load; a DVR mode makes it conditional (start in place). The
28
- * **window-exit guard** (`effects`) is the *general windowed-live* behavior
29
- * applies to sliding-window live, DVR, and EVENT alike. Because the command is an
30
- * `entry`, it fires once per entry into `live`; a source change exits to
31
- * `inactive`, so the next source re-commands (no closure latch to reset). The
32
- * guard stays a direct seek — it is recurring, while `startPosition` is a
33
- * self-clearing one-shot.
20
+ * The two pieces split along the axis a future DVR / EVENT mode will care about: the **one-time start position**
21
+ * (`entry`) is the _live-specific_ behavior — start near the edge on load; a DVR mode makes it conditional (start in
22
+ * place). The **window-exit guard** (`effects`) is the _general windowed-live_ behavior applies to sliding-window
23
+ * live, DVR, and EVENT alike. Because the command is an `entry`, it fires once per entry into `live`; a source change
24
+ * exits to `inactive`, so the next source re-commands (no closure latch to reset). The guard stays a direct seek — it
25
+ * is recurring, while `startPosition` is a self-clearing one-shot.
34
26
  *
35
- * Window-exit guard: while playing (not paused), reposition to the live edge
36
- * when the playhead has fallen behind the window start including when a seek
37
- * to a now-evicted position has stranded the playhead (such a seek can never
38
- * settle, so we rescue rather than wait on it). Two triggers:
39
- * the **window-update re-fire** (the guard reads the live edge, so each reload /
40
- * slide re-runs it this catches a stall, where `timeupdate` stops but the
41
- * playlist keeps reloading) and a **`play` listener** for immediate reactivity on
42
- * resume, since the reload interval can be seconds. `play`, not `playing`: after
43
- * a long pause the playhead sits behind the window at an unseekable position,
44
- * where the browser stalls and `playing` never fires; `play` fires on the
45
- * paused→false transition regardless, so we snap before the stall. In-window
46
- * pause / DVR scrub-back are left untouched.
27
+ * Window-exit guard: while playing (not paused), reposition to the live edge when the playhead has fallen behind the
28
+ * window start — including when a seek to a now-evicted position has stranded the playhead (such a seek can never
29
+ * settle, so we rescue rather than wait on it). Two triggers: the **window-update re-fire** (the guard reads the live
30
+ * edge, so each reload / slide re-runs it this catches a stall, where `timeupdate` stops but the playlist keeps
31
+ * reloading) and a **`play` listener** for immediate reactivity on resume, since the reload interval can be seconds.
32
+ * `play`, not `playing`: after a long pause the playhead sits behind the window at an unseekable position, where the
33
+ * browser stalls and `playing` never fires; `play` fires on the paused→false transition regardless, so we snap before
34
+ * the stall. In-window pause / DVR scrub-back are left untouched.
47
35
  *
48
- * The latency comes from the injected `resolveLiveLatency` seam (HLS:
49
- * `HOLD-BACK`), so this behavior carries no delivery-format specifics.
50
- * `applyStartPosition` performs the seek, gated on `loadedmetadata` — which
51
- * implies an open MediaSource and hence a declared seekable range (a seek outside
52
- * `seekable` is clamped) — so this behavior needs no MediaSource precondition of
53
- * its own.
36
+ * The latency comes from the injected `resolveLiveLatency` seam (HLS: `HOLD-BACK`), so this behavior carries no
37
+ * delivery-format specifics. `applyStartPosition` performs the seek, gated on `loadedmetadata` which implies an open
38
+ * MediaSource and hence a declared seekable range (a seek outside `seekable` is clamped) so this behavior needs no
39
+ * MediaSource precondition of its own.
54
40
  */
55
41
  /**
56
- * Tolerance (seconds) around the window edges before the guard repositions, so
57
- * boundary / floating-point noise doesn't trigger a spurious seek.
42
+ * Tolerance (seconds) around the window edges before the guard repositions, so boundary / floating-point noise doesn't
43
+ * trigger a spurious seek.
58
44
  */
59
45
  const REPOSITION_TOLERANCE = .1;
60
46
  /**
61
- * `'live'` once the preconditions hold: a media element and a derivable live edge
62
- * (whose placement is final by construction — see the module docstring).
63
- * `'inactive'` otherwise.
47
+ * `'live'` once the preconditions hold: a media element and a derivable live edge (whose placement is final by
48
+ * construction — see the module docstring). `'inactive'` otherwise.
64
49
  *
65
- * Deliberately narrow: every signal here can flip the reactor out of and back into
66
- * `live`, re-firing `entry`. Neither blinks mid-source — the edge can't, because
67
- * `liveWindowForType` falls back to any resolved track of the type so `entry`
68
- * fires once per source without a latch, and a live reload (same source, slid
69
- * window) correctly doesn't re-command.
50
+ * Deliberately narrow: every signal here can flip the reactor out of and back into `live`, re-firing `entry`. Neither
51
+ * blinks mid-source — the edge can't, because `liveWindowForType` falls back to any resolved track of the type — so
52
+ * `entry` fires once per source without a latch, and a live reload (same source, slid window) correctly doesn't
53
+ * re-command.
70
54
  *
71
- * The flip side: the presentation *url* is not part of this state, so replacing one
72
- * already-resolved live presentation with another would stay in `live` and never
73
- * command a start position for the new source. Unreachable today every writer
74
- * sets `{ url }` (unresolved) or `undefined` first, so a source change always
75
- * transits `inactive`. Supporting a seeded pre-resolved presentation (via
76
- * `initialState`) that later changes would mean folding the url in here.
55
+ * The flip side: the presentation _url_ is not part of this state, so replacing one already-resolved live presentation
56
+ * with another would stay in `live` and never command a start position for the new source. Unreachable today — every
57
+ * writer sets `{ url }` (unresolved) or `undefined` first, so a source change always transits `inactive`. Supporting a
58
+ * seeded pre-resolved presentation (via `initialState`) that later changes would mean folding the url in here.
77
59
  */
78
60
  function deriveState(mediaElement, edge) {
79
61
  return mediaElement && edge ? "live" : "inactive";
@@ -116,10 +98,9 @@ function seekToLiveEdgeSetup({ state, context, config }) {
116
98
  });
117
99
  }
118
100
  /**
119
- * Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares
120
- * only `presentation` + `startPosition` in stateKeys while reading
121
- * `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the
122
- * switch* behaviors), so it composes without a stateKeys/type conflict.
101
+ * Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares only `presentation` + `startPosition` in
102
+ * stateKeys while reading `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the switch*
103
+ * behaviors), so it composes without a stateKeys/type conflict.
123
104
  */
124
105
  const seekToLiveEdge = {
125
106
  stateKeys: ["presentation", "startPosition"],
@@ -1 +1 @@
1
- {"version":3,"file":"seek-to-live-edge.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/seek-to-live-edge.ts"],"sourcesContent":["/**\n * Keep the playhead in the live window, via a two-state reactor gated on the\n * preconditions for \"we know where live is\":\n *\n * - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`:\n * VOD, ended, or unresolved). Idle.\n * - **`live`** — preconditions met. `entry` commands `state.startPosition` once\n * to the target live latency behind the edge (clamped to the window start) so\n * playback begins near the edge and the loader dispatches an in-window range;\n * `effects` runs the window-exit guard.\n *\n * A derivable live edge is itself the establishment gate: segment placement is\n * settled at parse time — the reference track's local placement *is* the\n * presentation timeline, and every other track's first parse is held until the\n * anchor is stamped (`resolve-track`'s gate + `establishStartMediaTime`) — so\n * any window derived from resolved segments is already final, with no separate\n * anchor signal to wait on (see\n * `internal/design/spf/live-presentation-timeline-model.md`).\n *\n * The two pieces split along the axis a future DVR / EVENT mode will care about:\n * the **one-time start position** (`entry`) is the *live-specific* behavior — start\n * near the edge on load; a DVR mode makes it conditional (start in place). The\n * **window-exit guard** (`effects`) is the *general windowed-live* behavior —\n * applies to sliding-window live, DVR, and EVENT alike. Because the command is an\n * `entry`, it fires once per entry into `live`; a source change exits to\n * `inactive`, so the next source re-commands (no closure latch to reset). The\n * guard stays a direct seek — it is recurring, while `startPosition` is a\n * self-clearing one-shot.\n *\n * Window-exit guard: while playing (not paused), reposition to the live edge\n * when the playhead has fallen behind the window start — including when a seek\n * to a now-evicted position has stranded the playhead (such a seek can never\n * settle, so we rescue rather than wait on it). Two triggers:\n * the **window-update re-fire** (the guard reads the live edge, so each reload /\n * slide re-runs it — this catches a stall, where `timeupdate` stops but the\n * playlist keeps reloading) and a **`play` listener** for immediate reactivity on\n * resume, since the reload interval can be seconds. `play`, not `playing`: after\n * a long pause the playhead sits behind the window at an unseekable position,\n * where the browser stalls and `playing` never fires; `play` fires on the\n * paused→false transition regardless, so we snap before the stall. In-window\n * pause / DVR scrub-back are left untouched.\n *\n * The latency comes from the injected `resolveLiveLatency` seam (HLS:\n * `HOLD-BACK`), so this behavior carries no delivery-format specifics.\n * `applyStartPosition` performs the seek, gated on `loadedmetadata` — which\n * implies an open MediaSource and hence a declared seekable range (a seek outside\n * `seekable` is clamped) — so this behavior needs no MediaSource precondition of\n * its own.\n */\nimport { listen } from '@videojs/utils/dom';\nimport type { Behavior } from '../../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport { getLiveEdge, type LiveEdge, type ResolveLiveLatency } from '../../primitives/live-window';\n\n/**\n * Tolerance (seconds) around the window edges before the guard repositions, so\n * boundary / floating-point noise doesn't trigger a spurious seek.\n */\nconst REPOSITION_TOLERANCE = 0.1;\n\nexport interface SeekToLiveEdgeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * One-shot start-position command in presentation-timeline seconds. Written\n * here on entry into `live`; consumed (cleared) by `applyStartPosition`.\n */\n startPosition?: number;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\nexport interface SeekToLiveEdgeContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\nexport interface SeekToLiveEdgeConfig {\n /**\n * Resolve the target live latency (seconds the playhead should trail the live\n * edge) for the timeline-bearing track. Injected by the engine so the latency\n * rule stays format-specific (HLS: `HOLD-BACK`, default 3× target duration;\n * DASH would read `suggestedPresentationDelay`) while this behavior stays\n * neutral. Absent → `0` (seek straight to the edge).\n */\n resolveLiveLatency?: ResolveLiveLatency;\n}\n\ntype SeekToLiveEdgeFsmState = 'inactive' | 'live';\n\n/**\n * `'live'` once the preconditions hold: a media element and a derivable live edge\n * (whose placement is final by construction — see the module docstring).\n * `'inactive'` otherwise.\n *\n * Deliberately narrow: every signal here can flip the reactor out of and back into\n * `live`, re-firing `entry`. Neither blinks mid-source — the edge can't, because\n * `liveWindowForType` falls back to any resolved track of the type — so `entry`\n * fires once per source without a latch, and a live reload (same source, slid\n * window) correctly doesn't re-command.\n *\n * The flip side: the presentation *url* is not part of this state, so replacing one\n * already-resolved live presentation with another would stay in `live` and never\n * command a start position for the new source. Unreachable today — every writer\n * sets `{ url }` (unresolved) or `undefined` first, so a source change always\n * transits `inactive`. Supporting a seeded pre-resolved presentation (via\n * `initialState`) that later changes would mean folding the url in here.\n */\nfunction deriveState(mediaElement: HTMLMediaElement | undefined, edge: LiveEdge | null): SeekToLiveEdgeFsmState {\n return mediaElement && edge ? 'live' : 'inactive';\n}\n\nfunction seekToLiveEdgeSetup({\n state,\n context,\n config,\n}: {\n state: {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n selectedVideoTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedAudioTrackId']>;\n };\n context: {\n mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']>;\n };\n config?: SeekToLiveEdgeConfig;\n}): Reactor<SeekToLiveEdgeFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), getLiveEdge({ state, config })));\n\n return createMachineReactor<SeekToLiveEdgeFsmState>({\n initial: 'inactive',\n monitor: () => derivedStateSignal.get(),\n states: {\n inactive: {},\n\n live: {\n // One-time-per-source start position so the loader dispatches an in-window\n // range and preload shows the right frame. The window-exit guard below is\n // the separate, continuous rule. Live-specific — a future DVR mode skips\n // even this first command (starts in place).\n entry: () => {\n // The monitor guarantees a media element + live edge while in `live`.\n const mediaElement = context.mediaElement.get()!;\n const { liveEdgeStart } = getLiveEdge({ state, config })!;\n // Never command backwards: seeding `state.currentTime` behind a playhead\n // already at/past the edge would drag the loaders back.\n if (mediaElement.currentTime >= liveEdgeStart) return;\n state.startPosition.set(liveEdgeStart);\n },\n\n // Window-exit guard. Repositions to the live edge when the playhead has\n // fallen behind the window start, while playing. Two triggers: this\n // effect's window-update re-fire (it reads the live edge, so each reload\n // re-runs it — catches a stall, where `timeupdate` is silent) and a\n // `play` listener for immediate reactivity on resume.\n effects: () => {\n // Read the live edge first — the only tracked dependency — so window\n // slides keep re-firing this effect even while paused. Bailing before\n // it (on paused) would drop the dependency and the effect would go\n // inert, missing the next slide. The monitor guarantees the edge while\n // in `live`; the media element is read untracked (the window update,\n // not element identity, is the re-fire trigger).\n const { start: windowStart, liveEdgeStart } = getLiveEdge({ state, config })!;\n const mediaElement = peek(context.mediaElement)!;\n const reposition = () => {\n // Don't yank a paused viewer. We deliberately DON'T bail on\n // `mediaElement.seeking`: a seek whose target sits behind the window\n // start can never complete (the data has slid out of the window and\n // been evicted), so `seeking` would stay true forever and strand the\n // playhead — the very stall this guard exists to rescue. The\n // `currentTime < windowStart` test is itself the precise\n // discriminator: an in-window DVR scrub-back lands at\n // `currentTime >= windowStart` and never trips it, so only a stuck\n // out-of-window seek is repositioned.\n if (mediaElement.paused) return;\n if (mediaElement.currentTime < windowStart - REPOSITION_TOLERANCE) {\n mediaElement.currentTime = liveEdgeStart;\n }\n };\n reposition();\n // `play` (not `playing`): a slid-past playhead is at an unseekable\n // position where `playing` would stall and never fire; `play` fires on\n // unpause regardless, so we snap before the stall.\n return listen(mediaElement, 'play', reposition);\n },\n },\n },\n });\n}\n\n/**\n * Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares\n * only `presentation` + `startPosition` in stateKeys while reading\n * `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the\n * switch* behaviors), so it composes without a stateKeys/type conflict.\n */\nexport const seekToLiveEdge: Behavior<\n {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n },\n { mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']> },\n SeekToLiveEdgeConfig\n> = {\n stateKeys: ['presentation', 'startPosition'],\n contextKeys: ['mediaElement'],\n setup: seekToLiveEdgeSetup,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;AAgD7B,SAAS,YAAY,cAA4C,MAA+C;CAC9G,OAAO,gBAAgB,OAAO,SAAS;AACzC;AAEA,SAAS,oBAAoB,EAC3B,OACA,SACA,UAY+D;CAC/D,MAAM,qBAAqB,eAAe,YAAY,QAAQ,aAAa,IAAI,GAAG,YAAY;EAAE;EAAO;CAAO,CAAC,CAAC,CAAC;CAEjH,OAAO,qBAA6C;EAClD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,UAAU,CAAC;GAEX,MAAM;IAKJ,aAAa;KAEX,MAAM,eAAe,QAAQ,aAAa,IAAI;KAC9C,MAAM,EAAE,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAGvD,IAAI,aAAa,eAAe,eAAe;KAC/C,MAAM,cAAc,IAAI,aAAa;IACvC;IAOA,eAAe;KAOb,MAAM,EAAE,OAAO,aAAa,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAC3E,MAAM,eAAe,KAAK,QAAQ,YAAY;KAC9C,MAAM,mBAAmB;MAUvB,IAAI,aAAa,QAAQ;MACzB,IAAI,aAAa,cAAc,cAAc,sBAC3C,aAAa,cAAc;KAE/B;KACA,WAAW;KAIX,OAAO,OAAO,cAAc,QAAQ,UAAU;IAChD;GACF;EACF;CACF,CAAC;AACH;;;;;;;AAQA,MAAa,iBAOT;CACF,WAAW,CAAC,gBAAgB,eAAe;CAC3C,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT"}
1
+ {"version":3,"file":"seek-to-live-edge.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/seek-to-live-edge.ts"],"sourcesContent":["/**\n * Keep the playhead in the live window, via a two-state reactor gated on the preconditions for \"we know where live is\":\n *\n * - **`inactive`** — no media element, or no live edge (`getLiveEdge` is `null`: VOD, ended, or unresolved). Idle.\n * - **`live`** — preconditions met. `entry` commands `state.startPosition` once to the target live latency behind the\n * edge (clamped to the window start) so playback begins near the edge and the loader dispatches an in-window range;\n * `effects` runs the window-exit guard.\n *\n * A derivable live edge is itself the establishment gate: segment placement is settled at parse time — the reference\n * track's local placement _is_ the presentation timeline, and every other track's first parse is held until the anchor\n * is stamped (`resolve-track`'s gate + `establishStartMediaTime`) — so any window derived from resolved segments is\n * already final, with no separate anchor signal to wait on (see\n * `internal/design/spf/live-presentation-timeline-model.md`).\n *\n * The two pieces split along the axis a future DVR / EVENT mode will care about: the **one-time start position**\n * (`entry`) is the _live-specific_ behavior — start near the edge on load; a DVR mode makes it conditional (start in\n * place). The **window-exit guard** (`effects`) is the _general windowed-live_ behavior — applies to sliding-window\n * live, DVR, and EVENT alike. Because the command is an `entry`, it fires once per entry into `live`; a source change\n * exits to `inactive`, so the next source re-commands (no closure latch to reset). The guard stays a direct seek — it\n * is recurring, while `startPosition` is a self-clearing one-shot.\n *\n * Window-exit guard: while playing (not paused), reposition to the live edge when the playhead has fallen behind the\n * window start — including when a seek to a now-evicted position has stranded the playhead (such a seek can never\n * settle, so we rescue rather than wait on it). Two triggers: the **window-update re-fire** (the guard reads the live\n * edge, so each reload / slide re-runs it — this catches a stall, where `timeupdate` stops but the playlist keeps\n * reloading) and a **`play` listener** for immediate reactivity on resume, since the reload interval can be seconds.\n * `play`, not `playing`: after a long pause the playhead sits behind the window at an unseekable position, where the\n * browser stalls and `playing` never fires; `play` fires on the paused→false transition regardless, so we snap before\n * the stall. In-window pause / DVR scrub-back are left untouched.\n *\n * The latency comes from the injected `resolveLiveLatency` seam (HLS: `HOLD-BACK`), so this behavior carries no\n * delivery-format specifics. `applyStartPosition` performs the seek, gated on `loadedmetadata` — which implies an open\n * MediaSource and hence a declared seekable range (a seek outside `seekable` is clamped) — so this behavior needs no\n * MediaSource precondition of its own.\n */\nimport { listen } from '@videojs/utils/dom';\n\nimport type { Behavior } from '../../../core/composition/create-composition';\nimport { createMachineReactor, type Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport { getLiveEdge, type LiveEdge, type ResolveLiveLatency } from '../../primitives/live-window';\n\n/**\n * Tolerance (seconds) around the window edges before the guard repositions, so boundary / floating-point noise doesn't\n * trigger a spurious seek.\n */\nconst REPOSITION_TOLERANCE = 0.1;\n\nexport interface SeekToLiveEdgeState {\n presentation?: MaybeResolvedPresentation;\n /**\n * One-shot start-position command in presentation-timeline seconds. Written here on entry into `live`; consumed\n * (cleared) by `applyStartPosition`.\n */\n startPosition?: number;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\nexport interface SeekToLiveEdgeContext {\n mediaElement?: HTMLMediaElement | undefined;\n}\n\nexport interface SeekToLiveEdgeConfig {\n /**\n * Resolve the target live latency (seconds the playhead should trail the live edge) for the timeline-bearing track.\n * Injected by the engine so the latency rule stays format-specific (HLS: `HOLD-BACK`, default 3× target duration;\n * DASH would read `suggestedPresentationDelay`) while this behavior stays neutral. Absent → `0` (seek straight to the\n * edge).\n */\n resolveLiveLatency?: ResolveLiveLatency;\n}\n\ntype SeekToLiveEdgeFsmState = 'inactive' | 'live';\n\n/**\n * `'live'` once the preconditions hold: a media element and a derivable live edge (whose placement is final by\n * construction — see the module docstring). `'inactive'` otherwise.\n *\n * Deliberately narrow: every signal here can flip the reactor out of and back into `live`, re-firing `entry`. Neither\n * blinks mid-source — the edge can't, because `liveWindowForType` falls back to any resolved track of the type — so\n * `entry` fires once per source without a latch, and a live reload (same source, slid window) correctly doesn't\n * re-command.\n *\n * The flip side: the presentation _url_ is not part of this state, so replacing one already-resolved live presentation\n * with another would stay in `live` and never command a start position for the new source. Unreachable today — every\n * writer sets `{ url }` (unresolved) or `undefined` first, so a source change always transits `inactive`. Supporting a\n * seeded pre-resolved presentation (via `initialState`) that later changes would mean folding the url in here.\n */\nfunction deriveState(mediaElement: HTMLMediaElement | undefined, edge: LiveEdge | null): SeekToLiveEdgeFsmState {\n return mediaElement && edge ? 'live' : 'inactive';\n}\n\nfunction seekToLiveEdgeSetup({\n state,\n context,\n config,\n}: {\n state: {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n selectedVideoTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<SeekToLiveEdgeState['selectedAudioTrackId']>;\n };\n context: {\n mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']>;\n };\n config?: SeekToLiveEdgeConfig;\n}): Reactor<SeekToLiveEdgeFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), getLiveEdge({ state, config })));\n\n return createMachineReactor<SeekToLiveEdgeFsmState>({\n initial: 'inactive',\n monitor: () => derivedStateSignal.get(),\n states: {\n inactive: {},\n\n live: {\n // One-time-per-source start position so the loader dispatches an in-window\n // range and preload shows the right frame. The window-exit guard below is\n // the separate, continuous rule. Live-specific — a future DVR mode skips\n // even this first command (starts in place).\n entry: () => {\n // The monitor guarantees a media element + live edge while in `live`.\n const mediaElement = context.mediaElement.get()!;\n const { liveEdgeStart } = getLiveEdge({ state, config })!;\n // Never command backwards: seeding `state.currentTime` behind a playhead\n // already at/past the edge would drag the loaders back.\n if (mediaElement.currentTime >= liveEdgeStart) return;\n\n state.startPosition.set(liveEdgeStart);\n },\n\n // Window-exit guard. Repositions to the live edge when the playhead has\n // fallen behind the window start, while playing. Two triggers: this\n // effect's window-update re-fire (it reads the live edge, so each reload\n // re-runs it — catches a stall, where `timeupdate` is silent) and a\n // `play` listener for immediate reactivity on resume.\n effects: () => {\n // Read the live edge first — the only tracked dependency — so window\n // slides keep re-firing this effect even while paused. Bailing before\n // it (on paused) would drop the dependency and the effect would go\n // inert, missing the next slide. The monitor guarantees the edge while\n // in `live`; the media element is read untracked (the window update,\n // not element identity, is the re-fire trigger).\n const { start: windowStart, liveEdgeStart } = getLiveEdge({ state, config })!;\n const mediaElement = peek(context.mediaElement)!;\n const reposition = () => {\n // Don't yank a paused viewer. We deliberately DON'T bail on\n // `mediaElement.seeking`: a seek whose target sits behind the window\n // start can never complete (the data has slid out of the window and\n // been evicted), so `seeking` would stay true forever and strand the\n // playhead — the very stall this guard exists to rescue. The\n // `currentTime < windowStart` test is itself the precise\n // discriminator: an in-window DVR scrub-back lands at\n // `currentTime >= windowStart` and never trips it, so only a stuck\n // out-of-window seek is repositioned.\n if (mediaElement.paused) return;\n\n if (mediaElement.currentTime < windowStart - REPOSITION_TOLERANCE) {\n mediaElement.currentTime = liveEdgeStart;\n }\n };\n\n reposition();\n // `play` (not `playing`): a slid-past playhead is at an unseekable\n // position where `playing` would stall and never fire; `play` fires on\n // unpause regardless, so we snap before the stall.\n return listen(mediaElement, 'play', reposition);\n },\n },\n },\n });\n}\n\n/**\n * Manual `Behavior<>` literal (like `calculatePresentationDuration`): declares only `presentation` + `startPosition` in\n * stateKeys while reading `selectedVideoTrackId` / `selectedAudioTrackId` defensively (contributed by the switch*\n * behaviors), so it composes without a stateKeys/type conflict.\n */\nexport const seekToLiveEdge: Behavior<\n {\n presentation: ReadonlySignal<SeekToLiveEdgeState['presentation']>;\n startPosition: Signal<SeekToLiveEdgeState['startPosition']>;\n },\n { mediaElement: ReadonlySignal<SeekToLiveEdgeContext['mediaElement']> },\n SeekToLiveEdgeConfig\n> = {\n stateKeys: ['presentation', 'startPosition'],\n contextKeys: ['mediaElement'],\n setup: seekToLiveEdgeSetup,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,MAAM,uBAAuB;;;;;;;;;;;;;;;AA2C7B,SAAS,YAAY,cAA4C,MAA+C;CAC9G,OAAO,gBAAgB,OAAO,SAAS;AACzC;AAEA,SAAS,oBAAoB,EAC3B,OACA,SACA,UAY+D;CAC/D,MAAM,qBAAqB,eAAe,YAAY,QAAQ,aAAa,IAAI,GAAG,YAAY;EAAE;EAAO;CAAO,CAAC,CAAC,CAAC;CAEjH,OAAO,qBAA6C;EAClD,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,UAAU,CAAC;GAEX,MAAM;IAKJ,aAAa;KAEX,MAAM,eAAe,QAAQ,aAAa,IAAI;KAC9C,MAAM,EAAE,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAGvD,IAAI,aAAa,eAAe,eAAe;KAE/C,MAAM,cAAc,IAAI,aAAa;IACvC;IAOA,eAAe;KAOb,MAAM,EAAE,OAAO,aAAa,kBAAkB,YAAY;MAAE;MAAO;KAAO,CAAC;KAC3E,MAAM,eAAe,KAAK,QAAQ,YAAY;KAC9C,MAAM,mBAAmB;MAUvB,IAAI,aAAa,QAAQ;MAEzB,IAAI,aAAa,cAAc,cAAc,sBAC3C,aAAa,cAAc;KAE/B;KAEA,WAAW;KAIX,OAAO,OAAO,cAAc,QAAQ,UAAU;IAChD;GACF;EACF;CACF,CAAC;AACH;;;;;;AAOA,MAAa,iBAOT;CACF,WAAW,CAAC,gBAAgB,eAAe;CAC3C,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT"}
@@ -12,74 +12,55 @@ import { failoverFetch } from "../../primitives/failover-fetch.js";
12
12
  import { listen } from "@videojs/utils/dom";
13
13
  //#region src/playback/behaviors/dom/setup-buffer-actors.ts
14
14
  /**
15
- * **Per-type buffer + segment-loader actor setup.** Per available track
16
- * type (video / audio), when `mediaSource` is attached and the selected
17
- * track of that type is present in the presentation with codecs (partial
18
- * resolution from the multivariant playlist is enough codecs live on
19
- * the `EXT-X-STREAM-INF` line, not in the per-type media playlist),
20
- * creates a `SourceBuffer`, a `SourceBufferActor`, and a
21
- * `SegmentLoaderActor` bound to that buffer-actor; publishes the per-type
22
- * actor slots. On `mediaSource` detach or behavior destroy, destroys both
23
- * actors in reverse order and clears the per-type slots so the next
24
- * source starts fresh.
15
+ * **Per-type buffer + segment-loader actor setup.** Per available track type (video / audio), when `mediaSource` is
16
+ * attached and the selected track of that type is present in the presentation with codecs (partial resolution from the
17
+ * multivariant playlist is enough codecs live on the `EXT-X-STREAM-INF` line, not in the per-type media playlist),
18
+ * creates a `SourceBuffer`, a `SourceBufferActor`, and a `SegmentLoaderActor` bound to that buffer-actor; publishes the
19
+ * per-type actor slots. On `mediaSource` detach or behavior destroy, destroys both actors in reverse order and clears
20
+ * the per-type slots so the next source starts fresh.
25
21
  *
26
- * Each per-type variant (`setupVideoBufferActors` /
27
- * `setupAudioBufferActors`) is a single-positive-state reactor
28
- * (`'preconditions-unmet'` `'buffer-ready'`) gating only on its own
29
- * type. No cross-type coupling in `stateKeys` —
30
- * `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus
31
- * `bandwidthState`, written by its trackedFetch), and audio mirrors.
22
+ * Each per-type variant (`setupVideoBufferActors` / `setupAudioBufferActors`) is a single-positive-state reactor
23
+ * (`'preconditions-unmet'` ↔ `'buffer-ready'`) gating only on its own type. No cross-type coupling in `stateKeys` —
24
+ * `setupVideoBufferActors` carries only `selectedVideoTrackId` (plus `bandwidthState`, written by its trackedFetch),
25
+ * and audio mirrors.
32
26
  *
33
27
  * # Firefox `mozHasAudio` invariant
34
28
  *
35
- * Appending to a video `SourceBuffer` before the audio `SourceBuffer`
36
- * exists causes `mozHasAudio` to be permanently false in Firefox. With
37
- * the two per-type variants decoupled, the invariant is no longer
38
- * structural to a single `entry` body (as it was when both buffers were
39
- * created in one synchronous block inside a merged behavior). It's now
40
- * preserved by a chain of assumptions about how this behavior composes
41
- * with its upstream and downstream siblings:
29
+ * Appending to a video `SourceBuffer` before the audio `SourceBuffer` exists causes `mozHasAudio` to be permanently
30
+ * false in Firefox. With the two per-type variants decoupled, the invariant is no longer structural to a single `entry`
31
+ * body (as it was when both buffers were created in one synchronous block inside a merged behavior). It's now preserved
32
+ * by a chain of assumptions about how this behavior composes with its upstream and downstream siblings:
42
33
  *
43
- * 1. **Upstream — default selections land in one `runPending`.**
44
- * `selectAudioTrack` (default audio) and `switchVideoTrack`
45
- * (default video) both subscribe to `state.presentation` flipping to
46
- * resolved; their effects run in the same `runPending` iteration and
47
- * write `selectedAudioTrackId` + `selectedVideoTrackId` within it.
48
- * 2. **Self both per-type monitors flip in one `runPending`.**
49
- * After (1), both monitors re-evaluate and flip to `'buffer-ready'`
50
- * in the next `runPending`. Both `entry` bodies run synchronously
51
- * within that iteration — both `addSourceBuffer` calls land before
52
- * the iteration ends.
53
- * 3. **Downstream — `appendBuffer` is async.** `loadVideoSegments` /
54
- * `loadAudioSegments` read the per-type `xSegmentLoaderActor` slots;
55
- * their effects fire in the *next* `runPending` and the actual
56
- * `appendBuffer` requires a network round-trip via the
57
- * `SegmentLoaderActor` — many microtasks past both `addSourceBuffer`
58
- * calls.
34
+ * 1. **Upstream — default selections land in one `runPending`.** `selectAudioTrack` (default audio) and `switchVideoTrack`
35
+ * (default video) both subscribe to `state.presentation` flipping to resolved; their effects run in the same
36
+ * `runPending` iteration and write `selectedAudioTrackId` + `selectedVideoTrackId` within it.
37
+ * 2. **Self both per-type monitors flip in one `runPending`.** After (1), both monitors re-evaluate and flip to
38
+ * `'buffer-ready'` in the next `runPending`. Both `entry` bodies run synchronously within that iteration — both
39
+ * `addSourceBuffer` calls land before the iteration ends.
40
+ * 3. **Downstream `appendBuffer` is async.** `loadVideoSegments` / `loadAudioSegments` read the per-type
41
+ * `xSegmentLoaderActor` slots; their effects fire in the _next_ `runPending` and the actual `appendBuffer` requires
42
+ * a network round-trip via the `SegmentLoaderActor` many microtasks past both `addSourceBuffer` calls.
59
43
  *
60
- * The cross-tick failure mode — a user-initiated audio track switch
61
- * *after* video segments have begun appending is out of scope for
62
- * this behavior and would be addressed in the buffer/segment-loading
63
- * path via `changeType`-aware logic.
44
+ * The cross-tick failure mode — a user-initiated audio track switch _after_ video segments have begun appending — is
45
+ * out of scope for this behavior and would be addressed in the buffer/segment-loading path via `changeType`-aware
46
+ * logic.
64
47
  *
65
48
  * # Sole writer
66
49
  *
67
- * `setupVideoBufferActors` is sole writer of `videoBufferActor` +
68
- * `videoSegmentLoaderActor` (and `bandwidthState` via its
69
- * trackedFetch); `setupAudioBufferActors` is sole writer of
70
- * `audioBufferActor` + `audioSegmentLoaderActor`. Both read
71
- * `mediaSource` from `setupMediaSource`. Downstream MSE behaviors
72
- * (`loadVideoSegments`, `loadAudioSegments`, `endOfStream`,
73
- * `updateMediaSourceDuration`) only read these slots.
50
+ * `setupVideoBufferActors` is sole writer of `videoBufferActor` + `videoSegmentLoaderActor` (and `bandwidthState` via
51
+ * its trackedFetch); `setupAudioBufferActors` is sole writer of `audioBufferActor` + `audioSegmentLoaderActor`. Both
52
+ * read `mediaSource` from `setupMediaSource`. Downstream MSE behaviors (`loadVideoSegments`, `loadAudioSegments`,
53
+ * `endOfStream`, `updateMediaSourceDuration`) only read these slots.
74
54
  */
75
55
  function setupBufferActors({ state, context, config }) {
76
56
  const { type, selectedKey, actorKey, loaderKey, fetch, forwardBuffer, backBuffer, messagePipelines } = config;
77
57
  const derivedStateSignal = computed(() => {
78
58
  if (!context.mediaSource.get()) return "preconditions-unmet";
79
- return hasCodecs(getSelectedTrack({
59
+ const selection = {
80
60
  presentation: state.presentation.get(),
81
61
  [selectedKey]: state[selectedKey].get()
82
- }, type)) ? "buffer-ready" : "preconditions-unmet";
62
+ };
63
+ return hasCodecs(getSelectedTrack(selection, type)) ? "buffer-ready" : "preconditions-unmet";
83
64
  });
84
65
  return createMachineReactor({
85
66
  initial: "preconditions-unmet",
@@ -88,10 +69,13 @@ function setupBufferActors({ state, context, config }) {
88
69
  "preconditions-unmet": {},
89
70
  "buffer-ready": { entry: () => {
90
71
  const mediaSource = context.mediaSource.get();
91
- const bufferActor = createSourceBufferActor(createSourceBuffer(mediaSource, buildMimeCodec(getSelectedTrack({
72
+ const selection = {
92
73
  presentation: state.presentation.get(),
93
74
  [selectedKey]: state[selectedKey].get()
94
- }, type))));
75
+ };
76
+ const track = getSelectedTrack(selection, type);
77
+ const buffer = createSourceBuffer(mediaSource, buildMimeCodec(track));
78
+ const bufferActor = createSourceBufferActor(buffer);
95
79
  const segmentLoader = createSegmentLoaderActor(bufferActor, fetch, {
96
80
  forwardBuffer,
97
81
  backBuffer,
@@ -118,11 +102,9 @@ function setupBufferActors({ state, context, config }) {
118
102
  });
119
103
  }
120
104
  /**
121
- * Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires
122
- * when `mediaSource` is attached and the selected video track is
123
- * present in the presentation with codecs. Gates only on video state
124
- * no cross-type coupling. Owns a bandwidth-sampling `trackedFetch` and
125
- * is sole writer of `state.bandwidthState`.
105
+ * Set up the video `SourceBufferActor` + `SegmentLoaderActor`. Fires when `mediaSource` is attached and the selected
106
+ * video track is present in the presentation with codecs. Gates only on video state — no cross-type coupling. Owns a
107
+ * bandwidth-sampling `trackedFetch` and is sole writer of `state.bandwidthState`.
126
108
  */
127
109
  const setupVideoBufferActors = defineBehavior({
128
110
  stateKeys: [
@@ -159,22 +141,16 @@ const setupVideoBufferActors = defineBehavior({
159
141
  }
160
142
  });
161
143
  /**
162
- * Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same
163
- * shape as `setupVideoBufferActors`, narrowed to audio. Today supplies
164
- * a non-sampling `fetchStream` (no audio ABR); adding audio ABR is a
165
- * localized change to this setup body (swap `fetchStream` for a
166
- * `createTrackedFetch` call + declare `bandwidthState` writable here)
167
- * without touching the shared helper. See
168
- * `internal/design/spf/features/audio-abr.md` for the design surface
169
- * (bandwidth-state sharing, multi-writer coordination, EWMA mixed-
170
- * source sampling).
144
+ * Set up the audio `SourceBufferActor` + `SegmentLoaderActor`. Same shape as `setupVideoBufferActors`, narrowed to
145
+ * audio. Today supplies a non-sampling `fetchStream` (no audio ABR); adding audio ABR is a localized change to this
146
+ * setup body (swap `fetchStream` for a `createTrackedFetch` call + declare `bandwidthState` writable here) without
147
+ * touching the shared helper. See `internal/design/spf/features/audio-abr.md` for the design surface (bandwidth-state
148
+ * sharing, multi-writer coordination, EWMA mixed- source sampling).
171
149
  *
172
- * **Mid-stream audio track switching is NOT this behavior's concern.**
173
- * Slot writes to `selectedAudioTrackId` (default selection, programmatic
174
- * filter-driven, future ABR) are owned by `switchAudioTrack` / future
175
- * `switchAudioQuality` in `track-switching.ts`. Flush orchestration on
176
- * track change is dispatched from there via `audioBufferActor.send(...)`
177
- * — keeping this setup behavior focused on per-source actor lifecycle.
150
+ * **Mid-stream audio track switching is NOT this behavior's concern.** Slot writes to `selectedAudioTrackId` (default
151
+ * selection, programmatic filter-driven, future ABR) are owned by `switchAudioTrack` / future `switchAudioQuality` in
152
+ * `track-switching.ts`. Flush orchestration on track change is dispatched from there via `audioBufferActor.send(...)`
153
+ * keeping this setup behavior focused on per-source actor lifecycle.
178
154
  */
179
155
  const setupAudioBufferActors = defineBehavior({
180
156
  stateKeys: ["presentation", "selectedAudioTrackId"],