@videojs/spf 10.0.0-beta.25 → 10.0.0-beta.27

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 (489) 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/signals/when.js +38 -0
  12. package/dist/default/core/signals/when.js.map +1 -0
  13. package/dist/default/core/tasks/delayed-reschedule.js +31 -0
  14. package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
  15. package/dist/default/core/tasks/task.js +133 -3
  16. package/dist/default/core/tasks/task.js.map +1 -1
  17. package/dist/default/hls-audio.js +3 -0
  18. package/dist/default/hls-background-video.js +3 -0
  19. package/dist/default/hls-video.js +4 -0
  20. package/dist/default/hls.js +7 -5
  21. package/dist/default/media/abr/quality-selection.js.map +1 -1
  22. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  23. package/dist/default/media/buffer/forward-buffer.js +37 -3
  24. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  25. package/dist/default/media/dom/capabilities.js +2 -0
  26. package/dist/default/media/dom/capabilities.js.map +1 -1
  27. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  28. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  29. package/dist/default/media/dom/mse/duration.js +27 -10
  30. package/dist/default/media/dom/mse/duration.js.map +1 -1
  31. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  32. package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
  33. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  34. package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
  35. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  36. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  37. package/dist/default/media/errors.js +62 -0
  38. package/dist/default/media/errors.js.map +1 -0
  39. package/dist/default/media/hls/parse-attributes.js +1 -1
  40. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  41. package/dist/default/media/hls/parse-media-playlist.js +175 -19
  42. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  43. package/dist/default/media/hls/parse-multivariant.js +14 -3
  44. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  45. package/dist/default/media/hls/reload-policy.js +71 -0
  46. package/dist/default/media/hls/reload-policy.js.map +1 -0
  47. package/dist/default/media/hls/resolve-url.js.map +1 -1
  48. package/dist/default/media/live-window.js +36 -0
  49. package/dist/default/media/live-window.js.map +1 -0
  50. package/dist/default/media/media-tracks/media-tracks.js +101 -0
  51. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  52. package/dist/default/media/mp4/box.js +58 -0
  53. package/dist/default/media/mp4/box.js.map +1 -0
  54. package/dist/default/media/mp4/timestamp-origin.js +107 -0
  55. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  56. package/dist/default/media/primitives/select-tracks.js +1 -24
  57. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  58. package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
  59. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  60. package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
  61. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  62. package/dist/default/media/types/index.js +15 -1
  63. package/dist/default/media/types/index.js.map +1 -1
  64. package/dist/default/media/utils/cdn.js.map +1 -1
  65. package/dist/default/media/utils/preload.js.map +1 -1
  66. package/dist/default/media/utils/track-selection.js +1 -1
  67. package/dist/default/media/utils/track-selection.js.map +1 -1
  68. package/dist/default/media/utils/tracks.js +17 -3
  69. package/dist/default/media/utils/tracks.js.map +1 -1
  70. package/dist/default/media-tracks.js +2 -0
  71. package/dist/default/mux-audio.js +3 -0
  72. package/dist/default/mux-background-video.js +3 -0
  73. package/dist/default/mux-video.js +3 -0
  74. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  75. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  76. package/dist/default/network/ewma.js +1 -1
  77. package/dist/default/network/ewma.js.map +1 -1
  78. package/dist/default/network/fetch.js.map +1 -1
  79. package/dist/default/playback/actors/dom/segment-loader.js +61 -95
  80. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  81. package/dist/default/playback/actors/dom/source-buffer.js +1 -0
  82. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  83. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  84. package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
  85. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  86. package/dist/default/playback/adapters/hls-audio/adapter.js +186 -0
  87. package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
  88. package/dist/default/playback/adapters/hls-audio/media.js +9 -0
  89. package/dist/default/playback/adapters/hls-audio/media.js.map +1 -0
  90. package/dist/default/playback/adapters/hls-background-video/adapter.js +123 -0
  91. package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
  92. package/dist/default/playback/adapters/hls-background-video/host.js +60 -0
  93. package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
  94. package/dist/default/playback/adapters/hls-background-video/media.js +21 -0
  95. package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -0
  96. package/dist/default/playback/adapters/hls-video/adapter.js +316 -0
  97. package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
  98. package/dist/default/playback/adapters/hls-video/error-surface.js +51 -0
  99. package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
  100. package/dist/default/playback/adapters/hls-video/media-tracks.js +126 -0
  101. package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
  102. package/dist/default/playback/adapters/hls-video/media.js +11 -0
  103. package/dist/default/playback/adapters/hls-video/media.js.map +1 -0
  104. package/dist/default/playback/adapters/mux-audio/media.js +26 -0
  105. package/dist/default/playback/adapters/mux-audio/media.js.map +1 -0
  106. package/dist/default/playback/adapters/mux-video/adapter.js +92 -0
  107. package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -0
  108. package/dist/default/playback/adapters/mux-video/media.js +23 -0
  109. package/dist/default/playback/adapters/mux-video/media.js.map +1 -0
  110. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  111. package/dist/default/playback/behaviors/collect-errors.js +77 -0
  112. package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
  113. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
  114. package/dist/default/playback/behaviors/dom/airplay.js +211 -0
  115. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  116. package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
  117. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  118. package/dist/default/playback/behaviors/dom/end-of-stream.js +13 -1
  119. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  120. package/dist/default/playback/behaviors/dom/load-segments.js +5 -1
  121. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  122. package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
  123. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  124. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +132 -0
  125. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  126. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +17 -4
  127. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  128. package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
  129. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  130. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  131. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  132. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
  133. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  134. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  135. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  136. package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
  137. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  138. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  139. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +31 -8
  140. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  141. package/dist/default/playback/behaviors/establish-start-media-time.js +197 -0
  142. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  143. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  144. package/dist/default/playback/behaviors/resolve-track.js +29 -8
  145. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  146. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  147. package/dist/default/playback/behaviors/setup-failover-monitor.js +1 -1
  148. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
  149. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  150. package/dist/default/playback/behaviors/track-switching.js +76 -5
  151. package/dist/default/playback/behaviors/track-switching.js.map +1 -1
  152. package/dist/default/playback/engines/hls/engine-audio-only.js +25 -7
  153. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
  154. package/dist/{dev/playback/engines/background-video/engine.js → default/playback/engines/hls/engine-background-video.js} +3 -3
  155. package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
  156. package/dist/default/playback/engines/hls/engine.js +36 -6
  157. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  158. package/dist/default/playback/primitives/error-messages.js +39 -0
  159. package/dist/default/playback/primitives/error-messages.js.map +1 -0
  160. package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
  161. package/dist/default/playback/primitives/head-peek.js +44 -0
  162. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  163. package/dist/default/playback/primitives/live-window.js +66 -0
  164. package/dist/default/playback/primitives/live-window.js.map +1 -0
  165. package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
  166. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  167. package/dist/default/playback/primitives/report-track-conditions.js +86 -0
  168. package/dist/default/playback/primitives/report-track-conditions.js.map +1 -0
  169. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  170. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  171. package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
  172. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  173. package/dist/default/playback/primitives/track-types.js.map +1 -1
  174. package/dist/dev/core/actors/actor.d.ts +0 -1
  175. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  176. package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
  177. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  178. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  179. package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
  180. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  181. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  182. package/dist/dev/core/composition/create-composition.d.ts +10 -11
  183. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  184. package/dist/dev/core/composition/create-composition.js +1 -1
  185. package/dist/dev/core/composition/create-composition.js.map +1 -1
  186. package/dist/dev/core/composition/share-signals.d.ts +0 -1
  187. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  188. package/dist/dev/core/composition/share-signals.js.map +1 -1
  189. package/dist/dev/core/machine.d.ts +1 -0
  190. package/dist/dev/core/machine.d.ts.map +1 -1
  191. package/dist/dev/core/machine.js.map +1 -1
  192. package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
  193. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  194. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  195. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  196. package/dist/dev/core/signals/effect.js.map +1 -1
  197. package/dist/dev/core/signals/primitives.d.ts +1 -2
  198. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  199. package/dist/dev/core/signals/primitives.js.map +1 -1
  200. package/dist/dev/core/signals/when.js +38 -0
  201. package/dist/dev/core/signals/when.js.map +1 -0
  202. package/dist/dev/core/tasks/delayed-reschedule.js +31 -0
  203. package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
  204. package/dist/dev/core/tasks/task.d.ts +53 -2
  205. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  206. package/dist/dev/core/tasks/task.js +133 -3
  207. package/dist/dev/core/tasks/task.js.map +1 -1
  208. package/dist/dev/hls-audio.d.ts +3 -0
  209. package/dist/dev/hls-audio.js +3 -0
  210. package/dist/dev/hls-background-video.d.ts +3 -0
  211. package/dist/dev/hls-background-video.js +3 -0
  212. package/dist/dev/hls-video.d.ts +5 -0
  213. package/dist/dev/hls-video.js +4 -0
  214. package/dist/dev/hls.d.ts +8 -5
  215. package/dist/dev/hls.js +7 -5
  216. package/dist/dev/index.d.ts +0 -1
  217. package/dist/dev/index.d.ts.map +1 -1
  218. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  219. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  220. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  221. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  222. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  223. package/dist/dev/media/buffer/forward-buffer.js +37 -3
  224. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  225. package/dist/dev/media/dom/capabilities.js +2 -0
  226. package/dist/dev/media/dom/capabilities.js.map +1 -1
  227. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
  228. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  229. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  230. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
  231. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  232. package/dist/dev/media/dom/mse/duration.js +27 -10
  233. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  234. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  235. package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
  236. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  237. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
  238. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  239. package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
  240. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  241. package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
  242. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  243. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  244. package/dist/dev/media/errors.d.ts +82 -0
  245. package/dist/dev/media/errors.d.ts.map +1 -0
  246. package/dist/dev/media/errors.js +62 -0
  247. package/dist/dev/media/errors.js.map +1 -0
  248. package/dist/dev/media/hls/parse-attributes.js +1 -1
  249. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  250. package/dist/dev/media/hls/parse-media-playlist.js +175 -19
  251. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  252. package/dist/dev/media/hls/parse-multivariant.js +14 -3
  253. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  254. package/dist/dev/media/hls/reload-policy.js +71 -0
  255. package/dist/dev/media/hls/reload-policy.js.map +1 -0
  256. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  257. package/dist/dev/media/live-window.js +36 -0
  258. package/dist/dev/media/live-window.js.map +1 -0
  259. package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
  260. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  261. package/dist/dev/media/media-tracks/media-tracks.js +101 -0
  262. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  263. package/dist/dev/media/mp4/box.js +58 -0
  264. package/dist/dev/media/mp4/box.js.map +1 -0
  265. package/dist/dev/media/mp4/timestamp-origin.js +107 -0
  266. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  267. package/dist/dev/media/primitives/select-tracks.d.ts +0 -1
  268. package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -1
  269. package/dist/dev/media/primitives/select-tracks.js +1 -24
  270. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  271. package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
  272. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  273. package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
  274. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  275. package/dist/dev/media/types/index.d.ts +163 -4
  276. package/dist/dev/media/types/index.d.ts.map +1 -1
  277. package/dist/dev/media/types/index.js +15 -1
  278. package/dist/dev/media/types/index.js.map +1 -1
  279. package/dist/dev/media/utils/cdn.d.ts.map +1 -1
  280. package/dist/dev/media/utils/cdn.js.map +1 -1
  281. package/dist/dev/media/utils/preload.js.map +1 -1
  282. package/dist/dev/media/utils/track-selection.js +1 -1
  283. package/dist/dev/media/utils/track-selection.js.map +1 -1
  284. package/dist/dev/media/utils/tracks.js +17 -3
  285. package/dist/dev/media/utils/tracks.js.map +1 -1
  286. package/dist/dev/media-tracks.d.ts +3 -0
  287. package/dist/dev/media-tracks.js +2 -0
  288. package/dist/dev/mux-audio.d.ts +4 -0
  289. package/dist/dev/mux-audio.js +3 -0
  290. package/dist/dev/mux-background-video.d.ts +3 -0
  291. package/dist/dev/mux-background-video.js +3 -0
  292. package/dist/dev/mux-video.d.ts +4 -0
  293. package/dist/dev/mux-video.js +3 -0
  294. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  295. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  296. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  297. package/dist/dev/network/ewma.js +1 -1
  298. package/dist/dev/network/ewma.js.map +1 -1
  299. package/dist/dev/network/fetch.js.map +1 -1
  300. package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
  301. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  302. package/dist/dev/playback/actors/dom/segment-loader.js +61 -95
  303. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  304. package/dist/dev/playback/actors/dom/source-buffer.d.ts +3 -33
  305. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  306. package/dist/dev/playback/actors/dom/source-buffer.js +1 -0
  307. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  308. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
  309. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  310. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
  311. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  312. package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
  313. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  314. package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
  315. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  316. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +54 -0
  317. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
  318. package/dist/dev/playback/adapters/hls-audio/adapter.js +186 -0
  319. package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
  320. package/dist/dev/playback/adapters/hls-audio/media.d.ts +10 -0
  321. package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -0
  322. package/dist/dev/playback/adapters/hls-audio/media.js +9 -0
  323. package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -0
  324. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +59 -0
  325. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
  326. package/dist/dev/playback/adapters/hls-background-video/adapter.js +123 -0
  327. package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
  328. package/dist/dev/playback/adapters/hls-background-video/host.d.ts +38 -0
  329. package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
  330. package/dist/dev/playback/adapters/hls-background-video/host.js +60 -0
  331. package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
  332. package/dist/dev/playback/adapters/hls-background-video/media.d.ts +20 -0
  333. package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -0
  334. package/dist/dev/playback/adapters/hls-background-video/media.js +21 -0
  335. package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -0
  336. package/dist/dev/playback/adapters/hls-video/adapter.d.ts +63 -0
  337. package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
  338. package/dist/dev/playback/adapters/hls-video/adapter.js +316 -0
  339. package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
  340. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +18 -0
  341. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
  342. package/dist/dev/playback/adapters/hls-video/error-surface.js +51 -0
  343. package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
  344. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +22 -0
  345. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
  346. package/dist/dev/playback/adapters/hls-video/media-tracks.js +126 -0
  347. package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
  348. package/dist/dev/playback/adapters/hls-video/media.d.ts +10 -0
  349. package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -0
  350. package/dist/dev/playback/adapters/hls-video/media.js +11 -0
  351. package/dist/dev/playback/adapters/hls-video/media.js.map +1 -0
  352. package/dist/dev/playback/adapters/mux-audio/media.d.ts +28 -0
  353. package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -0
  354. package/dist/dev/playback/adapters/mux-audio/media.js +26 -0
  355. package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -0
  356. package/dist/dev/playback/adapters/mux-video/adapter.d.ts +33 -0
  357. package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -0
  358. package/dist/dev/playback/adapters/mux-video/adapter.js +92 -0
  359. package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -0
  360. package/dist/dev/playback/adapters/mux-video/media.d.ts +24 -0
  361. package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -0
  362. package/dist/dev/playback/adapters/mux-video/media.js +23 -0
  363. package/dist/dev/playback/adapters/mux-video/media.js.map +1 -0
  364. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +3 -2
  365. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  366. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  367. package/dist/dev/playback/behaviors/collect-errors.js +77 -0
  368. package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
  369. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
  370. package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
  371. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  372. package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
  373. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  374. package/dist/dev/playback/behaviors/dom/end-of-stream.js +13 -1
  375. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  376. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
  377. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  378. package/dist/dev/playback/behaviors/dom/load-segments.js +5 -1
  379. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  380. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
  381. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  382. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +132 -0
  383. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  384. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +17 -4
  385. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  386. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
  387. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  388. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
  389. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  390. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  391. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  392. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
  393. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  394. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  395. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
  396. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  397. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  398. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
  399. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  400. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
  401. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  402. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
  403. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  404. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  405. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +31 -8
  406. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  407. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +36 -0
  408. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  409. package/dist/dev/playback/behaviors/establish-start-media-time.js +197 -0
  410. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  411. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
  412. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  413. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  414. package/dist/dev/playback/behaviors/resolve-track.js +29 -8
  415. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  416. package/dist/dev/playback/behaviors/select-tracks.d.ts +2 -1
  417. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
  418. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  419. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +2 -0
  420. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
  421. package/dist/dev/playback/behaviors/setup-failover-monitor.js +1 -1
  422. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
  423. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  424. package/dist/dev/playback/behaviors/track-switching.js +76 -5
  425. package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
  426. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +63 -17
  427. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
  428. package/dist/dev/playback/engines/hls/engine-audio-only.js +25 -7
  429. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
  430. package/dist/dev/playback/engines/{background-video/engine.d.ts → hls/engine-background-video.d.ts} +8 -9
  431. package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
  432. package/dist/{default/playback/engines/background-video/engine.js → dev/playback/engines/hls/engine-background-video.js} +3 -3
  433. package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
  434. package/dist/dev/playback/engines/hls/engine.d.ts +90 -16
  435. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  436. package/dist/dev/playback/engines/hls/engine.js +36 -6
  437. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  438. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  439. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  440. package/dist/dev/playback/primitives/error-messages.js +39 -0
  441. package/dist/dev/playback/primitives/error-messages.js.map +1 -0
  442. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
  443. package/dist/dev/playback/primitives/head-peek.js +44 -0
  444. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  445. package/dist/dev/playback/primitives/live-window.js +66 -0
  446. package/dist/dev/playback/primitives/live-window.js.map +1 -0
  447. package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
  448. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  449. package/dist/dev/playback/primitives/report-track-conditions.d.ts +11 -0
  450. package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
  451. package/dist/dev/playback/primitives/report-track-conditions.js +86 -0
  452. package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -0
  453. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  454. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  455. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  456. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
  457. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  458. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
  459. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  460. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
  461. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  462. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  463. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  464. package/dist/dev/playback/primitives/track-types.js.map +1 -1
  465. package/package.json +42 -11
  466. package/dist/default/background-video.js +0 -3
  467. package/dist/default/playback/engines/background-video/adapter.js +0 -158
  468. package/dist/default/playback/engines/background-video/adapter.js.map +0 -1
  469. package/dist/default/playback/engines/background-video/engine.js.map +0 -1
  470. package/dist/default/playback/engines/hls/adapter-audio-only.js +0 -119
  471. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +0 -1
  472. package/dist/default/playback/engines/hls/adapter.js +0 -118
  473. package/dist/default/playback/engines/hls/adapter.js.map +0 -1
  474. package/dist/dev/background-video.d.ts +0 -3
  475. package/dist/dev/background-video.js +0 -3
  476. package/dist/dev/playback/engines/background-video/adapter.d.ts +0 -60
  477. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +0 -1
  478. package/dist/dev/playback/engines/background-video/adapter.js +0 -158
  479. package/dist/dev/playback/engines/background-video/adapter.js.map +0 -1
  480. package/dist/dev/playback/engines/background-video/engine.d.ts.map +0 -1
  481. package/dist/dev/playback/engines/background-video/engine.js.map +0 -1
  482. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +0 -45
  483. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +0 -1
  484. package/dist/dev/playback/engines/hls/adapter-audio-only.js +0 -119
  485. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +0 -1
  486. package/dist/dev/playback/engines/hls/adapter.d.ts +0 -44
  487. package/dist/dev/playback/engines/hls/adapter.d.ts.map +0 -1
  488. package/dist/dev/playback/engines/hls/adapter.js +0 -118
  489. package/dist/dev/playback/engines/hls/adapter.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../../../../src/playback/adapters/mux-audio/media.ts"],"sourcesContent":["// Deliberately the mixin's own module, not `../mux-video`: that barrel reaches\n// `MuxVideoMedia`, and through it `HlsVideoMedia` and the full HLS engine. The\n// mixin alone carries no engine, which is what keeps this entry point\n// audio-only. Same shape as `hls-audio` importing its error surface\n// from `../hls-video`.\n\nimport { HlsAudioMedia } from '../hls-audio/media';\nimport { MuxMediaMixin } from '../mux-video/adapter';\n\nconst MuxAudioMediaBase = MuxMediaMixin(HlsAudioMedia);\n\n/**\n * The Mux Media over the SPF audio-only HLS engine.\n *\n * Same Mux surface as the video flavor — `src`, the structured `source`, and the\n * derived `contentData` all come from the shared mixin — over the subtractive\n * engine, so only the audio renditions of whatever the playback ID names are\n * fetched. That is the one place this diverges from the hls.js-backed\n * `<mux-audio>`, which runs the full engine and downloads video renditions it\n * never shows.\n *\n * `contentData` is kept rather than dropped, for the same reason its hls.js\n * counterpart has it: a playback ID played as audio is usually a *video* asset,\n * whose poster and storyboard exist and which an audio skin may well want. The\n * element ignores it either way. Mux publishes neither for a genuinely\n * audio-only asset, so those URLs 404 — see the known shortcoming on the video\n * flavor, which shares the derivation.\n */\nexport class MuxAudioMedia extends MuxAudioMediaBase {}\n"],"mappings":";;;AASA,MAAM,oBAAoB,cAAc,aAAa;;;;;;;;;;;;;;;;;;AAmBrD,IAAa,gBAAb,cAAmC,kBAAkB,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { createMuxPosterURL, createMuxStoryboardURL, createMuxVideoURL, parseMuxVideoURL } from "@videojs/media/dom/mux/source";
