@videojs/spf 10.0.0-beta.25 → 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 (329) hide show
  1. package/README.md +5 -0
  2. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  3. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  4. package/dist/default/core/composition/create-composition.js +1 -1
  5. package/dist/default/core/composition/create-composition.js.map +1 -1
  6. package/dist/default/core/composition/share-signals.js.map +1 -1
  7. package/dist/default/core/machine.js.map +1 -1
  8. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  9. package/dist/default/core/signals/effect.js.map +1 -1
  10. package/dist/default/core/signals/primitives.js.map +1 -1
  11. package/dist/default/core/tasks/task.js.map +1 -1
  12. package/dist/default/hls.js +2 -1
  13. package/dist/default/media/abr/quality-selection.js.map +1 -1
  14. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  15. package/dist/default/media/buffer/forward-buffer.js +37 -3
  16. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  17. package/dist/default/media/dom/capabilities.js.map +1 -1
  18. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  19. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  20. package/dist/default/media/dom/mse/duration.js +27 -10
  21. package/dist/default/media/dom/mse/duration.js.map +1 -1
  22. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  23. package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
  24. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  25. package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
  26. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  27. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  28. package/dist/default/media/hls/parse-attributes.js +1 -1
  29. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  30. package/dist/default/media/hls/parse-media-playlist.js +8 -3
  31. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  32. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  33. package/dist/default/media/hls/resolve-url.js.map +1 -1
  34. package/dist/default/media/media-tracks/media-tracks.js +101 -0
  35. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  36. package/dist/default/media/mp4/box.js +58 -0
  37. package/dist/default/media/mp4/box.js.map +1 -0
  38. package/dist/default/media/mp4/timestamp-origin.js +107 -0
  39. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  40. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  41. package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
  42. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  43. package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
  44. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  45. package/dist/default/media/types/index.js.map +1 -1
  46. package/dist/default/media/utils/cdn.js.map +1 -1
  47. package/dist/default/media/utils/preload.js.map +1 -1
  48. package/dist/default/media/utils/track-selection.js +1 -1
  49. package/dist/default/media/utils/track-selection.js.map +1 -1
  50. package/dist/default/media/utils/tracks.js +3 -3
  51. package/dist/default/media/utils/tracks.js.map +1 -1
  52. package/dist/default/media-tracks.js +2 -0
  53. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  54. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  55. package/dist/default/network/ewma.js +1 -1
  56. package/dist/default/network/ewma.js.map +1 -1
  57. package/dist/default/network/fetch.js.map +1 -1
  58. package/dist/default/playback/actors/dom/segment-loader.js +61 -95
  59. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  60. package/dist/default/playback/actors/dom/source-buffer.js +1 -0
  61. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  62. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  63. package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
  64. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  65. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  66. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
  67. package/dist/default/playback/behaviors/dom/airplay.js +211 -0
  68. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  69. package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
  70. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  71. package/dist/default/playback/behaviors/dom/end-of-stream.js +12 -1
  72. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  73. package/dist/default/playback/behaviors/dom/load-segments.js +5 -1
  74. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  75. package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
  76. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  77. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +17 -4
  78. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  79. package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
  80. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  81. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  82. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  83. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  84. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  85. package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
  86. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  87. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  88. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +2 -1
  89. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  90. package/dist/default/playback/behaviors/establish-start-media-time.js +123 -0
  91. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  92. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  93. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  94. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  95. package/dist/default/playback/behaviors/setup-failover-monitor.js +1 -1
  96. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
  97. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  98. package/dist/default/playback/behaviors/track-switching.js +63 -1
  99. package/dist/default/playback/behaviors/track-switching.js.map +1 -1
  100. package/dist/default/playback/engines/background-video/adapter.js.map +1 -1
  101. package/dist/default/playback/engines/background-video/engine.js.map +1 -1
  102. package/dist/default/playback/engines/hls/adapter-audio-only.js +14 -9
  103. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -1
  104. package/dist/default/playback/engines/hls/adapter.js +13 -11
  105. package/dist/default/playback/engines/hls/adapter.js.map +1 -1
  106. package/dist/default/playback/engines/hls/engine-audio-only.js +16 -2
  107. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
  108. package/dist/default/playback/engines/hls/engine.js +19 -2
  109. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  110. package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
  111. package/dist/default/playback/primitives/head-peek.js +44 -0
  112. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  113. package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
  114. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  115. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  116. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  117. package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
  118. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  119. package/dist/default/playback/primitives/track-types.js.map +1 -1
  120. package/dist/dev/core/actors/actor.d.ts +0 -1
  121. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  122. package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
  123. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  124. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  125. package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
  126. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  127. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  128. package/dist/dev/core/composition/create-composition.d.ts +10 -11
  129. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  130. package/dist/dev/core/composition/create-composition.js +1 -1
  131. package/dist/dev/core/composition/create-composition.js.map +1 -1
  132. package/dist/dev/core/composition/share-signals.d.ts +0 -1
  133. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  134. package/dist/dev/core/composition/share-signals.js.map +1 -1
  135. package/dist/dev/core/machine.d.ts +1 -0
  136. package/dist/dev/core/machine.d.ts.map +1 -1
  137. package/dist/dev/core/machine.js.map +1 -1
  138. package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
  139. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  140. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  141. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  142. package/dist/dev/core/signals/effect.js.map +1 -1
  143. package/dist/dev/core/signals/primitives.d.ts +1 -2
  144. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  145. package/dist/dev/core/signals/primitives.js.map +1 -1
  146. package/dist/dev/core/tasks/task.d.ts +2 -2
  147. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  148. package/dist/dev/core/tasks/task.js.map +1 -1
  149. package/dist/dev/hls.d.ts +3 -1
  150. package/dist/dev/hls.js +2 -1
  151. package/dist/dev/index.d.ts +0 -1
  152. package/dist/dev/index.d.ts.map +1 -1
  153. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  154. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  155. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  156. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  157. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  158. package/dist/dev/media/buffer/forward-buffer.js +37 -3
  159. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  160. package/dist/dev/media/dom/capabilities.js.map +1 -1
  161. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
  162. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  163. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  164. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
  165. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  166. package/dist/dev/media/dom/mse/duration.js +27 -10
  167. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  168. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  169. package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
  170. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  171. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
  172. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  173. package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
  174. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  175. package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
  176. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  177. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  178. package/dist/dev/media/hls/parse-attributes.js +1 -1
  179. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  180. package/dist/dev/media/hls/parse-media-playlist.js +8 -3
  181. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  182. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  183. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  184. package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
  185. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  186. package/dist/dev/media/media-tracks/media-tracks.js +101 -0
  187. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  188. package/dist/dev/media/mp4/box.js +58 -0
  189. package/dist/dev/media/mp4/box.js.map +1 -0
  190. package/dist/dev/media/mp4/timestamp-origin.js +107 -0
  191. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  192. package/dist/dev/media/primitives/select-tracks.d.ts +0 -1
  193. package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -1
  194. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  195. package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
  196. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  197. package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
  198. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  199. package/dist/dev/media/types/index.d.ts +44 -1
  200. package/dist/dev/media/types/index.d.ts.map +1 -1
  201. package/dist/dev/media/types/index.js.map +1 -1
  202. package/dist/dev/media/utils/cdn.d.ts.map +1 -1
  203. package/dist/dev/media/utils/cdn.js.map +1 -1
  204. package/dist/dev/media/utils/preload.js.map +1 -1
  205. package/dist/dev/media/utils/track-selection.js +1 -1
  206. package/dist/dev/media/utils/track-selection.js.map +1 -1
  207. package/dist/dev/media/utils/tracks.js +3 -3
  208. package/dist/dev/media/utils/tracks.js.map +1 -1
  209. package/dist/dev/media-tracks.d.ts +3 -0
  210. package/dist/dev/media-tracks.js +2 -0
  211. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  212. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  213. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  214. package/dist/dev/network/ewma.js +1 -1
  215. package/dist/dev/network/ewma.js.map +1 -1
  216. package/dist/dev/network/fetch.js.map +1 -1
  217. package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
  218. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  219. package/dist/dev/playback/actors/dom/segment-loader.js +61 -95
  220. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  221. package/dist/dev/playback/actors/dom/source-buffer.d.ts +3 -33
  222. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  223. package/dist/dev/playback/actors/dom/source-buffer.js +1 -0
  224. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  225. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
  226. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  227. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
  228. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  229. package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
  230. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  231. package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
  232. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  233. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +3 -2
  234. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  235. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  236. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
  237. package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
  238. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  239. package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
  240. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  241. package/dist/dev/playback/behaviors/dom/end-of-stream.js +12 -1
  242. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  243. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
  244. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  245. package/dist/dev/playback/behaviors/dom/load-segments.js +5 -1
  246. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  247. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
  248. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  249. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +17 -4
  250. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  251. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
  252. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  253. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
  254. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  255. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  256. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  257. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  258. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
  259. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  260. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  261. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
  262. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  263. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
  264. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  265. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
  266. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  267. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  268. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +2 -1
  269. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  270. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +31 -0
  271. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  272. package/dist/dev/playback/behaviors/establish-start-media-time.js +123 -0
  273. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  274. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
  275. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  276. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  277. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  278. package/dist/dev/playback/behaviors/select-tracks.d.ts +2 -1
  279. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
  280. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  281. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +2 -0
  282. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
  283. package/dist/dev/playback/behaviors/setup-failover-monitor.js +1 -1
  284. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
  285. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  286. package/dist/dev/playback/behaviors/track-switching.js +63 -1
  287. package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
  288. package/dist/dev/playback/engines/background-video/adapter.d.ts +0 -1
  289. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -1
  290. package/dist/dev/playback/engines/background-video/adapter.js.map +1 -1
  291. package/dist/dev/playback/engines/background-video/engine.d.ts +1 -2
  292. package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -1
  293. package/dist/dev/playback/engines/background-video/engine.js.map +1 -1
  294. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +4 -3
  295. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -1
  296. package/dist/dev/playback/engines/hls/adapter-audio-only.js +14 -9
  297. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -1
  298. package/dist/dev/playback/engines/hls/adapter.d.ts +3 -5
  299. package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -1
  300. package/dist/dev/playback/engines/hls/adapter.js +13 -11
  301. package/dist/dev/playback/engines/hls/adapter.js.map +1 -1
  302. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +31 -2
  303. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
  304. package/dist/dev/playback/engines/hls/engine-audio-only.js +16 -2
  305. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
  306. package/dist/dev/playback/engines/hls/engine.d.ts +49 -3
  307. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  308. package/dist/dev/playback/engines/hls/engine.js +19 -2
  309. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  310. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  311. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  312. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
  313. package/dist/dev/playback/primitives/head-peek.js +44 -0
  314. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  315. package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
  316. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  317. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  318. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  319. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  320. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
  321. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  322. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
  323. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  324. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
  325. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  326. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  327. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  328. package/dist/dev/playback/primitives/track-types.js.map +1 -1
  329. package/package.json +12 -7
