@videojs/spf 10.0.0-beta.24 → 10.0.0-beta.26

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 (375) hide show
  1. package/README.md +5 -0
  2. package/dist/default/background-video.js +3 -0
  3. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  5. package/dist/default/core/composition/create-composition.js +2 -2
  6. package/dist/default/core/composition/create-composition.js.map +1 -1
  7. package/dist/default/core/composition/share-signals.js +13 -11
  8. package/dist/default/core/composition/share-signals.js.map +1 -1
  9. package/dist/default/core/machine.js.map +1 -1
  10. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  11. package/dist/default/core/signals/effect.js.map +1 -1
  12. package/dist/default/core/signals/primitives.js.map +1 -1
  13. package/dist/default/core/tasks/task.js.map +1 -1
  14. package/dist/default/hls.js +4 -1
  15. package/dist/default/media/abr/quality-selection.js +6 -68
  16. package/dist/default/media/abr/quality-selection.js.map +1 -1
  17. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  18. package/dist/default/media/buffer/forward-buffer.js +37 -3
  19. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  20. package/dist/default/media/dom/capabilities.js +70 -0
  21. package/dist/default/media/dom/capabilities.js.map +1 -0
  22. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  23. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  24. package/dist/default/media/dom/mse/duration.js +27 -10
  25. package/dist/default/media/dom/mse/duration.js.map +1 -1
  26. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  27. package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
  28. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  29. package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
  30. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  31. package/dist/default/media/dom/text/text-track-slots.js +3 -3
  32. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  33. package/dist/default/media/hls/parse-attributes.js +17 -3
  34. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  35. package/dist/default/media/hls/parse-media-playlist.js +29 -1
  36. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  37. package/dist/default/media/hls/parse-multivariant.js +15 -6
  38. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  39. package/dist/default/media/hls/resolve-url.js.map +1 -1
  40. package/dist/default/media/media-tracks/media-tracks.js +101 -0
  41. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  42. package/dist/default/media/mp4/box.js +58 -0
  43. package/dist/default/media/mp4/box.js.map +1 -0
  44. package/dist/default/media/mp4/timestamp-origin.js +107 -0
  45. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  46. package/dist/default/media/primitives/select-tracks.js +98 -13
  47. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  48. package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
  49. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  50. package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
  51. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  52. package/dist/default/media/types/index.js.map +1 -1
  53. package/dist/default/media/utils/cdn.js +56 -0
  54. package/dist/default/media/utils/cdn.js.map +1 -0
  55. package/dist/default/media/utils/preload.js.map +1 -1
  56. package/dist/default/media/utils/track-selection.js +1 -1
  57. package/dist/default/media/utils/track-selection.js.map +1 -1
  58. package/dist/default/media/utils/tracks.js +31 -4
  59. package/dist/default/media/utils/tracks.js.map +1 -1
  60. package/dist/default/media-tracks.js +2 -0
  61. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  62. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  63. package/dist/default/network/ewma.js +1 -1
  64. package/dist/default/network/ewma.js.map +1 -1
  65. package/dist/default/network/fetch.js +7 -1
  66. package/dist/default/network/fetch.js.map +1 -1
  67. package/dist/default/playback/actors/dom/segment-loader.js +103 -106
  68. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  69. package/dist/default/playback/actors/dom/source-buffer.js +3 -1
  70. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  71. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  72. package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
  73. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  74. package/dist/default/playback/behaviors/calculate-presentation-duration.js +21 -43
  75. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  76. package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
  77. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
  78. package/dist/default/playback/behaviors/dom/airplay.js +211 -0
  79. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  80. package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
  81. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  82. package/dist/default/playback/behaviors/dom/end-of-stream.js +25 -102
  83. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  84. package/dist/default/playback/behaviors/dom/load-segments.js +6 -2
  85. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  86. package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
  87. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  88. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +50 -18
  89. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  90. package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
  91. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  92. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  93. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  94. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +43 -18
  95. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  96. package/dist/default/playback/behaviors/dom/track-current-time.js +6 -0
  97. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  98. package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
  99. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  100. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  101. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +3 -2
  102. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  103. package/dist/default/playback/behaviors/establish-start-media-time.js +123 -0
  104. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  105. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  106. package/dist/default/playback/behaviors/resolve-track.js +45 -22
  107. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  108. package/dist/default/playback/behaviors/select-tracks.js +46 -77
  109. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  110. package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
  111. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
  112. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  113. package/dist/default/playback/behaviors/track-switching.js +403 -0
  114. package/dist/default/playback/behaviors/track-switching.js.map +1 -0
  115. package/dist/default/playback/engines/background-video/adapter.js +158 -0
  116. package/dist/default/playback/engines/background-video/adapter.js.map +1 -0
  117. package/dist/default/playback/engines/background-video/engine.js +75 -0
  118. package/dist/default/playback/engines/background-video/engine.js.map +1 -0
  119. package/dist/default/playback/engines/hls/adapter-audio-only.js +124 -0
  120. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -0
  121. package/dist/default/playback/engines/hls/adapter.js +20 -11
  122. package/dist/default/playback/engines/hls/adapter.js.map +1 -1
  123. package/dist/default/playback/engines/hls/engine-audio-only.js +94 -0
  124. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
  125. package/dist/default/playback/engines/hls/engine.js +38 -8
  126. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  127. package/dist/default/playback/primitives/failover-fetch.js +42 -0
  128. package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
  129. package/dist/default/playback/primitives/head-peek.js +44 -0
  130. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  131. package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
  132. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  133. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  134. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  135. package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
  136. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  137. package/dist/{dev/playback/behaviors → default/playback/primitives}/track-types.js +1 -1
  138. package/dist/default/playback/primitives/track-types.js.map +1 -0
  139. package/dist/dev/background-video.d.ts +3 -0
  140. package/dist/dev/background-video.js +3 -0
  141. package/dist/dev/core/actors/actor.d.ts +0 -1
  142. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  143. package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
  144. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  145. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  146. package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
  147. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  148. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  149. package/dist/dev/core/composition/create-composition.d.ts +10 -11
  150. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  151. package/dist/dev/core/composition/create-composition.js +2 -2
  152. package/dist/dev/core/composition/create-composition.js.map +1 -1
  153. package/dist/dev/core/composition/share-signals.d.ts +11 -10
  154. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  155. package/dist/dev/core/composition/share-signals.js +13 -11
  156. package/dist/dev/core/composition/share-signals.js.map +1 -1
  157. package/dist/dev/core/machine.d.ts +1 -0
  158. package/dist/dev/core/machine.d.ts.map +1 -1
  159. package/dist/dev/core/machine.js.map +1 -1
  160. package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
  161. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  162. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  163. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  164. package/dist/dev/core/signals/effect.js.map +1 -1
  165. package/dist/dev/core/signals/primitives.d.ts +1 -2
  166. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  167. package/dist/dev/core/signals/primitives.js.map +1 -1
  168. package/dist/dev/core/tasks/task.d.ts +2 -2
  169. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  170. package/dist/dev/core/tasks/task.js.map +1 -1
  171. package/dist/dev/hls.d.ts +5 -1
  172. package/dist/dev/hls.js +4 -1
  173. package/dist/dev/index.d.ts +0 -1
  174. package/dist/dev/index.d.ts.map +1 -1
  175. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  176. package/dist/dev/media/abr/quality-selection.js +6 -68
  177. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  178. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  179. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  180. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  181. package/dist/dev/media/buffer/forward-buffer.js +37 -3
  182. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  183. package/dist/dev/media/dom/capabilities.js +70 -0
  184. package/dist/dev/media/dom/capabilities.js.map +1 -0
  185. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
  186. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  187. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  188. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
  189. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  190. package/dist/dev/media/dom/mse/duration.js +27 -10
  191. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  192. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  193. package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
  194. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  195. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
  196. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  197. package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
  198. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  199. package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
  200. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  201. package/dist/dev/media/dom/text/text-track-slots.js +3 -3
  202. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  203. package/dist/dev/media/hls/parse-attributes.js +17 -3
  204. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  205. package/dist/dev/media/hls/parse-media-playlist.js +29 -1
  206. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  207. package/dist/dev/media/hls/parse-multivariant.js +15 -6
  208. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  209. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  210. package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
  211. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  212. package/dist/dev/media/media-tracks/media-tracks.js +101 -0
  213. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  214. package/dist/dev/media/mp4/box.js +58 -0
  215. package/dist/dev/media/mp4/box.js.map +1 -0
  216. package/dist/dev/media/mp4/timestamp-origin.js +107 -0
  217. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  218. package/dist/dev/media/primitives/select-tracks.d.ts +35 -0
  219. package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -0
  220. package/dist/dev/media/primitives/select-tracks.js +98 -13
  221. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  222. package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
  223. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  224. package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
  225. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  226. package/dist/dev/media/types/index.d.ts +69 -2
  227. package/dist/dev/media/types/index.d.ts.map +1 -1
  228. package/dist/dev/media/types/index.js.map +1 -1
  229. package/dist/dev/media/utils/cdn.d.ts +13 -0
  230. package/dist/dev/media/utils/cdn.d.ts.map +1 -0
  231. package/dist/dev/media/utils/cdn.js +56 -0
  232. package/dist/dev/media/utils/cdn.js.map +1 -0
  233. package/dist/dev/media/utils/preload.js.map +1 -1
  234. package/dist/dev/media/utils/track-selection.js +1 -1
  235. package/dist/dev/media/utils/track-selection.js.map +1 -1
  236. package/dist/dev/media/utils/tracks.js +31 -4
  237. package/dist/dev/media/utils/tracks.js.map +1 -1
  238. package/dist/dev/media-tracks.d.ts +3 -0
  239. package/dist/dev/media-tracks.js +2 -0
  240. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  241. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  242. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  243. package/dist/dev/network/ewma.js +1 -1
  244. package/dist/dev/network/ewma.js.map +1 -1
  245. package/dist/dev/network/fetch.js +7 -1
  246. package/dist/dev/network/fetch.js.map +1 -1
  247. package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
  248. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  249. package/dist/dev/playback/actors/dom/segment-loader.js +103 -106
  250. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  251. package/dist/dev/playback/actors/dom/source-buffer.d.ts +10 -25
  252. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  253. package/dist/dev/playback/actors/dom/source-buffer.js +3 -1
  254. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  255. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
  256. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  257. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
  258. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  259. package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
  260. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  261. package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
  262. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  263. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +11 -2
  264. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  265. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +21 -43
  266. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  267. package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
  268. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
  269. package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
  270. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  271. package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
  272. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  273. package/dist/dev/playback/behaviors/dom/end-of-stream.js +25 -102
  274. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  275. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
  276. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  277. package/dist/dev/playback/behaviors/dom/load-segments.js +6 -2
  278. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  279. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
  280. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  281. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +50 -18
  282. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  283. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
  284. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  285. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
  286. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  287. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  288. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  289. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +43 -18
  290. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  291. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
  292. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  293. package/dist/dev/playback/behaviors/dom/track-current-time.js +6 -0
  294. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  295. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
  296. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  297. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
  298. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  299. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
  300. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  301. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  302. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +3 -2
  303. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  304. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +31 -0
  305. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  306. package/dist/dev/playback/behaviors/establish-start-media-time.js +123 -0
  307. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  308. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
  309. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  310. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  311. package/dist/dev/playback/behaviors/resolve-track.js +45 -22
  312. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  313. package/dist/dev/playback/behaviors/select-tracks.d.ts +14 -0
  314. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
  315. package/dist/dev/playback/behaviors/select-tracks.js +46 -77
  316. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  317. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +14 -0
  318. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
  319. package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
  320. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
  321. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  322. package/dist/dev/playback/behaviors/track-switching.js +403 -0
  323. package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
  324. package/dist/dev/playback/engines/background-video/adapter.d.ts +59 -0
  325. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -0
  326. package/dist/dev/playback/engines/background-video/adapter.js +158 -0
  327. package/dist/dev/playback/engines/background-video/adapter.js.map +1 -0
  328. package/dist/dev/playback/engines/background-video/engine.d.ts +106 -0
  329. package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -0
  330. package/dist/dev/playback/engines/background-video/engine.js +75 -0
  331. package/dist/dev/playback/engines/background-video/engine.js.map +1 -0
  332. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +46 -0
  333. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -0
  334. package/dist/dev/playback/engines/hls/adapter-audio-only.js +124 -0
  335. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -0
  336. package/dist/dev/playback/engines/hls/adapter.d.ts +3 -5
  337. package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -1
  338. package/dist/dev/playback/engines/hls/adapter.js +20 -11
  339. package/dist/dev/playback/engines/hls/adapter.js.map +1 -1
  340. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +156 -0
  341. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
  342. package/dist/dev/playback/engines/hls/engine-audio-only.js +94 -0
  343. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
  344. package/dist/dev/playback/engines/hls/engine.d.ts +109 -4
  345. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  346. package/dist/dev/playback/engines/hls/engine.js +38 -8
  347. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  348. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  349. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  350. package/dist/dev/playback/primitives/failover-fetch.js +42 -0
  351. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
  352. package/dist/dev/playback/primitives/head-peek.js +44 -0
  353. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  354. package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
  355. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  356. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  357. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  358. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  359. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
  360. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  361. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
  362. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  363. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
  364. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  365. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  366. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  367. package/dist/{default/playback/behaviors → dev/playback/primitives}/track-types.js +1 -1
  368. package/dist/dev/playback/primitives/track-types.js.map +1 -0
  369. package/package.json +17 -7
  370. package/dist/default/playback/behaviors/quality-switching.js +0 -96
  371. package/dist/default/playback/behaviors/quality-switching.js.map +0 -1
  372. package/dist/default/playback/behaviors/track-types.js.map +0 -1
  373. package/dist/dev/playback/behaviors/quality-switching.js +0 -96
  374. package/dist/dev/playback/behaviors/quality-switching.js.map +0 -1
  375. package/dist/dev/playback/behaviors/track-types.js.map +0 -1
