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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/README.md +5 -0
  2. package/dist/default/background-video.js +3 -0
  3. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  5. package/dist/default/core/composition/create-composition.js +2 -2
  6. package/dist/default/core/composition/create-composition.js.map +1 -1
  7. package/dist/default/core/composition/share-signals.js +13 -11
  8. package/dist/default/core/composition/share-signals.js.map +1 -1
  9. package/dist/default/core/machine.js.map +1 -1
  10. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  11. package/dist/default/core/signals/effect.js.map +1 -1
  12. package/dist/default/core/signals/primitives.js.map +1 -1
  13. package/dist/default/core/tasks/task.js.map +1 -1
  14. package/dist/default/hls.js +4 -1
  15. package/dist/default/media/abr/quality-selection.js +6 -68
  16. package/dist/default/media/abr/quality-selection.js.map +1 -1
  17. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  18. package/dist/default/media/buffer/forward-buffer.js +37 -3
  19. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  20. package/dist/default/media/dom/capabilities.js +70 -0
  21. package/dist/default/media/dom/capabilities.js.map +1 -0
  22. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  23. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  24. package/dist/default/media/dom/mse/duration.js +27 -10
  25. package/dist/default/media/dom/mse/duration.js.map +1 -1
  26. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  27. package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
  28. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  29. package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
  30. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  31. package/dist/default/media/dom/text/text-track-slots.js +3 -3
  32. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  33. package/dist/default/media/hls/parse-attributes.js +17 -3
  34. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  35. package/dist/default/media/hls/parse-media-playlist.js +29 -1
  36. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  37. package/dist/default/media/hls/parse-multivariant.js +15 -6
  38. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  39. package/dist/default/media/hls/resolve-url.js.map +1 -1
  40. package/dist/default/media/media-tracks/media-tracks.js +101 -0
  41. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  42. package/dist/default/media/mp4/box.js +58 -0
  43. package/dist/default/media/mp4/box.js.map +1 -0
  44. package/dist/default/media/mp4/timestamp-origin.js +107 -0
  45. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  46. package/dist/default/media/primitives/select-tracks.js +98 -13
  47. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  48. package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
  49. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  50. package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
  51. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  52. package/dist/default/media/types/index.js.map +1 -1
  53. package/dist/default/media/utils/cdn.js +56 -0
  54. package/dist/default/media/utils/cdn.js.map +1 -0
  55. package/dist/default/media/utils/preload.js.map +1 -1
  56. package/dist/default/media/utils/track-selection.js +1 -1
  57. package/dist/default/media/utils/track-selection.js.map +1 -1
  58. package/dist/default/media/utils/tracks.js +31 -4
  59. package/dist/default/media/utils/tracks.js.map +1 -1
  60. package/dist/default/media-tracks.js +2 -0
  61. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  62. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  63. package/dist/default/network/ewma.js +1 -1
  64. package/dist/default/network/ewma.js.map +1 -1
  65. package/dist/default/network/fetch.js +7 -1
  66. package/dist/default/network/fetch.js.map +1 -1
  67. package/dist/default/playback/actors/dom/segment-loader.js +103 -106
  68. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  69. package/dist/default/playback/actors/dom/source-buffer.js +3 -1
  70. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  71. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  72. package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
  73. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  74. package/dist/default/playback/behaviors/calculate-presentation-duration.js +21 -43
  75. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  76. package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
  77. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
  78. package/dist/default/playback/behaviors/dom/airplay.js +211 -0
  79. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  80. package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
  81. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  82. package/dist/default/playback/behaviors/dom/end-of-stream.js +25 -102
  83. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  84. package/dist/default/playback/behaviors/dom/load-segments.js +6 -2
  85. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  86. package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
  87. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  88. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +50 -18
  89. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  90. package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
  91. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  92. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  93. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  94. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +43 -18
  95. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  96. package/dist/default/playback/behaviors/dom/track-current-time.js +6 -0
  97. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  98. package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
  99. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  100. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  101. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +3 -2
  102. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  103. package/dist/default/playback/behaviors/establish-start-media-time.js +123 -0
  104. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  105. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  106. package/dist/default/playback/behaviors/resolve-track.js +45 -22
  107. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  108. package/dist/default/playback/behaviors/select-tracks.js +46 -77
  109. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  110. package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
  111. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
  112. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  113. package/dist/default/playback/behaviors/track-switching.js +403 -0
  114. package/dist/default/playback/behaviors/track-switching.js.map +1 -0
  115. package/dist/default/playback/engines/background-video/adapter.js +158 -0
  116. package/dist/default/playback/engines/background-video/adapter.js.map +1 -0
  117. package/dist/default/playback/engines/background-video/engine.js +75 -0
  118. package/dist/default/playback/engines/background-video/engine.js.map +1 -0
  119. package/dist/default/playback/engines/hls/adapter-audio-only.js +124 -0
  120. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -0
  121. package/dist/default/playback/engines/hls/adapter.js +20 -11
  122. package/dist/default/playback/engines/hls/adapter.js.map +1 -1
  123. package/dist/default/playback/engines/hls/engine-audio-only.js +94 -0
  124. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
  125. package/dist/default/playback/engines/hls/engine.js +38 -8
  126. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  127. package/dist/default/playback/primitives/failover-fetch.js +42 -0
  128. package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
  129. package/dist/default/playback/primitives/head-peek.js +44 -0
  130. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  131. package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
  132. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  133. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  134. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  135. package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
  136. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  137. package/dist/{dev/playback/behaviors → default/playback/primitives}/track-types.js +1 -1
  138. package/dist/default/playback/primitives/track-types.js.map +1 -0
  139. package/dist/dev/background-video.d.ts +3 -0
  140. package/dist/dev/background-video.js +3 -0
  141. package/dist/dev/core/actors/actor.d.ts +0 -1
  142. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  143. package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
  144. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  145. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  146. package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
  147. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  148. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  149. package/dist/dev/core/composition/create-composition.d.ts +10 -11
  150. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  151. package/dist/dev/core/composition/create-composition.js +2 -2
  152. package/dist/dev/core/composition/create-composition.js.map +1 -1
  153. package/dist/dev/core/composition/share-signals.d.ts +11 -10
  154. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  155. package/dist/dev/core/composition/share-signals.js +13 -11
  156. package/dist/dev/core/composition/share-signals.js.map +1 -1
  157. package/dist/dev/core/machine.d.ts +1 -0
  158. package/dist/dev/core/machine.d.ts.map +1 -1
  159. package/dist/dev/core/machine.js.map +1 -1
  160. package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
  161. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  162. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  163. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  164. package/dist/dev/core/signals/effect.js.map +1 -1
  165. package/dist/dev/core/signals/primitives.d.ts +1 -2
  166. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  167. package/dist/dev/core/signals/primitives.js.map +1 -1
  168. package/dist/dev/core/tasks/task.d.ts +2 -2
  169. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  170. package/dist/dev/core/tasks/task.js.map +1 -1
  171. package/dist/dev/hls.d.ts +5 -1
  172. package/dist/dev/hls.js +4 -1
  173. package/dist/dev/index.d.ts +0 -1
  174. package/dist/dev/index.d.ts.map +1 -1
  175. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  176. package/dist/dev/media/abr/quality-selection.js +6 -68
  177. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  178. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  179. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  180. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  181. package/dist/dev/media/buffer/forward-buffer.js +37 -3
  182. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  183. package/dist/dev/media/dom/capabilities.js +70 -0
  184. package/dist/dev/media/dom/capabilities.js.map +1 -0
  185. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
  186. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  187. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  188. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
  189. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  190. package/dist/dev/media/dom/mse/duration.js +27 -10
  191. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  192. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  193. package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
  194. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  195. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
  196. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  197. package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
  198. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  199. package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
  200. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  201. package/dist/dev/media/dom/text/text-track-slots.js +3 -3
  202. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  203. package/dist/dev/media/hls/parse-attributes.js +17 -3
  204. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  205. package/dist/dev/media/hls/parse-media-playlist.js +29 -1
  206. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  207. package/dist/dev/media/hls/parse-multivariant.js +15 -6
  208. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  209. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  210. package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
  211. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  212. package/dist/dev/media/media-tracks/media-tracks.js +101 -0
  213. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  214. package/dist/dev/media/mp4/box.js +58 -0
  215. package/dist/dev/media/mp4/box.js.map +1 -0
  216. package/dist/dev/media/mp4/timestamp-origin.js +107 -0
  217. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  218. package/dist/dev/media/primitives/select-tracks.d.ts +35 -0
  219. package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -0
  220. package/dist/dev/media/primitives/select-tracks.js +98 -13
  221. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  222. package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
  223. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  224. package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
  225. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  226. package/dist/dev/media/types/index.d.ts +69 -2
  227. package/dist/dev/media/types/index.d.ts.map +1 -1
  228. package/dist/dev/media/types/index.js.map +1 -1
  229. package/dist/dev/media/utils/cdn.d.ts +13 -0
  230. package/dist/dev/media/utils/cdn.d.ts.map +1 -0
  231. package/dist/dev/media/utils/cdn.js +56 -0
  232. package/dist/dev/media/utils/cdn.js.map +1 -0
  233. package/dist/dev/media/utils/preload.js.map +1 -1
  234. package/dist/dev/media/utils/track-selection.js +1 -1
  235. package/dist/dev/media/utils/track-selection.js.map +1 -1
  236. package/dist/dev/media/utils/tracks.js +31 -4
  237. package/dist/dev/media/utils/tracks.js.map +1 -1
  238. package/dist/dev/media-tracks.d.ts +3 -0
  239. package/dist/dev/media-tracks.js +2 -0
  240. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  241. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  242. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  243. package/dist/dev/network/ewma.js +1 -1
  244. package/dist/dev/network/ewma.js.map +1 -1
  245. package/dist/dev/network/fetch.js +7 -1
  246. package/dist/dev/network/fetch.js.map +1 -1
  247. package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
  248. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  249. package/dist/dev/playback/actors/dom/segment-loader.js +103 -106
  250. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  251. package/dist/dev/playback/actors/dom/source-buffer.d.ts +10 -25
  252. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  253. package/dist/dev/playback/actors/dom/source-buffer.js +3 -1
  254. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  255. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
  256. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  257. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
  258. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  259. package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
  260. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  261. package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
  262. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  263. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +11 -2
  264. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  265. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +21 -43
  266. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  267. package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
  268. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
  269. package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
  270. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  271. package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
  272. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  273. package/dist/dev/playback/behaviors/dom/end-of-stream.js +25 -102
  274. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  275. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
  276. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  277. package/dist/dev/playback/behaviors/dom/load-segments.js +6 -2
  278. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  279. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
  280. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  281. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +50 -18
  282. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  283. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
  284. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  285. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
  286. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  287. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  288. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  289. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +43 -18
  290. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  291. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
  292. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  293. package/dist/dev/playback/behaviors/dom/track-current-time.js +6 -0
  294. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  295. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
  296. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  297. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
  298. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  299. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
  300. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  301. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  302. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +3 -2
  303. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  304. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +31 -0
  305. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  306. package/dist/dev/playback/behaviors/establish-start-media-time.js +123 -0
  307. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  308. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
  309. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  310. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  311. package/dist/dev/playback/behaviors/resolve-track.js +45 -22
  312. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  313. package/dist/dev/playback/behaviors/select-tracks.d.ts +14 -0
  314. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
  315. package/dist/dev/playback/behaviors/select-tracks.js +46 -77
  316. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  317. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +14 -0
  318. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
  319. package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
  320. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
  321. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  322. package/dist/dev/playback/behaviors/track-switching.js +403 -0
  323. package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
  324. package/dist/dev/playback/engines/background-video/adapter.d.ts +59 -0
  325. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -0
  326. package/dist/dev/playback/engines/background-video/adapter.js +158 -0
  327. package/dist/dev/playback/engines/background-video/adapter.js.map +1 -0
  328. package/dist/dev/playback/engines/background-video/engine.d.ts +106 -0
  329. package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -0
  330. package/dist/dev/playback/engines/background-video/engine.js +75 -0
  331. package/dist/dev/playback/engines/background-video/engine.js.map +1 -0
  332. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +46 -0
  333. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -0
  334. package/dist/dev/playback/engines/hls/adapter-audio-only.js +124 -0
  335. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -0
  336. package/dist/dev/playback/engines/hls/adapter.d.ts +3 -5
  337. package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -1
  338. package/dist/dev/playback/engines/hls/adapter.js +20 -11
  339. package/dist/dev/playback/engines/hls/adapter.js.map +1 -1
  340. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +156 -0
  341. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
  342. package/dist/dev/playback/engines/hls/engine-audio-only.js +94 -0
  343. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
  344. package/dist/dev/playback/engines/hls/engine.d.ts +109 -4
  345. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  346. package/dist/dev/playback/engines/hls/engine.js +38 -8
  347. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  348. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  349. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  350. package/dist/dev/playback/primitives/failover-fetch.js +42 -0
  351. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
  352. package/dist/dev/playback/primitives/head-peek.js +44 -0
  353. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  354. package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
  355. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  356. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  357. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  358. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  359. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
  360. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  361. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
  362. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  363. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
  364. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  365. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  366. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  367. package/dist/{default/playback/behaviors → dev/playback/primitives}/track-types.js +1 -1
  368. package/dist/dev/playback/primitives/track-types.js.map +1 -0
  369. package/package.json +17 -7
  370. package/dist/default/playback/behaviors/quality-switching.js +0 -96
  371. package/dist/default/playback/behaviors/quality-switching.js.map +0 -1
  372. package/dist/default/playback/behaviors/track-types.js.map +0 -1
  373. package/dist/dev/playback/behaviors/quality-switching.js +0 -96
  374. package/dist/dev/playback/behaviors/quality-switching.js.map +0 -1
  375. package/dist/dev/playback/behaviors/track-types.js.map +0 -1