@@ -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,5 +1,6 @@
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
4
5
  /**
5
6
  * Input shape passed to the duration resolver. Represents the union of
@@ -21,5 +22,5 @@ interface PresentationDurationState {
21
22
  */
22
23
  type PresentationDurationResolver = (state: PresentationDurationState) => number | undefined;
23
24
  //#endregion
24
- export { PresentationDurationResolver };
25
+ export { PresentationDurationResolver, PresentationDurationState };
25
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":";;;;;;;;;;;UA0CiB,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 +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 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;AACb,QAAO,aAAa;EAClB,MAAM,eAAe,MAAM,aAAa,KAAK;AAC7C,MAAI,CAAC,gBAAgB,aAAa,aAAa,KAAA,EAAW;EAM1D,MAAM,gBAA2C,eAAe;GAC9D;GACA,sBAAsB,MAAM,sBAAsB,KAAK;GACvD,sBAAsB,MAAM,sBAAsB,KAAK;GACxD,EAAE;EACH,MAAM,WAAW,OAAO,gBAAgB,cAAc;AACtD,MAAI,aAAa,KAAA,KAAa,OAAO,MAAM,SAAS,IAAI,YAAY,EAAG;AAOvE,SAAO,MAAM,cAAmD,EAAE,UAAU,CAAC;GAC7E;;;;;;;;;;;;AAaJ,MAAa,gCAIT;CACF,WAAW,CAAC,eAAe;CAC3B,aAAa,EAAE;CACf,OAAO;CACR"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"derive-cdn-priority.js","names":["getCdnId","defaultGetCdnId"],"sources":["../../../../src/playback/behaviors/derive-cdn-priority.ts"],"sourcesContent":["/**\n * **Session-level CDN priority.** While a presentation is resolved, owns the\n * `cdnPriority` signal: the distinct CDNs the source is served from (origin of\n * each track's URL), in manifest priority order — most-preferred first. Cleared\n * on src unload. The name mirrors HLS content steering's `PATHWAY-PRIORITY`.\n *\n * Redundant-stream sources (e.g. Mux's `?redundant_streams=true`) list the same\n * content on multiple hosts, so the candidate tracks already include one variant\n * per CDN. This behavior publishes *which* CDNs exist and their priority; the\n * `preferActiveCdn` scope rule in `track-switching` reads `cdnPriority` and\n * narrows each type's candidates to the first CDN with surviving tracks — so\n * video / audio / text all resolve from one host (the shared list is the\n * per-presentation coherence guarantee).\n *\n * The \"active\" CDN is not stored — it's derived by the scope as the\n * highest-priority entry in `cdnPriority` that still has tracks after the\n * constraints pre-pass. That makes failover a pure consequence of the (future)\n * failed-CDN constraint: when the primary's tracks are pruned during cooldown,\n * the scope falls to the next CDN; when the primary recovers, it snaps back.\n * Content steering, when it lands, reorders `cdnPriority` (pathway priority as a\n * sort key).\n *\n * Lifecycle: `'presentation-unresolved'` ↔ `'presentation-resolved'`, mirroring\n * `setupTrackSwitching`. The resolved state owns the signal; its entry-returned\n * cleanup clears it on exit (canonical cleanup-binds-to-setup per `reactors.md`).\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\nimport { getCdnId as defaultGetCdnId, type GetCdnId, getOrderedCdnIds } from '../../media/utils/cdn';\n\nexport interface DeriveCdnPriorityState {\n presentation?: MaybeResolvedPresentation;\n cdnPriority?: string[];\n}\n\nconst samePriority = (a: string[] | undefined, b: string[]): boolean =>\n !!a && a.length === b.length && a.every((cdn, i) => cdn === b[i]);\n\n/**\n * Manage `cdnPriority`: publish the manifest-ordered CDN list on src load, clear\n * on src unload.\n *\n * @example\n * const reactor = deriveCdnPriority.setup({ state });\n */\nexport const deriveCdnPriority = defineBehavior({\n stateKeys: ['presentation', 'cdnPriority'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: {\n presentation: ReadonlySignal<DeriveCdnPriorityState['presentation']>;\n cdnPriority: Signal<DeriveCdnPriorityState['cdnPriority']>;\n };\n config?: { getCdnId?: GetCdnId };\n }) => {\n const getCdnId = config.getCdnId ?? defaultGetCdnId;\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Cleanup-binds-to-setup: the CDN priority list is valid for exactly\n // 'presentation-resolved'. Clear fires on exit (src unload + destroy).\n entry: () => () => state.cdnPriority.set(undefined),\n effects: [\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return;\n const next = getOrderedCdnIds(presentation, getCdnId);\n // Skip the write when the CDN set is unchanged — a live reload swaps\n // in a new presentation object with the same hosts, and re-setting a\n // fresh array would re-fire the scope for an identical result.\n // `peek` reads without subscribing, so this never self-triggers.\n if (!samePriority(peek(state.cdnPriority), next)) state.cdnPriority.set(next);\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAM,gBAAgB,GAAyB,MAC7C,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,KAAK,MAAM,QAAQ,EAAE,GAAG;;;;;;;;AASnE,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,cAAc;CAC1C,aAAa,EAAE;CACf,QAAQ,EACN,OACA,SAAS,EAAE,OAOP;EACJ,MAAMA,aAAW,OAAO,YAAYC;EACpC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,KAAK,CAAC,GAC3C,0BACA,0BACN;AAED,SAAO,qBAAqB;GAC1B,SAAS;GACT,eAAe,mBAAmB,KAAK;GACvC,QAAQ;IACN,2BAA2B,EAAE;IAC7B,yBAAyB;KAGvB,mBAAmB,MAAM,YAAY,IAAI,KAAA,EAAU;KACnD,SAAS,OACD;MACJ,MAAM,eAAe,MAAM,aAAa,KAAK;AAC7C,UAAI,CAAC,uBAAuB,aAAa,CAAE;MAC3C,MAAM,OAAO,iBAAiB,cAAcD,WAAS;AAKrD,UAAI,CAAC,aAAa,KAAK,MAAM,YAAY,EAAE,KAAK,CAAE,OAAM,YAAY,IAAI,KAAK;OAEhF;KACF;IACF;GACF,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"derive-cdn-priority.js","names":["getCdnId","defaultGetCdnId"],"sources":["../../../../src/playback/behaviors/derive-cdn-priority.ts"],"sourcesContent":["/**\n * **Session-level CDN priority.** While a presentation is resolved, owns the\n * `cdnPriority` signal: the distinct CDNs the source is served from (origin of\n * each track's URL), in manifest priority order — most-preferred first. Cleared\n * on src unload. The name mirrors HLS content steering's `PATHWAY-PRIORITY`.\n *\n * Redundant-stream sources (e.g. Mux's `?redundant_streams=true`) list the same\n * content on multiple hosts, so the candidate tracks already include one variant\n * per CDN. This behavior publishes *which* CDNs exist and their priority; the\n * `preferActiveCdn` scope rule in `track-switching` reads `cdnPriority` and\n * narrows each type's candidates to the first CDN with surviving tracks — so\n * video / audio / text all resolve from one host (the shared list is the\n * per-presentation coherence guarantee).\n *\n * The \"active\" CDN is not stored — it's derived by the scope as the\n * highest-priority entry in `cdnPriority` that still has tracks after the\n * constraints pre-pass. That makes failover a pure consequence of the (future)\n * failed-CDN constraint: when the primary's tracks are pruned during cooldown,\n * the scope falls to the next CDN; when the primary recovers, it snaps back.\n * Content steering, when it lands, reorders `cdnPriority` (pathway priority as a\n * sort key).\n *\n * Lifecycle: `'presentation-unresolved'` ↔ `'presentation-resolved'`, mirroring\n * `setupTrackSwitching`. The resolved state owns the signal; its entry-returned\n * cleanup clears it on exit (canonical cleanup-binds-to-setup per `reactors.md`).\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\nimport { getCdnId as defaultGetCdnId, type GetCdnId, getOrderedCdnIds } from '../../media/utils/cdn';\n\nexport interface DeriveCdnPriorityState {\n presentation?: MaybeResolvedPresentation;\n cdnPriority?: string[];\n}\n\nconst samePriority = (a: string[] | undefined, b: string[]): boolean =>\n !!a && a.length === b.length && a.every((cdn, i) => cdn === b[i]);\n\n/**\n * Manage `cdnPriority`: publish the manifest-ordered CDN list on src load, clear\n * on src unload.\n *\n * @example\n * const reactor = deriveCdnPriority.setup({ state });\n */\nexport const deriveCdnPriority = defineBehavior({\n stateKeys: ['presentation', 'cdnPriority'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: {\n presentation: ReadonlySignal<DeriveCdnPriorityState['presentation']>;\n cdnPriority: Signal<DeriveCdnPriorityState['cdnPriority']>;\n };\n config?: { getCdnId?: GetCdnId };\n }) => {\n const getCdnId = config.getCdnId ?? defaultGetCdnId;\n const derivedStateSignal = computed(() =>\n isResolvedPresentation(state.presentation.get())\n ? ('presentation-resolved' as const)\n : ('presentation-unresolved' as const)\n );\n\n return createMachineReactor({\n initial: 'presentation-unresolved',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'presentation-unresolved': {},\n 'presentation-resolved': {\n // Cleanup-binds-to-setup: the CDN priority list is valid for exactly\n // 'presentation-resolved'. Clear fires on exit (src unload + destroy).\n entry: () => () => state.cdnPriority.set(undefined),\n effects: [\n () => {\n const presentation = state.presentation.get();\n if (!isResolvedPresentation(presentation)) return;\n const next = getOrderedCdnIds(presentation, getCdnId);\n // Skip the write when the CDN set is unchanged — a live reload swaps\n // in a new presentation object with the same hosts, and re-setting a\n // fresh array would re-fire the scope for an identical result.\n // `peek` reads without subscribing, so this never self-triggers.\n if (!samePriority(peek(state.cdnPriority), next)) state.cdnPriority.set(next);\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAM,gBAAgB,GAAyB,MAC7C,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,KAAK,MAAM,QAAQ,EAAE,EAAE;;;;;;;;AASlE,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,aAAa;CACzC,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAON;EACJ,MAAMA,aAAW,OAAO,YAAYC;EACpC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;EAEA,OAAO,qBAAqB;GAC1B,SAAS;GACT,eAAe,mBAAmB,IAAI;GACtC,QAAQ;IACN,2BAA2B,CAAC;IAC5B,yBAAyB;KAGvB,mBAAmB,MAAM,YAAY,IAAI,KAAA,CAAS;KAClD,SAAS,OACD;MACJ,MAAM,eAAe,MAAM,aAAa,IAAI;MAC5C,IAAI,CAAC,uBAAuB,YAAY,GAAG;MAC3C,MAAM,OAAO,iBAAiB,cAAcD,UAAQ;MAKpD,IAAI,CAAC,aAAa,KAAK,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,YAAY,IAAI,IAAI;KAC9E,CACF;IACF;GACF;EACF,CAAC;CACH;AACF,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { computed, peek } from "../../../core/signals/primitives.js";
2
+ import { defineBehavior } from "../../../core/composition/create-composition.js";
3
+ import { effect } from "../../../core/signals/effect.js";
4
+ import { createMachineReactor } from "../../../core/reactors/create-machine-reactor.js";
5
+ import { isWebKitAirPlayCapable, listen } from "@videojs/utils/dom";
6
+ //#region src/playback/behaviors/dom/airplay.ts
7
+ /**
8
+ * **Bridge MSE playback to AirPlay on WebKit.**
9
+ * MSE streams can't be handed to an AirPlay receiver directly.
10
+ * The WebKit-recommended workaround is to append a fallback
11
+ * `<source type="application/x-mpegURL">` carrying the original manifest URL:
12
+ * Safari exposes the AirPlay picker and, when a wireless target is selected,
13
+ * plays that native-HLS source on the receiver. The session state (WebKit's
14
+ * wireless flag, falling edge debounced — see `REMOTE_INACTIVE_SETTLE_MS`) is
15
+ * written straight to its policy consequences, declared here so the
16
+ * cause→policy mapping stays with the feature:
17
+ *
18
+ * - `state.loadingSuspended` — held while the session is live. Observed by
19
+ * the `loadXSegments` dispatchers (no fetching alongside the receiver) and
20
+ * by `setupMediaSource` (its post-close rebuild waits — attaching runs
21
+ * `element.load()` under the live receiver, which destroys a session still
22
+ * being established). The suspension this behavior holds doubles as its own
23
+ * session fact — same writer, same edges.
24
+ * - `state.startPosition` — one-shot command: the position is captured from
25
+ * the element at the session's settled end (still receiver-mirrored) and
26
+ * written once the rebuild's `load()` resets the element (its `'emptied'`),
27
+ * so `applyStartPosition` applies it to the rebuilt source — never to the
28
+ * pre-rebuild element — and starts it where the receiver left off. The
29
+ * playing state rides the same snapshot but stays behavior-local: this
30
+ * behavior itself calls `play()` once the command has been *consumed* — i.e.
31
+ * after the seek — when the receiver was playing at session end. The whole
32
+ * restore is bound to the presentation the session owned and retracted if
33
+ * that changes.
34
+ *
35
+ * A source change during a live session releases the hold rather than deferring
36
+ * until the session ends, so the rebuild runs and WebKit switches the receiver
37
+ * to the newly-built AirPlay alternate. Measured, not contracted — see the
38
+ * effect below.
39
+ * https://webkit.org/blog/15036/how-to-use-media-source-extensions-with-airplay/
40
+ *
41
+ * Single-positive-state reactor (`'preconditions-unmet'` ↔ `'airplay-capable'`):
42
+ * gated on a WebKit-AirPlay-capable media element being in scope. The entry —
43
+ * gated on `context.mediaSource` — appends the fallback `<source>` (kept
44
+ * current from `state.presentation`) and enables the AirPlay picker once the
45
+ * MediaSource is open, removing the source the moment the MediaSource detaches
46
+ * so it never survives an MSE teardown. State-exit cleanup (author opt-out,
47
+ * detach, source reset, behavior destroy) removes the source and restores the
48
+ * element's `disableRemotePlayback` default. No-op on non-WebKit platforms
49
+ * (Chromium, Firefox) — `deriveState` never leaves `'preconditions-unmet'`.
50
+ *
51
+ * MMS and AirPlay want *opposite* values of `disableRemotePlayback` on the same
52
+ * element, so it is **sequenced**:
53
+ *
54
+ * - **MMS needs `true` to open.** `setupMediaSource` sets
55
+ * `disableRemotePlayback = true` when it attaches a ManagedMediaSource —
56
+ * Safari won't fire `sourceopen` (and MSE playback never starts) otherwise.
57
+ * - **AirPlay needs `false` to offer the picker.** Flipping to `false` *before*
58
+ * the source opens would prevent `sourceopen`, so the flip is gated on
59
+ * `context.mediaSource` — which `setupMediaSource` publishes exactly once the
60
+ * MS is open. Re-fires per source (the slot clears + republishes on reset).
61
+ * - **Author opt-out wins.** `state.disableRemotePlayback` is the author's
62
+ * intent, written only by the media adapter's IDL property; MMS/programmatic
63
+ * code touch the element's own `disableRemotePlayback` instead. A `true`
64
+ * there is unambiguously the author's choice to disable remote playback, so
65
+ * it holds the machine in `'preconditions-unmet'` and nothing is set up.
66
+ */
67
+ /**
68
+ * How long WebKit's wireless flag must read *inactive* before the
69
+ * session-driven `loadingSuspended` clears.
70
+ *
71
+ * Measured on Safari 26.4 (macOS): when an AirPlay session engages, Safari
72
+ * closes the ManagedMediaSource and — while its pipeline switches to the
73
+ * native-HLS fallback source — transiently reports
74
+ * `webkitCurrentPlaybackTargetIsWireless === false`, firing the changed
75
+ * event. Trusting an instantaneous inactive reading would release
76
+ * `setupMediaSource`'s rebuild hold mid-handoff; its recovery `load()` then
77
+ * destroys the very session being established. Rising edges apply
78
+ * immediately; only the falling edge waits out this settle window.
79
+ */
80
+ const REMOTE_INACTIVE_SETTLE_MS = 1e3;
81
+ function deriveState(mediaElement, authorDisabledRemotePlayback) {
82
+ if (!mediaElement || !isWebKitAirPlayCapable(mediaElement)) return "preconditions-unmet";
83
+ if (authorDisabledRemotePlayback) return "preconditions-unmet";
84
+ return "airplay-capable";
85
+ }
86
+ function setupAirPlaySetup({ state, context }) {
87
+ const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), state.disableRemotePlayback.get()));
88
+ return createMachineReactor({
89
+ initial: "preconditions-unmet",
90
+ monitor: () => derivedStateSignal.get(),
91
+ states: {
92
+ "preconditions-unmet": {},
93
+ "airplay-capable": { entry: () => {
94
+ const mediaElement = context.mediaElement.get();
95
+ const isSessionActive = () => !!mediaElement.webkitCurrentPlaybackTargetIsWireless;
96
+ let settleTimer;
97
+ let sessionPresentationUrl;
98
+ let pendingRestore;
99
+ /** Presentation an already-written `state.startPosition` belongs to. */
100
+ let restoreOwnerUrl;
101
+ let resumeWhenRestored = false;
102
+ const sync = () => {
103
+ if (isSessionActive()) {
104
+ clearTimeout(settleTimer);
105
+ settleTimer = void 0;
106
+ if (!peek(state.loadingSuspended)) sessionPresentationUrl = peek(state.presentation)?.url;
107
+ state.loadingSuspended.set(true);
108
+ } else if (peek(state.loadingSuspended)) settleTimer ??= setTimeout(() => {
109
+ settleTimer = void 0;
110
+ const stillActive = isSessionActive();
111
+ if (!stillActive) {
112
+ const ownerUrl = sessionPresentationUrl;
113
+ sessionPresentationUrl = void 0;
114
+ if (peek(state.presentation)?.url === ownerUrl) pendingRestore = {
115
+ position: mediaElement.currentTime,
116
+ wasPlaying: !mediaElement.paused,
117
+ presentationUrl: ownerUrl
118
+ };
119
+ }
120
+ state.loadingSuspended.set(stillActive);
121
+ }, REMOTE_INACTIVE_SETTLE_MS);
122
+ else state.loadingSuspended.set(false);
123
+ };
124
+ const listenerCleanup = new AbortController();
125
+ listen(mediaElement, "webkitcurrentplaybacktargetiswirelesschanged", sync, { signal: listenerCleanup.signal });
126
+ listen(mediaElement, "emptied", () => {
127
+ if (!pendingRestore) return;
128
+ const { position, wasPlaying, presentationUrl } = pendingRestore;
129
+ pendingRestore = void 0;
130
+ if (peek(state.presentation)?.url !== presentationUrl) return;
131
+ state.startPosition.set(position);
132
+ restoreOwnerUrl = presentationUrl;
133
+ resumeWhenRestored = wasPlaying;
134
+ }, { signal: listenerCleanup.signal });
135
+ const disposeSourceChangeEnd = effect(() => {
136
+ const url = state.presentation.get()?.url;
137
+ if (!peek(state.loadingSuspended) || url === sessionPresentationUrl) return;
138
+ sessionPresentationUrl = void 0;
139
+ clearTimeout(settleTimer);
140
+ settleTimer = void 0;
141
+ state.loadingSuspended.set(false);
142
+ });
143
+ const disposeRestoreWatch = effect(() => {
144
+ const url = state.presentation.get()?.url;
145
+ const position = state.startPosition.get();
146
+ if (!restoreOwnerUrl) return;
147
+ if (url !== restoreOwnerUrl) {
148
+ restoreOwnerUrl = void 0;
149
+ resumeWhenRestored = false;
150
+ if (position !== void 0) state.startPosition.set(void 0);
151
+ return;
152
+ }
153
+ if (position !== void 0) return;
154
+ restoreOwnerUrl = void 0;
155
+ if (!resumeWhenRestored) return;
156
+ resumeWhenRestored = false;
157
+ mediaElement.play().catch((err) => {
158
+ console.warn("[setupAirPlay] session-end resume play() rejected — staying paused:", err);
159
+ });
160
+ });
161
+ let sourceEl = null;
162
+ const disposeSource = effect(() => {
163
+ const hasMediaSource = !!context.mediaSource.get();
164
+ const sessionActive = !!state.loadingSuspended.get();
165
+ const url = state.presentation.get()?.url ?? "";
166
+ if (!hasMediaSource && !sessionActive) {
167
+ sourceEl?.remove();
168
+ sourceEl = null;
169
+ } else if (hasMediaSource && (!sourceEl || sourceEl.parentNode !== mediaElement)) {
170
+ sourceEl = document.createElement("source");
171
+ sourceEl.type = "application/x-mpegURL";
172
+ mediaElement.append(sourceEl);
173
+ mediaElement.disableRemotePlayback = false;
174
+ }
175
+ if (sourceEl) sourceEl.src = url;
176
+ });
177
+ sync();
178
+ return () => {
179
+ disposeSource();
180
+ disposeSourceChangeEnd();
181
+ disposeRestoreWatch();
182
+ listenerCleanup.abort();
183
+ clearTimeout(settleTimer);
184
+ sourceEl?.remove();
185
+ sourceEl = null;
186
+ mediaElement.disableRemotePlayback = true;
187
+ state.loadingSuspended.set(false);
188
+ if (restoreOwnerUrl) {
189
+ restoreOwnerUrl = void 0;
190
+ resumeWhenRestored = false;
191
+ if (peek(state.startPosition) !== void 0) state.startPosition.set(void 0);
192
+ }
193
+ };
194
+ } }
195
+ }
196
+ });
197
+ }
198
+ const setupAirPlay = defineBehavior({
199
+ stateKeys: [
200
+ "presentation",
201
+ "disableRemotePlayback",
202
+ "loadingSuspended",
203
+ "startPosition"
204
+ ],
205
+ contextKeys: ["mediaElement", "mediaSource"],
206
+ setup: setupAirPlaySetup
207
+ });
208
+ //#endregion
209
+ export { setupAirPlay };
210
+
211
+ //# sourceMappingURL=airplay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airplay.js","names":[],"sources":["../../../../../src/playback/behaviors/dom/airplay.ts"],"sourcesContent":["/**\n * **Bridge MSE playback to AirPlay on WebKit.**\n * MSE streams can't be handed to an AirPlay receiver directly.\n * The WebKit-recommended workaround is to append a fallback\n * `<source type=\"application/x-mpegURL\">` carrying the original manifest URL:\n * Safari exposes the AirPlay picker and, when a wireless target is selected,\n * plays that native-HLS source on the receiver. The session state (WebKit's\n * wireless flag, falling edge debounced — see `REMOTE_INACTIVE_SETTLE_MS`) is\n * written straight to its policy consequences, declared here so the\n * cause→policy mapping stays with the feature:\n *\n * - `state.loadingSuspended` — held while the session is live. Observed by\n * the `loadXSegments` dispatchers (no fetching alongside the receiver) and\n * by `setupMediaSource` (its post-close rebuild waits — attaching runs\n * `element.load()` under the live receiver, which destroys a session still\n * being established). The suspension this behavior holds doubles as its own\n * session fact — same writer, same edges.\n * - `state.startPosition` — one-shot command: the position is captured from\n * the element at the session's settled end (still receiver-mirrored) and\n * written once the rebuild's `load()` resets the element (its `'emptied'`),\n * so `applyStartPosition` applies it to the rebuilt source — never to the\n * pre-rebuild element — and starts it where the receiver left off. The\n * playing state rides the same snapshot but stays behavior-local: this\n * behavior itself calls `play()` once the command has been *consumed* — i.e.\n * after the seek — when the receiver was playing at session end. The whole\n * restore is bound to the presentation the session owned and retracted if\n * that changes.\n *\n * A source change during a live session releases the hold rather than deferring\n * until the session ends, so the rebuild runs and WebKit switches the receiver\n * to the newly-built AirPlay alternate. Measured, not contracted — see the\n * effect below.\n * https://webkit.org/blog/15036/how-to-use-media-source-extensions-with-airplay/\n *\n * Single-positive-state reactor (`'preconditions-unmet'` ↔ `'airplay-capable'`):\n * gated on a WebKit-AirPlay-capable media element being in scope. The entry —\n * gated on `context.mediaSource` — appends the fallback `<source>` (kept\n * current from `state.presentation`) and enables the AirPlay picker once the\n * MediaSource is open, removing the source the moment the MediaSource detaches\n * so it never survives an MSE teardown. State-exit cleanup (author opt-out,\n * detach, source reset, behavior destroy) removes the source and restores the\n * element's `disableRemotePlayback` default. No-op on non-WebKit platforms\n * (Chromium, Firefox) — `deriveState` never leaves `'preconditions-unmet'`.\n *\n * MMS and AirPlay want *opposite* values of `disableRemotePlayback` on the same\n * element, so it is **sequenced**:\n *\n * - **MMS needs `true` to open.** `setupMediaSource` sets\n * `disableRemotePlayback = true` when it attaches a ManagedMediaSource —\n * Safari won't fire `sourceopen` (and MSE playback never starts) otherwise.\n * - **AirPlay needs `false` to offer the picker.** Flipping to `false` *before*\n * the source opens would prevent `sourceopen`, so the flip is gated on\n * `context.mediaSource` — which `setupMediaSource` publishes exactly once the\n * MS is open. Re-fires per source (the slot clears + republishes on reset).\n * - **Author opt-out wins.** `state.disableRemotePlayback` is the author's\n * intent, written only by the media adapter's IDL property; MMS/programmatic\n * code touch the element's own `disableRemotePlayback` instead. A `true`\n * there is unambiguously the author's choice to disable remote playback, so\n * it holds the machine in `'preconditions-unmet'` and nothing is set up.\n */\n\nimport { isWebKitAirPlayCapable, listen, type WebKitVideoElement } from '@videojs/utils/dom';\nimport { defineBehavior } from '../../../core/composition/create-composition';\nimport type { Reactor } from '../../../core/reactors/create-machine-reactor';\nimport { createMachineReactor } from '../../../core/reactors/create-machine-reactor';\nimport { effect } from '../../../core/signals/effect';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../../core/signals/primitives';\nimport type { MaybeResolvedPresentation } from '../../../media/types';\nimport type { StartPositionState } from './apply-start-position';\nimport type { SegmentLoadingState } from './load-segments';\n\n/**\n * How long WebKit's wireless flag must read *inactive* before the\n * session-driven `loadingSuspended` clears.\n *\n * Measured on Safari 26.4 (macOS): when an AirPlay session engages, Safari\n * closes the ManagedMediaSource and — while its pipeline switches to the\n * native-HLS fallback source — transiently reports\n * `webkitCurrentPlaybackTargetIsWireless === false`, firing the changed\n * event. Trusting an instantaneous inactive reading would release\n * `setupMediaSource`'s rebuild hold mid-handoff; its recovery `load()` then\n * destroys the very session being established. Rising edges apply\n * immediately; only the falling edge waits out this settle window.\n */\nconst REMOTE_INACTIVE_SETTLE_MS = 1000;\n\ntype AirPlayFsmState = 'preconditions-unmet' | 'airplay-capable';\n\nfunction deriveState(\n mediaElement: HTMLMediaElement | undefined,\n authorDisabledRemotePlayback: boolean | undefined\n): AirPlayFsmState {\n if (!mediaElement || !isWebKitAirPlayCapable(mediaElement)) return 'preconditions-unmet';\n if (authorDisabledRemotePlayback) return 'preconditions-unmet';\n return 'airplay-capable';\n}\n\nfunction setupAirPlaySetup({\n state,\n context,\n}: {\n state: {\n presentation: ReadonlySignal<MaybeResolvedPresentation | undefined>;\n disableRemotePlayback: ReadonlySignal<boolean | undefined>;\n loadingSuspended: Signal<SegmentLoadingState['loadingSuspended']>;\n startPosition: Signal<StartPositionState['startPosition']>;\n };\n context: {\n mediaElement: ReadonlySignal<HTMLMediaElement | undefined>;\n mediaSource: ReadonlySignal<MediaSource | undefined>;\n };\n}): Reactor<AirPlayFsmState | 'destroying' | 'destroyed'> {\n const derivedStateSignal = computed(() => deriveState(context.mediaElement.get(), state.disableRemotePlayback.get()));\n\n return createMachineReactor<AirPlayFsmState>({\n initial: 'preconditions-unmet',\n monitor: () => derivedStateSignal.get(),\n states: {\n 'preconditions-unmet': {},\n\n 'airplay-capable': {\n entry: () => {\n const mediaElement = context.mediaElement.get() as WebKitVideoElement;\n\n // WebKit's wireless flag is the only session signal. The standard\n // Remote Playback API's `remote.state` is deliberately not consulted:\n // it doesn't track AirPlay sessions reliably on WebKit (same reason\n // `remotePlaybackFeature` in @videojs/core skips it), and a stale\n // `'connected'` would pin `loadingSuspended` on — stranding the\n // rebuild in a way the settle window below can't recover from.\n const isSessionActive = () => !!mediaElement.webkitCurrentPlaybackTargetIsWireless;\n\n let settleTimer: ReturnType<typeof setTimeout> | undefined;\n // Session-end restore. The position/playing snapshot is taken at the\n // settled falling edge (the element still mirrors the receiver) but\n // acted on only at the element's next `'emptied'` — once the\n // rebuild's `load()` has reset the element. Writing it earlier would\n // hand `applyStartPosition` a command against the pre-rebuild element\n // (readyState still >= HAVE_METADATA), which it would apply and\n // consume before the rebuilt source exists.\n //\n // The restore is bound to the presentation the session owned, and\n // that identity is re-checked at *every* async hop — settle timer,\n // `'emptied'`, and the outstanding-command effect below. Checking at\n // only one hop leaves the others open: a source change inside the\n // settle window, or between `'emptied'` and the element becoming\n // seekable, would otherwise strand the old session's position (and an\n // unrequested `play()`) on the new source.\n let sessionPresentationUrl: string | undefined;\n let pendingRestore:\n | { position: number; wasPlaying: boolean; presentationUrl: string | undefined }\n | undefined;\n /** Presentation an already-written `state.startPosition` belongs to. */\n let restoreOwnerUrl: string | undefined;\n let resumeWhenRestored = false;\n\n const sync = () => {\n if (isSessionActive()) {\n clearTimeout(settleTimer);\n settleTimer = undefined;\n // Rising edge — the presentation the receiver is taking over.\n if (!peek(state.loadingSuspended)) sessionPresentationUrl = peek(state.presentation)?.url;\n state.loadingSuspended.set(true);\n } else if (peek(state.loadingSuspended)) {\n // Falling edge: don't trust an instantaneous inactive reading —\n // re-check once the flag has settled.\n settleTimer ??= setTimeout(() => {\n settleTimer = undefined;\n const stillActive = isSessionActive();\n if (!stillActive) {\n const ownerUrl = sessionPresentationUrl;\n sessionPresentationUrl = undefined;\n // Snapshot only while the session's own presentation is still\n // current. `currentTime` here belongs to that presentation —\n // during a session `setupMediaSource` has already torn down,\n // so a source change inside this window resets neither the\n // element nor its position, and pairing that position with the\n // incoming URL would smuggle it onto the new source.\n if (peek(state.presentation)?.url === ownerUrl) {\n pendingRestore = {\n position: mediaElement.currentTime,\n wasPlaying: !mediaElement.paused,\n presentationUrl: ownerUrl,\n };\n }\n }\n state.loadingSuspended.set(stillActive);\n }, REMOTE_INACTIVE_SETTLE_MS);\n } else {\n state.loadingSuspended.set(false);\n }\n };\n const listenerCleanup = new AbortController();\n listen(mediaElement, 'webkitcurrentplaybacktargetiswirelesschanged', sync, {\n signal: listenerCleanup.signal,\n });\n listen(\n mediaElement,\n 'emptied',\n () => {\n if (!pendingRestore) return;\n const { position, wasPlaying, presentationUrl } = pendingRestore;\n pendingRestore = undefined;\n if (peek(state.presentation)?.url !== presentationUrl) return;\n state.startPosition.set(position);\n restoreOwnerUrl = presentationUrl;\n resumeWhenRestored = wasPlaying;\n },\n { signal: listenerCleanup.signal }\n );\n\n // A source change during a live session releases the hold, letting the\n // rebuild run instead of waiting for the session to end.\n //\n // Doing nothing is not a neutral choice — it silently defers the\n // change. `<source>` children are only consulted while resource\n // selection runs, so the effect below rewriting the fallback's `src`\n // does nothing to what the receiver is already playing, and the\n // rebuild is held by `loadingSuspended`, so the receiver stays on the\n // outgoing stream until the user disengages.\n //\n // Releasing the hold lets the rebuild proceed, and the receiver ends\n // up on the new stream — measured on Safari 26.4. Note where the\n // handover actually happens, because it is not the rebuild's `load()`:\n // clearing the hold also makes the source effect below see an inactive\n // session with no `context.mediaSource`, so it drops the fallback, and\n // the rebuild's resource selection runs with the MSE source alone. The\n // switch comes afterwards, when that effect re-creates the fallback\n // against the new presentation once the MediaSource opens and WebKit\n // moves the live session onto the newly-appeared alternate.\n //\n // That leaves a window where an active session has no compatible\n // alternate on the element, and it survives it. Holding the fallback\n // across the rebuild instead would close the window and make the\n // handover selection-driven, but it swaps a measured path for an\n // argued one — don't without a device pass.\n //\n // Measured, not contracted: this is under-specified territory, so\n // nothing here depends on the handover succeeding. If a UA drops the\n // session instead, the falling edge runs its ordinary course; the\n // restore is suppressed either way because the snapshot is bound to\n // the presentation the session owned.\n //\n // Note this is *not* an attempt to end the session. Setting\n // `disableRemotePlayback = true` was tried: the Remote Playback API\n // requires it to disconnect (spec §5.3.2) but WebKit does not honor\n // that for AirPlay — the session survived, measured on Safari 26.4.\n // Deliberately not written here, so that behavior can't silently flip\n // to \"receiver drops\" if WebKit ever implements the disconnect.\n const disposeSourceChangeEnd = effect(() => {\n const url = state.presentation.get()?.url;\n // `loadingSuspended` is this behavior's own session fact; peeked so\n // clearing it below doesn't re-trigger.\n if (!peek(state.loadingSuspended) || url === sessionPresentationUrl) return;\n sessionPresentationUrl = undefined;\n clearTimeout(settleTimer);\n settleTimer = undefined;\n state.loadingSuspended.set(false);\n });\n\n // Watches an outstanding restore command to its end. Two outcomes:\n //\n // - **Consumed** (`startPosition` back to `undefined`): the element is\n // at the restored position, so resuming is safe. Keying the resume\n // off consumption rather than off `'loadedmetadata'` orders it after\n // `applyStartPosition`'s seek — arming a listener for the same event\n // would put `play()` ahead of the seek, since this behavior registers\n // synchronously while `applyStartPosition` only reaches its state a\n // microtask later. The rebuild's load algorithm forced `paused =\n // true`; a rejection here (autoplay policy) degrades to\n // paused-at-position. Requires a `startPosition` consumer in the\n // composition (`applyStartPosition`) — without one the position is\n // never restored either, so resuming would be wrong anyway.\n // - **Superseded** (presentation changed): retract the command and\n // disarm, so neither the seek nor the resume reaches the new source.\n const disposeRestoreWatch = effect(() => {\n const url = state.presentation.get()?.url;\n const position = state.startPosition.get();\n if (!restoreOwnerUrl) return;\n\n if (url !== restoreOwnerUrl) {\n restoreOwnerUrl = undefined;\n resumeWhenRestored = false;\n if (position !== undefined) state.startPosition.set(undefined);\n return;\n }\n\n if (position !== undefined) return;\n restoreOwnerUrl = undefined;\n if (!resumeWhenRestored) return;\n resumeWhenRestored = false;\n mediaElement.play().catch((err) => {\n console.warn('[setupAirPlay] session-end resume play() rejected — staying paused:', err);\n });\n });\n\n // This effect combines:\n // - adding a native HLS fallback source when the mediaSource is\n // attached / removing it when it's detached — UNLESS a session is\n // live: during an AirPlay session the engine detaches its dead\n // MediaSource (see setupMediaSource's sourceclose recovery) while\n // the receiver is playing exactly this fallback source, so it\n // must survive until the session's falling edge (the rebuild's\n // republish then adopts it again).\n // - keeping this sourceEl's src in sync with the current presentation.\n // The created source is also cleaned on state exit.\n // The dependence on context.mediaSource means the source is appended\n // only after the MMS has been attached and opened (therefore we can\n // flip disableRemotePlayback). Being in this state also implies\n // that the author has not disabled remote playback.\n let sourceEl: HTMLSourceElement | null = null;\n const disposeSource = effect(() => {\n const hasMediaSource = !!context.mediaSource.get();\n const sessionActive = !!state.loadingSuspended.get();\n const url = state.presentation.get()?.url ?? '';\n\n if (!hasMediaSource && !sessionActive) {\n sourceEl?.remove();\n sourceEl = null;\n } else if (hasMediaSource && (!sourceEl || sourceEl.parentNode !== mediaElement)) {\n sourceEl = document.createElement('source');\n sourceEl.type = 'application/x-mpegURL';\n mediaElement.append(sourceEl);\n mediaElement.disableRemotePlayback = false;\n }\n if (sourceEl) sourceEl.src = url;\n });\n\n // AirPlay may already be active at (re)attach.\n sync();\n\n return () => {\n disposeSource();\n disposeSourceChangeEnd();\n disposeRestoreWatch();\n listenerCleanup.abort();\n clearTimeout(settleTimer);\n sourceEl?.remove();\n sourceEl = null;\n // Undo the picker enable: hand the element back to its MMS-default\n // `disableRemotePlayback = true`.\n mediaElement.disableRemotePlayback = true;\n // Don't strand the engine held/suspended if we tear down\n // mid-session (author opt-out, detach, destroy).\n state.loadingSuspended.set(false);\n // Nor leave a restore command behind for the next source to apply.\n if (restoreOwnerUrl) {\n restoreOwnerUrl = undefined;\n resumeWhenRestored = false;\n if (peek(state.startPosition) !== undefined) state.startPosition.set(undefined);\n }\n };\n },\n },\n },\n });\n}\n\nexport const setupAirPlay = defineBehavior({\n stateKeys: ['presentation', 'disableRemotePlayback', 'loadingSuspended', 'startPosition'],\n contextKeys: ['mediaElement', 'mediaSource'],\n setup: setupAirPlaySetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,MAAM,4BAA4B;AAIlC,SAAS,YACP,cACA,8BACiB;CACjB,IAAI,CAAC,gBAAgB,CAAC,uBAAuB,YAAY,GAAG,OAAO;CACnE,IAAI,8BAA8B,OAAO;CACzC,OAAO;AACT;AAEA,SAAS,kBAAkB,EACzB,OACA,WAYwD;CACxD,MAAM,qBAAqB,eAAe,YAAY,QAAQ,aAAa,IAAI,GAAG,MAAM,sBAAsB,IAAI,CAAC,CAAC;CAEpH,OAAO,qBAAsC;EAC3C,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,uBAAuB,CAAC;GAExB,mBAAmB,EACjB,aAAa;IACX,MAAM,eAAe,QAAQ,aAAa,IAAI;IAQ9C,MAAM,wBAAwB,CAAC,CAAC,aAAa;IAE7C,IAAI;IAgBJ,IAAI;IACJ,IAAI;;IAIJ,IAAI;IACJ,IAAI,qBAAqB;IAEzB,MAAM,aAAa;KACjB,IAAI,gBAAgB,GAAG;MACrB,aAAa,WAAW;MACxB,cAAc,KAAA;MAEd,IAAI,CAAC,KAAK,MAAM,gBAAgB,GAAG,yBAAyB,KAAK,MAAM,YAAY,CAAC,EAAE;MACtF,MAAM,iBAAiB,IAAI,IAAI;KACjC,OAAO,IAAI,KAAK,MAAM,gBAAgB,GAGpC,gBAAgB,iBAAiB;MAC/B,cAAc,KAAA;MACd,MAAM,cAAc,gBAAgB;MACpC,IAAI,CAAC,aAAa;OAChB,MAAM,WAAW;OACjB,yBAAyB,KAAA;OAOzB,IAAI,KAAK,MAAM,YAAY,CAAC,EAAE,QAAQ,UACpC,iBAAiB;QACf,UAAU,aAAa;QACvB,YAAY,CAAC,aAAa;QAC1B,iBAAiB;OACnB;MAEJ;MACA,MAAM,iBAAiB,IAAI,WAAW;KACxC,GAAG,yBAAyB;UAE5B,MAAM,iBAAiB,IAAI,KAAK;IAEpC;IACA,MAAM,kBAAkB,IAAI,gBAAgB;IAC5C,OAAO,cAAc,gDAAgD,MAAM,EACzE,QAAQ,gBAAgB,OAC1B,CAAC;IACD,OACE,cACA,iBACM;KACJ,IAAI,CAAC,gBAAgB;KACrB,MAAM,EAAE,UAAU,YAAY,oBAAoB;KAClD,iBAAiB,KAAA;KACjB,IAAI,KAAK,MAAM,YAAY,CAAC,EAAE,QAAQ,iBAAiB;KACvD,MAAM,cAAc,IAAI,QAAQ;KAChC,kBAAkB;KAClB,qBAAqB;IACvB,GACA,EAAE,QAAQ,gBAAgB,OAAO,CACnC;IAwCA,MAAM,yBAAyB,aAAa;KAC1C,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,EAAE;KAGtC,IAAI,CAAC,KAAK,MAAM,gBAAgB,KAAK,QAAQ,wBAAwB;KACrE,yBAAyB,KAAA;KACzB,aAAa,WAAW;KACxB,cAAc,KAAA;KACd,MAAM,iBAAiB,IAAI,KAAK;IAClC,CAAC;IAiBD,MAAM,sBAAsB,aAAa;KACvC,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,EAAE;KACtC,MAAM,WAAW,MAAM,cAAc,IAAI;KACzC,IAAI,CAAC,iBAAiB;KAEtB,IAAI,QAAQ,iBAAiB;MAC3B,kBAAkB,KAAA;MAClB,qBAAqB;MACrB,IAAI,aAAa,KAAA,GAAW,MAAM,cAAc,IAAI,KAAA,CAAS;MAC7D;KACF;KAEA,IAAI,aAAa,KAAA,GAAW;KAC5B,kBAAkB,KAAA;KAClB,IAAI,CAAC,oBAAoB;KACzB,qBAAqB;KACrB,aAAa,KAAK,CAAC,CAAC,OAAO,QAAQ;MACjC,QAAQ,KAAK,uEAAuE,GAAG;KACzF,CAAC;IACH,CAAC;IAgBD,IAAI,WAAqC;IACzC,MAAM,gBAAgB,aAAa;KACjC,MAAM,iBAAiB,CAAC,CAAC,QAAQ,YAAY,IAAI;KACjD,MAAM,gBAAgB,CAAC,CAAC,MAAM,iBAAiB,IAAI;KACnD,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,EAAE,OAAO;KAE7C,IAAI,CAAC,kBAAkB,CAAC,eAAe;MACrC,UAAU,OAAO;MACjB,WAAW;KACb,OAAO,IAAI,mBAAmB,CAAC,YAAY,SAAS,eAAe,eAAe;MAChF,WAAW,SAAS,cAAc,QAAQ;MAC1C,SAAS,OAAO;MAChB,aAAa,OAAO,QAAQ;MAC5B,aAAa,wBAAwB;KACvC;KACA,IAAI,UAAU,SAAS,MAAM;IAC/B,CAAC;IAGD,KAAK;IAEL,aAAa;KACX,cAAc;KACd,uBAAuB;KACvB,oBAAoB;KACpB,gBAAgB,MAAM;KACtB,aAAa,WAAW;KACxB,UAAU,OAAO;KACjB,WAAW;KAGX,aAAa,wBAAwB;KAGrC,MAAM,iBAAiB,IAAI,KAAK;KAEhC,IAAI,iBAAiB;MACnB,kBAAkB,KAAA;MAClB,qBAAqB;MACrB,IAAI,KAAK,MAAM,aAAa,MAAM,KAAA,GAAW,MAAM,cAAc,IAAI,KAAA,CAAS;KAChF;IACF;GACF,EACF;EACF;CACF,CAAC;AACH;AAEA,MAAa,eAAe,eAAe;CACzC,WAAW;EAAC;EAAgB;EAAyB;EAAoB;CAAe;CACxF,aAAa,CAAC,gBAAgB,aAAa;CAC3C,OAAO;AACT,CAAC"}