@@ -1,34 +1,12 @@
1
+ import { Segment, Track } from "../../../media/types/index.js";
1
2
  import { AppendData } from "../../../media/dom/mse/append-segment.js";
2
3
  import { MessageActor } from "../../../core/actors/create-machine-actor.js";
3
- import { Segment, Track } from "../../../media/types/index.js";
4
-
4
+ import { IndividualSourceBufferMessage } from "../../primitives/source-buffer-messages.js";
5
5
  //#region src/playback/actors/dom/source-buffer.d.ts
6
6
  interface BufferedRange {
7
7
  start: number;
8
8
  end: number;
9
9
  }
10
- type AppendSegmentMeta = Pick<Segment, 'id' | 'startTime' | 'duration'> & {
11
- trackId: Track['id']; /** Declared track bandwidth in bps (from playlist BANDWIDTH attribute). */
12
- trackBandwidth?: number;
13
- };
14
- type AppendInitMessage = {
15
- type: 'append-init';
16
- data: AppendData;
17
- meta: {
18
- trackId: Track['id'];
19
- };
20
- };
21
- type AppendSegmentMessage = {
22
- type: 'append-segment';
23
- data: AppendData;
24
- meta: AppendSegmentMeta;
25
- };
26
- type RemoveMessage = {
27
- type: 'remove';
28
- start: number;
29
- end: number;
30
- };
31
- type IndividualSourceBufferMessage = AppendInitMessage | AppendSegmentMessage | RemoveMessage;
32
10
  type BatchMessage = {
33
11
  type: 'batch';
34
12
  messages: IndividualSourceBufferMessage[];
@@ -43,6 +21,13 @@ type SourceBufferActorState = 'idle' | 'updating' | 'destroyed';
43
21
  /** Non-finite (extended) data managed by the actor — the XState "context". */
44
22
  interface SourceBufferActorContext {
45
23
  initTrackId?: string | undefined;
24
+ /**
25
+ * Language of the most recently appended init segment's track (when
26
+ * present on the playlist). Used by the segment-loader's `planTasks`
27
+ * to detect cross-language switches and schedule ahead-buffer flush.
28
+ * Undefined for video and for language-less audio.
29
+ */
30
+ initTrackLanguage?: string | undefined;
46
31
  segments: Array<Pick<Segment, 'id' | 'startTime' | 'duration'> & {
47
32
  trackId: Track['id'];
48
33
  trackBandwidth?: number;
@@ -58,5 +43,5 @@ interface SourceBufferActorContext {
58
43
  /** SourceBuffer actor: queues operations, owns its snapshot. */
59
44
  type SourceBufferActor = MessageActor<SourceBufferActorState, SourceBufferActorContext, SourceBufferMessage>;
60
45
  //#endregion
61
- export { SourceBufferActor };
46
+ export { BatchMessage, BufferedRange, CancelMessage, SourceBufferActor, SourceBufferActorContext, SourceBufferActorState, SourceBufferMessage };
62
47
  //# sourceMappingURL=source-buffer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-buffer.d.ts","names":[],"sources":["../../../../../src/playback/actors/dom/source-buffer.ts"],"mappings":";;;;;UAUiB,aAAA;EACf,KAAA;EACA,GAAA;AAAA;AAAA,KAGU,iBAAA,GAAoB,IAAA,CAAK,OAAA;EACnC,OAAA,EAAS,KAAA,QAJN;EAMH,cAAA;AAAA;AAAA,KAKU,iBAAA;EAAsB,IAAA;EAAqB,IAAA,EAAM,UAAA;EAAY,IAAA;IAAQ,OAAA,EAAS,KAAA;EAAA;AAAA;AAAA,KAC9E,oBAAA;EAAyB,IAAA;EAAwB,IAAA,EAAM,UAAA;EAAY,IAAA,EAAM,iBAAA;AAAA;AAAA,KACzE,aAAA;EAAkB,IAAA;EAAgB,KAAA;EAAe,GAAA;AAAA;AAAA,KACjD,6BAAA,GAAgC,iBAAA,GAAoB,oBAAA,GAAuB,aAAA;AAAA,KAC3E,YAAA;EAAiB,IAAA;EAAe,QAAA,EAAU,6BAAA;AAAA;AAAA,KAC1C,aAAA;EAAkB,IAAA;AAAA;;KAGlB,mBAAA,GAAsB,6BAAA,GAAgC,YAAA,GAAe,aAAA;;KAGrE,sBAAA;;UAGK,wBAAA;EACf,WAAA;EACA,QAAA,EAAU,KAAA,CACR,IAAA,CAAK,OAAA;IACH,OAAA,EAAS,KAAA;IACT,cAAA;IAjBM;;;;;IAuBN,OAAA;EAAA;EAGJ,cAAA,EAAgB,aAAA;AAAA;AAzBlB;AAAA,KA6BY,iBAAA,GAAoB,YAAA,CAAa,sBAAA,EAAwB,wBAAA,EAA0B,mBAAA"}
1
+ {"version":3,"file":"source-buffer.d.ts","names":[],"sources":["../../../../../src/playback/actors/dom/source-buffer.ts"],"mappings":";;;;;UAgBiB;EACf;EACA;;KAKU;EAAiB;EAAe,UAAU;;KAC1C;EAAkB;;;KAGlB,sBAAsB,gCAAgC,eAAe;;KAGrE;;UAGK;EACf;;;;;;;EAOA;EACA,UAAU,MACR,KAAK;IACH,SAAS;IACT;;;;;;IAMA;;EAGJ,gBAAgB;;;KAIN,oBAAoB,aAAa,wBAAwB,0BAA0B"}
@@ -19,7 +19,8 @@ function appendInitTask(message, { getContext, sourceBuffer }) {
19
19
  await appendSegment(sourceBuffer, message.data);
20
20
  return {
21
21
  ...ctx,
22
- initTrackId: message.meta.trackId
22
+ initTrackId: message.meta.trackId,
23
+ initTrackLanguage: message.meta.language
23
24
  };
24
25
  });
25
26
  }
@@ -41,6 +42,7 @@ function appendSegmentTask(message, { getContext, sourceBuffer, setContext }) {
41
42
  }],
42
43
  bufferedRanges: ctx.bufferedRanges
43
44
  });
45
+ if (meta.timestampOffset != null && sourceBuffer.timestampOffset !== meta.timestampOffset) sourceBuffer.timestampOffset = meta.timestampOffset;
44
46
  await appendSegment(sourceBuffer, message.data, taskSignal);