@@ -3,12 +3,13 @@ import { defineBehavior } from "../../core/composition/create-composition.js";
3
3
  import { ConcurrentRunner, Task } from "../../core/tasks/task.js";
4
4
  import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
5
5
  import { isResolvedPresentation, isResolvedTrack } from "../../media/types/index.js";
6
- import { findTrack, updateTrackInPresentation } from "../../media/utils/tracks.js";
7
- import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "./track-types.js";
8
- import { fetchResolvable, getResponseText } from "../../network/fetch.js";
9
- import { parseMediaPlaylist } from "../../media/hls/parse-media-playlist.js";
6
+ import { applyContainerMimeType, findTrack, updateTrackInPresentation } from "../../media/utils/tracks.js";
7
+ import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
8
+ import { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from "../../media/hls/parse-media-playlist.js";
9
+ import { fetchResolvableText } from "../../network/fetch.js";
10
+ import { failoverFetch } from "../primitives/failover-fetch.js";
10
11
  //#region src/playback/behaviors/resolve-track.ts
11
- function setupTrackResolution({ state, config: { selectedKey, findTrackToResolve } }) {
12
+ function setupTrackResolution({ state, config: { selectedKey, findTrackToResolve, fetchResolvableText: fetchResolvableText$1 = fetchResolvableText } }) {
12
13
  const runner = new ConcurrentRunner();
13
14
  const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
14
15
  return createMachineReactor({
@@ -25,8 +26,12 @@ function setupTrackResolution({ state, config: { selectedKey, findTrackToResolve
25
26
  const track = findTrackToResolve(presentation, trackId);
26
27
  if (!track || isResolvedTrack(track)) return;
27
28
  runner.schedule(new Task(async (signal) => {
28
- const mediaTrack = parseMediaPlaylist(await getResponseText(await fetchResolvable(track, { signal })), track);
29
- update(state.presentation, (current) => isResolvedPresentation(current) ? updateTrackInPresentation(current, mediaTrack) : current);
29
+ const mediaTrack = parseMediaPlaylist(await fetchResolvableText$1(track, { signal }), track);
30
+ update(state.presentation, (current) => {
31
+ if (!isResolvedPresentation(current)) return current;
32
+ const patched = updateTrackInPresentation(current, mediaTrack);
33
+ return NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType) ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType) : patched;
34
+ });
30
35
  }, { id: track.id }));
31
36
  }]
32
37
  }
@@ -53,13 +58,19 @@ const TEXT_TRACK_RESOLUTION_CONFIG = {
53
58
  const resolveVideoTrack = defineBehavior({
54
59
  stateKeys: ["presentation", "selectedVideoTrackId"],
55
60
  contextKeys: [],
56
- setup: ({ state, config = {} }) => setupTrackResolution({
57
- state,
58
- config: {
61
+ setup: ({ state, config = {} }) => {
62
+ const trackConfig = {
59
63
  ...VIDEO_TRACK_RESOLUTION_CONFIG,
60
64
  ...config
61
- }
62
- })
65
+ };
66
+ return setupTrackResolution({
67
+ state,
68
+ config: {
69
+ ...trackConfig,
70
+ fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
71
+ }
72
+ });
73
+ }
63
74
  });
64
75
  /**
65
76
  * Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,
@@ -68,13 +79,19 @@ const resolveVideoTrack = defineBehavior({
68
79
  const resolveAudioTrack = defineBehavior({
69
80
  stateKeys: ["presentation", "selectedAudioTrackId"],
70
81
  contextKeys: [],
71
- setup: ({ state, config = {} }) => setupTrackResolution({
72
- state,
73
- config: {
82
+ setup: ({ state, config = {} }) => {
83
+ const trackConfig = {
74
84
  ...AUDIO_TRACK_RESOLUTION_CONFIG,
75
85
  ...config
76
- }
77
- })
86
+ };
87
+ return setupTrackResolution({
88
+ state,
89
+ config: {
90
+ ...trackConfig,
91
+ fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
92
+ }
93
+ });
94
+ }
78
95
  });
79
96
  /**
80
97
  * Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,
@@ -83,13 +100,19 @@ const resolveAudioTrack = defineBehavior({
83
100
  const resolveTextTrack = defineBehavior({
84
101
  stateKeys: ["presentation", "selectedTextTrackId"],
85
102
  contextKeys: [],
86
- setup: ({ state, config = {} }) => setupTrackResolution({
87
- state,
88
- config: {
103
+ setup: ({ state, config = {} }) => {
104
+ const trackConfig = {
89
105
  ...TEXT_TRACK_RESOLUTION_CONFIG,
90
106
  ...config
91
- }
92
- })
107
+ };
108
+ return setupTrackResolution({
109
+ state,
110
+ config: {
111
+ ...trackConfig,
112
+ fetchResolvableText: failoverFetch(fetchResolvableText, state, trackConfig)
113
+ }
114
+ });
115
+ }
93
116
  });
94
117
  //#endregion
95
118
  export { resolveAudioTrack, resolveTextTrack, resolveVideoTrack };
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-track.js","names":[],"sources":["../../../../src/playback/behaviors/resolve-track.ts"],"sourcesContent":["import { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { ConcurrentRunner, Task } from '../../core/tasks/task';\nimport { parseMediaPlaylist } from '../../media/hls/parse-media-playlist';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack, ResolvedTrack } from '../../media/types';\nimport { isResolvedPresentation, isResolvedTrack } from '../../media/types';\nimport { findTrack, updateTrackInPresentation } from '../../media/utils/tracks';\nimport { fetchResolvable, getResponseText } from '../../network/fetch';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from './track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackResolution` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => cleanup`. Each `resolveXTrack` export below calls it\n// from inside its own `defineBehavior` setup, supplying its per-type config\n// inline. The orchestration — gate on a selection, short-circuit when the\n// track is already resolved or missing, schedule the fetch+parse, and patch\n// the resolved track back into `state.presentation` — is shared.\n// ============================================================================\n\n/**\n * State shape for track resolution. Uses `MaybeResolvedPresentation` so it\n * matches the engine's slot type; resolution narrows internally.\n */\nexport interface ResolveTrackState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n}\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\ntype ResolveTrackStateMap<K extends SelectedTrackKey> = {\n presentation: Signal<ResolveTrackState['presentation']>;\n} & { [P in K]: ReadonlySignal<ResolveTrackState[P]> };\n\ninterface TrackResolutionConfig<K extends SelectedTrackKey> {\n selectedKey: K;\n findTrackToResolve: (\n presentation: MaybeResolvedPresentation,\n trackId: string\n ) => PartiallyResolvedTrack | ResolvedTrack | undefined;\n}\n\nfunction setupTrackResolution<K extends SelectedTrackKey>({\n state,\n config: { selectedKey, findTrackToResolve },\n}: {\n state: ResolveTrackStateMap<K>;\n config: TrackResolutionConfig<K>;\n}) {\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createTaskRunner() with args TBD),\n // likely eventually passed down via config or a new \"definitions\" argument. This will allow us to decide if we want our task runner/scheduler\n // to e.g. run concurrently (like we currently are), serially with a queue, or abort the previous task and replace it with the newly scheduled one. (CJP).\n const runner = new ConcurrentRunner();\n\n // Reactor states model the FSM the previous effect-based body was\n // hand-rolling. 'presentation-resolved' is entered when the\n // presentation is fully parsed (has a Ham id + selectionSets); leaving\n // it (presentation cleared or reset to an unresolved value) aborts all\n // in-flight tasks via the entry-cleanup. Most URL changes go through\n // 'presentation-unresolved' naturally (set undefined → set new partial\n // → re-parse), so the common case is covered by state-exit alone; the\n // task body's commit-time id check covers the pathological\n // resolved→resolved-without-unresolved transition.\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 // `entry` runs on state entry; the function it returns is the\n // state-exit cleanup. Returning `() => runner.abortAll()` binds\n // abort-of-in-flight-resolutions to leaving 'presentation-resolved'\n // (presentation cleared/reset, or behavior destroyed) —\n // source-change cancellation expressed structurally through the\n // state machine.\n entry: () => () => runner.abortAll(),\n effects: [\n () => {\n // The reactor's state transitions handle relevant presentation\n // changes (presentation-resolved ↔ presentation-unresolved);\n // within 'presentation-resolved' we peek (untracked read) so\n // internal updates (segments added by sibling tasks) don't\n // re-fire the effect.\n const presentation = peek(state.presentation);\n const trackId = state[selectedKey].get();\n if (!presentation || !trackId) return;\n\n const track = findTrackToResolve(presentation, trackId);\n if (!track || isResolvedTrack(track)) return;\n\n runner.schedule(\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createResolveTrackTask(track, context, config)),\n // likely eventually passed down via config or a new \"definitions\" argument (CJP).\n new Task(\n async (signal) => {\n const response = await fetchResolvable(track, { signal });\n const text = await getResponseText(response);\n const mediaTrack = parseMediaPlaylist(text, track);\n\n // Updater handles undefined inputs by returning current\n // unchanged; isResolvedPresentation narrows for the patch.\n // State-exit on resolving→unresolved fires runner.abortAll\n // before any URL change settles, and per the Fetch spec the\n // signal abort cancels in-flight body reads — so by the\n // time we reach this point the presentation we resolved\n // against is the live one.\n update(state.presentation, (current) =>\n isResolvedPresentation(current) ? updateTrackInPresentation(current, mediaTrack) : current\n );\n },\n { id: track.id }\n )\n );\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_TRACK_RESOLUTION_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'video', trackId),\n} as const;\n\nconst AUDIO_TRACK_RESOLUTION_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'audio', trackId),\n} as const;\n\nconst TEXT_TRACK_RESOLUTION_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'text', trackId),\n} as const;\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Resolve unresolved video tracks. Schedules a fetch task whenever the\n * selected video track is partially resolved, parses the manifest, and\n * writes the resolved track back into `state.presentation`.\n */\nexport const resolveVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({ state, config = {} }: { state: ResolveTrackStateMap<'selectedVideoTrackId'>; config?: object }) =>\n setupTrackResolution({\n state,\n config: { ...VIDEO_TRACK_RESOLUTION_CONFIG, ...config },\n }),\n});\n\n/**\n * Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,\n * narrowed to audio.\n */\nexport const resolveAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({ state, config = {} }: { state: ResolveTrackStateMap<'selectedAudioTrackId'>; config?: object }) =>\n setupTrackResolution({\n state,\n config: { ...AUDIO_TRACK_RESOLUTION_CONFIG, ...config },\n }),\n});\n\n/**\n * Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,\n * narrowed to text.\n */\nexport const resolveTextTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedTextTrackId'],\n contextKeys: [],\n setup: ({ state, config = {} }: { state: ResolveTrackStateMap<'selectedTextTrackId'>; config?: object }) =>\n setupTrackResolution({\n state,\n config: { ...TEXT_TRACK_RESOLUTION_CONFIG, ...config },\n }),\n});\n"],"mappings":";;;;;;;;;;AA+CA,SAAS,qBAAiD,EACxD,OACA,QAAQ,EAAE,aAAa,wBAItB;CAID,MAAM,SAAS,IAAI,kBAAkB;CAWrC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,KAAK,CAAC,GAC3C,0BACA,0BACN;AAED,QAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,KAAK;EACvC,QAAQ;GACN,2BAA2B,EAAE;GAC7B,yBAAyB;IAOvB,mBAAmB,OAAO,UAAU;IACpC,SAAS,OACD;KAMJ,MAAM,eAAe,KAAK,MAAM,aAAa;KAC7C,MAAM,UAAU,MAAM,aAAa,KAAK;AACxC,SAAI,CAAC,gBAAgB,CAAC,QAAS;KAE/B,MAAM,QAAQ,mBAAmB,cAAc,QAAQ;AACvD,SAAI,CAAC,SAAS,gBAAgB,MAAM,CAAE;AAEtC,YAAO,SAGL,IAAI,KACF,OAAO,WAAW;MAGhB,MAAM,aAAa,mBADN,MAAM,gBADF,MAAM,gBAAgB,OAAO,EAAE,QAAQ,CAAC,CACb,EACA,MAAM;AASlD,aAAO,MAAM,eAAe,YAC1B,uBAAuB,QAAQ,GAAG,0BAA0B,SAAS,WAAW,GAAG,QACpF;QAEH,EAAE,IAAI,MAAM,IAAI,CACjB,CACF;MAEJ;IACF;GACF;EACF,CAAC;;AAOJ,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,QAAQ;CAC5C;AAED,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,QAAQ;CAC5C;AAED,MAAM,+BAA+B;CACnC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,QAAQ,QAAQ;CAC3C;;;;;;AAWD,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,SAAS,EAAE,OAC1B,qBAAqB;EACnB;EACA,QAAQ;GAAE,GAAG;GAA+B,GAAG;GAAQ;EACxD,CAAC;CACL,CAAC;;;;;AAMF,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,SAAS,EAAE,OAC1B,qBAAqB;EACnB;EACA,QAAQ;GAAE,GAAG;GAA+B,GAAG;GAAQ;EACxD,CAAC;CACL,CAAC;;;;;AAMF,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,SAAS,EAAE,OAC1B,qBAAqB;EACnB;EACA,QAAQ;GAAE,GAAG;GAA8B,GAAG;GAAQ;EACvD,CAAC;CACL,CAAC"}
1
+ {"version":3,"file":"resolve-track.js","names":["defaultFetchResolvableText","fetchResolvableText"],"sources":["../../../../src/playback/behaviors/resolve-track.ts"],"sourcesContent":["import { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, peek, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';\nimport { ConcurrentRunner, Task } from '../../core/tasks/task';\nimport { NON_FMP4_CONTAINER_MIMES, parseMediaPlaylist } from '../../media/hls/parse-media-playlist';\nimport type { MaybeResolvedPresentation, PartiallyResolvedTrack, ResolvedTrack } from '../../media/types';\nimport { isResolvedPresentation, isResolvedTrack } from '../../media/types';\nimport type { GetCdnId } from '../../media/utils/cdn';\nimport { applyContainerMimeType, findTrack, updateTrackInPresentation } from '../../media/utils/tracks';\nimport { fetchResolvableText as defaultFetchResolvableText, type FetchText } from '../../network/fetch';\nimport { failoverFetch } from '../primitives/failover-fetch';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackResolution` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => cleanup`. Each `resolveXTrack` export below calls it\n// from inside its own `defineBehavior` setup, supplying its per-type config\n// inline. The orchestration — gate on a selection, short-circuit when the\n// track is already resolved or missing, schedule the fetch+parse, and patch\n// the resolved track back into `state.presentation` — is shared.\n// ============================================================================\n\n/**\n * State shape for track resolution. Uses `MaybeResolvedPresentation` so it\n * matches the engine's slot type; resolution narrows internally.\n */\nexport interface ResolveTrackState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string;\n selectedAudioTrackId?: string;\n selectedTextTrackId?: string;\n failedCdns?: string[];\n}\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\ntype ResolveTrackStateMap<K extends SelectedTrackKey> = {\n presentation: Signal<ResolveTrackState['presentation']>;\n} & { [P in K]: ReadonlySignal<ResolveTrackState[P]> };\n\ninterface TrackResolutionConfig<K extends SelectedTrackKey> {\n selectedKey: K;\n findTrackToResolve: (\n presentation: MaybeResolvedPresentation,\n trackId: string\n ) => PartiallyResolvedTrack | ResolvedTrack | undefined;\n /** Fetch a track's media-playlist text — already failover-decorated by the behavior. */\n fetchResolvableText?: FetchText;\n}\n\n/**\n * Engine-config slice each `resolve*` behavior reads to build its failover-\n * decorated playlist fetch.\n */\ninterface ResolveTrackConfig {\n /** CDN-id derivation for the failover trip; defaults to origin-based `getCdnId`. */\n getCdnId?: GetCdnId;\n}\n\nfunction setupTrackResolution<K extends SelectedTrackKey>({\n state,\n config: { selectedKey, findTrackToResolve, fetchResolvableText = defaultFetchResolvableText },\n}: {\n state: ResolveTrackStateMap<K>;\n config: TrackResolutionConfig<K>;\n}) {\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createTaskRunner() with args TBD),\n // likely eventually passed down via config or a new \"definitions\" argument. This will allow us to decide if we want our task runner/scheduler\n // to e.g. run concurrently (like we currently are), serially with a queue, or abort the previous task and replace it with the newly scheduled one. (CJP).\n const runner = new ConcurrentRunner();\n\n // Reactor states model the FSM the previous effect-based body was\n // hand-rolling. 'presentation-resolved' is entered when the\n // presentation is fully parsed (has a Ham id + selectionSets); leaving\n // it (presentation cleared or reset to an unresolved value) aborts all\n // in-flight tasks via the entry-cleanup. Most URL changes go through\n // 'presentation-unresolved' naturally (set undefined → set new partial\n // → re-parse), so the common case is covered by state-exit alone; the\n // task body's commit-time id check covers the pathological\n // resolved→resolved-without-unresolved transition.\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 // `entry` runs on state entry; the function it returns is the\n // state-exit cleanup. Returning `() => runner.abortAll()` binds\n // abort-of-in-flight-resolutions to leaving 'presentation-resolved'\n // (presentation cleared/reset, or behavior destroyed) —\n // source-change cancellation expressed structurally through the\n // state machine.\n entry: () => () => runner.abortAll(),\n effects: [\n () => {\n // The reactor's state transitions handle relevant presentation\n // changes (presentation-resolved ↔ presentation-unresolved);\n // within 'presentation-resolved' we peek (untracked read) so\n // internal updates (segments added by sibling tasks) don't\n // re-fire the effect.\n const presentation = peek(state.presentation);\n const trackId = state[selectedKey].get();\n if (!presentation || !trackId) return;\n\n const track = findTrackToResolve(presentation, trackId);\n if (!track || isResolvedTrack(track)) return;\n\n runner.schedule(\n // NOTE: This can/maybe will be pulled into a per-use case factory (e.g. something like createResolveTrackTask(track, context, config)),\n // likely eventually passed down via config or a new \"definitions\" argument (CJP).\n new Task(\n async (signal) => {\n // `fetchResolvableText` is the behavior's failover-decorated\n // fetch: it trips the CDN on a failed fetch (network error or\n // non-OK status). A parse failure is a content issue, not a\n // CDN-availability one, so it doesn't trip.\n const text = await fetchResolvableText(track, { signal });\n const mediaTrack = parseMediaPlaylist(text, track);\n\n // Updater handles undefined inputs by returning current\n // unchanged; isResolvedPresentation narrows for the patch.\n // State-exit on resolving→unresolved fires runner.abortAll\n // before any URL change settles, and per the Fetch spec the\n // signal abort cancels in-flight body reads — so by the\n // time we reach this point the presentation we resolved\n // against is the live one.\n update(state.presentation, (current) => {\n if (!isResolvedPresentation(current)) return current;\n const patched = updateTrackInPresentation(current, mediaTrack);\n // Container is uniform within a type (an ABR ladder shares\n // its container), so a detected non-fMP4 rendition (TS,\n // raw AAC) implies every rendition of *this* type matches —\n // relabel them all from one resolved playlist instead of\n // fetching each. Scoped to this track's own type: never cross\n // audio↔video (mixed-container sources exist, e.g. muxed-TS\n // video + raw-.aac audio), which also keeps per-type\n // resolutions' writes disjoint (no race).\n return NON_FMP4_CONTAINER_MIMES.has(mediaTrack.mimeType)\n ? applyContainerMimeType(patched, mediaTrack.type, mediaTrack.mimeType)\n : patched;\n });\n },\n { id: track.id }\n )\n );\n },\n ],\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n// ============================================================================\n\nconst VIDEO_TRACK_RESOLUTION_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'video', trackId),\n} as const;\n\nconst AUDIO_TRACK_RESOLUTION_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'audio', trackId),\n} as const;\n\nconst TEXT_TRACK_RESOLUTION_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n findTrackToResolve: (presentation: MaybeResolvedPresentation, trackId: string) =>\n findTrack(presentation, 'text', trackId),\n} as const;\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Resolve unresolved video tracks. Schedules a fetch task whenever the\n * selected video track is partially resolved, parses the manifest, and\n * writes the resolved track back into `state.presentation`.\n */\nexport const resolveVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedVideoTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Engine `config` layers over the per-type defaults (mirrors the other\n // per-type variants, see track-types.ts); `failoverFetch` reads its\n // `selectedKey` + `getCdnId` from the merged result. `fetchResolvableText`\n // is then placed AFTER the spread so the failover-decorated fetch wins —\n // unlike segments, playlists expose no overridable per-type fetch.\n const trackConfig = { ...VIDEO_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/**\n * Resolve unresolved audio tracks. Same shape as `resolveVideoTrack`,\n * narrowed to audio.\n */\nexport const resolveAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedAudioTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...AUDIO_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n\n/**\n * Resolve unresolved text tracks. Same shape as `resolveVideoTrack`,\n * narrowed to text.\n */\nexport const resolveTextTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedTextTrackId'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: ResolveTrackStateMap<'selectedTextTrackId'>;\n config?: ResolveTrackConfig;\n }) => {\n // Key order is load-bearing — see resolveVideoTrack.\n const trackConfig = { ...TEXT_TRACK_RESOLUTION_CONFIG, ...config };\n return setupTrackResolution({\n state,\n config: { ...trackConfig, fetchResolvableText: failoverFetch(defaultFetchResolvableText, state, trackConfig) },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;AA6DA,SAAS,qBAAiD,EACxD,OACA,QAAQ,EAAE,aAAa,oBAAoB,qBAAA,wBAAsBA,yBAIhE;CAID,MAAM,SAAS,IAAI,iBAAiB;CAWpC,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;CAEA,OAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,2BAA2B,CAAC;GAC5B,yBAAyB;IAOvB,mBAAmB,OAAO,SAAS;IACnC,SAAS,OACD;KAMJ,MAAM,eAAe,KAAK,MAAM,YAAY;KAC5C,MAAM,UAAU,MAAM,YAAY,CAAC,IAAI;KACvC,IAAI,CAAC,gBAAgB,CAAC,SAAS;KAE/B,MAAM,QAAQ,mBAAmB,cAAc,OAAO;KACtD,IAAI,CAAC,SAAS,gBAAgB,KAAK,GAAG;KAEtC,OAAO,SAGL,IAAI,KACF,OAAO,WAAW;MAMhB,MAAM,aAAa,mBAAmB,MADnBC,sBAAoB,OAAO,EAAE,OAAO,CAAC,GACZ,KAAK;MASjD,OAAO,MAAM,eAAe,YAAY;OACtC,IAAI,CAAC,uBAAuB,OAAO,GAAG,OAAO;OAC7C,MAAM,UAAU,0BAA0B,SAAS,UAAU;OAS7D,OAAO,yBAAyB,IAAI,WAAW,QAAQ,IACnD,uBAAuB,SAAS,WAAW,MAAM,WAAW,QAAQ,IACpE;MACN,CAAC;KACH,GACA,EAAE,IAAI,MAAM,GAAG,CACjB,CACF;IACF,CACF;GACF;EACF;CACF,CAAC;AACH;AAMA,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,OAAO;AAC5C;AAEA,MAAM,gCAAgC;CACpC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,SAAS,OAAO;AAC5C;AAEA,MAAM,+BAA+B;CACnC,GAAG;CACH,qBAAqB,cAAyC,YAC5D,UAAU,cAAc,QAAQ,OAAO;AAC3C;;;;;;AAWA,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAIN;EAMJ,MAAM,cAAc;GAAE,GAAG;GAA+B,GAAG;EAAO;EAClE,OAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcD,qBAA4B,OAAO,WAAW;GAAE;EAC/G,CAAC;CACH;AACF,CAAC;;;;;AAMD,MAAa,oBAAoB,eAAe;CAC9C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAIN;EAEJ,MAAM,cAAc;GAAE,GAAG;GAA+B,GAAG;EAAO;EAClE,OAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcA,qBAA4B,OAAO,WAAW;GAAE;EAC/G,CAAC;CACH;AACF,CAAC;;;;;AAMD,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,qBAAqB;CACjD,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAIN;EAEJ,MAAM,cAAc;GAAE,GAAG;GAA8B,GAAG;EAAO;EACjE,OAAO,qBAAqB;GAC1B;GACA,QAAQ;IAAE,GAAG;IAAa,qBAAqB,cAAcA,qBAA4B,OAAO,WAAW;GAAE;EAC/G,CAAC;CACH;AACF,CAAC"}
@@ -2,34 +2,41 @@ import { computed } from "../../core/signals/primitives.js";
2
2
  import { defineBehavior } from "../../core/composition/create-composition.js";
3
3
  import { createMachineReactor } from "../../core/reactors/create-machine-reactor.js";
4
4
  import { isResolvedPresentation } from "../../media/types/index.js";
5
- import { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "./track-types.js";
6
- import { pickFirstTrackId, pickTextTrack } from "../../media/primitives/select-tracks.js";
5
+ import { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from "../primitives/track-types.js";
6
+ import { pickAudioTrack, pickFirstTrackId } from "../../media/primitives/select-tracks.js";
7
7
  //#region src/playback/behaviors/select-tracks.ts
8
8
  /**
9
- * **Default audio/text track selection on src load / unselect on src unload.**
10
- * When a presentation is resolved, sets `selectedAudioTrackId` /
11
- * `selectedTextTrackId` to a per-type-picker default if no selection already
12
- * exists. When the presentation is unset/reset (transitions back to
13
- * unresolved), clears the selection so a stale id from the previous source
14
- * doesn't persist.
9
+ * **Default audio/video track selection on src load / unselect on src unload.**
10
+ * When a presentation is resolved, sets `selectedVideoTrackId` /
11
+ * `selectedAudioTrackId` to a per-type-picker default if no selection already
12
+ * exists. When the presentation is unset/reset (transitions back to unresolved),
13
+ * clears the selection so a stale id from the previous source doesn't persist.
15
14
  *
16
- * Lifecycle-driven: each transition fires its work once. Does not police
17
- * the selection between transitions; external writes (user picks) are left
18
- * alone.
15
+ * Lifecycle-driven: each transition fires its work once. Does not police the
16
+ * selection between transitions; external writes (user picks, ABR, programmatic
17
+ * filter-driven re-picks) are left alone.
19
18
  *
20
19
  * Picker is config-driven: each per-type export wires a sensible default
21
- * (`pickFirstTrackId` for audio, `pickTextTrack` for text) and the caller
22
- * can supply their own via `config.picker` for custom selection logic
23
- * (language preferences, default-track handling, etc.). The behavior's
24
- * `config` is forwarded to the picker as its second argument, so options
25
- * like `preferredAudioLanguage` / `preferredSubtitleLanguage` reach the
26
- * picker without an intermediate wrapping layer.
20
+ * (`pickAudioTrack` for audio three-tier language-aware; `pickFirstTrackId`
21
+ * for video) and the caller can supply their own via `config.picker` for custom
22
+ * selection logic. The behavior's `config` is forwarded to the picker as its
23
+ * second argument, so options like `preferredAudioLanguage` reach the picker
24
+ * without an intermediate wrapping layer.
27
25
  *
28
26
  * Compose `selectVideoTrack` for the simple "pick a default video track"
29
- * behavior, or `switchVideoQuality` (`./quality-switching.ts`) for the
30
- * ABR-driven variant they're alternatives, not stackable (both write
31
- * `selectedVideoTrackId`). The simple variant tree-shakes out the
32
- * ABR machinery (bandwidth-estimator, quality-selection algorithms).
27
+ * behavior, or `switchVideoTrack` (`./track-switching.ts`) for the
28
+ * ABR-driven variant. Compose `selectAudioTrack` for the simple default
29
+ * pick, or `switchAudioTrack` (`./track-switching.ts`) for the
30
+ * filter-reactive + mid-stream-flush slot-owner variant — when audio-abr
31
+ * lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose
32
+ * only one per type — they're alternatives, not stackable (each writes
33
+ * the same `selected*TrackId` slot). The simple variants tree-shake out
34
+ * the heavier machinery (bandwidth estimator, quality selection, flush
35
+ * orchestration).
36
+ *
37
+ * Text selection has no simple variant here — it's owned by `switchTextTrack`
38
+ * (`./track-switching.ts`), which resolves standing `userTextTrackSelection`
39
+ * intent against the constrained, CDN-scoped renditions.
33
40
  */
34
41
  function setupTrackSelection({ state, config: { selectedKey, picker, pickerConfig } }) {
35
42
  const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
@@ -50,83 +57,45 @@ function setupTrackSelection({ state, config: { selectedKey, picker, pickerConfi
50
57
  }
51
58
  /** Default video picker: first track in the video selection set. */
52
59
  const defaultVideoPicker = (presentation) => pickFirstTrackId(presentation, "video");
53
- const VIDEO_TRACK_SELECTION_CONFIG = {
54
- ...VIDEO_TYPE_CONFIG,
55
- picker: defaultVideoPicker
56
- };
57
- /** Default audio picker: first track in the audio selection set. */
58
- const defaultAudioPicker = (presentation) => pickFirstTrackId(presentation, "audio");
59
- const AUDIO_TRACK_SELECTION_CONFIG = {
60
- ...AUDIO_TYPE_CONFIG,
61
- picker: defaultAudioPicker
62
- };
63
- const TEXT_TRACK_SELECTION_CONFIG = {
64
- ...TEXT_TYPE_CONFIG,
65
- picker: pickTextTrack
66
- };
67
- defineBehavior({
68
- stateKeys: ["presentation", "selectedVideoTrackId"],
69
- contextKeys: [],
70
- setup: ({ state, config }) => setupTrackSelection({
71
- state,
72
- config: {
73
- ...VIDEO_TRACK_SELECTION_CONFIG,
74
- picker: config?.picker ?? VIDEO_TRACK_SELECTION_CONFIG.picker,
75
- pickerConfig: config
76
- }
77
- })
78
- });
79
60
  /**
80
- * Select an audio track when a presentation loads. Clears the selection
81
- * on src unload.
61
+ * Select a video track when a presentation loads. Clears the selection on
62
+ * src unload.
82
63
  *
83
- * @example
84
- * const reactor = selectAudioTrack.setup({ state });
64
+ * This is the simple, non-ABR counterpart to `switchVideoTrack` — compose
65
+ * one or the other, not both (both write `selectedVideoTrackId`). Composing
66
+ * `selectVideoTrack` alone tree-shakes out the ABR code path
67
+ * (bandwidth-estimator, quality-selection); use it for sources without
68
+ * meaningful quality variants, test setups, or players that intentionally
69
+ * pin a quality.
85
70
  *
86
71
  * @example
87
- * // Custom picker with language preference
88
- * const reactor = selectAudioTrack.setup({
89
- * state,
90
- * config: { preferredAudioLanguage: 'en', picker: myLanguageAwarePicker },
91
- * });
72
+ * const reactor = selectVideoTrack.setup({ state });
92
73
  */
93
- const selectAudioTrack = defineBehavior({
94
- stateKeys: ["presentation", "selectedAudioTrackId"],
74
+ const selectVideoTrack = defineBehavior({
75
+ stateKeys: ["presentation", "selectedVideoTrackId"],
95
76
  contextKeys: [],
96
77
  setup: ({ state, config }) => setupTrackSelection({
97
78
  state,
98
79
  config: {
99
- ...AUDIO_TRACK_SELECTION_CONFIG,
100
- picker: config?.picker ?? AUDIO_TRACK_SELECTION_CONFIG.picker,
80
+ selectedKey: VIDEO_TYPE_CONFIG.selectedKey,
81
+ picker: config?.picker ?? defaultVideoPicker,
101
82
  pickerConfig: config
102
83
  }
103
84
  })
104
85
  });
105
- /**
106
- * Select a text track based on user preferences (preferred language,
107
- * default-track auto-select, forced-track filtering). Clears the selection
108
- * on src unload.
109
- *
110
- * Unlike audio selection, the default text picker returns `undefined` when
111
- * no preference matches, leaving the selection unset — text-track
112
- * selection is user opt-in.
113
- *
114
- * @example
115
- * const reactor = selectTextTrack.setup({ state, config: { preferredSubtitleLanguage: 'en' } });
116
- */
117
- const selectTextTrack = defineBehavior({
118
- stateKeys: ["presentation", "selectedTextTrackId"],
86
+ defineBehavior({
87
+ stateKeys: ["presentation", "selectedAudioTrackId"],
119
88
  contextKeys: [],
120
89
  setup: ({ state, config }) => setupTrackSelection({
121
90
  state,
122
91
  config: {
123
- ...TEXT_TRACK_SELECTION_CONFIG,
124
- picker: config?.picker ?? TEXT_TRACK_SELECTION_CONFIG.picker,
92
+ selectedKey: AUDIO_TYPE_CONFIG.selectedKey,
93
+ picker: config?.picker ?? pickAudioTrack,
125
94
  pickerConfig: config
126
95
  }
127
96
  })
128
97
  });
129
98
  //#endregion
130
- export { selectAudioTrack, selectTextTrack };
99
+ export { selectVideoTrack };
131
100
 
132
101
  //# sourceMappingURL=select-tracks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"select-tracks.js","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"sourcesContent":["/**\n * **Default audio/text track selection on src load / unselect on src unload.**\n * When a presentation is resolved, sets `selectedAudioTrackId` /\n * `selectedTextTrackId` to a per-type-picker default if no selection already\n * exists. When the presentation is unset/reset (transitions back to\n * unresolved), clears the selection so a stale id from the previous source\n * doesn't persist.\n *\n * Lifecycle-driven: each transition fires its work once. Does not police\n * the selection between transitions; external writes (user picks) are left\n * alone.\n *\n * Picker is config-driven: each per-type export wires a sensible default\n * (`pickFirstTrackId` for audio, `pickTextTrack` for text) and the caller\n * can supply their own via `config.picker` for custom selection logic\n * (language preferences, default-track handling, etc.). The behavior's\n * `config` is forwarded to the picker as its second argument, so options\n * like `preferredAudioLanguage` / `preferredSubtitleLanguage` reach the\n * picker without an intermediate wrapping layer.\n *\n * Compose `selectVideoTrack` for the simple \"pick a default video track\"\n * behavior, or `switchVideoQuality` (`./quality-switching.ts`) for the\n * ABR-driven variant — they're alternatives, not stackable (both write\n * `selectedVideoTrackId`). The simple variant tree-shakes out the\n * ABR machinery (bandwidth-estimator, quality-selection algorithms).\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport {\n type AudioSelectionConfig,\n pickFirstTrackId,\n pickTextTrack,\n type TextSelectionConfig,\n type TrackPicker,\n type TrackSelectionState,\n type VideoSelectionConfig,\n} from '../../media/primitives/select-tracks';\nimport { isResolvedPresentation } from '../../media/types';\nimport { AUDIO_TYPE_CONFIG, TEXT_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from './track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackSelection` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => Reactor`. Each `selectXTrack` export below calls\n// it from inside its own `defineBehavior` setup, supplying its per-type\n// `selectedKey`, default picker, and forwarded picker config. The lifecycle\n// — pick on entering 'presentation-resolved' if not already selected; clear\n// on entering 'presentation-unresolved' — is shared.\n// ============================================================================\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId' | 'selectedTextTrackId';\n\ntype SelectStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<TrackSelectionState['presentation']>;\n} & { [P in K]: Signal<TrackSelectionState[P]> };\n\ninterface TrackSelectionSetupConfig<K extends SelectedTrackKey, PickerConfig> {\n selectedKey: K;\n picker: TrackPicker<PickerConfig>;\n pickerConfig?: PickerConfig;\n}\n\nfunction setupTrackSelection<K extends SelectedTrackKey, PickerConfig>({\n state,\n config: { selectedKey, picker, pickerConfig },\n}: {\n state: SelectStateMap<K>;\n config: TrackSelectionSetupConfig<K, PickerConfig>;\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 // Entry: pick a default on entering presentation-resolved if none\n // is set. External writes (user picks, ABR) that already populated\n // the slot are left alone.\n //\n // The returned cleanup runs on state exit — which fires on src\n // unload (presentation-resolved → presentation-unresolved) AND on\n // behavior destroy (presentation-resolved → destroying →\n // destroyed). Putting the clear here rather than as\n // presentation-unresolved.entry is more cohesive (operation +\n // cleanup co-located) and correctly covers destroy (destroy\n // doesn't pass through presentation-unresolved).\n entry: () => {\n if (!state[selectedKey].get()) {\n // `state.presentation.get()` is non-null inside this entry —\n // the reactor's `'presentation-resolved'` gate is exactly\n // `isResolvedPresentation(state.presentation.get())`, which\n // requires a truthy Presentation.\n const id = picker(state.presentation.get()!, pickerConfig);\n if (id) state[selectedKey].set(id);\n }\n return () => state[selectedKey].set(undefined);\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Per-helper-per-type configs — defaults that variants spread engine config over\n//\n// The engine-facing config carries `picker?` (optional override) plus\n// fields the default picker consults (preferred language, etc.). The\n// variant resolves the final picker (engine override or default) and\n// forwards the whole engine config as `pickerConfig` so rich pickers can\n// read their options.\n// ============================================================================\n\n/** Default video picker: first track in the video selection set. */\nconst defaultVideoPicker: TrackPicker = (presentation) => pickFirstTrackId(presentation, 'video');\n\nconst VIDEO_TRACK_SELECTION_CONFIG = {\n ...VIDEO_TYPE_CONFIG,\n picker: defaultVideoPicker,\n} as const;\n\n/** Default audio picker: first track in the audio selection set. */\nconst defaultAudioPicker: TrackPicker = (presentation) => pickFirstTrackId(presentation, 'audio');\n\nconst AUDIO_TRACK_SELECTION_CONFIG = {\n ...AUDIO_TYPE_CONFIG,\n picker: defaultAudioPicker,\n} as const;\n\nconst TEXT_TRACK_SELECTION_CONFIG = {\n ...TEXT_TYPE_CONFIG,\n picker: pickTextTrack,\n} as const;\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Config for `selectVideoTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickFirstTrackId` is used.\n */\nexport interface SelectVideoTrackConfig extends Omit<VideoSelectionConfig, 'type'> {\n picker?: TrackPicker<SelectVideoTrackConfig>;\n}\n\n/**\n * Select a video track when a presentation loads. Clears the selection on\n * src unload.\n *\n * This is the simple, non-ABR counterpart to `switchVideoQuality` — compose\n * one or the other, not both (both write `selectedVideoTrackId`). Composing\n * `selectVideoTrack` alone tree-shakes out the ABR code path\n * (bandwidth-estimator, quality-selection); use it for sources without\n * meaningful quality variants, test setups, or players that intentionally\n * pin a quality.\n *\n * @example\n * const reactor = selectVideoTrack.setup({ state });\n */\nexport const selectVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedVideoTrackId'>; config?: SelectVideoTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n ...VIDEO_TRACK_SELECTION_CONFIG,\n picker: config?.picker ?? VIDEO_TRACK_SELECTION_CONFIG.picker,\n pickerConfig: config,\n },\n }),\n});\n\n/**\n * Config for `selectAudioTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickFirstTrackId` is used (which ignores\n * the other fields — they reach the picker as its second arg for callers\n * who supply a richer picker, like a language-aware one).\n */\nexport interface SelectAudioTrackConfig extends Omit<AudioSelectionConfig, 'type'> {\n picker?: TrackPicker<SelectAudioTrackConfig>;\n}\n\n/**\n * Select an audio track when a presentation loads. Clears the selection\n * on src unload.\n *\n * @example\n * const reactor = selectAudioTrack.setup({ state });\n *\n * @example\n * // Custom picker with language preference\n * const reactor = selectAudioTrack.setup({\n * state,\n * config: { preferredAudioLanguage: 'en', picker: myLanguageAwarePicker },\n * });\n */\nexport const selectAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedAudioTrackId'>; config?: SelectAudioTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n ...AUDIO_TRACK_SELECTION_CONFIG,\n picker: config?.picker ?? AUDIO_TRACK_SELECTION_CONFIG.picker,\n pickerConfig: config,\n },\n }),\n});\n\n/**\n * Config for `selectTextTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickTextTrack` is used, which honors the\n * other fields.\n */\nexport interface SelectTextTrackConfig extends TextSelectionConfig {\n picker?: TrackPicker<SelectTextTrackConfig>;\n}\n\n/**\n * Select a text track based on user preferences (preferred language,\n * default-track auto-select, forced-track filtering). Clears the selection\n * on src unload.\n *\n * Unlike audio selection, the default text picker returns `undefined` when\n * no preference matches, leaving the selection unset — text-track\n * selection is user opt-in.\n *\n * @example\n * const reactor = selectTextTrack.setup({ state, config: { preferredSubtitleLanguage: 'en' } });\n */\nexport const selectTextTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedTextTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedTextTrackId'>; config?: SelectTextTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n ...TEXT_TRACK_SELECTION_CONFIG,\n picker: config?.picker ?? TEXT_TRACK_SELECTION_CONFIG.picker,\n pickerConfig: config,\n },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,SAAS,oBAA8D,EACrE,OACA,QAAQ,EAAE,aAAa,QAAQ,kBAI9B;CACD,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,KAAK,CAAC,GAC3C,0BACA,0BACN;AAED,QAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,KAAK;EACvC,QAAQ;GACN,2BAA2B,EAAE;GAC7B,yBAAyB,EAYvB,aAAa;AACX,QAAI,CAAC,MAAM,aAAa,KAAK,EAAE;KAK7B,MAAM,KAAK,OAAO,MAAM,aAAa,KAAK,EAAG,aAAa;AAC1D,SAAI,GAAI,OAAM,aAAa,IAAI,GAAG;;AAEpC,iBAAa,MAAM,aAAa,IAAI,KAAA,EAAU;MAEjD;GACF;EACF,CAAC;;;AAcJ,MAAM,sBAAmC,iBAAiB,iBAAiB,cAAc,QAAQ;AAEjG,MAAM,+BAA+B;CACnC,GAAG;CACH,QAAQ;CACT;;AAGD,MAAM,sBAAmC,iBAAiB,iBAAiB,cAAc,QAAQ;AAEjG,MAAM,+BAA+B;CACnC,GAAG;CACH,QAAQ;CACT;AAED,MAAM,8BAA8B;CAClC,GAAG;CACH,QAAQ;CACT;AA4B+B,eAAe;CAC7C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,GAAG;GACH,QAAQ,QAAQ,UAAU,6BAA6B;GACvD,cAAc;GACf;EACF,CAAC;CACL,CAAC;;;;;;;;;;;;;;;AA0BF,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,uBAAuB;CACnD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,GAAG;GACH,QAAQ,QAAQ,UAAU,6BAA6B;GACvD,cAAc;GACf;EACF,CAAC;CACL,CAAC;;;;;;;;;;;;;AAuBF,MAAa,kBAAkB,eAAe;CAC5C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,EAAE;CACf,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,GAAG;GACH,QAAQ,QAAQ,UAAU,4BAA4B;GACtD,cAAc;GACf;EACF,CAAC;CACL,CAAC"}
1
+ {"version":3,"file":"select-tracks.js","names":[],"sources":["../../../../src/playback/behaviors/select-tracks.ts"],"sourcesContent":["/**\n * **Default audio/video track selection on src load / unselect on src unload.**\n * When a presentation is resolved, sets `selectedVideoTrackId` /\n * `selectedAudioTrackId` to a per-type-picker default if no selection already\n * exists. When the presentation is unset/reset (transitions back to unresolved),\n * clears the selection so a stale id from the previous source doesn't persist.\n *\n * Lifecycle-driven: each transition fires its work once. Does not police the\n * selection between transitions; external writes (user picks, ABR, programmatic\n * filter-driven re-picks) are left alone.\n *\n * Picker is config-driven: each per-type export wires a sensible default\n * (`pickAudioTrack` for audio — three-tier language-aware; `pickFirstTrackId`\n * for video) and the caller can supply their own via `config.picker` for custom\n * selection logic. The behavior's `config` is forwarded to the picker as its\n * second argument, so options like `preferredAudioLanguage` reach the picker\n * without an intermediate wrapping layer.\n *\n * Compose `selectVideoTrack` for the simple \"pick a default video track\"\n * behavior, or `switchVideoTrack` (`./track-switching.ts`) for the\n * ABR-driven variant. Compose `selectAudioTrack` for the simple default\n * pick, or `switchAudioTrack` (`./track-switching.ts`) for the\n * filter-reactive + mid-stream-flush slot-owner variant — when audio-abr\n * lands, `switchAudioTrack` extends into `switchAudioQuality`. Compose\n * only one per type — they're alternatives, not stackable (each writes\n * the same `selected*TrackId` slot). The simple variants tree-shake out\n * the heavier machinery (bandwidth estimator, quality selection, flush\n * orchestration).\n *\n * Text selection has no simple variant here — it's owned by `switchTextTrack`\n * (`./track-switching.ts`), which resolves standing `userTextTrackSelection`\n * intent against the constrained, CDN-scoped renditions.\n */\n\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { createMachineReactor } from '../../core/reactors/create-machine-reactor';\nimport { computed, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport {\n type AudioSelectionConfig,\n pickAudioTrack,\n pickFirstTrackId,\n type TrackPicker,\n type TrackSelectionState,\n type VideoSelectionConfig,\n} from '../../media/primitives/select-tracks';\nimport { isResolvedPresentation } from '../../media/types';\nimport { AUDIO_TYPE_CONFIG, VIDEO_TYPE_CONFIG } from '../primitives/track-types';\n\n// ============================================================================\n// Specialization helper\n//\n// `setupTrackSelection` has the same shape as a Behavior `setup` function:\n// `({ state, config }) => Reactor`. Each `selectXTrack` export below calls\n// it from inside its own `defineBehavior` setup, supplying its per-type\n// `selectedKey`, default picker, and forwarded picker config. The lifecycle\n// — pick on entering 'presentation-resolved' if not already selected; clear\n// on entering 'presentation-unresolved' — is shared.\n// ============================================================================\n\ntype SelectedTrackKey = 'selectedVideoTrackId' | 'selectedAudioTrackId';\n\ntype SelectStateMap<K extends SelectedTrackKey> = {\n presentation: ReadonlySignal<TrackSelectionState['presentation']>;\n} & { [P in K]: Signal<TrackSelectionState[P]> };\n\ninterface TrackSelectionSetupConfig<K extends SelectedTrackKey, PickerConfig> {\n selectedKey: K;\n picker: TrackPicker<PickerConfig>;\n pickerConfig?: PickerConfig;\n}\n\nfunction setupTrackSelection<K extends SelectedTrackKey, PickerConfig>({\n state,\n config: { selectedKey, picker, pickerConfig },\n}: {\n state: SelectStateMap<K>;\n config: TrackSelectionSetupConfig<K, PickerConfig>;\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 // Entry: pick a default on entering presentation-resolved if none\n // is set. External writes (user picks, ABR) that already populated\n // the slot are left alone.\n //\n // The returned cleanup runs on state exit — which fires on src\n // unload (presentation-resolved → presentation-unresolved) AND on\n // behavior destroy (presentation-resolved → destroying →\n // destroyed). Putting the clear here rather than as\n // presentation-unresolved.entry is more cohesive (operation +\n // cleanup co-located) and correctly covers destroy (destroy\n // doesn't pass through presentation-unresolved).\n entry: () => {\n if (!state[selectedKey].get()) {\n // `state.presentation.get()` is non-null inside this entry —\n // the reactor's `'presentation-resolved'` gate is exactly\n // `isResolvedPresentation(state.presentation.get())`, which\n // requires a truthy Presentation.\n const id = picker(state.presentation.get()!, pickerConfig);\n if (id) state[selectedKey].set(id);\n }\n return () => state[selectedKey].set(undefined);\n },\n },\n },\n });\n}\n\n// ============================================================================\n// Default pickers\n//\n// Each variant resolves its picker as `config?.picker ?? <default>` and\n// forwards the whole engine config as `pickerConfig`, so a rich picker\n// (`pickAudioTrack`) reads its options directly. Audio uses its primitive\n// picker as-is; video adapts `pickFirstTrackId` (positional `type` arg) into\n// the `TrackPicker` shape.\n// ============================================================================\n\n/** Default video picker: first track in the video selection set. */\nconst defaultVideoPicker: TrackPicker = (presentation) => pickFirstTrackId(presentation, 'video');\n\n// ============================================================================\n// Specialized exports — one per track type\n// ============================================================================\n\n/**\n * Config for `selectVideoTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickFirstTrackId` is used.\n */\nexport interface SelectVideoTrackConfig extends VideoSelectionConfig {\n picker?: TrackPicker<SelectVideoTrackConfig>;\n}\n\n/**\n * Select a video track when a presentation loads. Clears the selection on\n * src unload.\n *\n * This is the simple, non-ABR counterpart to `switchVideoTrack` — compose\n * one or the other, not both (both write `selectedVideoTrackId`). Composing\n * `selectVideoTrack` alone tree-shakes out the ABR code path\n * (bandwidth-estimator, quality-selection); use it for sources without\n * meaningful quality variants, test setups, or players that intentionally\n * pin a quality.\n *\n * @example\n * const reactor = selectVideoTrack.setup({ state });\n */\nexport const selectVideoTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedVideoTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedVideoTrackId'>; config?: SelectVideoTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n selectedKey: VIDEO_TYPE_CONFIG.selectedKey,\n picker: config?.picker ?? defaultVideoPicker,\n pickerConfig: config,\n },\n }),\n});\n\n/**\n * Config for `selectAudioTrack`. Pass `picker` to fully override selection\n * logic; otherwise the default `pickAudioTrack` is used (three-tier:\n * `preferredAudioLanguage` → `DEFAULT=YES` → first track).\n */\nexport interface SelectAudioTrackConfig extends AudioSelectionConfig {\n picker?: TrackPicker<SelectAudioTrackConfig>;\n}\n\n/**\n * Select an audio track when a presentation loads. Clears the selection\n * on src unload.\n *\n * This is the simple, lifecycle-only counterpart to `switchAudioTrack`\n * (in `./track-switching.ts`) — compose one or the other, not both\n * (both write `selectedAudioTrackId`). `switchAudioTrack` adds\n * filter-reactivity (`userAudioTrackSelection`) and mid-stream-flush\n * orchestration; `selectAudioTrack` covers the default-on-load case\n * without those. Use this variant for test setups, audio-only flows\n * that don't expose language switching, or composition variants that\n * intentionally pin a track.\n *\n * @example\n * const reactor = selectAudioTrack.setup({ state });\n *\n * @example\n * // Custom picker with language preference\n * const reactor = selectAudioTrack.setup({\n * state,\n * config: { preferredAudioLanguage: 'en', picker: myLanguageAwarePicker },\n * });\n */\nexport const selectAudioTrack = defineBehavior({\n stateKeys: ['presentation', 'selectedAudioTrackId'],\n contextKeys: [],\n setup: ({ state, config }: { state: SelectStateMap<'selectedAudioTrackId'>; config?: SelectAudioTrackConfig }) =>\n setupTrackSelection({\n state,\n config: {\n selectedKey: AUDIO_TYPE_CONFIG.selectedKey,\n picker: config?.picker ?? pickAudioTrack,\n pickerConfig: config,\n },\n }),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAS,oBAA8D,EACrE,OACA,QAAQ,EAAE,aAAa,QAAQ,kBAI9B;CACD,MAAM,qBAAqB,eACzB,uBAAuB,MAAM,aAAa,IAAI,CAAC,IAC1C,0BACA,yBACP;CAEA,OAAO,qBAAqB;EAC1B,SAAS;EACT,eAAe,mBAAmB,IAAI;EACtC,QAAQ;GACN,2BAA2B,CAAC;GAC5B,yBAAyB,EAYvB,aAAa;IACX,IAAI,CAAC,MAAM,YAAY,CAAC,IAAI,GAAG;KAK7B,MAAM,KAAK,OAAO,MAAM,aAAa,IAAI,GAAI,YAAY;KACzD,IAAI,IAAI,MAAM,YAAY,CAAC,IAAI,EAAE;IACnC;IACA,aAAa,MAAM,YAAY,CAAC,IAAI,KAAA,CAAS;GAC/C,EACF;EACF;CACF,CAAC;AACH;;AAaA,MAAM,sBAAmC,iBAAiB,iBAAiB,cAAc,OAAO;;;;;;;;;;;;;;;AA4BhG,MAAa,mBAAmB,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,QAAQ,QAAQ,UAAU;GAC1B,cAAc;EAChB;CACF,CAAC;AACL,CAAC;AAkC+B,eAAe;CAC7C,WAAW,CAAC,gBAAgB,sBAAsB;CAClD,aAAa,CAAC;CACd,QAAQ,EAAE,OAAO,aACf,oBAAoB;EAClB;EACA,QAAQ;GACN,aAAa,kBAAkB;GAC/B,QAAQ,QAAQ,UAAU;GAC1B,cAAc;EAChB;CACF,CAAC;AACL,CAAC"}
@@ -0,0 +1,66 @@
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/setup-failover-monitor.ts
6
+ /**
7
+ * **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites
8
+ * own the *trip*: on a failed fetch they add the failing CDN (origin) to the
9
+ * `failedCdns` state signal directly. This behavior owns the *expiry*: while a
10
+ * presentation is resolved, it watches `failedCdns` and, for each CDN that
11
+ * appears, schedules a timer to remove it once its cooldown lapses.
12
+ * `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's
13
+ * tracks and the active-CDN scope falls to the next one — and back, once the
14
+ * cooldown removes it here.
15
+ *
16
+ * Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new
17
+ * source starts with a clean slate). Policy (cooldown) is engine config. This is
18
+ * the minimal `network-resilience` slice — a single failure trips a CDN, since
19
+ * transient blips are the retry layer's job (it sits below the fetch sites, so
20
+ * anything that reaches `failedCdns` is already terminal).
21
+ */
22
+ const DEFAULT_FAILOVER_MONITOR_CONFIG = { cooldownMs: 3e5 };
23
+ /**
24
+ * Expire failed CDNs from `failedCdns` once their cooldown lapses, for the
25
+ * resolved source.
26
+ *
27
+ * @example
28
+ * const reactor = setupFailoverMonitor.setup({ state });
29
+ */
30
+ const setupFailoverMonitor = defineBehavior({
31
+ stateKeys: ["presentation", "failedCdns"],
32
+ contextKeys: [],
33
+ setup: ({ state, config = {} }) => {
34
+ const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;
35
+ const timers = /* @__PURE__ */ new Map();
36
+ const derivedStateSignal = computed(() => isResolvedPresentation(state.presentation.get()) ? "presentation-resolved" : "presentation-unresolved");
37
+ return createMachineReactor({
38
+ initial: "presentation-unresolved",
39
+ monitor: () => derivedStateSignal.get(),
40
+ states: {
41
+ "presentation-unresolved": {},
42
+ "presentation-resolved": {
43
+ entry: () => () => {
44
+ timers.forEach((timer) => clearTimeout(timer));
45
+ timers.clear();
46
+ state.failedCdns.set(void 0);
47
+ },
48
+ effects: [() => {
49
+ (state.failedCdns.get() ?? []).forEach((cdn) => {
50
+ if (timers.has(cdn)) return;
51
+ const timer = setTimeout(() => {
52
+ timers.delete(cdn);
53
+ update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));
54
+ }, cooldownMs);
55
+ timers.set(cdn, timer);
56
+ });
57
+ }]
58
+ }
59
+ }
60
+ });
61
+ }
62
+ });
63
+ //#endregion
64
+ export { DEFAULT_FAILOVER_MONITOR_CONFIG, setupFailoverMonitor };
65
+
66
+ //# sourceMappingURL=setup-failover-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-failover-monitor.js","names":[],"sources":["../../../../src/playback/behaviors/setup-failover-monitor.ts"],"sourcesContent":["/**\n * **CDN failover cooldown.** The expiry half of multi-CDN failover. Fetch sites\n * own the *trip*: on a failed fetch they add the failing CDN (origin) to the\n * `failedCdns` state signal directly. This behavior owns the *expiry*: while a\n * presentation is resolved, it watches `failedCdns` and, for each CDN that\n * appears, schedules a timer to remove it once its cooldown lapses.\n * `track-switching`'s `excludeFailedCdns` constraint prunes a failed CDN's\n * tracks and the active-CDN scope falls to the next one — and back, once the\n * cooldown removes it here.\n *\n * Lifecycle is per-source: timers + `failedCdns` are cleared on exit (a new\n * source starts with a clean slate). Policy (cooldown) is engine config. This is\n * the minimal `network-resilience` slice — a single failure trips a CDN, since\n * transient blips are the retry layer's job (it sits below the fetch sites, so\n * anything that reaches `failedCdns` is already terminal).\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 { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';\n\n/**\n * Failover policy: how long a CDN stays excluded after a failed fetch trips it.\n * Supplied via engine config.\n */\nexport interface FailoverMonitorConfig {\n /** How long a tripped CDN stays excluded, in milliseconds. */\n cooldownMs: number;\n}\n\nexport const DEFAULT_FAILOVER_MONITOR_CONFIG: FailoverMonitorConfig = {\n // 5 minutes — a CDN outage is an infrastructure problem that outlasts a\n // transient blip, so re-probing it sooner mostly re-trips. Matches the\n // prevailing prior-art default (ExoPlayer's location exclusion, hls.js's\n // content-steering penalty box).\n cooldownMs: 300_000,\n};\n\nexport interface SetupFailoverMonitorState {\n presentation?: MaybeResolvedPresentation;\n failedCdns?: string[];\n}\n\nexport interface SetupFailoverMonitorConfig {\n /** Failover policy (cooldown); defaults to `DEFAULT_FAILOVER_MONITOR_CONFIG`. */\n failover?: Partial<FailoverMonitorConfig>;\n}\n\n/**\n * Expire failed CDNs from `failedCdns` once their cooldown lapses, for the\n * resolved source.\n *\n * @example\n * const reactor = setupFailoverMonitor.setup({ state });\n */\nexport const setupFailoverMonitor = defineBehavior({\n stateKeys: ['presentation', 'failedCdns'],\n contextKeys: [],\n setup: ({\n state,\n config = {},\n }: {\n state: {\n presentation: ReadonlySignal<SetupFailoverMonitorState['presentation']>;\n failedCdns: Signal<SetupFailoverMonitorState['failedCdns']>;\n };\n config?: SetupFailoverMonitorConfig;\n }) => {\n const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;\n // CDN id → its pending cooldown-removal timer. Shared by the `effects`\n // scheduler (adds a timer per newly-failed CDN) and the exit cleanup\n // (clears them). Per-source: emptied on exit, so it re-enters clean.\n const timers = new Map<string, ReturnType<typeof setTimeout>>();\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: on exit (src unload + destroy) clear the\n // pending timers and reset `failedCdns` for the next source.\n entry: () => () => {\n timers.forEach((timer) => clearTimeout(timer));\n timers.clear();\n state.failedCdns.set(undefined);\n },\n effects: [\n () => {\n const failed = state.failedCdns.get() ?? [];\n failed.forEach((cdn) => {\n // Idempotent: a CDN already counting down keeps its original\n // deadline (re-failing it mid-cooldown doesn't extend it).\n if (timers.has(cdn)) return;\n const timer = setTimeout(() => {\n timers.delete(cdn);\n update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));\n }, cooldownMs);\n timers.set(cdn, timer);\n });\n },\n ],\n },\n },\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,kCAAyD,EAKpE,YAAY,IACd;;;;;;;;AAmBA,MAAa,uBAAuB,eAAe;CACjD,WAAW,CAAC,gBAAgB,YAAY;CACxC,aAAa,CAAC;CACd,QAAQ,EACN,OACA,SAAS,CAAC,QAON;EACJ,MAAM,aAAa,OAAO,UAAU,cAAc,gCAAgC;EAIlF,MAAM,yBAAS,IAAI,IAA2C;EAC9D,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;MACjB,OAAO,SAAS,UAAU,aAAa,KAAK,CAAC;MAC7C,OAAO,MAAM;MACb,MAAM,WAAW,IAAI,KAAA,CAAS;KAChC;KACA,SAAS,OACD;MAEJ,CADe,MAAM,WAAW,IAAI,KAAK,CAAC,EAAA,CACnC,SAAS,QAAQ;OAGtB,IAAI,OAAO,IAAI,GAAG,GAAG;OACrB,MAAM,QAAQ,iBAAiB;QAC7B,OAAO,OAAO,GAAG;QACjB,OAAO,MAAM,aAAa,YAAY,SAAS,QAAQ,MAAM,MAAM,GAAG,CAAC;OACzE,GAAG,UAAU;OACb,OAAO,IAAI,KAAK,KAAK;MACvB,CAAC;KACH,CACF;IACF;GACF;EACF,CAAC;CACH;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync-preload.js","names":[],"sources":["../../../../src/playback/behaviors/sync-preload.ts"],"sourcesContent":["/**\n * **Bidirectional sync between `state.preload` and `mediaElement.preload`.**\n *\n * Two effects:\n * - **Read (DOM → state)** — on `context.mediaElement` swap or\n * `state.presentation.url` change, copies `mediaElement.preload` into\n * `state.preload` if it's a W3C value and `state.preload` isn't holding\n * an extended (non-W3C) value. When the DOM has no W3C opinion and\n * `state.preload` is undefined, backfills from `config.defaultPreload`\n * (default-default `'metadata'`) so `state.preload` is never undefined\n * in steady state.\n * - **Write (state → DOM)** — on `state.preload` change or\n * `context.mediaElement` swap, writes `state.preload` back to\n * `mediaElement.preload` if the value is W3C.\n *\n * Extended values (e.g. `'canplay'`) written externally to `state.preload`\n * are sticky: read won't overwrite them, write won't push them to the DOM.\n * All writes are deduped to break echo loops and avoid spurious re-triggers\n * downstream (notably `resolvePresentation`, which reads `state.preload`).\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport type { MediaElementLike } from '../../media/types';\nimport { DEFAULT_PRELOAD, isStandardPreload, type StandardPreload } from '../../media/utils/preload';\nimport type { PresentationState } from './resolve-presentation';\n\nexport interface SyncPreloadConfig {\n /**\n * Backfill applied to `state.preload` when neither the host element nor\n * external code has supplied a W3C value. Defaults to `'metadata'`.\n */\n defaultPreload?: StandardPreload;\n}\n\ntype State = Pick<PresentationState, 'preload' | 'presentation'>;\n\nfunction syncPreloadSetup({\n state,\n context,\n config,\n}: {\n state: {\n preload: Signal<State['preload']>;\n presentation: ReadonlySignal<State['presentation']>;\n };\n context: { mediaElement: ReadonlySignal<MediaElementLike | undefined> };\n config?: SyncPreloadConfig;\n}): () => void {\n const defaultPreload: StandardPreload = config?.defaultPreload ?? DEFAULT_PRELOAD;\n const presentationUrl = computed(() => state.presentation.get()?.url);\n\n // Read must be registered before write: effects re-run in registration\n // order (see core/signals/effect.ts → runPending). On a shared-signal\n // change (e.g. `context.mediaElement` swap), read fires first and gets to\n // claim `state.preload`, giving \"most-recent-wins on attach\" — a freshly\n // mounted <video> with a W3C `preload` attribute overrides whatever\n // `state.preload` previously held. Swap the order or split these into\n // separate behaviors and the \"most-recent-wins on attach\" test pins the\n // regression.\n const cleanupRead = effect(() => {\n presentationUrl.get();\n const mediaElement = context.mediaElement.get();\n // peek (not get): external writes to state.preload shouldn't re-trigger\n // this effect, only mediaElement / presentation-url changes should.\n const current = peek(state.preload);\n // Extended sticky: leave non-W3C values (e.g. 'canplay') alone.\n if (current !== undefined && !isStandardPreload(current)) return;\n\n const target: StandardPreload | undefined =\n mediaElement && isStandardPreload(mediaElement.preload)\n ? mediaElement.preload\n : current === undefined\n ? defaultPreload\n : undefined;\n if (target === undefined || target === current) return;\n state.preload.set(target);\n });\n\n const cleanupWrite = effect(() => {\n const next = state.preload.get();\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) return;\n if (!isStandardPreload(next)) return;\n if (mediaElement.preload === next) return;\n mediaElement.preload = next;\n });\n\n return () => {\n cleanupRead();\n cleanupWrite();\n };\n}\n\nexport const syncPreload = defineBehavior({\n stateKeys: ['preload', 'presentation'],\n contextKeys: ['mediaElement'],\n setup: syncPreloadSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,iBAAiB,EACxB,OACA,SACA,UAQa;CACb,MAAM,iBAAkC,QAAQ,kBAAA;CAChD,MAAM,kBAAkB,eAAe,MAAM,aAAa,KAAK,EAAE,IAAI;CAUrE,MAAM,cAAc,aAAa;AAC/B,kBAAgB,KAAK;EACrB,MAAM,eAAe,QAAQ,aAAa,KAAK;EAG/C,MAAM,UAAU,KAAK,MAAM,QAAQ;AAEnC,MAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,QAAQ,CAAE;EAE1D,MAAM,SACJ,gBAAgB,kBAAkB,aAAa,QAAQ,GACnD,aAAa,UACb,YAAY,KAAA,IACV,iBACA,KAAA;AACR,MAAI,WAAW,KAAA,KAAa,WAAW,QAAS;AAChD,QAAM,QAAQ,IAAI,OAAO;GACzB;CAEF,MAAM,eAAe,aAAa;EAChC,MAAM,OAAO,MAAM,QAAQ,KAAK;EAChC,MAAM,eAAe,QAAQ,aAAa,KAAK;AAC/C,MAAI,CAAC,aAAc;AACnB,MAAI,CAAC,kBAAkB,KAAK,CAAE;AAC9B,MAAI,aAAa,YAAY,KAAM;AACnC,eAAa,UAAU;GACvB;AAEF,cAAa;AACX,eAAa;AACb,gBAAc;;;AAIlB,MAAa,cAAc,eAAe;CACxC,WAAW,CAAC,WAAW,eAAe;CACtC,aAAa,CAAC,eAAe;CAC7B,OAAO;CACR,CAAC"}
1
+ {"version":3,"file":"sync-preload.js","names":[],"sources":["../../../../src/playback/behaviors/sync-preload.ts"],"sourcesContent":["/**\n * **Bidirectional sync between `state.preload` and `mediaElement.preload`.**\n *\n * Two effects:\n * - **Read (DOM → state)** — on `context.mediaElement` swap or\n * `state.presentation.url` change, copies `mediaElement.preload` into\n * `state.preload` if it's a W3C value and `state.preload` isn't holding\n * an extended (non-W3C) value. When the DOM has no W3C opinion and\n * `state.preload` is undefined, backfills from `config.defaultPreload`\n * (default-default `'metadata'`) so `state.preload` is never undefined\n * in steady state.\n * - **Write (state → DOM)** — on `state.preload` change or\n * `context.mediaElement` swap, writes `state.preload` back to\n * `mediaElement.preload` if the value is W3C.\n *\n * Extended values (e.g. `'canplay'`) written externally to `state.preload`\n * are sticky: read won't overwrite them, write won't push them to the DOM.\n * All writes are deduped to break echo loops and avoid spurious re-triggers\n * downstream (notably `resolvePresentation`, which reads `state.preload`).\n */\nimport { defineBehavior } from '../../core/composition/create-composition';\nimport { effect } from '../../core/signals/effect';\nimport { computed, peek, type ReadonlySignal, type Signal } from '../../core/signals/primitives';\nimport type { MediaElementLike } from '../../media/types';\nimport { DEFAULT_PRELOAD, isStandardPreload, type StandardPreload } from '../../media/utils/preload';\nimport type { PresentationState } from './resolve-presentation';\n\nexport interface SyncPreloadConfig {\n /**\n * Backfill applied to `state.preload` when neither the host element nor\n * external code has supplied a W3C value. Defaults to `'metadata'`.\n */\n defaultPreload?: StandardPreload;\n}\n\ntype State = Pick<PresentationState, 'preload' | 'presentation'>;\n\nfunction syncPreloadSetup({\n state,\n context,\n config,\n}: {\n state: {\n preload: Signal<State['preload']>;\n presentation: ReadonlySignal<State['presentation']>;\n };\n context: { mediaElement: ReadonlySignal<MediaElementLike | undefined> };\n config?: SyncPreloadConfig;\n}): () => void {\n const defaultPreload: StandardPreload = config?.defaultPreload ?? DEFAULT_PRELOAD;\n const presentationUrl = computed(() => state.presentation.get()?.url);\n\n // Read must be registered before write: effects re-run in registration\n // order (see core/signals/effect.ts → runPending). On a shared-signal\n // change (e.g. `context.mediaElement` swap), read fires first and gets to\n // claim `state.preload`, giving \"most-recent-wins on attach\" — a freshly\n // mounted <video> with a W3C `preload` attribute overrides whatever\n // `state.preload` previously held. Swap the order or split these into\n // separate behaviors and the \"most-recent-wins on attach\" test pins the\n // regression.\n const cleanupRead = effect(() => {\n presentationUrl.get();\n const mediaElement = context.mediaElement.get();\n // peek (not get): external writes to state.preload shouldn't re-trigger\n // this effect, only mediaElement / presentation-url changes should.\n const current = peek(state.preload);\n // Extended sticky: leave non-W3C values (e.g. 'canplay') alone.\n if (current !== undefined && !isStandardPreload(current)) return;\n\n const target: StandardPreload | undefined =\n mediaElement && isStandardPreload(mediaElement.preload)\n ? mediaElement.preload\n : current === undefined\n ? defaultPreload\n : undefined;\n if (target === undefined || target === current) return;\n state.preload.set(target);\n });\n\n const cleanupWrite = effect(() => {\n const next = state.preload.get();\n const mediaElement = context.mediaElement.get();\n if (!mediaElement) return;\n if (!isStandardPreload(next)) return;\n if (mediaElement.preload === next) return;\n mediaElement.preload = next;\n });\n\n return () => {\n cleanupRead();\n cleanupWrite();\n };\n}\n\nexport const syncPreload = defineBehavior({\n stateKeys: ['preload', 'presentation'],\n contextKeys: ['mediaElement'],\n setup: syncPreloadSetup,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,iBAAiB,EACxB,OACA,SACA,UAQa;CACb,MAAM,iBAAkC,QAAQ,kBAAA;CAChD,MAAM,kBAAkB,eAAe,MAAM,aAAa,IAAI,CAAC,EAAE,GAAG;CAUpE,MAAM,cAAc,aAAa;EAC/B,gBAAgB,IAAI;EACpB,MAAM,eAAe,QAAQ,aAAa,IAAI;EAG9C,MAAM,UAAU,KAAK,MAAM,OAAO;EAElC,IAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,OAAO,GAAG;EAE1D,MAAM,SACJ,gBAAgB,kBAAkB,aAAa,OAAO,IAClD,aAAa,UACb,YAAY,KAAA,IACV,iBACA,KAAA;EACR,IAAI,WAAW,KAAA,KAAa,WAAW,SAAS;EAChD,MAAM,QAAQ,IAAI,MAAM;CAC1B,CAAC;CAED,MAAM,eAAe,aAAa;EAChC,MAAM,OAAO,MAAM,QAAQ,IAAI;EAC/B,MAAM,eAAe,QAAQ,aAAa,IAAI;EAC9C,IAAI,CAAC,cAAc;EACnB,IAAI,CAAC,kBAAkB,IAAI,GAAG;EAC9B,IAAI,aAAa,YAAY,MAAM;EACnC,aAAa,UAAU;CACzB,CAAC;CAED,aAAa;EACX,YAAY;EACZ,aAAa;CACf;AACF;AAEA,MAAa,cAAc,eAAe;CACxC,WAAW,CAAC,WAAW,cAAc;CACrC,aAAa,CAAC,cAAc;CAC5B,OAAO;AACT,CAAC"}