2
+ //#region src/playback/adapters/mux-video/adapter.ts
3
+ const muxMediaDefaultProps = {
4
+ src: "",
5
+ source: null
6
+ };
7
+ /**
8
+ * Mux identity over any SPF Media: the structured `source`, the `src` derived
9
+ * from it, and the image URLs it describes.
10
+ *
11
+ * Everything here is Mux identity, so it carries no engine and both flavors get
12
+ * it unchanged — the video Media over the full HLS engine, the audio-only Media
13
+ * over the subtractive one. A mixin rather than a shared base class because each
14
+ * flavor extends a different SPF Media, so there is no common class to put this
15
+ * on, only a common `src` accessor to write through.
16
+ *
17
+ * Unlike the hls.js-backed `MuxMedia`, there is no inherited `source` to
18
+ * delegate to — the SPF Medias know only `src` — so this owns the structured
19
+ * source and dispatches `sourcechange` itself.
20
+ *
21
+ * @fires sourcechange - Fired when `source` changes, either directly or by parsing a new `src`. Read `source` for the new value.
22
+ */
23
+ function MuxMediaMixin(BaseClass) {
24
+ class MuxMediaImpl extends BaseClass {
25
+ /**
26
+ * Named on the error copy when this engine can't play a source: the
27
+ * hls.js-backed Mux Media plays the MPEG-TS and DRM-protected sources that
28
+ * SPF does not, and it backs both `<mux-video>` and `<mux-audio>`.
29
+ *
30
+ * Names the flavor rather than an import path, because one Media is reached
31
+ * through three of them — `@videojs/html`, `@videojs/react`, and this package
32
+ * — and each has a different counterpart. The flavor suffix is the one thing
33
+ * common to the layers a consumer imports elements and components from.
34
+ */
35
+ static get alternativeMediaSuggestion() {
36
+ return "Try the hls.js-backed Mux media instead: import the `hls-js` flavor in place of the `spf` one.";
37
+ }
38
+ #source = muxMediaDefaultProps.source;
39
+ /**
40
+ * Media source URL. Setting a Mux stream URL
41
+ * (`https://stream.mux.com/<playback-id>.m3u8?...`) extracts the playback ID
42
+ * and query params into `source`; other URLs are kept as a plain `source.src`.
43
+ *
44
+ * Only playback options carry over. Mux identity comes from the URL, and the
45
+ * signed `poster`, `storyboard`, and `drm` tokens are scoped to a playback ID,
46
+ * so carrying them onto a different source would build rejected URLs.
47
+ */
48
+ get src() {
49
+ return super.src;
50
+ }
51
+ set src(value) {
52
+ if (super.src === value) return;
53
+ this.source = parseMuxVideoURL(value) ?? (value ? { src: value } : null);
54
+ }
55
+ /**
56
+ * Structured Mux source. Setting it derives `src` from the playback ID, custom
57
+ * domain, and `playback` params (appended as `snake_case` query params). A
58
+ * `playback.token` replaces all other params — signed URLs bake them into the
59
+ * token.
60
+ */
61
+ get source() {
62
+ return this.#source;
63
+ }
64
+ set source(value) {
65
+ const source = value ?? null;
66
+ if (source === this.#source) return;
67
+ this.#source = source;
68
+ super.src = source && (createMuxVideoURL(source) ?? source.src) || "";
69
+ this.dispatchEvent?.(new Event("sourcechange"));
70
+ }
71
+ /**
72
+ * Image URLs `source` describes rather than plays: `poster` from its `poster`
73
+ * params, `storyboard` from its `storyboard` params.
74
+ *
75
+ * Read-only and re-derived on read, so read it again after `sourcechange`.
76
+ * Nothing here is applied for you.
77
+ */
78
+ get contentData() {
79
+ const poster = createMuxPosterURL(this.#source);
80
+ const storyboard = createMuxStoryboardURL(this.#source);
81
+ return {
82
+ ...poster && { poster },
83
+ ...storyboard && { storyboard }
84
+ };
85
+ }
86
+ }
87
+ return MuxMediaImpl;
88
+ }
89
+ //#endregion
90
+ export { MuxMediaMixin, muxMediaDefaultProps };
91
+
92
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","names":["#source"],"sources":["../../../../../src/playback/adapters/mux-video/adapter.ts"],"sourcesContent":["import {\n createMuxPosterURL,\n createMuxStoryboardURL,\n createMuxVideoURL,\n type MuxContentData,\n type MuxSourceBase,\n parseMuxVideoURL,\n} from '@videojs/media/dom/mux/source';\nimport type { Constructor, MixinReturn } from '@videojs/utils/types';\n\nexport interface MuxMediaProps {\n src: string;\n source: MuxSourceBase | null;\n}\n\nexport const muxMediaDefaultProps: MuxMediaProps = {\n src: '',\n source: null,\n};\n\nexport interface MuxMediaAPI extends MuxMediaProps {\n readonly contentData: MuxContentData;\n}\n\n/**\n * Mux identity over any SPF Media: the structured `source`, the `src` derived\n * from it, and the image URLs it describes.\n *\n * Everything here is Mux identity, so it carries no engine and both flavors get\n * it unchanged — the video Media over the full HLS engine, the audio-only Media\n * over the subtractive one. A mixin rather than a shared base class because each\n * flavor extends a different SPF Media, so there is no common class to put this\n * on, only a common `src` accessor to write through.\n *\n * Unlike the hls.js-backed `MuxMedia`, there is no inherited `source` to\n * delegate to — the SPF Medias know only `src` — so this owns the structured\n * source and dispatches `sourcechange` itself.\n *\n * @fires sourcechange - Fired when `source` changes, either directly or by parsing a new `src`. Read `source` for the new value.\n */\nexport function MuxMediaMixin<Base extends Constructor<any>>(BaseClass: Base) {\n class MuxMediaImpl extends BaseClass {\n /**\n * Named on the error copy when this engine can't play a source: the\n * hls.js-backed Mux Media plays the MPEG-TS and DRM-protected sources that\n * SPF does not, and it backs both `<mux-video>` and `<mux-audio>`.\n *\n * Names the flavor rather than an import path, because one Media is reached\n * through three of them — `@videojs/html`, `@videojs/react`, and this package\n * — and each has a different counterpart. The flavor suffix is the one thing\n * common to the layers a consumer imports elements and components from.\n */\n static get alternativeMediaSuggestion(): string | undefined {\n return 'Try the hls.js-backed Mux media instead: import the `hls-js` flavor in place of the `spf` one.';\n }\n\n #source: MuxSourceBase | null = muxMediaDefaultProps.source;\n\n /**\n * Media source URL. Setting a Mux stream URL\n * (`https://stream.mux.com/<playback-id>.m3u8?...`) extracts the playback ID\n * and query params into `source`; other URLs are kept as a plain `source.src`.\n *\n * Only playback options carry over. Mux identity comes from the URL, and the\n * signed `poster`, `storyboard`, and `drm` tokens are scoped to a playback ID,\n * so carrying them onto a different source would build rejected URLs.\n */\n get src(): string {\n return super.src;\n }\n\n set src(value: string) {\n // A URL already describing the current source leaves it alone. The elements\n // reflect the derived `src` back to the host, and re-deriving would drop the\n // params a Mux URL does not carry, such as `poster`.\n if (super.src === value) return;\n\n this.source = parseMuxVideoURL(value) ?? (value ? { src: value } : null);\n }\n\n /**\n * Structured Mux source. Setting it derives `src` from the playback ID, custom\n * domain, and `playback` params (appended as `snake_case` query params). A\n * `playback.token` replaces all other params — signed URLs bake them into the\n * token.\n */\n get source(): MuxSourceBase | null {\n return this.#source;\n }\n\n set source(value: MuxSourceBase | null) {\n const source = value ?? null;\n // Changing anything takes a new object, so handing the same one back costs\n // nothing.\n if (source === this.#source) return;\n\n this.#source = source;\n super.src = (source && (createMuxVideoURL(source) ?? source.src)) || '';\n\n this.dispatchEvent?.(new Event('sourcechange'));\n }\n\n /**\n * Image URLs `source` describes rather than plays: `poster` from its `poster`\n * params, `storyboard` from its `storyboard` params.\n *\n * Read-only and re-derived on read, so read it again after `sourcechange`.\n * Nothing here is applied for you.\n */\n get contentData(): MuxContentData {\n const poster = createMuxPosterURL(this.#source);\n const storyboard = createMuxStoryboardURL(this.#source);\n\n return {\n ...(poster && { poster }),\n ...(storyboard && { storyboard }),\n };\n }\n }\n\n // `MixinReturn` sources statics from `Base`, so this mixin's own needs adding\n // back to the type or callers can't read it.\n return MuxMediaImpl as unknown as MixinReturn<Base, MuxMediaAPI> & {\n readonly alternativeMediaSuggestion: string | undefined;\n };\n}\n"],"mappings":";;AAeA,MAAa,uBAAsC;CACjD,KAAK;CACL,QAAQ;AACV;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cAA6C,WAAiB;CAC5E,MAAM,qBAAqB,UAAU;;;;;;;;;;;EAWnC,WAAW,6BAAiD;GAC1D,OAAO;EACT;EAEA,UAAgC,qBAAqB;;;;;;;;;;EAWrD,IAAI,MAAc;GAChB,OAAO,MAAM;EACf;EAEA,IAAI,IAAI,OAAe;GAIrB,IAAI,MAAM,QAAQ,OAAO;GAEzB,KAAK,SAAS,iBAAiB,KAAK,MAAM,QAAQ,EAAE,KAAK,MAAM,IAAI;EACrE;;;;;;;EAQA,IAAI,SAA+B;GACjC,OAAO,KAAKA;EACd;EAEA,IAAI,OAAO,OAA6B;GACtC,MAAM,SAAS,SAAS;GAGxB,IAAI,WAAW,KAAKA,SAAS;GAE7B,KAAKA,UAAU;GACf,MAAM,MAAO,WAAW,kBAAkB,MAAM,KAAK,OAAO,QAAS;GAErE,KAAK,gBAAgB,IAAI,MAAM,cAAc,CAAC;EAChD;;;;;;;;EASA,IAAI,cAA8B;GAChC,MAAM,SAAS,mBAAmB,KAAKA,OAAO;GAC9C,MAAM,aAAa,uBAAuB,KAAKA,OAAO;GAEtD,OAAO;IACL,GAAI,UAAU,EAAE,OAAO;IACvB,GAAI,cAAc,EAAE,WAAW;GACjC;EACF;CACF;CAIA,OAAO;AAGT"}
@@ -0,0 +1,23 @@
1
+ import { HlsVideoMedia } from "../hls-video/media.js";
2
+ import { MuxMediaMixin } from "./adapter.js";
3
+ //#region src/playback/adapters/mux-video/media.ts
4
+ const MuxVideoMediaBase = MuxMediaMixin(HlsVideoMedia);
5
+ /**
6
+ * The Mux Media over the SPF HLS engine.
7
+ *
8
+ * Mirrors `@videojs/media/dom/mux`'s hls.js-backed `MuxMedia` — same class shape,
9
+ * same `src`/`source` relationship, same derived `contentData` — over a different
10
+ * engine, though named for the flavor rather than sharing that class's name. It
11
+ * carries no `engine` or `preferPlayback`: SPF publishes no
12
+ * engine-shaped config for a consumer to pass, so the source is Mux identity and
13
+ * nothing else.
14
+ *
15
+ * `source.drm` is accepted but inert. SPF prunes encrypted renditions and reports
16
+ * unsupported DRM, and `alternativeMediaSuggestion` points at the hls.js-backed
17
+ * import, so a protected source fails with copy that says where to go.
18
+ */
19
+ var MuxVideoMedia = class extends MuxVideoMediaBase {};
20
+ //#endregion
21
+ export { MuxVideoMedia };
22
+
23
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../../../../src/playback/adapters/mux-video/media.ts"],"sourcesContent":["import { HlsVideoMedia } from '../hls-video/media';\nimport { MuxMediaMixin } from './adapter';\n\nconst MuxVideoMediaBase = MuxMediaMixin(HlsVideoMedia);\n\n/**\n * The Mux Media over the SPF HLS engine.\n *\n * Mirrors `@videojs/media/dom/mux`'s hls.js-backed `MuxMedia` — same class shape,\n * same `src`/`source` relationship, same derived `contentData` — over a different\n * engine, though named for the flavor rather than sharing that class's name. It\n * carries no `engine` or `preferPlayback`: SPF publishes no\n * engine-shaped config for a consumer to pass, so the source is Mux identity and\n * nothing else.\n *\n * `source.drm` is accepted but inert. SPF prunes encrypted renditions and reports\n * unsupported DRM, and `alternativeMediaSuggestion` points at the hls.js-backed\n * import, so a protected source fails with copy that says where to go.\n */\nexport class MuxVideoMedia extends MuxVideoMediaBase {}\n"],"mappings":";;;AAGA,MAAM,oBAAoB,cAAc,aAAa;;;;;;;;;;;;;;;AAgBrD,IAAa,gBAAb,cAAmC,kBAAkB,CAAC"}
@@ -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"}
@@ -0,0 +1,77 @@
1
+ import { computed, update } from "../../core/signals/primitives.js";
2
+ import { defineBehavior } from "../../core/composition/create-composition.js";
3
+ import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
4
+ import { isResolvedPresentation } from "../../media/types/index.js";
5
+ //#region src/playback/behaviors/collect-errors.ts
6
+ /**
7
+ * **Owns the engine's error sequence.** Reporters append through
8
+ * {@link emitError}; this behavior owns the slot and its per-source lifecycle,
9
+ * clearing it on exit so a new source starts clean and the sequence can't grow
10
+ * unbounded across a session.
11
+ *
12
+ * Same split as `setupFailoverMonitor` and `failedCdns`: writes come from
13
+ * wherever the condition is detected, one behavior owns the slot. Deliberately
14
+ * has no `effects` — it holds no policy and derives nothing. Severity is decided
15
+ * at the adapter, not here (see `internal/design/spf/features/errors.md`), which
16
+ * is why this is a lifecycle owner rather than an error *handler*.
17
+ *
18
+ * Clearing binds to *exit* of `presentation-resolved`, mirroring the sibling
19
+ * mixins' clear-on-teardown (`emptied` / `MEDIA_DETACHED`). A live reload swaps
20
+ * the presentation object without leaving the resolved state, so it doesn't
21
+ * clear — only an actual source change or destroy does. Known gap: a
22
+ * resolved→resolved source swap that never passes through unresolved carries the
23
+ * prior source's errors forward; `resolve-track` guards the same transition with
24
+ * a commit-time id check, and doing likewise here is a follow-up.
25
+ *
26
+ * The vocabulary itself ({@link SvtaError} and the codes) is DOM- and
27
+ * signal-free in `media/errors`; only the write seam lives here, with the slot
28
+ * it writes.
29
+ */
30
+ /**
31
+ * Append `error` to the engine's error sequence. No-op when no owner is
32
+ * composed. Replaces the array rather than mutating it, so signal consumers
33
+ * notify; duplicates are kept, since a repeated condition is a real observation.
34
+ * Writes go through `update` so concurrent reporters can't lose each other's
35
+ * appends.
36
+ *
37
+ * Every emission is logged, deliberately *before* the owner check. A condition
38
+ * emitted with no `collectErrors` composed is dropped on the floor — that's the
39
+ * case where a log is the only evidence it happened at all, so gating the log on
40
+ * the same check would hide exactly what's worth seeing. Emissions that *are*
41
+ * collected still get logged, because reaching `state.errors` is no guarantee of
42
+ * reaching a person: only *verdicts* are promoted to the media surface, so every
43
+ * cause (and any non-fatal notice) is otherwise invisible outside a debugger.
44
+ *
45
+ * Ungated rather than `__DEV__`-only, matching the other reporting paths in this
46
+ * package (`resolve-presentation`, `track-switching`, the segment actors).
47
+ */
48
+ function emitError(state, error) {
49
+ console.error("[spf] reported condition", error);
50
+ if (!state.errors) return;
51
+ update(state.errors, (errors) => [...errors ?? [], error]);
52
+ }
53
+ /**
54
+ * Own `errors` for the resolved source's lifetime.
55
+ *
56
+ * @example
57
+ * const reactor = collectErrors.setup({ state });
58
+ */
59
+ const collectErrors = defineBehavior({
60
+ stateKeys: ["presentation", "errors"],
61
+ contextKeys: [],
62
+ setup: ({ state }) => {
63
+ const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
64
+ return createMachineReactor({
65
+ initial: "presentation-unresolved",
66
+ monitor: () => derivedStateSignal.get(),
67
+ states: {
68
+ "presentation-unresolved": {},
69
+ "presentation-resolved": { entry: () => () => state.errors.set(void 0) }
70
+ }
71
+ });
72
+ }
73
+ });
74
+ //#endregion
75
+ export { collectErrors, emitError };
76
+
77
+ //# sourceMappingURL=collect-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-errors.js","names":[],"sources":["../../../../src/playback/behaviors/collect-errors.ts"],"sourcesContent":["/**\n * **Owns the engine's error sequence.** Reporters append through\n * {@link emitError}; this behavior owns the slot and its per-source lifecycle,\n * clearing it on exit so a new source starts clean and the sequence can't grow\n * unbounded across a session.\n *\n * Same split as `setupFailoverMonitor` and `failedCdns`: writes come from\n * wherever the condition is detected, one behavior owns the slot. Deliberately\n * has no `effects` — it holds no policy and derives nothing. Severity is decided\n * at the adapter, not here (see `internal/design/spf/features/errors.md`), which\n * is why this is a lifecycle owner rather than an error *handler*.\n *\n * Clearing binds to *exit* of `presentation-resolved`, mirroring the sibling\n * mixins' clear-on-teardown (`emptied` / `MEDIA_DETACHED`). A live reload swaps\n * the presentation object without leaving the resolved state, so it doesn't\n * clear — only an actual source change or destroy does. Known gap: a\n * resolved→resolved source swap that never passes through unresolved carries the\n * prior source's errors forward; `resolve-track` guards the same transition with\n * a commit-time id check, and doing likewise here is a follow-up.\n *\n * The vocabulary itself ({@link SvtaError} and the codes) is DOM- and\n * signal-free in `media/errors`; only the write seam lives here, with the slot\n * it writes.\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport type { SvtaError } from '../../media/errors';\nimport { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\n\nexport interface CollectErrorsState {\n presentation?: MaybeResolvedPresentation;\n errors?: SvtaError[];\n}\n\n/**\n * State an error reporter writes into. The slot is *optional*: a behavior reports\n * through this seam without declaring ownership in its own typed slice, and\n * emission no-ops when `collectErrors` isn't composed. Same contract as\n * `failedCdns` / `failoverFetch`.\n */\nexport interface ErrorEmitterState {\n errors?: Signal<SvtaError[] | undefined>;\n}\n\n/**\n * Append `error` to the engine's error sequence. No-op when no owner is\n * composed. Replaces the array rather than mutating it, so signal consumers\n * notify; duplicates are kept, since a repeated condition is a real observation.\n * Writes go through `update` so concurrent reporters can't lose each other's\n * appends.\n *\n * Every emission is logged, deliberately *before* the owner check. A condition\n * emitted with no `collectErrors` composed is dropped on the floor — that's the\n * case where a log is the only evidence it happened at all, so gating the log on\n * the same check would hide exactly what's worth seeing. Emissions that *are*\n * collected still get logged, because reaching `state.errors` is no guarantee of\n * reaching a person: only *verdicts* are promoted to the media surface, so every\n * cause (and any non-fatal notice) is otherwise invisible outside a debugger.\n *\n * Ungated rather than `__DEV__`-only, matching the other reporting paths in this\n * package (`resolve-presentation`, `track-switching`, the segment actors).\n */\nexport function emitError(state: ErrorEmitterState, error: SvtaError): void {\n console.error('[spf] reported condition', error);\n if (!state.errors) return;\n update(state.errors, (errors) => [...(errors ?? []), error]);\n}\n\n/**\n * Own `errors` for the resolved source's lifetime.\n *\n * @example\n * const reactor = collectErrors.setup({ state });\n */\nexport const collectErrors = defineBehavior({\n stateKeys: ['presentation', 'errors'],\n contextKeys: [],\n setup: ({\n state,\n }: {\n state: {\n presentation: ReadonlySignal<CollectErrorsState['presentation']>;\n errors: Signal<CollectErrorsState['errors']>;\n };\n }) => {\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: reset for the next source on exit (src\n // unload + destroy).\n entry: () => () => state.errors.set(undefined),\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,SAAgB,UAAU,OAA0B,OAAwB;CAC1E,QAAQ,MAAM,4BAA4B,KAAK;CAC/C,IAAI,CAAC,MAAM,QAAQ;CACnB,OAAO,MAAM,SAAS,WAAW,CAAC,GAAI,UAAU,CAAC,GAAI,KAAK,CAAC;AAC7D;;;;;;;AAQA,MAAa,gBAAgB,eAAe;CAC1C,WAAW,CAAC,gBAAgB,QAAQ;CACpC,aAAa,CAAC;CACd,QAAQ,EACN,YAMI;EACJ,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,EAGvB,mBAAmB,MAAM,OAAO,IAAI,KAAA,CAAS,EAC/C;GACF;EACF,CAAC;CACH;AACF,CAAC"}
@@ -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"}