45
47
  return {
46
48
  ...ctx,
@@ -1 +1 @@
1
- {"version":3,"file":"source-buffer.js","names":[],"sources":["../../../../../src/playback/actors/dom/source-buffer.ts"],"sourcesContent":["import { createMachineActor, type HandlerContext, type MessageActor } from '../../../core/actors/create-machine-actor';\nimport { SerialRunner, Task } from '../../../core/tasks/task';\nimport { type AppendData, appendSegment } from '../../../media/dom/mse/append-segment';\nimport { flushBuffer } from '../../../media/dom/mse/buffer-flusher';\nimport { SEGMENT_TIME_EPSILON, type Segment, type Track } from '../../../media/types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface BufferedRange {\n start: number;\n end: number;\n}\n\nexport type AppendSegmentMeta = Pick<Segment, 'id' | 'startTime' | 'duration'> & {\n trackId: Track['id'];\n /** Declared track bandwidth in bps (from playlist BANDWIDTH attribute). */\n trackBandwidth?: number;\n};\n\nexport type { AppendData };\n\nexport type AppendInitMessage = { type: 'append-init'; data: AppendData; meta: { trackId: Track['id'] } };\nexport type AppendSegmentMessage = { type: 'append-segment'; data: AppendData; meta: AppendSegmentMeta };\nexport type RemoveMessage = { type: 'remove'; start: number; end: number };\nexport type IndividualSourceBufferMessage = AppendInitMessage | AppendSegmentMessage | RemoveMessage;\nexport type BatchMessage = { type: 'batch'; messages: IndividualSourceBufferMessage[] };\nexport type CancelMessage = { type: 'cancel' };\n\n/** All messages accepted by a SourceBufferActor. */\nexport type SourceBufferMessage = IndividualSourceBufferMessage | BatchMessage | CancelMessage;\n\n/** Finite states of the actor. */\nexport type SourceBufferActorState = 'idle' | 'updating' | 'destroyed';\n\n/** Non-finite (extended) data managed by the actor — the XState \"context\". */\nexport interface SourceBufferActorContext {\n initTrackId?: string | undefined;\n segments: Array<\n Pick<Segment, 'id' | 'startTime' | 'duration'> & {\n trackId: Track['id'];\n trackBandwidth?: number;\n /**\n * True while a streaming append is in progress for this segment.\n * The segment's data is partially present in the SourceBuffer.\n * Downstream code must not treat a partial segment as fully buffered.\n */\n partial?: boolean;\n }\n >;\n bufferedRanges: BufferedRange[];\n}\n\n/** SourceBuffer actor: queues operations, owns its snapshot. */\nexport type SourceBufferActor = MessageActor<SourceBufferActorState, SourceBufferActorContext, SourceBufferMessage>;\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction snapshotBuffered(buffered: TimeRanges): BufferedRange[] {\n const ranges: BufferedRange[] = [];\n for (let i = 0; i < buffered.length; i++) {\n ranges.push({ start: buffered.start(i), end: buffered.end(i) });\n }\n return ranges;\n}\n\n// =============================================================================\n// Message task factories\n// =============================================================================\n\n// Context is read lazily via getContext at task execution time — not at creation\n// time — so each task always operates on the most recent context regardless of\n// when it was scheduled.\n\ninterface MessageTaskOptions {\n getContext: () => SourceBufferActorContext;\n sourceBuffer: SourceBuffer;\n setContext: (ctx: SourceBufferActorContext) => void;\n}\n\nfunction appendInitTask(\n message: AppendInitMessage,\n { getContext, sourceBuffer }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n await appendSegment(sourceBuffer, message.data);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n return { ...ctx, initTrackId: message.meta.trackId };\n });\n}\n\nfunction appendSegmentTask(\n message: AppendSegmentMessage,\n { getContext, sourceBuffer, setContext }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n\n const { meta } = message;\n // Remove any existing entry at the same start time (same \"slot\" in the\n // timeline), then record the new segment. Assumes time-aligned segments\n // across playlists. `SEGMENT_TIME_EPSILON` guards against floating-point\n // drift in parsed timestamps (shared with the segment-loader quality\n // filter — single source of truth).\n const filtered = ctx.segments.filter((s) => Math.abs(s.startTime - meta.startTime) >= SEGMENT_TIME_EPSILON);\n\n // For streaming data: emit partial state before the first chunk so\n // downstream code can see the in-progress segment and treat it as\n // incomplete. ArrayBuffer appends are atomic so no partial state is\n // needed — context is updated once at task completion.\n if (!(message.data instanceof ArrayBuffer)) {\n setContext({\n ...ctx,\n segments: [\n ...filtered,\n {\n id: meta.id,\n startTime: meta.startTime,\n duration: meta.duration,\n trackId: meta.trackId,\n ...(meta.trackBandwidth !== undefined && { trackBandwidth: meta.trackBandwidth }),\n partial: true,\n },\n ],\n bufferedRanges: ctx.bufferedRanges,\n });\n }\n\n await appendSegment(sourceBuffer, message.data, taskSignal);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n return {\n ...ctx,\n segments: [\n ...filtered,\n {\n id: meta.id,\n startTime: meta.startTime,\n duration: meta.duration,\n trackId: meta.trackId,\n ...(meta.trackBandwidth !== undefined && { trackBandwidth: meta.trackBandwidth }),\n },\n ],\n bufferedRanges: snapshotBuffered(sourceBuffer.buffered),\n };\n });\n}\n\nfunction removeTask(\n message: RemoveMessage,\n { getContext, sourceBuffer }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n await flushBuffer(sourceBuffer, message.start, message.end);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n //\n // Use the post-flush buffered ranges as ground truth. A segment is kept\n // in the model only if its midpoint falls within a buffered range.\n // Midpoint-based membership handles flush boundaries that don't align\n // exactly with segment edges without over-removing adjacent segments.\n const bufferedRanges = snapshotBuffered(sourceBuffer.buffered);\n const filtered = ctx.segments.filter((s) => {\n const midpoint = s.startTime + s.duration / 2;\n return bufferedRanges.some((r) => midpoint >= r.start && midpoint < r.end);\n });\n return { ...ctx, segments: filtered, bufferedRanges };\n });\n}\n\ntype MessageTaskFactory<T extends IndividualSourceBufferMessage> = (\n message: T,\n options: MessageTaskOptions\n) => Task<SourceBufferActorContext>;\n\nconst messageTaskFactories = {\n 'append-init': appendInitTask,\n 'append-segment': appendSegmentTask,\n remove: removeTask,\n} satisfies {\n [K in IndividualSourceBufferMessage['type']]: MessageTaskFactory<Extract<IndividualSourceBufferMessage, { type: K }>>;\n};\n\nfunction messageToTask(\n message: IndividualSourceBufferMessage,\n options: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n const factory = messageTaskFactories[message.type] as MessageTaskFactory<typeof message>;\n return factory(message, options);\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport function createSourceBufferActor(\n sourceBuffer: SourceBuffer,\n initialContext?: Partial<SourceBufferActorContext>\n): SourceBufferActor {\n type UserState = Exclude<SourceBufferActorState, 'destroyed'>;\n\n const handleError = (e: unknown): void => {\n if (!(e instanceof Error && e.name === 'AbortError')) {\n console.error('SourceBuffer operation failed:', e);\n }\n };\n\n type Ctx = HandlerContext<UserState, SourceBufferActorContext, () => SerialRunner>;\n\n const onMessage = (msg: IndividualSourceBufferMessage, { transition, setContext, getContext, runner }: Ctx): void => {\n transition('updating');\n const task = messageToTask(msg, { getContext, sourceBuffer, setContext });\n runner.schedule(task).then(setContext, handleError);\n };\n\n return createMachineActor<UserState, SourceBufferActorContext, SourceBufferMessage, () => SerialRunner>({\n runner: () => new SerialRunner(),\n initial: 'idle',\n context: { segments: [], bufferedRanges: [], initTrackId: undefined, ...initialContext },\n states: {\n idle: {\n on: {\n 'append-init': onMessage,\n 'append-segment': onMessage,\n remove: onMessage,\n batch: (msg, { transition, setContext, getContext, runner }) => {\n const { messages } = msg;\n if (messages.length === 0) return;\n\n transition('updating');\n messages.forEach((msg) => {\n const task = messageToTask(msg, { getContext, sourceBuffer, setContext });\n runner.schedule(task).then(setContext, handleError);\n });\n },\n },\n },\n updating: {\n // Automatically return to idle once all scheduled tasks settle.\n onSettled: 'idle',\n on: {\n // Abort all in-progress and pending tasks. onSettled handles → 'idle'\n // once the aborted tasks drain.\n cancel: (_, { runner }) => {\n runner.abortAll();\n },\n },\n },\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"source-buffer.js","names":[],"sources":["../../../../../src/playback/actors/dom/source-buffer.ts"],"sourcesContent":["import { createMachineActor, type HandlerContext, type MessageActor } from '../../../core/actors/create-machine-actor';\nimport { SerialRunner, Task } from '../../../core/tasks/task';\nimport { type AppendData, appendSegment } from '../../../media/dom/mse/append-segment';\nimport { flushBuffer } from '../../../media/dom/mse/buffer-flusher';\nimport { SEGMENT_TIME_EPSILON, type Segment, type Track } from '../../../media/types';\nimport type {\n AppendInitMessage,\n AppendSegmentMessage,\n IndividualSourceBufferMessage,\n RemoveMessage,\n} from '../../primitives/source-buffer-messages';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface BufferedRange {\n start: number;\n end: number;\n}\n\nexport type { AppendData };\n\nexport type BatchMessage = { type: 'batch'; messages: IndividualSourceBufferMessage[] };\nexport type CancelMessage = { type: 'cancel' };\n\n/** All messages accepted by a SourceBufferActor. */\nexport type SourceBufferMessage = IndividualSourceBufferMessage | BatchMessage | CancelMessage;\n\n/** Finite states of the actor. */\nexport type SourceBufferActorState = 'idle' | 'updating' | 'destroyed';\n\n/** Non-finite (extended) data managed by the actor — the XState \"context\". */\nexport interface SourceBufferActorContext {\n initTrackId?: string | undefined;\n /**\n * Language of the most recently appended init segment's track (when\n * present on the playlist). Used by the segment-loader's `planTasks`\n * to detect cross-language switches and schedule ahead-buffer flush.\n * Undefined for video and for language-less audio.\n */\n initTrackLanguage?: string | undefined;\n segments: Array<\n Pick<Segment, 'id' | 'startTime' | 'duration'> & {\n trackId: Track['id'];\n trackBandwidth?: number;\n /**\n * True while a streaming append is in progress for this segment.\n * The segment's data is partially present in the SourceBuffer.\n * Downstream code must not treat a partial segment as fully buffered.\n */\n partial?: boolean;\n }\n >;\n bufferedRanges: BufferedRange[];\n}\n\n/** SourceBuffer actor: queues operations, owns its snapshot. */\nexport type SourceBufferActor = MessageActor<SourceBufferActorState, SourceBufferActorContext, SourceBufferMessage>;\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction snapshotBuffered(buffered: TimeRanges): BufferedRange[] {\n const ranges: BufferedRange[] = [];\n for (let i = 0; i < buffered.length; i++) {\n ranges.push({ start: buffered.start(i), end: buffered.end(i) });\n }\n return ranges;\n}\n\n// =============================================================================\n// Message task factories\n// =============================================================================\n\n// Context is read lazily via getContext at task execution time — not at creation\n// time — so each task always operates on the most recent context regardless of\n// when it was scheduled.\n\ninterface MessageTaskOptions {\n getContext: () => SourceBufferActorContext;\n sourceBuffer: SourceBuffer;\n setContext: (ctx: SourceBufferActorContext) => void;\n}\n\nfunction appendInitTask(\n message: AppendInitMessage,\n { getContext, sourceBuffer }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n await appendSegment(sourceBuffer, message.data);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n return { ...ctx, initTrackId: message.meta.trackId, initTrackLanguage: message.meta.language };\n });\n}\n\nfunction appendSegmentTask(\n message: AppendSegmentMessage,\n { getContext, sourceBuffer, setContext }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n\n const { meta } = message;\n // Remove any existing entry at the same start time (same \"slot\" in the\n // timeline), then record the new segment. Assumes time-aligned segments\n // across playlists. `SEGMENT_TIME_EPSILON` guards against floating-point\n // drift in parsed timestamps (shared with the segment-loader quality\n // filter — single source of truth).\n //\n // Misaligned renditions (e.g. 30fps vs 60fps rungs) break the same-slot\n // assumption: the switched-to segment's differing startTime is kept as a\n // separate overlapping entry rather than truncating the older one to the\n // portion MSE didn't overwrite. Cosmetic today (see #1865 for the\n // most-recent-append-wins model); coverage/flush stay correct.\n const filtered = ctx.segments.filter((s) => Math.abs(s.startTime - meta.startTime) >= SEGMENT_TIME_EPSILON);\n\n // For streaming data: emit partial state before the first chunk so\n // downstream code can see the in-progress segment and treat it as\n // incomplete. ArrayBuffer appends are atomic so no partial state is\n // needed — context is updated once at task completion.\n if (!(message.data instanceof ArrayBuffer)) {\n setContext({\n ...ctx,\n segments: [\n ...filtered,\n {\n id: meta.id,\n startTime: meta.startTime,\n duration: meta.duration,\n trackId: meta.trackId,\n ...(meta.trackBandwidth !== undefined && { trackBandwidth: meta.trackBandwidth }),\n partial: true,\n },\n ],\n bufferedRanges: ctx.bufferedRanges,\n });\n }\n\n // Relocation: set the offset before the coded frames are appended. The\n // SerialRunner guarantees the buffer is idle here, so the assignment is safe.\n // Guarded so re-stamping the (constant) offset on later appends is a no-op.\n if (meta.timestampOffset != null && sourceBuffer.timestampOffset !== meta.timestampOffset) {\n sourceBuffer.timestampOffset = meta.timestampOffset;\n }\n await appendSegment(sourceBuffer, message.data, taskSignal);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n return {\n ...ctx,\n segments: [\n ...filtered,\n {\n id: meta.id,\n startTime: meta.startTime,\n duration: meta.duration,\n trackId: meta.trackId,\n ...(meta.trackBandwidth !== undefined && { trackBandwidth: meta.trackBandwidth }),\n },\n ],\n bufferedRanges: snapshotBuffered(sourceBuffer.buffered),\n };\n });\n}\n\nfunction removeTask(\n message: RemoveMessage,\n { getContext, sourceBuffer }: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n return new Task(async (taskSignal) => {\n const ctx = getContext();\n if (taskSignal.aborted) return ctx;\n await flushBuffer(sourceBuffer, message.start, message.end);\n // No abort check here: the physical SourceBuffer has been modified, so\n // the model must be updated to match regardless of signal state.\n //\n // Use the post-flush buffered ranges as ground truth. A segment is kept\n // in the model only if its midpoint falls within a buffered range.\n // Midpoint-based membership handles flush boundaries that don't align\n // exactly with segment edges without over-removing adjacent segments.\n const bufferedRanges = snapshotBuffered(sourceBuffer.buffered);\n const filtered = ctx.segments.filter((s) => {\n const midpoint = s.startTime + s.duration / 2;\n return bufferedRanges.some((r) => midpoint >= r.start && midpoint < r.end);\n });\n return { ...ctx, segments: filtered, bufferedRanges };\n });\n}\n\ntype MessageTaskFactory<T extends IndividualSourceBufferMessage> = (\n message: T,\n options: MessageTaskOptions\n) => Task<SourceBufferActorContext>;\n\nconst messageTaskFactories = {\n 'append-init': appendInitTask,\n 'append-segment': appendSegmentTask,\n remove: removeTask,\n} satisfies {\n [K in IndividualSourceBufferMessage['type']]: MessageTaskFactory<Extract<IndividualSourceBufferMessage, { type: K }>>;\n};\n\nfunction messageToTask(\n message: IndividualSourceBufferMessage,\n options: MessageTaskOptions\n): Task<SourceBufferActorContext> {\n const factory = messageTaskFactories[message.type] as MessageTaskFactory<typeof message>;\n return factory(message, options);\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\nexport function createSourceBufferActor(\n sourceBuffer: SourceBuffer,\n initialContext?: Partial<SourceBufferActorContext>\n): SourceBufferActor {\n type UserState = Exclude<SourceBufferActorState, 'destroyed'>;\n\n const handleError = (e: unknown): void => {\n if (!(e instanceof Error && e.name === 'AbortError')) {\n console.error('SourceBuffer operation failed:', e);\n }\n };\n\n type Ctx = HandlerContext<UserState, SourceBufferActorContext, () => SerialRunner>;\n\n const onMessage = (msg: IndividualSourceBufferMessage, { transition, setContext, getContext, runner }: Ctx): void => {\n transition('updating');\n const task = messageToTask(msg, { getContext, sourceBuffer, setContext });\n runner.schedule(task).then(setContext, handleError);\n };\n\n return createMachineActor<UserState, SourceBufferActorContext, SourceBufferMessage, () => SerialRunner>({\n runner: () => new SerialRunner(),\n initial: 'idle',\n context: { segments: [], bufferedRanges: [], initTrackId: undefined, ...initialContext },\n states: {\n idle: {\n on: {\n 'append-init': onMessage,\n 'append-segment': onMessage,\n remove: onMessage,\n batch: (msg, { transition, setContext, getContext, runner }) => {\n const { messages } = msg;\n if (messages.length === 0) return;\n\n transition('updating');\n messages.forEach((msg) => {\n const task = messageToTask(msg, { getContext, sourceBuffer, setContext });\n runner.schedule(task).then(setContext, handleError);\n });\n },\n },\n },\n updating: {\n // Automatically return to idle once all scheduled tasks settle.\n onSettled: 'idle',\n on: {\n // Abort all in-progress and pending tasks. onSettled handles → 'idle'\n // once the aborted tasks drain.\n cancel: (_, { runner }) => {\n runner.abortAll();\n },\n },\n },\n },\n });\n}\n"],"mappings":"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"}
@@ -1 +1,3 @@
1
- import { AddCuesMessage, ClearMessage, CueSegmentMeta, TextTracksActor, TextTracksActorContext, TextTracksActorMessage } from "../text-tracks.js";
1
+ import { AddCuesMessage, CueSegmentMeta } from "../../primitives/text-track-messages.js";
2
+ import { ClearMessage, TextTracksActor, TextTracksActorContext, TextTracksActorMessage } from "../text-tracks.js";
3
+ export type { TextTracksActor };
@@ -1 +1 @@
1
- {"version":3,"file":"text-tracks.js","names":[],"sources":["../../../../../src/playback/actors/dom/text-tracks.ts"],"sourcesContent":["import { createTransitionActor } from '../../../core/actors/create-transition-actor';\nimport type { Cue } from '../../../media/types';\nimport type {\n AddCuesMessage,\n ClearMessage,\n CueSegmentMeta,\n TextTracksActor,\n TextTracksActorContext,\n TextTracksActorMessage,\n} from '../text-tracks';\n\n// Re-export the host-agnostic types so existing dom-side consumers can keep\n// importing from this module.\nexport type {\n AddCuesMessage,\n ClearMessage,\n CueSegmentMeta,\n TextTracksActor,\n TextTracksActorContext,\n TextTracksActorMessage,\n};\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction isDuplicateCue(cue: VTTCue, existing: Cue[]): boolean {\n return existing.some((r) => r.startTime === cue.startTime && r.endTime === cue.endTime && r.text === cue.text);\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** TextTrack actor: wraps all text tracks on a media element, owns cue operations. */\nexport function createTextTracksActor(mediaElement: HTMLMediaElement): TextTracksActor<VTTCue> {\n return createTransitionActor({ loaded: {}, segments: {} } as TextTracksActorContext, (context, message) => {\n if (message.type === 'clear') {\n // Reset the cue + segment cache. DOM cleanup is the caller's job —\n // by the time we get here, `syncTextTracks` has already removed\n // the `<track>` children (and their cues) via\n // `removeAllSubtitlesTracksFromMedia`.\n return { loaded: {}, segments: {} };\n }\n\n // NOTE: Currently assumes cues are applied to a non-disabled TextTrack. Discuss different approaches here, including:\n // - Making the message responsible for auto-selection of the textTrack (changes logic in sync-text-tracks)\n // - Silent gating/console warning + early bail\n // - throwing a domain-specific error\n // - accepting as is (which would result in errors, but also \"shouldn't ever happen\" unless a bug is introduced)\n // (CJP)\n const { meta, cues } = message;\n const { trackId, id: segmentId, startTime, duration } = meta;\n const textTrack = Array.from(mediaElement.textTracks).find((t) => t.id === trackId);\n if (!textTrack) return context;\n\n const existingCues = context.loaded[trackId] ?? [];\n const existingSegments = context.segments[trackId] ?? [];\n const prunedCues = cues.filter((cue) => !isDuplicateCue(cue, existingCues));\n const segmentAlreadyLoaded = existingSegments.some((s) => s.id === segmentId);\n\n if (prunedCues.length === 0 && segmentAlreadyLoaded) return context;\n\n for (const cue of prunedCues) textTrack.addCue(cue);\n return {\n ...context,\n loaded: {\n ...context.loaded,\n [trackId]: [...existingCues, ...prunedCues],\n },\n segments: segmentAlreadyLoaded\n ? context.segments\n : {\n ...context.segments,\n [trackId]: [...existingSegments, { id: segmentId, startTime, duration }],\n },\n };\n });\n}\n"],"mappings":";;AA0BA,SAAS,eAAe,KAAa,UAA0B;AAC7D,QAAO,SAAS,MAAM,MAAM,EAAE,cAAc,IAAI,aAAa,EAAE,YAAY,IAAI,WAAW,EAAE,SAAS,IAAI,KAAK;;;AAQhH,SAAgB,sBAAsB,cAAyD;AAC7F,QAAO,sBAAsB;EAAE,QAAQ,EAAE;EAAE,UAAU,EAAE;EAAE,GAA6B,SAAS,YAAY;AACzG,MAAI,QAAQ,SAAS,QAKnB,QAAO;GAAE,QAAQ,EAAE;GAAE,UAAU,EAAE;GAAE;EASrC,MAAM,EAAE,MAAM,SAAS;EACvB,MAAM,EAAE,SAAS,IAAI,WAAW,WAAW,aAAa;EACxD,MAAM,YAAY,MAAM,KAAK,aAAa,WAAW,CAAC,MAAM,MAAM,EAAE,OAAO,QAAQ;AACnF,MAAI,CAAC,UAAW,QAAO;EAEvB,MAAM,eAAe,QAAQ,OAAO,YAAY,EAAE;EAClD,MAAM,mBAAmB,QAAQ,SAAS,YAAY,EAAE;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ,CAAC,eAAe,KAAK,aAAa,CAAC;EAC3E,MAAM,uBAAuB,iBAAiB,MAAM,MAAM,EAAE,OAAO,UAAU;AAE7E,MAAI,WAAW,WAAW,KAAK,qBAAsB,QAAO;AAE5D,OAAK,MAAM,OAAO,WAAY,WAAU,OAAO,IAAI;AACnD,SAAO;GACL,GAAG;GACH,QAAQ;IACN,GAAG,QAAQ;KACV,UAAU,CAAC,GAAG,cAAc,GAAG,WAAW;IAC5C;GACD,UAAU,uBACN,QAAQ,WACR;IACE,GAAG,QAAQ;KACV,UAAU,CAAC,GAAG,kBAAkB;KAAE,IAAI;KAAW;KAAW;KAAU,CAAC;IACzE;GACN;GACD"}
1
+ {"version":3,"file":"text-tracks.js","names":[],"sources":["../../../../../src/playback/actors/dom/text-tracks.ts"],"sourcesContent":["import { createTransitionActor } from '../../../core/actors/create-transition-actor';\nimport type { Cue } from '../../../media/types';\nimport type { AddCuesMessage, CueSegmentMeta } from '../../primitives/text-track-messages';\nimport type { ClearMessage, TextTracksActor, TextTracksActorContext, TextTracksActorMessage } from '../text-tracks';\n\n// Re-export the host-agnostic types so existing dom-side consumers can keep\n// importing from this module.\nexport type {\n AddCuesMessage,\n ClearMessage,\n CueSegmentMeta,\n TextTracksActor,\n TextTracksActorContext,\n TextTracksActorMessage,\n};\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction isDuplicateCue(cue: VTTCue, existing: Cue[]): boolean {\n return existing.some((r) => r.startTime === cue.startTime && r.endTime === cue.endTime && r.text === cue.text);\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** TextTrack actor: wraps all text tracks on a media element, owns cue operations. */\nexport function createTextTracksActor(mediaElement: HTMLMediaElement): TextTracksActor<VTTCue> {\n return createTransitionActor({ loaded: {}, segments: {} } as TextTracksActorContext, (context, message) => {\n if (message.type === 'clear') {\n // Reset the cue + segment cache. DOM cleanup is the caller's job —\n // by the time we get here, `syncTextTracks` has already removed\n // the `<track>` children (and their cues) via\n // `removeAllSubtitlesTracksFromMedia`.\n return { loaded: {}, segments: {} };\n }\n\n // NOTE: Currently assumes cues are applied to a non-disabled TextTrack. Discuss different approaches here, including:\n // - Making the message responsible for auto-selection of the textTrack (changes logic in sync-text-tracks)\n // - Silent gating/console warning + early bail\n // - throwing a domain-specific error\n // - accepting as is (which would result in errors, but also \"shouldn't ever happen\" unless a bug is introduced)\n // (CJP)\n const { meta, cues } = message;\n const { trackId, id: segmentId, startTime, duration } = meta;\n const textTrack = Array.from(mediaElement.textTracks).find((t) => t.id === trackId);\n if (!textTrack) return context;\n\n const existingCues = context.loaded[trackId] ?? [];\n const existingSegments = context.segments[trackId] ?? [];\n const prunedCues = cues.filter((cue) => !isDuplicateCue(cue, existingCues));\n const segmentAlreadyLoaded = existingSegments.some((s) => s.id === segmentId);\n\n if (prunedCues.length === 0 && segmentAlreadyLoaded) return context;\n\n for (const cue of prunedCues) textTrack.addCue(cue);\n return {\n ...context,\n loaded: {\n ...context.loaded,\n [trackId]: [...existingCues, ...prunedCues],\n },\n segments: segmentAlreadyLoaded\n ? context.segments\n : {\n ...context.segments,\n [trackId]: [...existingSegments, { id: segmentId, startTime, duration }],\n },\n };\n });\n}\n"],"mappings":";;AAoBA,SAAS,eAAe,KAAa,UAA0B;CAC7D,OAAO,SAAS,MAAM,MAAM,EAAE,cAAc,IAAI,aAAa,EAAE,YAAY,IAAI,WAAW,EAAE,SAAS,IAAI,IAAI;AAC/G;;AAOA,SAAgB,sBAAsB,cAAyD;CAC7F,OAAO,sBAAsB;EAAE,QAAQ,CAAC;EAAG,UAAU,CAAC;CAAE,IAA8B,SAAS,YAAY;EACzG,IAAI,QAAQ,SAAS,SAKnB,OAAO;GAAE,QAAQ,CAAC;GAAG,UAAU,CAAC;EAAE;EASpC,MAAM,EAAE,MAAM,SAAS;EACvB,MAAM,EAAE,SAAS,IAAI,WAAW,WAAW,aAAa;EACxD,MAAM,YAAY,MAAM,KAAK,aAAa,UAAU,CAAC,CAAC,MAAM,MAAM,EAAE,OAAO,OAAO;EAClF,IAAI,CAAC,WAAW,OAAO;EAEvB,MAAM,eAAe,QAAQ,OAAO,YAAY,CAAC;EACjD,MAAM,mBAAmB,QAAQ,SAAS,YAAY,CAAC;EACvD,MAAM,aAAa,KAAK,QAAQ,QAAQ,CAAC,eAAe,KAAK,YAAY,CAAC;EAC1E,MAAM,uBAAuB,iBAAiB,MAAM,MAAM,EAAE,OAAO,SAAS;EAE5E,IAAI,WAAW,WAAW,KAAK,sBAAsB,OAAO;EAE5D,KAAK,MAAM,OAAO,YAAY,UAAU,OAAO,GAAG;EAClD,OAAO;GACL,GAAG;GACH,QAAQ;IACN,GAAG,QAAQ;KACV,UAAU,CAAC,GAAG,cAAc,GAAG,UAAU;GAC5C;GACA,UAAU,uBACN,QAAQ,WACR;IACE,GAAG,QAAQ;KACV,UAAU,CAAC,GAAG,kBAAkB;KAAE,IAAI;KAAW;KAAW;IAAS,CAAC;GACzE;EACN;CACF,CAAC;AACH"}
@@ -1,6 +1,8 @@
1
- import { MessageActor } from "../../core/actors/create-machine-actor.js";
2
1
  import { Cue, TextTrack } from "../../media/types/index.js";
2
+ import { MessageActor } from "../../core/actors/create-machine-actor.js";
3
3
  import { ForwardBufferConfig } from "../../media/buffer/forward-buffer.js";
4
+ import { TextMessagePipelines } from "../primitives/text-segment-load-pipeline.js";
5
+ import "./text-tracks.js";
4
6
  //#region src/playback/actors/text-track-segment-loader.d.ts
5
7
  /**
6
8
  * Mirrors the v/a `SegmentLoaderMessage` shape. `range` carries the
@@ -35,15 +37,6 @@ interface TextTrackSegmentLoaderActorContext {
35
37
  inFlightSegmentId: string | null;
36
38
  }
37
39
  type TextTrackSegmentLoaderActor = MessageActor<TextTrackSegmentLoaderActorState, TextTrackSegmentLoaderActorContext, TextTrackSegmentLoaderMessage>;
38
- /**
39
- * Resolves a text-track segment URL into the array of cues it contains.
40
- *
41
- * "Resolve" because the fn covers both network fetch and parse into the
42
- * domain model. Host-agnostic — the concrete resolver (e.g. the
43
- * browser's native VTT resolver) is supplied at engine-assembly time,
44
- * so this actor stays DOM-free.
45
- */
46
- type TextTrackSegmentResolver<C extends Cue = Cue> = (url: string) => Promise<C[]>;
47
40
  /**
48
41
  * Configuration for `createTextTrackSegmentLoaderActor`. Spread over
49
42
  * `DEFAULT_FORWARD_BUFFER_CONFIG` to override individual forward-window
@@ -51,9 +44,11 @@ type TextTrackSegmentResolver<C extends Cue = Cue> = (url: string) => Promise<C[
51
44
  * concern — cues evict by their playhead-relative window at runtime —
52
45
  * so no `backBuffer` config field.
53
46
  */
54
- interface TextTrackSegmentLoaderActorConfig {
47
+ interface TextTrackSegmentLoaderActorConfig<C extends Cue = Cue> {
55
48
  forwardBuffer?: Partial<ForwardBufferConfig>;
49
+ /** Ordered step pipeline. Defaults to {@link DEFAULT_TEXT_MESSAGE_PIPELINES} (`resolveCues → dispatchCues`). */
50
+ messagePipelines?: TextMessagePipelines<C>;
56
51
  }
57
52
  //#endregion
58
- export { TextTrackSegmentLoaderActor, TextTrackSegmentLoaderActorConfig, TextTrackSegmentResolver };
53
+ export { TextTrackSegmentLoaderActor, TextTrackSegmentLoaderActorConfig, TextTrackSegmentLoaderActorContext, TextTrackSegmentLoaderActorState, TextTrackSegmentLoaderMessage };
59
54
  //# sourceMappingURL=text-track-segment-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-track-segment-loader.d.ts","names":[],"sources":["../../../../src/playback/actors/text-track-segment-loader.ts"],"mappings":";;;;;;AAsBA;;;;;KAAY,6BAAA;EACV,IAAA;EACA,KAAA,EAAO,SAAA;EACP,KAAA;IAAU,KAAA;IAAe,GAAA;EAAA;AAAA;;KAIf,gCAAA;;UAGK,kCAAA;EAAA;;;;;AAejB;EARE,eAAA;;;;;EAKA,iBAAA;AAAA;AAAA,KAGU,2BAAA,GAA8B,YAAA,CACxC,gCAAA,EACA,kCAAA,EACA,6BAAA;;;;;;;AAWF;;KAAY,wBAAA,WAAmC,GAAA,GAAM,GAAA,KAAQ,GAAA,aAAgB,OAAA,CAAQ,CAAA;;;;;;;;UASpE,iCAAA;EACf,aAAA,GAAgB,OAAA,CAAQ,mBAAA;AAAA"}
1
+ {"version":3,"file":"text-track-segment-loader.d.ts","names":[],"sources":["../../../../src/playback/actors/text-track-segment-loader.ts"],"mappings":";;;;;;;;;;;;;KA8BY;EACV;EACA,OAAO;EACP;IAAU;IAAe;;;;KAIf;;UAGK;;;;;;;EAOf;;;;;EAKA;;KAGU,8BAA8B,aACxC,kCACA,oCACA;;;;;;;;UAUe,kCAAkC,UAAU,MAAM;EACjE,gBAAgB,QAAQ;;EAExB,mBAAmB,qBAAqB"}
@@ -2,6 +2,7 @@ import { peek } from "../../core/signals/primitives.js";
2
2
  import { SerialRunner, Task } from "../../core/tasks/task.js";
3
3
  import { createMachineActor } from "../../core/actors/create-machine-actor.js";
4
4
  import { DEFAULT_FORWARD_BUFFER_CONFIG, getSegmentsToLoad } from "../../media/buffer/forward-buffer.js";
5
+ import { DEFAULT_TEXT_MESSAGE_PIPELINES } from "../primitives/text-segment-load-pipeline.js";
5
6
  //#region src/playback/actors/text-track-segment-loader.ts
6
7
  /**
7
8
  * Loads text-track segments for a track and delegates cue management
@@ -30,11 +31,25 @@ import { DEFAULT_FORWARD_BUFFER_CONFIG, getSegmentsToLoad } from "../../media/bu
30
31
  * host supplies a VTT parser backed by `<track>`/`TextTrack` APIs; a
31
32
  * non-DOM host (worker, test fake, alternate runtime) supplies its own.
32
33
  */
33
- function createTextTrackSegmentLoaderActor(textTracksActor, resolveSegment, config = {}) {
34
+ function createTextTrackSegmentLoaderActor(textTracksActor, resolveSegment, config = {}, compositionDeps = {
35
+ state: {},
36
+ context: {},
37
+ config: {}
38
+ }) {
34
39
  const forwardBufferConfig = {
35
40
  ...DEFAULT_FORWARD_BUFFER_CONFIG,
36
41
  ...config.forwardBuffer
37
42
  };
43
+ const deps = {
44
+ state: compositionDeps.state,
45
+ context: compositionDeps.context,
46
+ config: {
47
+ ...compositionDeps.config,
48
+ textTracksActor,
49
+ resolveSegment
50
+ }
51
+ };
52
+ const pipeline = (config.messagePipelines ?? DEFAULT_TEXT_MESSAGE_PIPELINES)();
38
53
  /**
39
54
  * Translate a load message into an ordered TextLoadTask list based on
40
55
  * committed actor state. In-flight awareness is handled separately in
@@ -55,31 +70,30 @@ function createTextTrackSegmentLoaderActor(textTracksActor, resolveSegment, conf
55
70
  }));
56
71
  };
57
72
  /**
58
- * Wraps a TextLoadTask into a Task that fetches + dispatches `add-cues`.
59
- * Updates `inFlightSegmentId` around the fetch so the load handler can
60
- * make accurate continue/preempt decisions.
73
+ * Wraps a TextLoadTask into a Task that runs the op's step pipeline
74
+ * (resolve/relocate/dispatch, per the composition's `messagePipelines`).
75
+ * Updates `inFlightSegmentId` around the async region so the load handler can
76
+ * make accurate continue/preempt decisions, and checks the abort signal before
77
+ * each step.
78
+ *
79
+ * Text degrades gracefully: a step throwing (e.g. a failed segment fetch) is
80
+ * logged and swallowed so the runner continues to the next segment — unlike the
81
+ * v/a loader, where a failed init must abort the remaining tasks.
61
82
  */
62
83
  const makeLoadTask = (op, { getContext, setContext }) => {
63
84
  return new Task(async (signal) => {
64
85
  if (signal.aborted) return;
86
+ const frame = { op };
65
87
  setContext({
66
88
  ...getContext(),
67
89
  inFlightTrackId: op.trackId,
68
90
  inFlightSegmentId: op.segment.id
69
91
  });
70
92
  try {
71
- const cues = await resolveSegment(op.segment.url);
72
- if (signal.aborted) return;
73
- textTracksActor.send({
74
- type: "add-cues",
75
- meta: {
76
- trackId: op.trackId,
77
- id: op.segment.id,
78
- startTime: op.segment.startTime,
79
- duration: op.segment.duration
80
- },
81
- cues
82
- });
93
+ for (const step of pipeline) {
94
+ if (signal.aborted) return;
95
+ await step(frame, signal, deps);
96
+ }
83
97
  } catch (error) {
84
98
  console.error("Failed to load text-track segment:", error);
85
99
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"text-track-segment-loader.js","names":[],"sources":["../../../../src/playback/actors/text-track-segment-loader.ts"],"sourcesContent":["import { createMachineActor, type HandlerContext, type MessageActor } from '../../core/actors/create-machine-actor';\nimport { peek } from '../../core/signals/primitives';\nimport { SerialRunner, Task } from '../../core/tasks/task';\nimport {\n DEFAULT_FORWARD_BUFFER_CONFIG,\n type ForwardBufferConfig,\n getSegmentsToLoad,\n} from '../../media/buffer/forward-buffer';\nimport type { Cue, Segment, TextTrack } from '../../media/types';\nimport type { TextTracksActor } from './text-tracks';\n\n// =============================================================================\n// Message / actor types\n// =============================================================================\n\n/**\n * Mirrors the v/a `SegmentLoaderMessage` shape. `range` carries the\n * forward-window anchor (`range.start` is treated as the load anchor;\n * the actor computes its own forward window internally via\n * `getSegmentsToLoad`). When `range` is omitted (metadata mode), this\n * actor is a no-op — text tracks have no init-segment concept.\n */\nexport type TextTrackSegmentLoaderMessage = {\n type: 'load';\n track: TextTrack;\n range?: { start: number; end: number };\n};\n\n/** Finite states of the actor. */\nexport type TextTrackSegmentLoaderActorState = 'idle' | 'loading' | 'destroyed';\n\n/** Non-finite (extended) data managed by the actor. */\nexport interface TextTrackSegmentLoaderActorContext {\n /**\n * Track ID of the segment currently being fetched, or null. Paired\n * with `inFlightSegmentId` so the continue-vs-preempt check survives\n * cross-track segment-id collisions (e.g. each track starts at\n * `seg-0`).\n */\n inFlightTrackId: string | null;\n /**\n * Segment ID currently being fetched, or null. Used together with\n * `inFlightTrackId` by the `loading` state's `load` handler.\n */\n inFlightSegmentId: string | null;\n}\n\nexport type TextTrackSegmentLoaderActor = MessageActor<\n TextTrackSegmentLoaderActorState,\n TextTrackSegmentLoaderActorContext,\n TextTrackSegmentLoaderMessage\n>;\n\n/**\n * Resolves a text-track segment URL into the array of cues it contains.\n *\n * \"Resolve\" because the fn covers both network fetch and parse into the\n * domain model. Host-agnostic — the concrete resolver (e.g. the\n * browser's native VTT resolver) is supplied at engine-assembly time,\n * so this actor stays DOM-free.\n */\nexport type TextTrackSegmentResolver<C extends Cue = Cue> = (url: string) => Promise<C[]>;\n\n/**\n * Configuration for `createTextTrackSegmentLoaderActor`. Spread over\n * `DEFAULT_FORWARD_BUFFER_CONFIG` to override individual forward-window\n * fields (e.g. `bufferDuration`). Text tracks don't have a back-buffer\n * concern — cues evict by their playhead-relative window at runtime —\n * so no `backBuffer` config field.\n */\nexport interface TextTrackSegmentLoaderActorConfig {\n forwardBuffer?: Partial<ForwardBufferConfig>;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/** Internal load-task descriptor — one segment fetch + dispatch unit. */\ninterface TextLoadTask {\n segment: Segment;\n trackId: string;\n}\n\n/**\n * Loads text-track segments for a track and delegates cue management\n * to a TextTracksActor. Mirrors the v/a `SegmentLoaderActor` shape (FSM\n * with `idle` / `loading` and `inFlight*` context for continue-vs-preempt),\n * adapted to text:\n *\n * - No init segment, no flush ops (text cues don't need eviction — they're\n * small and the playhead-relative window is enforced by the runtime).\n * - Single in-flight identity (`inFlightSegmentId`) — text has only the\n * media-segment path, no init-segment path.\n *\n * Planning is done in the load handler on every incoming message:\n * `getSegmentsToLoad` filters to the forward window, then the segments\n * not already in `TextTracksActor`'s context are scheduled. When a new\n * `load` arrives mid-run, the handler replans and either:\n *\n * - **Continues**: the in-flight segment is still in the new plan →\n * `abortPending` only, schedule the rest of the plan (minus the\n * in-flight item, which covers its slot).\n * - **Preempts**: in-flight segment is no longer wanted (track switch,\n * large seek out of window) → `abortAll`, schedule the new plan\n * from scratch.\n *\n * The cue parser is injected so this factory is host-agnostic. A DOM\n * host supplies a VTT parser backed by `<track>`/`TextTrack` APIs; a\n * non-DOM host (worker, test fake, alternate runtime) supplies its own.\n */\nexport function createTextTrackSegmentLoaderActor<C extends Cue>(\n textTracksActor: TextTracksActor<C>,\n resolveSegment: TextTrackSegmentResolver<C>,\n config: TextTrackSegmentLoaderActorConfig = {}\n): TextTrackSegmentLoaderActor {\n type UserState = Exclude<TextTrackSegmentLoaderActorState, 'destroyed'>;\n type Ctx = HandlerContext<UserState, TextTrackSegmentLoaderActorContext, () => SerialRunner>;\n\n const forwardBufferConfig: ForwardBufferConfig = { ...DEFAULT_FORWARD_BUFFER_CONFIG, ...config.forwardBuffer };\n\n /**\n * Translate a load message into an ordered TextLoadTask list based on\n * committed actor state. In-flight awareness is handled separately in\n * the `loading` state's load handler.\n *\n * Metadata mode (no `range`) is a no-op for text — text tracks have\n * no init-segment concept, so there's nothing to load until a range\n * arrives via `'full-range'` dispatch.\n */\n const planTasks = (message: TextTrackSegmentLoaderMessage): TextLoadTask[] => {\n const { track, range } = message;\n if (!range) return [];\n const trackId = track.id;\n // Peek (don't track) the snapshot: the loader's `send` is typically\n // invoked from inside the dispatcher's effect body, so a tracked\n // `.get()` here would leak the textTracksActor snapshot into the\n // dispatcher's dep set — every `add-cues` would re-fire the dispatcher\n // and schedule duplicate loads.\n const bufferedSegments = peek(textTracksActor.snapshot).context.segments[trackId] ?? [];\n // `getSegmentsToLoad` uses the actor's threaded `forwardBufferConfig`\n // to compute its forward-window; only `range.start` is consulted\n // here as the playhead anchor. `range.end` is informational — it\n // carries the dispatcher's intended window upper bound but doesn't\n // override the actor's planning.\n const segmentsToLoad = getSegmentsToLoad(track.segments, bufferedSegments, range.start, forwardBufferConfig);\n return segmentsToLoad.map((segment) => ({ segment, trackId }));\n };\n\n /**\n * Wraps a TextLoadTask into a Task that fetches + dispatches `add-cues`.\n * Updates `inFlightSegmentId` around the fetch so the load handler can\n * make accurate continue/preempt decisions.\n */\n const makeLoadTask = (op: TextLoadTask, { getContext, setContext }: Ctx): Task<void> => {\n return new Task(async (signal) => {\n if (signal.aborted) return;\n setContext({ ...getContext(), inFlightTrackId: op.trackId, inFlightSegmentId: op.segment.id });\n try {\n const cues = await resolveSegment(op.segment.url);\n if (signal.aborted) return;\n textTracksActor.send({\n type: 'add-cues',\n meta: {\n trackId: op.trackId,\n id: op.segment.id,\n startTime: op.segment.startTime,\n duration: op.segment.duration,\n },\n cues,\n });\n } catch (error) {\n // Graceful degradation: log and continue to the next segment.\n console.error('Failed to load text-track segment:', error);\n } finally {\n setContext({ ...getContext(), inFlightTrackId: null, inFlightSegmentId: null });\n }\n });\n };\n\n const scheduleAll = (tasks: TextLoadTask[], ctx: Ctx): void => {\n for (const op of tasks) {\n ctx.runner.schedule(makeLoadTask(op, ctx)).then(undefined, (e: unknown) => {\n if (e instanceof Error && e.name === 'AbortError') return;\n console.error('Unexpected error in text-track segment loader:', e);\n ctx.runner.abortPending();\n });\n }\n };\n\n return createMachineActor<\n UserState,\n TextTrackSegmentLoaderActorContext,\n TextTrackSegmentLoaderMessage,\n () => SerialRunner\n >({\n runner: () => new SerialRunner(),\n initial: 'idle',\n context: { inFlightTrackId: null, inFlightSegmentId: null },\n states: {\n idle: {\n on: {\n load: (msg, ctx) => {\n const tasks = planTasks(msg);\n if (tasks.length === 0) return;\n ctx.transition('loading');\n scheduleAll(tasks, ctx);\n },\n },\n },\n loading: {\n onSettled: 'idle',\n on: {\n load: (msg, ctx) => {\n const { context, runner } = ctx;\n const tasks = planTasks(msg);\n\n const inFlightStillNeeded =\n context.inFlightTrackId !== null &&\n context.inFlightSegmentId !== null &&\n tasks.some((t) => t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId);\n\n if (inFlightStillNeeded) {\n // Continue: keep the in-flight fetch, schedule the rest.\n runner.abortPending();\n scheduleAll(\n tasks.filter(\n (t) => !(t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)\n ),\n ctx\n );\n } else {\n // Preempt: abort everything (including in-flight) and replan.\n runner.abortAll();\n scheduleAll(tasks, ctx);\n }\n },\n },\n },\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+GA,SAAgB,kCACd,iBACA,gBACA,SAA4C,EAAE,EACjB;CAI7B,MAAM,sBAA2C;EAAE,GAAG;EAA+B,GAAG,OAAO;EAAe;;;;;;;;;;CAW9G,MAAM,aAAa,YAA2D;EAC5E,MAAM,EAAE,OAAO,UAAU;AACzB,MAAI,CAAC,MAAO,QAAO,EAAE;EACrB,MAAM,UAAU,MAAM;EAMtB,MAAM,mBAAmB,KAAK,gBAAgB,SAAS,CAAC,QAAQ,SAAS,YAAY,EAAE;AAOvF,SADuB,kBAAkB,MAAM,UAAU,kBAAkB,MAAM,OAAO,oBAAoB,CACtF,KAAK,aAAa;GAAE;GAAS;GAAS,EAAE;;;;;;;CAQhE,MAAM,gBAAgB,IAAkB,EAAE,YAAY,iBAAkC;AACtF,SAAO,IAAI,KAAK,OAAO,WAAW;AAChC,OAAI,OAAO,QAAS;AACpB,cAAW;IAAE,GAAG,YAAY;IAAE,iBAAiB,GAAG;IAAS,mBAAmB,GAAG,QAAQ;IAAI,CAAC;AAC9F,OAAI;IACF,MAAM,OAAO,MAAM,eAAe,GAAG,QAAQ,IAAI;AACjD,QAAI,OAAO,QAAS;AACpB,oBAAgB,KAAK;KACnB,MAAM;KACN,MAAM;MACJ,SAAS,GAAG;MACZ,IAAI,GAAG,QAAQ;MACf,WAAW,GAAG,QAAQ;MACtB,UAAU,GAAG,QAAQ;MACtB;KACD;KACD,CAAC;YACK,OAAO;AAEd,YAAQ,MAAM,sCAAsC,MAAM;aAClD;AACR,eAAW;KAAE,GAAG,YAAY;KAAE,iBAAiB;KAAM,mBAAmB;KAAM,CAAC;;IAEjF;;CAGJ,MAAM,eAAe,OAAuB,QAAmB;AAC7D,OAAK,MAAM,MAAM,MACf,KAAI,OAAO,SAAS,aAAa,IAAI,IAAI,CAAC,CAAC,KAAK,KAAA,IAAY,MAAe;AACzE,OAAI,aAAa,SAAS,EAAE,SAAS,aAAc;AACnD,WAAQ,MAAM,kDAAkD,EAAE;AAClE,OAAI,OAAO,cAAc;IACzB;;AAIN,QAAO,mBAKL;EACA,cAAc,IAAI,cAAc;EAChC,SAAS;EACT,SAAS;GAAE,iBAAiB;GAAM,mBAAmB;GAAM;EAC3D,QAAQ;GACN,MAAM,EACJ,IAAI,EACF,OAAO,KAAK,QAAQ;IAClB,MAAM,QAAQ,UAAU,IAAI;AAC5B,QAAI,MAAM,WAAW,EAAG;AACxB,QAAI,WAAW,UAAU;AACzB,gBAAY,OAAO,IAAI;MAE1B,EACF;GACD,SAAS;IACP,WAAW;IACX,IAAI,EACF,OAAO,KAAK,QAAQ;KAClB,MAAM,EAAE,SAAS,WAAW;KAC5B,MAAM,QAAQ,UAAU,IAAI;AAO5B,SAJE,QAAQ,oBAAoB,QAC5B,QAAQ,sBAAsB,QAC9B,MAAM,MAAM,MAAM,EAAE,YAAY,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,QAAQ,kBAAkB,EAE/E;AAEvB,aAAO,cAAc;AACrB,kBACE,MAAM,QACH,MAAM,EAAE,EAAE,YAAY,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,QAAQ,mBAC5E,EACD,IACD;YACI;AAEL,aAAO,UAAU;AACjB,kBAAY,OAAO,IAAI;;OAG5B;IACF;GACF;EACF,CAAC"}
1
+ {"version":3,"file":"text-track-segment-loader.js","names":[],"sources":["../../../../src/playback/actors/text-track-segment-loader.ts"],"sourcesContent":["import { createMachineActor, type HandlerContext, type MessageActor } from '../../core/actors/create-machine-actor';\nimport { peek } from '../../core/signals/primitives';\nimport { SerialRunner, Task } from '../../core/tasks/task';\nimport {\n DEFAULT_FORWARD_BUFFER_CONFIG,\n type ForwardBufferConfig,\n getSegmentsToLoad,\n} from '../../media/buffer/forward-buffer';\nimport type { Cue, TextTrack } from '../../media/types';\nimport {\n DEFAULT_TEXT_MESSAGE_PIPELINES,\n type TextFrame,\n type TextLoadTask,\n type TextMessagePipelines,\n type TextStepDeps,\n type TextTrackSegmentResolver,\n} from '../primitives/text-segment-load-pipeline';\nimport type { TextTracksActor } from './text-tracks';\n\n// =============================================================================\n// Message / actor types\n// =============================================================================\n\n/**\n * Mirrors the v/a `SegmentLoaderMessage` shape. `range` carries the\n * forward-window anchor (`range.start` is treated as the load anchor;\n * the actor computes its own forward window internally via\n * `getSegmentsToLoad`). When `range` is omitted (metadata mode), this\n * actor is a no-op — text tracks have no init-segment concept.\n */\nexport type TextTrackSegmentLoaderMessage = {\n type: 'load';\n track: TextTrack;\n range?: { start: number; end: number };\n};\n\n/** Finite states of the actor. */\nexport type TextTrackSegmentLoaderActorState = 'idle' | 'loading' | 'destroyed';\n\n/** Non-finite (extended) data managed by the actor. */\nexport interface TextTrackSegmentLoaderActorContext {\n /**\n * Track ID of the segment currently being fetched, or null. Paired\n * with `inFlightSegmentId` so the continue-vs-preempt check survives\n * cross-track segment-id collisions (e.g. each track starts at\n * `seg-0`).\n */\n inFlightTrackId: string | null;\n /**\n * Segment ID currently being fetched, or null. Used together with\n * `inFlightTrackId` by the `loading` state's `load` handler.\n */\n inFlightSegmentId: string | null;\n}\n\nexport type TextTrackSegmentLoaderActor = MessageActor<\n TextTrackSegmentLoaderActorState,\n TextTrackSegmentLoaderActorContext,\n TextTrackSegmentLoaderMessage\n>;\n\n/**\n * Configuration for `createTextTrackSegmentLoaderActor`. Spread over\n * `DEFAULT_FORWARD_BUFFER_CONFIG` to override individual forward-window\n * fields (e.g. `bufferDuration`). Text tracks don't have a back-buffer\n * concern — cues evict by their playhead-relative window at runtime —\n * so no `backBuffer` config field.\n */\nexport interface TextTrackSegmentLoaderActorConfig<C extends Cue = Cue> {\n forwardBuffer?: Partial<ForwardBufferConfig>;\n /** Ordered step pipeline. Defaults to {@link DEFAULT_TEXT_MESSAGE_PIPELINES} (`resolveCues → dispatchCues`). */\n messagePipelines?: TextMessagePipelines<C>;\n}\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Loads text-track segments for a track and delegates cue management\n * to a TextTracksActor. Mirrors the v/a `SegmentLoaderActor` shape (FSM\n * with `idle` / `loading` and `inFlight*` context for continue-vs-preempt),\n * adapted to text:\n *\n * - No init segment, no flush ops (text cues don't need eviction — they're\n * small and the playhead-relative window is enforced by the runtime).\n * - Single in-flight identity (`inFlightSegmentId`) — text has only the\n * media-segment path, no init-segment path.\n *\n * Planning is done in the load handler on every incoming message:\n * `getSegmentsToLoad` filters to the forward window, then the segments\n * not already in `TextTracksActor`'s context are scheduled. When a new\n * `load` arrives mid-run, the handler replans and either:\n *\n * - **Continues**: the in-flight segment is still in the new plan →\n * `abortPending` only, schedule the rest of the plan (minus the\n * in-flight item, which covers its slot).\n * - **Preempts**: in-flight segment is no longer wanted (track switch,\n * large seek out of window) → `abortAll`, schedule the new plan\n * from scratch.\n *\n * The cue parser is injected so this factory is host-agnostic. A DOM\n * host supplies a VTT parser backed by `<track>`/`TextTrack` APIs; a\n * non-DOM host (worker, test fake, alternate runtime) supplies its own.\n */\nexport function createTextTrackSegmentLoaderActor<C extends Cue>(\n textTracksActor: TextTracksActor<C>,\n resolveSegment: TextTrackSegmentResolver<C>,\n config: TextTrackSegmentLoaderActorConfig<C> = {},\n // Composition deps threaded opaquely into each step's `TextStepDeps` (relocation\n // reads composition state). The loader never reads them. Defaults empty for\n // standalone / base-pipeline use.\n compositionDeps: TextStepDeps = { state: {}, context: {}, config: {} }\n): TextTrackSegmentLoaderActor {\n type UserState = Exclude<TextTrackSegmentLoaderActorState, 'destroyed'>;\n type Ctx = HandlerContext<UserState, TextTrackSegmentLoaderActorContext, () => SerialRunner>;\n\n const forwardBufferConfig: ForwardBufferConfig = { ...DEFAULT_FORWARD_BUFFER_CONFIG, ...config.forwardBuffer };\n // Fold the loader's wiring into the passthrough `config` (see `textStepWiring`) so\n // base steps read it from the uniform `{state,context,config}` — present in both\n // composition and standalone use.\n const deps: TextStepDeps = {\n state: compositionDeps.state,\n context: compositionDeps.context,\n config: { ...compositionDeps.config, textTracksActor, resolveSegment },\n };\n // Built once per actor; default is `resolveCues → dispatchCues`.\n const pipeline = (config.messagePipelines ?? DEFAULT_TEXT_MESSAGE_PIPELINES)();\n\n /**\n * Translate a load message into an ordered TextLoadTask list based on\n * committed actor state. In-flight awareness is handled separately in\n * the `loading` state's load handler.\n *\n * Metadata mode (no `range`) is a no-op for text — text tracks have\n * no init-segment concept, so there's nothing to load until a range\n * arrives via `'full-range'` dispatch.\n */\n const planTasks = (message: TextTrackSegmentLoaderMessage): TextLoadTask[] => {\n const { track, range } = message;\n if (!range) return [];\n const trackId = track.id;\n // Peek (don't track) the snapshot: the loader's `send` is typically\n // invoked from inside the dispatcher's effect body, so a tracked\n // `.get()` here would leak the textTracksActor snapshot into the\n // dispatcher's dep set — every `add-cues` would re-fire the dispatcher\n // and schedule duplicate loads.\n const bufferedSegments = peek(textTracksActor.snapshot).context.segments[trackId] ?? [];\n // `getSegmentsToLoad` uses the actor's threaded `forwardBufferConfig`\n // to compute its forward-window; only `range.start` is consulted\n // here as the playhead anchor. `range.end` is informational — it\n // carries the dispatcher's intended window upper bound but doesn't\n // override the actor's planning.\n const segmentsToLoad = getSegmentsToLoad(track.segments, bufferedSegments, range.start, forwardBufferConfig);\n return segmentsToLoad.map((segment) => ({ segment, trackId }));\n };\n\n /**\n * Wraps a TextLoadTask into a Task that runs the op's step pipeline\n * (resolve/relocate/dispatch, per the composition's `messagePipelines`).\n * Updates `inFlightSegmentId` around the async region so the load handler can\n * make accurate continue/preempt decisions, and checks the abort signal before\n * each step.\n *\n * Text degrades gracefully: a step throwing (e.g. a failed segment fetch) is\n * logged and swallowed so the runner continues to the next segment — unlike the\n * v/a loader, where a failed init must abort the remaining tasks.\n */\n const makeLoadTask = (op: TextLoadTask, { getContext, setContext }: Ctx): Task<void> => {\n return new Task(async (signal) => {\n if (signal.aborted) return;\n const frame: TextFrame<C> = { op };\n setContext({ ...getContext(), inFlightTrackId: op.trackId, inFlightSegmentId: op.segment.id });\n try {\n for (const step of pipeline) {\n if (signal.aborted) return;\n await step(frame, signal, deps);\n }\n } catch (error) {\n // Graceful degradation: log and continue to the next segment.\n console.error('Failed to load text-track segment:', error);\n } finally {\n setContext({ ...getContext(), inFlightTrackId: null, inFlightSegmentId: null });\n }\n });\n };\n\n const scheduleAll = (tasks: TextLoadTask[], ctx: Ctx): void => {\n for (const op of tasks) {\n ctx.runner.schedule(makeLoadTask(op, ctx)).then(undefined, (e: unknown) => {\n if (e instanceof Error && e.name === 'AbortError') return;\n console.error('Unexpected error in text-track segment loader:', e);\n ctx.runner.abortPending();\n });\n }\n };\n\n return createMachineActor<\n UserState,\n TextTrackSegmentLoaderActorContext,\n TextTrackSegmentLoaderMessage,\n () => SerialRunner\n >({\n runner: () => new SerialRunner(),\n initial: 'idle',\n context: { inFlightTrackId: null, inFlightSegmentId: null },\n states: {\n idle: {\n on: {\n load: (msg, ctx) => {\n const tasks = planTasks(msg);\n if (tasks.length === 0) return;\n ctx.transition('loading');\n scheduleAll(tasks, ctx);\n },\n },\n },\n loading: {\n onSettled: 'idle',\n on: {\n load: (msg, ctx) => {\n const { context, runner } = ctx;\n const tasks = planTasks(msg);\n\n const inFlightStillNeeded =\n context.inFlightTrackId !== null &&\n context.inFlightSegmentId !== null &&\n tasks.some((t) => t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId);\n\n if (inFlightStillNeeded) {\n // Continue: keep the in-flight fetch, schedule the rest.\n runner.abortPending();\n scheduleAll(\n tasks.filter(\n (t) => !(t.trackId === context.inFlightTrackId && t.segment.id === context.inFlightSegmentId)\n ),\n ctx\n );\n } else {\n // Preempt: abort everything (including in-flight) and replan.\n runner.abortAll();\n scheduleAll(tasks, ctx);\n }\n },\n },\n },\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGA,SAAgB,kCACd,iBACA,gBACA,SAA+C,CAAC,GAIhD,kBAAgC;CAAE,OAAO,CAAC;CAAG,SAAS,CAAC;CAAG,QAAQ,CAAC;AAAE,GACxC;CAI7B,MAAM,sBAA2C;EAAE,GAAG;EAA+B,GAAG,OAAO;CAAc;CAI7G,MAAM,OAAqB;EACzB,OAAO,gBAAgB;EACvB,SAAS,gBAAgB;EACzB,QAAQ;GAAE,GAAG,gBAAgB;GAAQ;GAAiB;EAAe;CACvE;CAEA,MAAM,YAAY,OAAO,oBAAoB,+BAAA,CAAgC;;;;;;;;;;CAW7E,MAAM,aAAa,YAA2D;EAC5E,MAAM,EAAE,OAAO,UAAU;EACzB,IAAI,CAAC,OAAO,OAAO,CAAC;EACpB,MAAM,UAAU,MAAM;EAMtB,MAAM,mBAAmB,KAAK,gBAAgB,QAAQ,CAAC,CAAC,QAAQ,SAAS,YAAY,CAAC;EAOtF,OADuB,kBAAkB,MAAM,UAAU,kBAAkB,MAAM,OAAO,mBACpE,CAAC,CAAC,KAAK,aAAa;GAAE;GAAS;EAAQ,EAAE;CAC/D;;;;;;;;;;;;CAaA,MAAM,gBAAgB,IAAkB,EAAE,YAAY,iBAAkC;EACtF,OAAO,IAAI,KAAK,OAAO,WAAW;GAChC,IAAI,OAAO,SAAS;GACpB,MAAM,QAAsB,EAAE,GAAG;GACjC,WAAW;IAAE,GAAG,WAAW;IAAG,iBAAiB,GAAG;IAAS,mBAAmB,GAAG,QAAQ;GAAG,CAAC;GAC7F,IAAI;IACF,KAAK,MAAM,QAAQ,UAAU;KAC3B,IAAI,OAAO,SAAS;KACpB,MAAM,KAAK,OAAO,QAAQ,IAAI;IAChC;GACF,SAAS,OAAO;IAEd,QAAQ,MAAM,sCAAsC,KAAK;GAC3D,UAAU;IACR,WAAW;KAAE,GAAG,WAAW;KAAG,iBAAiB;KAAM,mBAAmB;IAAK,CAAC;GAChF;EACF,CAAC;CACH;CAEA,MAAM,eAAe,OAAuB,QAAmB;EAC7D,KAAK,MAAM,MAAM,OACf,IAAI,OAAO,SAAS,aAAa,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,KAAA,IAAY,MAAe;GACzE,IAAI,aAAa,SAAS,EAAE,SAAS,cAAc;GACnD,QAAQ,MAAM,kDAAkD,CAAC;GACjE,IAAI,OAAO,aAAa;EAC1B,CAAC;CAEL;CAEA,OAAO,mBAKL;EACA,cAAc,IAAI,aAAa;EAC/B,SAAS;EACT,SAAS;GAAE,iBAAiB;GAAM,mBAAmB;EAAK;EAC1D,QAAQ;GACN,MAAM,EACJ,IAAI,EACF,OAAO,KAAK,QAAQ;IAClB,MAAM,QAAQ,UAAU,GAAG;IAC3B,IAAI,MAAM,WAAW,GAAG;IACxB,IAAI,WAAW,SAAS;IACxB,YAAY,OAAO,GAAG;GACxB,EACF,EACF;GACA,SAAS;IACP,WAAW;IACX,IAAI,EACF,OAAO,KAAK,QAAQ;KAClB,MAAM,EAAE,SAAS,WAAW;KAC5B,MAAM,QAAQ,UAAU,GAAG;KAO3B,IAJE,QAAQ,oBAAoB,QAC5B,QAAQ,sBAAsB,QAC9B,MAAM,MAAM,MAAM,EAAE,YAAY,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,QAAQ,iBAAiB,GAE9E;MAEvB,OAAO,aAAa;MACpB,YACE,MAAM,QACH,MAAM,EAAE,EAAE,YAAY,QAAQ,mBAAmB,EAAE,QAAQ,OAAO,QAAQ,kBAC7E,GACA,GACF;KACF,OAAO;MAEL,OAAO,SAAS;MAChB,YAAY,OAAO,GAAG;KACxB;IACF,EACF;GACF;EACF;CACF,CAAC;AACH"}
@@ -1,11 +1,7 @@
1
- import { TransitionActor } from "../../core/actors/create-transition-actor.js";
2
1
  import { Cue, Segment } from "../../media/types/index.js";
3
-
2
+ import { TransitionActor } from "../../core/actors/create-transition-actor.js";
3
+ import { AddCuesMessage } from "../primitives/text-track-messages.js";
4
4
  //#region src/playback/actors/text-tracks.d.ts
5
- /** Segment identity and timing — mirrors AppendSegmentMeta without trackId (keyed separately). */
6
- type CueSegmentMeta = Pick<Segment, 'id' | 'startTime' | 'duration'> & {
7
- trackId: string;
8
- };
9
5
  /** Non-finite (extended) data managed by the actor — the XState "context". */
10
6
  interface TextTracksActorContext {
11
7
  /** Cues added per track ID. Used for duplicate detection and snapshot observability. */
@@ -13,11 +9,6 @@ interface TextTracksActorContext {
13
9
  /** Segments whose cues have been fully added, keyed by track ID. Used for load planning. */
14
10
  segments: Record<string, Array<Pick<Segment, 'id' | 'startTime' | 'duration'>>>;
15
11
  }
16
- interface AddCuesMessage<C extends Cue = Cue> {
17
- type: 'add-cues';
18
- meta: CueSegmentMeta;
19
- cues: C[];
20
- }
21
12
  /**
22
13
  * Wipe the actor's `loaded` + `segments` context. Sent on source reset
23
14
  * (typically by `syncTextTracks` on state exit) so a subsequent
@@ -38,5 +29,5 @@ type TextTracksActorMessage<C extends Cue = Cue> = AddCuesMessage<C> | ClearMess
38
29
  */
39
30
  type TextTracksActor<C extends Cue = Cue> = TransitionActor<TextTracksActorContext, TextTracksActorMessage<C>>;
40
31
  //#endregion
41
- export { AddCuesMessage, ClearMessage, CueSegmentMeta, TextTracksActor, TextTracksActorContext, TextTracksActorMessage };
32
+ export { ClearMessage, TextTracksActor, TextTracksActorContext, TextTracksActorMessage };
42
33
  //# sourceMappingURL=text-tracks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-tracks.d.ts","names":[],"sources":["../../../../src/playback/actors/text-tracks.ts"],"mappings":";;;;;KAQY,cAAA,GAAiB,IAAA,CAAK,OAAA;EAA8C,OAAA;AAAA;;UAG/D,sBAAA;EAHY;EAK3B,MAAA,EAAQ,MAAA,SAAe,GAAA;EALuD;EAO9E,QAAA,EAAU,MAAA,SAAe,KAAA,CAAM,IAAA,CAAK,OAAA;AAAA;AAAA,UAGrB,cAAA,WAAyB,GAAA,GAAM,GAAA;EAC9C,IAAA;EACA,IAAA,EAAM,cAAA;EACN,IAAA,EAAM,CAAA;AAAA;;;;;;;;;UAWS,YAAA;EACf,IAAA;AAAA;AAAA,KAGU,sBAAA,WAAiC,GAAA,GAAM,GAAA,IAAO,cAAA,CAAe,CAAA,IAAK,YAAA;;;;;AAlB9E;;KA0BY,eAAA,WAA0B,GAAA,GAAM,GAAA,IAAO,eAAA,CAAgB,sBAAA,EAAwB,sBAAA,CAAuB,CAAA"}
1
+ {"version":3,"file":"text-tracks.d.ts","names":[],"sources":["../../../../src/playback/actors/text-tracks.ts"],"mappings":";;;;;UASiB;;EAEf,QAAQ,eAAe;;EAEvB,UAAU,eAAe,MAAM,KAAK;;;;;;;;;;UAWrB;EACf;;KAGU,uBAAuB,UAAU,MAAM,OAAO,eAAe,KAAK;;;;;;;KAQlE,gBAAgB,UAAU,MAAM,OAAO,gBAAgB,wBAAwB,uBAAuB"}
@@ -1,6 +1,15 @@
1
1
  import { MaybeResolvedPresentation } from "../../media/types/index.js";
2
-
2
+ import "../../core/signals/primitives.js";
3
+ import "../../core/composition/create-composition.js";
3
4
  //#region src/playback/behaviors/calculate-presentation-duration.d.ts
5
+ /**
6
+ * Input shape passed to the duration resolver. Represents the union of
7
+ * track-selection slots the default `getResolvedSelectedTrackDuration`
8
+ * resolver inspects to pick a representative resolved track. Variants
9
+ * that compose neither audio nor video selection still satisfy this
10
+ * shape — the missing fields read as `undefined` and the resolver
11
+ * falls through.
12
+ */
4
13
  interface PresentationDurationState {
5
14
  presentation?: MaybeResolvedPresentation;
6
15
  selectedVideoTrackId?: string;
@@ -13,5 +22,5 @@ interface PresentationDurationState {
13
22
  */
14
23
  type PresentationDurationResolver = (state: PresentationDurationState) => number | undefined;
15
24
  //#endregion
16
- export { PresentationDurationResolver };
25
+ export { PresentationDurationResolver, PresentationDurationState };
17
26
  //# sourceMappingURL=calculate-presentation-duration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"calculate-presentation-duration.d.ts","names":[],"sources":["../../../../src/playback/behaviors/calculate-presentation-duration.ts"],"mappings":";;;UAkCiB,yBAAA;EACf,YAAA,GAAe,yBAAA;EACf,oBAAA;EACA,oBAAA;AAAA;;;;;;KAQU,4BAAA,IAAgC,KAAA,EAAO,yBAAA"}
1
+ {"version":3,"file":"calculate-presentation-duration.d.ts","names":[],"sources":["../../../../src/playback/behaviors/calculate-presentation-duration.ts"],"mappings":";;;;;;;;;;;;UA0CiB;EACf,eAAe;EACf;EACA;;;;;;;KAQU,gCAAgC,OAAO"}
@@ -1,57 +1,35 @@
1
- import { snapshot, untrack, update } from "../../core/signals/primitives.js";
2
- import { defineBehavior } from "../../core/composition/create-composition.js";
1
+ import { untrack, update } from "../../core/signals/primitives.js";
3
2
  import { effect } from "../../core/signals/effect.js";
4
3
  //#region src/playback/behaviors/calculate-presentation-duration.ts
5
- /**
6
- * **Populate presentation duration via a config-supplied resolver.**
7
- *
8
- * Once a presentation is in place and its `duration` is still undefined,
9
- * calls `config.resolveDuration(state)` whenever a tracked slot changes. The
10
- * resolver decides what counts as "duration is now derivable" for the variant
11
- * in play; the behavior itself stays variant-agnostic:
12
- *
13
- * - **VoD** — derive from the first resolved selected track's `duration`
14
- * (video preferred, audio fallback). The HLS engine wires
15
- * `getResolvedSelectedTrackDuration` from `media/utils/track-selection.ts`
16
- * as the default. Audio-only falls out of the same resolver naturally
17
- * (no video selected → audio is the first resolved track).
18
- * - **Live** — return `Number.POSITIVE_INFINITY` once the presentation is
19
- * established as live. This is the MSE-spec value for `mediaSource.duration`
20
- * under live playback; downstream `updateMediaSourceDuration` propagates it through.
21
- *
22
- * Validation: writes whatever the resolver returns as long as it's a positive
23
- * number, including `Infinity`. `undefined` / `NaN` / `<= 0` are skipped.
24
- *
25
- * Fires at most once per presentation — an already-set `duration` is never
26
- * overwritten, and the next reset arrives structurally when a new
27
- * (unresolved) presentation replaces the current one. The resolver may
28
- * return `undefined` while duration is still indeterminate; subsequent
29
- * tracked-slot changes re-run the effect until the resolver commits a value.
30
- *
31
- * Downstream of `resolveVideoTrack` / `resolveAudioTrack`; upstream of
32
- * `updateMediaSourceDuration` (which writes the value through to `mediaSource.duration`).
33
- */
34
4
  function calculatePresentationDurationSetup({ state, config }) {
35
5
  return effect(() => {
36
6
  const presentation = state.presentation.get();
37
7
  if (!presentation || presentation.duration !== void 0) return;
38
- const duration = config.resolveDuration(untrack(() => snapshot(state)));
8
+ const resolverInput = untrack(() => ({
9
+ presentation,
10
+ selectedVideoTrackId: state.selectedVideoTrackId?.get(),
11
+ selectedAudioTrackId: state.selectedAudioTrackId?.get()
12
+ }));
13
+ const duration = config.resolveDuration(resolverInput);
39
14
  if (duration === void 0 || Number.isNaN(duration) || duration <= 0) return;
40
- update(state.presentation, (current) => current ? {
41
- ...current,
42
- duration
43
- } : current);
15
+ update(state.presentation, { duration });
44
16
  });
45
17
  }
46
- const calculatePresentationDuration = defineBehavior({
47
- stateKeys: [
48
- "presentation",
49
- "selectedVideoTrackId",
50
- "selectedAudioTrackId"
51
- ],
18
+ /**
19
+ * `calculatePresentationDuration` uses a manual `Behavior<>` literal
20
+ * (rather than `defineBehavior`) so it can declare just `presentation`
21
+ * in its stateKeys while the typed setup-param shape includes the
22
+ * optional `selectedVideoTrackId` / `selectedAudioTrackId` reads used
23
+ * at runtime. Mirrors the pattern in `endOfStream` for the same
24
+ * reason: the behavior is uniform-across-tracks and reads slots
25
+ * contributed by other behaviors, so it shouldn't leak those slot
26
+ * declarations into variants that don't compose the contributors.
27
+ */
28
+ const calculatePresentationDuration = {
29
+ stateKeys: ["presentation"],
52
30
  contextKeys: [],
53
31
  setup: calculatePresentationDurationSetup
54
- });
32
+ };
55
33
  //#endregion
56
34
  export { calculatePresentationDuration };
57
35
 
@@ -1 +1 @@
1
- {"version":3,"file":"calculate-presentation-duration.js","names":[],"sources":["../../../../src/playback/behaviors/calculate-presentation-duration.ts"],"sourcesContent":["/**\n * **Populate presentation duration via a config-supplied resolver.**\n *\n * Once a presentation is in place and its `duration` is still undefined,\n * calls `config.resolveDuration(state)` whenever a tracked slot changes. The\n * resolver decides what counts as \"duration is now derivable\" for the variant\n * in play; the behavior itself stays variant-agnostic:\n *\n * - **VoD** — derive from the first resolved selected track's `duration`\n * (video preferred, audio fallback). The HLS engine wires\n * `getResolvedSelectedTrackDuration` from `media/utils/track-selection.ts`\n * as the default. Audio-only falls out of the same resolver naturally\n * (no video selected → audio is the first resolved track).\n * - **Live** — return `Number.POSITIVE_INFINITY` once the presentation is\n * established as live. This is the MSE-spec value for `mediaSource.duration`\n * under live playback; downstream `updateMediaSourceDuration` propagates it through.\n *\n * Validation: writes whatever the resolver returns as long as it's a positive\n * number, including `Infinity`. `undefined` / `NaN` / `<= 0` are skipped.\n *\n * Fires at most once per presentation — an already-set `duration` is never\n * overwritten, and the next reset arrives structurally when a new\n * (unresolved) presentation replaces the current one. The resolver may\n * return `undefined` while duration is still indeterminate; subsequent\n * tracked-slot changes re-run the effect until the resolver commits a value.\n *\n * Downstream of `resolveVideoTrack` / `resolveAudioTrack`; upstream of\n * `updateMediaSourceDuration` (which writes the value through to `mediaSource.duration`).\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { type ReadonlySignal, type Signal, snapshot, untrack, update } from '../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../media/types';\n\nexport interface PresentationDurationState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * Resolver supplied via `config.resolveDuration`. Returns the duration to\n * write to `presentation.duration`, or `undefined` when it isn't yet\n * derivable. Positive `Infinity` is the canonical live value.\n */\nexport type PresentationDurationResolver = (state: PresentationDurationState) => number | undefined;\n\nexport interface PresentationDurationConfig {\n resolveDuration: PresentationDurationResolver;\n}\n\nfunction calculatePresentationDurationSetup({\n state,\n config,\n}: {\n state: {\n presentation: Signal<PresentationDurationState['presentation']>;\n selectedVideoTrackId: ReadonlySignal<PresentationDurationState['selectedVideoTrackId']>;\n selectedAudioTrackId: ReadonlySignal<PresentationDurationState['selectedAudioTrackId']>;\n };\n config: PresentationDurationConfig;\n}): () => void {\n return effect(() => {\n const presentation = state.presentation.get();\n if (!presentation || presentation.duration !== undefined) return;\n\n // presentation drives the effect; selection-only changes can't yield a\n // writable duration on their own (resolver returns undefined until a\n // track resolves, at which point resolve-track writes back through\n // state.presentation anyway), so we read the rest untracked.\n const duration = config.resolveDuration(untrack(() => snapshot(state)));\n if (duration === undefined || Number.isNaN(duration) || duration <= 0) return;\n\n update(state.presentation, (current) => (current ? { ...current, duration } : current));\n });\n}\n\nexport const calculatePresentationDuration = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: calculatePresentationDurationSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAS,mCAAmC,EAC1C,OACA,UAQa;AACb,QAAO,aAAa;EAClB,MAAM,eAAe,MAAM,aAAa,KAAK;AAC7C,MAAI,CAAC,gBAAgB,aAAa,aAAa,KAAA,EAAW;EAM1D,MAAM,WAAW,OAAO,gBAAgB,cAAc,SAAS,MAAM,CAAC,CAAC;AACvE,MAAI,aAAa,KAAA,KAAa,OAAO,MAAM,SAAS,IAAI,YAAY,EAAG;AAEvE,SAAO,MAAM,eAAe,YAAa,UAAU;GAAE,GAAG;GAAS;GAAU,GAAG,QAAS;GACvF;;AAGJ,MAAa,gCAAgC,eAAe;CAC1D,WAAW;EAAC;EAAgB;EAAwB;EAAuB;CAC3E,aAAa,EAAE;CACf,OAAO;CACR,CAAC"}
1
+ {"version":3,"file":"calculate-presentation-duration.js","names":[],"sources":["../../../../src/playback/behaviors/calculate-presentation-duration.ts"],"sourcesContent":["/**\n * **Populate presentation duration via a config-supplied resolver.**\n *\n * Once a presentation is in place and its `duration` is still undefined,\n * calls `config.resolveDuration(state)` whenever a tracked slot changes. The\n * resolver decides what counts as \"duration is now derivable\" for the variant\n * in play; the behavior itself stays variant-agnostic:\n *\n * - **VoD** — derive from the first resolved selected track's `duration`\n * (video preferred, audio fallback). The HLS engine wires\n * `getResolvedSelectedTrackDuration` from `media/utils/track-selection.ts`\n * as the default. Audio-only falls out of the same resolver naturally\n * (no video selected → audio is the first resolved track).\n * - **Live** — return `Number.POSITIVE_INFINITY` once the presentation is\n * established as live. This is the MSE-spec value for `mediaSource.duration`\n * under live playback; downstream `updateMediaSourceDuration` propagates it through.\n *\n * Validation: writes whatever the resolver returns as long as it's a positive\n * number, including `Infinity`. `undefined` / `NaN` / `<= 0` are skipped.\n *\n * Fires at most once per presentation — an already-set `duration` is never\n * overwritten, and the next reset arrives structurally when a new\n * (unresolved) presentation replaces the current one. The resolver may\n * return `undefined` while duration is still indeterminate; subsequent\n * tracked-slot changes re-run the effect until the resolver commits a value.\n *\n * Downstream of `resolveVideoTrack` / `resolveAudioTrack`; upstream of\n * `updateMediaSourceDuration` (which writes the value through to `mediaSource.duration`).\n */\nimport type { Behavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { type ReadonlySignal, type Signal, untrack, update } from '../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../media/types';\n\n/**\n * Input shape passed to the duration resolver. Represents the union of\n * track-selection slots the default `getResolvedSelectedTrackDuration`\n * resolver inspects to pick a representative resolved track. Variants\n * that compose neither audio nor video selection still satisfy this\n * shape — the missing fields read as `undefined` and the resolver\n * falls through.\n */\nexport interface PresentationDurationState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n}\n\n/**\n * Resolver supplied via `config.resolveDuration`. Returns the duration to\n * write to `presentation.duration`, or `undefined` when it isn't yet\n * derivable. Positive `Infinity` is the canonical live value.\n */\nexport type PresentationDurationResolver = (state: PresentationDurationState) => number | undefined;\n\nexport interface PresentationDurationConfig {\n resolveDuration: PresentationDurationResolver;\n}\n\nfunction calculatePresentationDurationSetup({\n state,\n config,\n}: {\n state: {\n presentation: Signal<PresentationDurationState['presentation']>;\n // selectedVideoTrackId / selectedAudioTrackId are read defensively —\n // they are *not* declared in this behavior's stateKeys. The slots are\n // contributed by other behaviors (`switchVideoTrack` writes the\n // video slot in the default engine; `selectAudioTrack` writes the\n // audio slot) which compose conditionally per engine variant. Treating\n // each signal as optional lets calculatePresentationDuration stay\n // variant-agnostic — it feeds whatever's present to the resolver and\n // lets the resolver decide.\n selectedVideoTrackId?: ReadonlySignal<PresentationDurationState['selectedVideoTrackId']>;\n selectedAudioTrackId?: ReadonlySignal<PresentationDurationState['selectedAudioTrackId']>;\n };\n config: PresentationDurationConfig;\n}): () => void {\n return effect(() => {\n const presentation = state.presentation.get();\n if (!presentation || presentation.duration !== undefined) return;\n\n // presentation drives the effect; selection-only changes can't yield a\n // writable duration on their own (resolver returns undefined until a\n // track resolves, at which point resolve-track writes back through\n // state.presentation anyway), so we read the rest untracked.\n const resolverInput: PresentationDurationState = untrack(() => ({\n presentation,\n selectedVideoTrackId: state.selectedVideoTrackId?.get(),\n selectedAudioTrackId: state.selectedAudioTrackId?.get(),\n }));\n const duration = config.resolveDuration(resolverInput);\n if (duration === undefined || Number.isNaN(duration) || duration <= 0) return;\n\n // Patch-object form requires `T extends object`; `state.presentation`'s\n // T is `MaybeResolvedPresentation | undefined`, which doesn't satisfy\n // the constraint. The guard at the top of the effect already\n // established that the slot holds a defined value, so cast to narrow\n // the signal type and use the cleaner merge form.\n update(state.presentation as Signal<MaybeResolvedPresentation>, { duration });\n });\n}\n\n/**\n * `calculatePresentationDuration` uses a manual `Behavior<>` literal\n * (rather than `defineBehavior`) so it can declare just `presentation`\n * in its stateKeys while the typed setup-param shape includes the\n * optional `selectedVideoTrackId` / `selectedAudioTrackId` reads used\n * at runtime. Mirrors the pattern in `endOfStream` for the same\n * reason: the behavior is uniform-across-tracks and reads slots\n * contributed by other behaviors, so it shouldn't leak those slot\n * declarations into variants that don't compose the contributors.\n */\nexport const calculatePresentationDuration: Behavior<\n { presentation: Signal<PresentationDurationState['presentation']> },\n Record<string, never>,\n PresentationDurationConfig\n> = {\n stateKeys: ['presentation'],\n contextKeys: [],\n setup: calculatePresentationDurationSetup,\n};\n"],"mappings":";;;AA2DA,SAAS,mCAAmC,EAC1C,OACA,UAgBa;CACb,OAAO,aAAa;EAClB,MAAM,eAAe,MAAM,aAAa,IAAI;EAC5C,IAAI,CAAC,gBAAgB,aAAa,aAAa,KAAA,GAAW;EAM1D,MAAM,gBAA2C,eAAe;GAC9D;GACA,sBAAsB,MAAM,sBAAsB,IAAI;GACtD,sBAAsB,MAAM,sBAAsB,IAAI;EACxD,EAAE;EACF,MAAM,WAAW,OAAO,gBAAgB,aAAa;EACrD,IAAI,aAAa,KAAA,KAAa,OAAO,MAAM,QAAQ,KAAK,YAAY,GAAG;EAOvE,OAAO,MAAM,cAAmD,EAAE,SAAS,CAAC;CAC9E,CAAC;AACH;;;;;;;;;;;AAYA,MAAa,gCAIT;CACF,WAAW,CAAC,cAAc;CAC1B,aAAa,CAAC;CACd,OAAO;AACT"}