@videojs/spf 10.0.0-beta.30 → 10.0.0-beta.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (520) hide show
  1. package/dist/default/core/actors/create-machine-actor.js +23 -25
  2. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  3. package/dist/default/core/actors/create-transition-actor.js +7 -9
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  5. package/dist/default/core/composition/create-composition.js +26 -29
  6. package/dist/default/core/composition/create-composition.js.map +1 -1
  7. package/dist/default/core/composition/share-signals.js +12 -17
  8. package/dist/default/core/composition/share-signals.js.map +1 -1
  9. package/dist/default/core/machine.js +3 -4
  10. package/dist/default/core/machine.js.map +1 -1
  11. package/dist/default/core/reactors/create-machine-reactor.js +20 -19
  12. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  13. package/dist/default/core/signals/effect.js +24 -4
  14. package/dist/default/core/signals/effect.js.map +1 -1
  15. package/dist/default/core/signals/primitives.js +10 -14
  16. package/dist/default/core/signals/primitives.js.map +1 -1
  17. package/dist/default/core/signals/when.js +6 -8
  18. package/dist/default/core/signals/when.js.map +1 -1
  19. package/dist/default/core/tasks/delayed-reschedule.js +7 -11
  20. package/dist/default/core/tasks/delayed-reschedule.js.map +1 -1
  21. package/dist/default/core/tasks/task.js +59 -82
  22. package/dist/default/core/tasks/task.js.map +1 -1
  23. package/dist/default/hls.js +3 -1
  24. package/dist/default/index.js +2 -3
  25. package/dist/default/index.js.map +1 -1
  26. package/dist/default/media/abr/quality-selection.js +3 -7
  27. package/dist/default/media/abr/quality-selection.js.map +1 -1
  28. package/dist/default/media/buffer/back-buffer.js +15 -19
  29. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  30. package/dist/default/media/buffer/forward-buffer.js +32 -41
  31. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  32. package/dist/default/media/dom/capabilities.js +26 -37
  33. package/dist/default/media/dom/capabilities.js.map +1 -1
  34. package/dist/default/media/dom/mse/append-segment.js +3 -5
  35. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  36. package/dist/default/media/dom/mse/buffer-flusher.js +5 -5
  37. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  38. package/dist/default/media/dom/mse/duration.js +23 -31
  39. package/dist/default/media/dom/mse/duration.js.map +1 -1
  40. package/dist/default/media/dom/mse/end-of-stream.js +3 -6
  41. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  42. package/dist/default/media/dom/mse/mediasource-setup.js +65 -81
  43. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  44. package/dist/default/media/dom/screen.js +37 -54
  45. package/dist/default/media/dom/screen.js.map +1 -1
  46. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  47. package/dist/default/media/dom/text/text-track-slots.js +15 -23
  48. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  49. package/dist/default/media/errors.js +25 -34
  50. package/dist/default/media/errors.js.map +1 -1
  51. package/dist/default/media/hls/parse-attributes.js +9 -26
  52. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  53. package/dist/default/media/hls/parse-media-playlist.js +25 -33
  54. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  55. package/dist/default/media/hls/parse-multivariant.js +3 -3
  56. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  57. package/dist/default/media/hls/reload-policy.js +19 -27
  58. package/dist/default/media/hls/reload-policy.js.map +1 -1
  59. package/dist/default/media/hls/resolve-url.js +1 -3
  60. package/dist/default/media/hls/resolve-url.js.map +1 -1
  61. package/dist/default/media/live-window.js +9 -13
  62. package/dist/default/media/live-window.js.map +1 -1
  63. package/dist/default/media/media-tracks/media-tracks.js +12 -17
  64. package/dist/default/media/media-tracks/media-tracks.js.map +1 -1
  65. package/dist/default/media/mp4/box.js +4 -5
  66. package/dist/default/media/mp4/box.js.map +1 -1
  67. package/dist/default/media/mp4/timestamp-origin.js +30 -42
  68. package/dist/default/media/mp4/timestamp-origin.js.map +1 -1
  69. package/dist/default/media/primitives/resolution.js +5 -8
  70. package/dist/default/media/primitives/resolution.js.map +1 -1
  71. package/dist/default/media/primitives/select-tracks.js +24 -34
  72. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  73. package/dist/default/media/text/parse-vtt-timestamp-map.js +6 -8
  74. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -1
  75. package/dist/default/media/text/resolve-vtt-metadata.js +8 -9
  76. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -1
  77. package/dist/default/media/types/index.js +10 -18
  78. package/dist/default/media/types/index.js.map +1 -1
  79. package/dist/default/media/utils/cdn.js +10 -16
  80. package/dist/default/media/utils/cdn.js.map +1 -1
  81. package/dist/default/media/utils/preload.js +4 -6
  82. package/dist/default/media/utils/preload.js.map +1 -1
  83. package/dist/default/media/utils/track-selection.js +10 -14
  84. package/dist/default/media/utils/track-selection.js.map +1 -1
  85. package/dist/default/media/utils/tracks.js +56 -54
  86. package/dist/default/media/utils/tracks.js.map +1 -1
  87. package/dist/default/network/bandwidth-estimator.js +18 -19
  88. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  89. package/dist/default/network/chunked-stream-iterable.js +4 -6
  90. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  91. package/dist/default/network/ewma.js +22 -25
  92. package/dist/default/network/ewma.js.map +1 -1
  93. package/dist/default/network/fetch.js +30 -37
  94. package/dist/default/network/fetch.js.map +1 -1
  95. package/dist/default/playback/actors/dom/segment-loader.js +28 -44
  96. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  97. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  98. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  99. package/dist/default/playback/actors/text-track-segment-loader.js +24 -35
  100. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  101. package/dist/default/playback/adapters/hls-audio/adapter.js +23 -31
  102. package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -1
  103. package/dist/default/playback/adapters/hls-audio/media.js.map +1 -1
  104. package/dist/default/playback/adapters/hls-background-video/adapter.js +50 -70
  105. package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -1
  106. package/dist/default/playback/adapters/hls-background-video/host.js +12 -16
  107. package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -1
  108. package/dist/default/playback/adapters/hls-background-video/media.js +5 -8
  109. package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -1
  110. package/dist/default/playback/adapters/hls-video/adapter.js +43 -60
  111. package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -1
  112. package/dist/default/playback/adapters/hls-video/error-surface.js +13 -18
  113. package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -1
  114. package/dist/default/playback/adapters/hls-video/media-tracks.js +4 -5
  115. package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -1
  116. package/dist/default/playback/adapters/hls-video/media.js.map +1 -1
  117. package/dist/default/playback/adapters/mux-audio/media.js +8 -12
  118. package/dist/default/playback/adapters/mux-audio/media.js.map +1 -1
  119. package/dist/default/playback/adapters/mux-video/adapter.js +25 -33
  120. package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -1
  121. package/dist/default/playback/adapters/mux-video/media.js +7 -9
  122. package/dist/default/playback/adapters/mux-video/media.js.map +1 -1
  123. package/dist/default/playback/behaviors/calculate-presentation-duration.js +5 -8
  124. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  125. package/dist/default/playback/behaviors/collect-errors.js +39 -52
  126. package/dist/default/playback/behaviors/collect-errors.js.map +1 -1
  127. package/dist/default/playback/behaviors/derive-cdn-priority.js +16 -23
  128. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
  129. package/dist/default/playback/behaviors/dom/airplay.js +38 -61
  130. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -1
  131. package/dist/default/playback/behaviors/dom/apply-start-position.js +23 -35
  132. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -1
  133. package/dist/default/playback/behaviors/dom/end-of-stream.js +11 -16
  134. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  135. package/dist/default/playback/behaviors/dom/load-segments.js +27 -39
  136. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  137. package/dist/default/playback/behaviors/dom/recover-end-stall.js +21 -26
  138. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -1
  139. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +41 -60
  140. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -1
  141. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +50 -74
  142. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  143. package/dist/default/playback/behaviors/dom/setup-mediasource.js +29 -43
  144. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  145. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  146. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +3 -4
  147. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -1
  148. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +30 -45
  149. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  150. package/dist/default/playback/behaviors/dom/track-current-time.js +9 -11
  151. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  152. package/dist/default/playback/behaviors/dom/track-load-triggers.js +24 -32
  153. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  154. package/dist/default/playback/behaviors/dom/track-playback-rate.js +6 -8
  155. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  156. package/dist/default/playback/behaviors/dom/track-player-resolution.js +10 -14
  157. package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -1
  158. package/dist/default/playback/behaviors/dom/track-screen-resolution.js +8 -9
  159. package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -1
  160. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +28 -43
  161. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  162. package/dist/default/playback/behaviors/establish-start-media-time.js +32 -42
  163. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -1
  164. package/dist/default/playback/behaviors/resolve-presentation.js +13 -22
  165. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  166. package/dist/default/playback/behaviors/resolve-track.js +5 -12
  167. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  168. package/dist/default/playback/behaviors/select-tracks.js +77 -104
  169. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  170. package/dist/default/playback/behaviors/setup-failover-monitor.js +11 -16
  171. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
  172. package/dist/default/playback/behaviors/sync-preload.js +9 -13
  173. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  174. package/dist/default/playback/behaviors/track-switching.js +189 -173
  175. package/dist/default/playback/behaviors/track-switching.js.map +1 -1
  176. package/dist/default/playback/engines/hls/engine-audio-only.js +20 -24
  177. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
  178. package/dist/default/playback/engines/hls/engine-background-video.js +22 -27
  179. package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -1
  180. package/dist/default/playback/engines/hls/engine.js +22 -25
  181. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  182. package/dist/default/playback/primitives/error-messages.js +16 -25
  183. package/dist/default/playback/primitives/error-messages.js.map +1 -1
  184. package/dist/default/playback/primitives/failover-fetch.js +9 -14
  185. package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
  186. package/dist/default/playback/primitives/head-peek.js +7 -10
  187. package/dist/default/playback/primitives/head-peek.js.map +1 -1
  188. package/dist/default/playback/primitives/live-window.js +12 -15
  189. package/dist/default/playback/primitives/live-window.js.map +1 -1
  190. package/dist/default/playback/primitives/relocation-pipelines.js +34 -46
  191. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -1
  192. package/dist/default/playback/primitives/report-track-conditions.js +26 -38
  193. package/dist/default/playback/primitives/report-track-conditions.js.map +1 -1
  194. package/dist/default/playback/primitives/segment-load-pipeline.js +15 -15
  195. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -1
  196. package/dist/default/playback/primitives/selection-rules.js +65 -36
  197. package/dist/default/playback/primitives/selection-rules.js.map +1 -1
  198. package/dist/default/playback/primitives/text-segment-load-pipeline.js +4 -5
  199. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -1
  200. package/dist/default/playback/primitives/track-types.js +10 -18
  201. package/dist/default/playback/primitives/track-types.js.map +1 -1
  202. package/dist/dev/core/actors/actor.d.ts +4 -4
  203. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  204. package/dist/dev/core/actors/create-machine-actor.d.ts +38 -47
  205. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  206. package/dist/dev/core/actors/create-machine-actor.js +23 -25
  207. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  208. package/dist/dev/core/actors/create-transition-actor.d.ts +11 -15
  209. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  210. package/dist/dev/core/actors/create-transition-actor.js +7 -9
  211. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  212. package/dist/dev/core/composition/create-composition.d.ts +70 -102
  213. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  214. package/dist/dev/core/composition/create-composition.js +26 -29
  215. package/dist/dev/core/composition/create-composition.js.map +1 -1
  216. package/dist/dev/core/composition/share-signals.d.ts +17 -24
  217. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  218. package/dist/dev/core/composition/share-signals.js +12 -17
  219. package/dist/dev/core/composition/share-signals.js.map +1 -1
  220. package/dist/dev/core/machine.d.ts +4 -4
  221. package/dist/dev/core/machine.js +3 -4
  222. package/dist/dev/core/machine.js.map +1 -1
  223. package/dist/dev/core/reactors/create-machine-reactor.d.ts +33 -39
  224. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  225. package/dist/dev/core/reactors/create-machine-reactor.js +20 -19
  226. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  227. package/dist/dev/core/signals/effect.d.ts +3 -4
  228. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  229. package/dist/dev/core/signals/effect.js +24 -4
  230. package/dist/dev/core/signals/effect.js.map +1 -1
  231. package/dist/dev/core/signals/primitives.d.ts +10 -12
  232. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  233. package/dist/dev/core/signals/primitives.js +10 -14
  234. package/dist/dev/core/signals/primitives.js.map +1 -1
  235. package/dist/dev/core/signals/when.js +6 -8
  236. package/dist/dev/core/signals/when.js.map +1 -1
  237. package/dist/dev/core/tasks/delayed-reschedule.js +7 -11
  238. package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -1
  239. package/dist/dev/core/tasks/task.d.ts +51 -73
  240. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  241. package/dist/dev/core/tasks/task.js +59 -82
  242. package/dist/dev/core/tasks/task.js.map +1 -1
  243. package/dist/dev/hls.d.ts +3 -1
  244. package/dist/dev/hls.js +3 -1
  245. package/dist/dev/index.d.ts +2 -3
  246. package/dist/dev/index.d.ts.map +1 -1
  247. package/dist/dev/index.js +2 -3
  248. package/dist/dev/index.js.map +1 -1
  249. package/dist/dev/media/abr/quality-selection.d.ts +7 -10
  250. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  251. package/dist/dev/media/abr/quality-selection.js +3 -7
  252. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  253. package/dist/dev/media/buffer/back-buffer.d.ts +2 -7
  254. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  255. package/dist/dev/media/buffer/back-buffer.js +15 -19
  256. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  257. package/dist/dev/media/buffer/forward-buffer.d.ts +2 -7
  258. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  259. package/dist/dev/media/buffer/forward-buffer.js +32 -41
  260. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  261. package/dist/dev/media/dom/capabilities.js +26 -37
  262. package/dist/dev/media/dom/capabilities.js.map +1 -1
  263. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -5
  264. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  265. package/dist/dev/media/dom/mse/append-segment.js +3 -5
  266. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  267. package/dist/dev/media/dom/mse/buffer-flusher.d.ts +5 -5
  268. package/dist/dev/media/dom/mse/buffer-flusher.js +5 -5
  269. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  270. package/dist/dev/media/dom/mse/duration.js +23 -31
  271. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  272. package/dist/dev/media/dom/mse/end-of-stream.js +3 -6
  273. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  274. package/dist/dev/media/dom/mse/mediasource-setup.js +65 -81
  275. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  276. package/dist/dev/media/dom/screen.d.ts.map +1 -1
  277. package/dist/dev/media/dom/screen.js +37 -54
  278. package/dist/dev/media/dom/screen.js.map +1 -1
  279. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  280. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  281. package/dist/dev/media/dom/text/text-track-slots.d.ts +10 -16
  282. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  283. package/dist/dev/media/dom/text/text-track-slots.js +15 -23
  284. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  285. package/dist/dev/media/errors.d.ts +32 -46
  286. package/dist/dev/media/errors.d.ts.map +1 -1
  287. package/dist/dev/media/errors.js +25 -34
  288. package/dist/dev/media/errors.js.map +1 -1
  289. package/dist/dev/media/hls/parse-attributes.js +9 -26
  290. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  291. package/dist/dev/media/hls/parse-media-playlist.js +25 -33
  292. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  293. package/dist/dev/media/hls/parse-multivariant.js +3 -3
  294. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  295. package/dist/dev/media/hls/reload-policy.js +19 -27
  296. package/dist/dev/media/hls/reload-policy.js.map +1 -1
  297. package/dist/dev/media/hls/resolve-url.js +1 -3
  298. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  299. package/dist/dev/media/live-window.js +9 -13
  300. package/dist/dev/media/live-window.js.map +1 -1
  301. package/dist/dev/media/media-tracks/media-tracks.d.ts +8 -13
  302. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -1
  303. package/dist/dev/media/media-tracks/media-tracks.js +12 -17
  304. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -1
  305. package/dist/dev/media/mp4/box.js +4 -5
  306. package/dist/dev/media/mp4/box.js.map +1 -1
  307. package/dist/dev/media/mp4/timestamp-origin.js +30 -42
  308. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -1
  309. package/dist/dev/media/primitives/resolution.d.ts +6 -8
  310. package/dist/dev/media/primitives/resolution.d.ts.map +1 -1
  311. package/dist/dev/media/primitives/resolution.js +5 -8
  312. package/dist/dev/media/primitives/resolution.js.map +1 -1
  313. package/dist/dev/media/primitives/select-tracks.js +24 -34
  314. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  315. package/dist/dev/media/text/parse-vtt-timestamp-map.js +6 -8
  316. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -1
  317. package/dist/dev/media/text/resolve-vtt-metadata.js +8 -9
  318. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -1
  319. package/dist/dev/media/types/index.d.ts +106 -177
  320. package/dist/dev/media/types/index.d.ts.map +1 -1
  321. package/dist/dev/media/types/index.js +10 -18
  322. package/dist/dev/media/types/index.js.map +1 -1
  323. package/dist/dev/media/utils/cdn.d.ts +4 -6
  324. package/dist/dev/media/utils/cdn.d.ts.map +1 -1
  325. package/dist/dev/media/utils/cdn.js +10 -16
  326. package/dist/dev/media/utils/cdn.js.map +1 -1
  327. package/dist/dev/media/utils/preload.js +4 -6
  328. package/dist/dev/media/utils/preload.js.map +1 -1
  329. package/dist/dev/media/utils/track-selection.js +10 -14
  330. package/dist/dev/media/utils/track-selection.js.map +1 -1
  331. package/dist/dev/media/utils/tracks.js +56 -54
  332. package/dist/dev/media/utils/tracks.js.map +1 -1
  333. package/dist/dev/network/bandwidth-estimator.d.ts +7 -9
  334. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  335. package/dist/dev/network/bandwidth-estimator.js +18 -19
  336. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  337. package/dist/dev/network/chunked-stream-iterable.js +4 -6
  338. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  339. package/dist/dev/network/ewma.js +22 -25
  340. package/dist/dev/network/ewma.js.map +1 -1
  341. package/dist/dev/network/fetch.js +30 -37
  342. package/dist/dev/network/fetch.js.map +1 -1
  343. package/dist/dev/playback/actors/dom/segment-loader.d.ts +7 -11
  344. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  345. package/dist/dev/playback/actors/dom/segment-loader.js +28 -44
  346. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  347. package/dist/dev/playback/actors/dom/source-buffer.d.ts +5 -7
  348. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  349. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  350. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  351. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +10 -16
  352. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  353. package/dist/dev/playback/actors/text-track-segment-loader.js +24 -35
  354. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  355. package/dist/dev/playback/actors/text-tracks.d.ts +5 -8
  356. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  357. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +11 -14
  358. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -1
  359. package/dist/dev/playback/adapters/hls-audio/adapter.js +23 -31
  360. package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -1
  361. package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -1
  362. package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -1
  363. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +28 -39
  364. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -1
  365. package/dist/dev/playback/adapters/hls-background-video/adapter.js +50 -70
  366. package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -1
  367. package/dist/dev/playback/adapters/hls-background-video/host.d.ts +12 -16
  368. package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -1
  369. package/dist/dev/playback/adapters/hls-background-video/host.js +12 -16
  370. package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -1
  371. package/dist/dev/playback/adapters/hls-background-video/media.d.ts +5 -8
  372. package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -1
  373. package/dist/dev/playback/adapters/hls-background-video/media.js +5 -8
  374. package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -1
  375. package/dist/dev/playback/adapters/hls-video/adapter.d.ts +12 -14
  376. package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -1
  377. package/dist/dev/playback/adapters/hls-video/adapter.js +43 -60
  378. package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -1
  379. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +4 -5
  380. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -1
  381. package/dist/dev/playback/adapters/hls-video/error-surface.js +13 -18
  382. package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -1
  383. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +4 -5
  384. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -1
  385. package/dist/dev/playback/adapters/hls-video/media-tracks.js +4 -5
  386. package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -1
  387. package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -1
  388. package/dist/dev/playback/adapters/hls-video/media.js.map +1 -1
  389. package/dist/dev/playback/adapters/mux-audio/media.d.ts +8 -12
  390. package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -1
  391. package/dist/dev/playback/adapters/mux-audio/media.js +8 -12
  392. package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -1
  393. package/dist/dev/playback/adapters/mux-video/adapter.d.ts +9 -11
  394. package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -1
  395. package/dist/dev/playback/adapters/mux-video/adapter.js +25 -33
  396. package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -1
  397. package/dist/dev/playback/adapters/mux-video/media.d.ts +7 -9
  398. package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -1
  399. package/dist/dev/playback/adapters/mux-video/media.js +7 -9
  400. package/dist/dev/playback/adapters/mux-video/media.js.map +1 -1
  401. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +5 -8
  402. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  403. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +5 -8
  404. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  405. package/dist/dev/playback/behaviors/collect-errors.d.ts +2 -0
  406. package/dist/dev/playback/behaviors/collect-errors.js +39 -52
  407. package/dist/dev/playback/behaviors/collect-errors.js.map +1 -1
  408. package/dist/dev/playback/behaviors/derive-cdn-priority.js +16 -23
  409. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
  410. package/dist/dev/playback/behaviors/dom/airplay.js +38 -61
  411. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -1
  412. package/dist/dev/playback/behaviors/dom/apply-start-position.js +23 -35
  413. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -1
  414. package/dist/dev/playback/behaviors/dom/end-of-stream.js +11 -16
  415. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  416. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +5 -8
  417. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  418. package/dist/dev/playback/behaviors/dom/load-segments.js +27 -39
  419. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  420. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +21 -26
  421. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -1
  422. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +41 -60
  423. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -1
  424. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +50 -74
  425. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  426. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +29 -43
  427. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  428. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +3 -3
  429. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  430. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  431. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +3 -4
  432. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -1
  433. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +30 -45
  434. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  435. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +2 -2
  436. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  437. package/dist/dev/playback/behaviors/dom/track-current-time.js +9 -11
  438. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  439. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +6 -12
  440. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  441. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +24 -32
  442. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  443. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +2 -2
  444. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  445. package/dist/dev/playback/behaviors/dom/track-playback-rate.js +6 -8
  446. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  447. package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -1
  448. package/dist/dev/playback/behaviors/dom/track-player-resolution.js +10 -14
  449. package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -1
  450. package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +8 -9
  451. package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -1
  452. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +28 -43
  453. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  454. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +15 -19
  455. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -1
  456. package/dist/dev/playback/behaviors/establish-start-media-time.js +32 -42
  457. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -1
  458. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  459. package/dist/dev/playback/behaviors/resolve-presentation.js +13 -22
  460. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  461. package/dist/dev/playback/behaviors/resolve-track.js +5 -12
  462. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  463. package/dist/dev/playback/behaviors/select-tracks.d.ts +28 -39
  464. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
  465. package/dist/dev/playback/behaviors/select-tracks.js +77 -104
  466. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  467. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +1 -4
  468. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
  469. package/dist/dev/playback/behaviors/setup-failover-monitor.js +11 -16
  470. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
  471. package/dist/dev/playback/behaviors/sync-preload.js +9 -13
  472. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  473. package/dist/dev/playback/behaviors/track-switching.d.ts +110 -0
  474. package/dist/dev/playback/behaviors/track-switching.d.ts.map +1 -0
  475. package/dist/dev/playback/behaviors/track-switching.js +189 -173
  476. package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
  477. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +59 -72
  478. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
  479. package/dist/dev/playback/engines/hls/engine-audio-only.js +20 -24
  480. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
  481. package/dist/dev/playback/engines/hls/engine-background-video.d.ts +51 -78
  482. package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -1
  483. package/dist/dev/playback/engines/hls/engine-background-video.js +22 -27
  484. package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -1
  485. package/dist/dev/playback/engines/hls/engine.d.ts +115 -155
  486. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  487. package/dist/dev/playback/engines/hls/engine.js +22 -25
  488. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  489. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +3 -3
  490. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -1
  491. package/dist/dev/playback/primitives/error-messages.js +16 -25
  492. package/dist/dev/playback/primitives/error-messages.js.map +1 -1
  493. package/dist/dev/playback/primitives/failover-fetch.js +9 -14
  494. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
  495. package/dist/dev/playback/primitives/head-peek.js +7 -10
  496. package/dist/dev/playback/primitives/head-peek.js.map +1 -1
  497. package/dist/dev/playback/primitives/live-window.js +12 -15
  498. package/dist/dev/playback/primitives/live-window.js.map +1 -1
  499. package/dist/dev/playback/primitives/relocation-pipelines.js +34 -46
  500. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -1
  501. package/dist/dev/playback/primitives/report-track-conditions.d.ts +1 -4
  502. package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -1
  503. package/dist/dev/playback/primitives/report-track-conditions.js +26 -38
  504. package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -1
  505. package/dist/dev/playback/primitives/segment-load-pipeline.js +15 -15
  506. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -1
  507. package/dist/dev/playback/primitives/selection-rules.d.ts +45 -17
  508. package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -1
  509. package/dist/dev/playback/primitives/selection-rules.js +65 -36
  510. package/dist/dev/playback/primitives/selection-rules.js.map +1 -1
  511. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +6 -9
  512. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -1
  513. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +20 -29
  514. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -1
  515. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +4 -5
  516. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -1
  517. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -1
  518. package/dist/dev/playback/primitives/track-types.js +10 -18
  519. package/dist/dev/playback/primitives/track-types.js.map +1 -1
  520. package/package.json +25 -28
@@ -1 +1 @@
1
- {"version":3,"file":"quality-selection.js","names":[],"sources":["../../../../src/media/abr/quality-selection.ts"],"sourcesContent":["/**\n * Quality Selection Algorithm\n *\n * Selects optimal video track based on bandwidth estimate with safety margin.\n * Stateless selection - picks highest quality that fits bandwidth.\n *\n * Key concepts:\n * - **Safety margin** (0.85): Pick track where bandwidth >= track.bandwidth / 0.85\n * - This ensures 15% headroom to avoid buffering\n * - At same bandwidth, prefer higher resolution\n */\n\nimport type { PartiallyResolvedVideoTrack, VideoTrack } from '../types';\n\n/**\n * Quality selection configuration.\n */\nexport interface QualityConfig {\n /**\n * Safety margin (0-1).\n * To select a track, need: currentBandwidth >= track.bandwidth / safetyMargin.\n * Default 0.85 means track must use ≤85% of available bandwidth (15% headroom).\n */\n safetyMargin: number;\n /**\n * Upgrade hysteresis ratio (>= 1). When `currentTrack` is supplied, an\n * upgrade is applied only if `optimal.bandwidth >= currentTrack.bandwidth * upgradeMargin`.\n * Downgrades are always applied. Default 1.15 means optimal must clear\n * the current bandwidth by at least 15% to trigger an upgrade.\n */\n upgradeMargin: number;\n}\n\n/**\n * Default quality selection configuration.\n * Values match Shaka Player upgrade threshold (0.85 = 15% headroom).\n */\nexport const DEFAULT_QUALITY_CONFIG: QualityConfig = {\n safetyMargin: 0.85,\n upgradeMargin: 1.15,\n};\n\n/**\n * Selection context for `selectQuality`. The `bandwidth` field carries the\n * current network estimate; `safetyMargin` / `upgradeMargin` override the\n * defaults; `currentTrack` enables upgrade-vs-downgrade hysteresis.\n *\n * Shape matches the unified `selectOptimal` contract of\n * `setupTrackSwitching` (`playback/behaviors/track-switching.ts`) so the\n * function can be passed directly as a variant's `selectOptimal`.\n */\nexport interface SelectQualityCtx<T extends { bandwidth: number } = PartiallyResolvedVideoTrack | VideoTrack>\n extends Partial<QualityConfig> {\n bandwidth: number;\n /**\n * Track currently selected. When supplied, `selectQuality` returns\n * `currentTrack` (no change) for upgrades that don't clear the\n * `upgradeMargin`. When omitted, no hysteresis is applied — the\n * computed optimal is returned regardless.\n */\n currentTrack?: T;\n}\n\n/**\n * Select the track to apply now, given a context that carries current\n * bandwidth, an optional `currentTrack`, and tuning overrides. Returns:\n *\n * - The bandwidth-fitting optimal when no `currentTrack` is supplied.\n * - The optimal when it's a downgrade vs. `currentTrack` (downgrades\n * apply immediately — no hysteresis).\n * - The optimal when it clears `currentTrack.bandwidth * upgradeMargin`\n * (upgrade clears hysteresis).\n * - `currentTrack` itself when an upgrade doesn't clear the margin\n * (stay put — caller checks identity to no-op).\n *\n * \"Optimal\" is the highest-bandwidth track where the available bandwidth\n * meets the safety requirement (`bandwidth >= track.bandwidth / safetyMargin`).\n * Falls back to the lowest-bandwidth track when nothing fits the safety\n * margin (preserves a definitive pick under under-bandwidth conditions).\n *\n * @example\n * const tracks = [low, mid, high];\n * selectQuality(tracks, { bandwidth: 5_000_000, currentTrack: low });\n * // Returns `high` if 5 Mbps clears safety AND high.bandwidth >= low.bandwidth * upgradeMargin.\n * // Returns `low` (no-op signal) otherwise.\n */\nexport function selectQuality(\n tracks: readonly (PartiallyResolvedVideoTrack | VideoTrack)[],\n ctx: SelectQualityCtx\n): PartiallyResolvedVideoTrack | VideoTrack | undefined {\n if (tracks.length === 0) {\n return undefined;\n }\n\n const safetyMargin = ctx.safetyMargin ?? DEFAULT_QUALITY_CONFIG.safetyMargin;\n const upgradeMargin = ctx.upgradeMargin ?? DEFAULT_QUALITY_CONFIG.upgradeMargin;\n const { bandwidth: currentBandwidth, currentTrack } = ctx;\n\n // Sort tracks by bandwidth (lowest first)\n const sortedTracks = tracks.slice().sort((a, b) => a.bandwidth - b.bandwidth);\n\n // Start with no selection\n let chosen: PartiallyResolvedVideoTrack | VideoTrack | undefined;\n\n for (const track of sortedTracks) {\n // Check if we have enough bandwidth for this track with safety margin\n // Required bandwidth = track.bandwidth / safetyMargin\n const requiredBandwidth = track.bandwidth / safetyMargin;\n\n if (currentBandwidth >= requiredBandwidth) {\n // We can support this track - prefer it if better than current choice\n if (\n !chosen ||\n track.bandwidth > chosen.bandwidth ||\n (track.bandwidth === chosen.bandwidth && hasHigherResolution(track, chosen))\n ) {\n chosen = track;\n }\n }\n }\n\n // If no track fits with safety margin, fall back to lowest quality\n const optimal = chosen ?? sortedTracks[0];\n if (!optimal) return undefined;\n\n // Apply upgrade hysteresis. No currentTrack → no hysteresis, return\n // optimal. Downgrade (optimal.bandwidth < current) → always apply.\n // Upgrade → only when `optimal.bandwidth >= current * upgradeMargin`,\n // else stay put (return currentTrack so caller's id-compare no-ops).\n if (!currentTrack) return optimal;\n if (optimal.bandwidth < currentTrack.bandwidth) return optimal;\n if (optimal.bandwidth >= currentTrack.bandwidth * upgradeMargin) return optimal;\n return currentTrack;\n}\n\n/**\n * Select the lowest-bandwidth track from the candidate set.\n *\n * Used as a safety-net fallback by callers that need a definitive pick when\n * a primary selection algorithm declines to choose. Pairs naturally with\n * `selectQuality` — when ABR has no good answer (e.g., all candidates\n * exceed available bandwidth and the algorithm doesn't fall back\n * internally), the lowest bitrate is the safest default.\n *\n * @param tracks - Candidate tracks (can be unsorted)\n * @returns The lowest-bandwidth track, or `undefined` if `tracks` is empty\n *\n * @example\n * const fallback = selectLowestQuality(tracks);\n */\nexport function selectLowestQuality<T extends { bandwidth: number }>(tracks: readonly T[]): T | undefined {\n if (tracks.length === 0) return undefined;\n return tracks.reduce((min, t) => (t.bandwidth < min.bandwidth ? t : min));\n}\n\n/**\n * Resolution as a total pixel count (`width × height`), the basis for\n * comparing two tracks at the same bitrate. Missing dimensions count as 0, so\n * tracks without resolution metadata (e.g. audio) area-compare equal.\n */\nexport function resolutionArea(track: { width?: number; height?: number }): number {\n return (track.width ?? 0) * (track.height ?? 0);\n}\n\n/**\n * Check if track A has higher resolution than track B.\n * Compares by total pixel count (width × height).\n *\n * @param trackA - First track to compare\n * @param trackB - Second track to compare\n * @returns True if trackA has more pixels than trackB\n */\nfunction hasHigherResolution(\n trackA: PartiallyResolvedVideoTrack | VideoTrack,\n trackB: PartiallyResolvedVideoTrack | VideoTrack\n): boolean {\n return resolutionArea(trackA) > resolutionArea(trackB);\n}\n"],"mappings":";;;;;AAqCA,MAAa,yBAAwC;CACnD,cAAc;CACd,eAAe;AACjB;;;;;;AAwHA,SAAgB,eAAe,OAAoD;CACjF,QAAQ,MAAM,SAAS,MAAM,MAAM,UAAU;AAC/C"}
1
+ {"version":3,"file":"quality-selection.js","names":[],"sources":["../../../../src/media/abr/quality-selection.ts"],"sourcesContent":["/**\n * Quality Selection Algorithm\n *\n * Selects optimal video track based on bandwidth estimate with safety margin. Stateless selection - picks highest\n * quality that fits bandwidth.\n *\n * Key concepts:\n *\n * - **Safety margin** (0.85): Pick track where bandwidth >= track.bandwidth / 0.85\n * - This ensures 15% headroom to avoid buffering\n * - At same bandwidth, prefer higher resolution\n */\n\nimport type { PartiallyResolvedVideoTrack, VideoTrack } from '../types';\n\n/** Quality selection configuration. */\nexport interface QualityConfig {\n /**\n * Safety margin (0-1). To select a track, need: currentBandwidth >= track.bandwidth / safetyMargin. Default 0.85\n * means track must use ≤85% of available bandwidth (15% headroom).\n */\n safetyMargin: number;\n /**\n * Upgrade hysteresis ratio (>= 1). When `currentTrack` is supplied, an upgrade is applied only if `optimal.bandwidth\n *\n * > = currentTrack.bandwidth * upgradeMargin`. Downgrades are always applied. Default 1.15 means optimal must clear the\n * > current bandwidth by at least 15% to trigger an upgrade.\n */\n upgradeMargin: number;\n}\n\n/** Default quality selection configuration. Values match Shaka Player upgrade threshold (0.85 = 15% headroom). */\nexport const DEFAULT_QUALITY_CONFIG: QualityConfig = {\n safetyMargin: 0.85,\n upgradeMargin: 1.15,\n};\n\n/**\n * Selection context for `selectQuality`. The `bandwidth` field carries the current network estimate; `safetyMargin` /\n * `upgradeMargin` override the defaults; `currentTrack` enables upgrade-vs-downgrade hysteresis.\n *\n * Shape matches the unified `selectOptimal` contract of `setupTrackSwitching` (`playback/behaviors/track-switching.ts`)\n * so the function can be passed directly as a variant's `selectOptimal`.\n */\nexport interface SelectQualityCtx<\n T extends { bandwidth: number } = PartiallyResolvedVideoTrack | VideoTrack,\n> extends Partial<QualityConfig> {\n bandwidth: number;\n /**\n * Track currently selected. When supplied, `selectQuality` returns `currentTrack` (no change) for upgrades that don't\n * clear the `upgradeMargin`. When omitted, no hysteresis is applied — the computed optimal is returned regardless.\n */\n currentTrack?: T;\n}\n\n/**\n * Select the track to apply now, given a context that carries current bandwidth, an optional `currentTrack`, and tuning\n * overrides. Returns:\n *\n * - The bandwidth-fitting optimal when no `currentTrack` is supplied.\n * - The optimal when it's a downgrade vs. `currentTrack` (downgrades apply immediately — no hysteresis).\n * - The optimal when it clears `currentTrack.bandwidth * upgradeMargin` (upgrade clears hysteresis).\n * - `currentTrack` itself when an upgrade doesn't clear the margin (stay put — caller checks identity to no-op).\n *\n * \"Optimal\" is the highest-bandwidth track where the available bandwidth meets the safety requirement (`bandwidth >=\n * track.bandwidth / safetyMargin`). Falls back to the lowest-bandwidth track when nothing fits the safety margin\n * (preserves a definitive pick under under-bandwidth conditions).\n *\n * @example\n * const tracks = [low, mid, high];\n * selectQuality(tracks, { bandwidth: 5_000_000, currentTrack: low });\n * // Returns `high` if 5 Mbps clears safety AND high.bandwidth >= low.bandwidth * upgradeMargin.\n * // Returns `low` (no-op signal) otherwise.\n */\nexport function selectQuality(\n tracks: readonly (PartiallyResolvedVideoTrack | VideoTrack)[],\n ctx: SelectQualityCtx\n): PartiallyResolvedVideoTrack | VideoTrack | undefined {\n if (tracks.length === 0) {\n return undefined;\n }\n\n const safetyMargin = ctx.safetyMargin ?? DEFAULT_QUALITY_CONFIG.safetyMargin;\n const upgradeMargin = ctx.upgradeMargin ?? DEFAULT_QUALITY_CONFIG.upgradeMargin;\n const { bandwidth: currentBandwidth, currentTrack } = ctx;\n\n // Sort tracks by bandwidth (lowest first)\n const sortedTracks = tracks.slice().sort((a, b) => a.bandwidth - b.bandwidth);\n\n // Start with no selection\n let chosen: PartiallyResolvedVideoTrack | VideoTrack | undefined;\n\n for (const track of sortedTracks) {\n // Check if we have enough bandwidth for this track with safety margin\n // Required bandwidth = track.bandwidth / safetyMargin\n const requiredBandwidth = track.bandwidth / safetyMargin;\n\n if (currentBandwidth >= requiredBandwidth) {\n // We can support this track - prefer it if better than current choice\n if (\n !chosen ||\n track.bandwidth > chosen.bandwidth ||\n (track.bandwidth === chosen.bandwidth && hasHigherResolution(track, chosen))\n ) {\n chosen = track;\n }\n }\n }\n\n // If no track fits with safety margin, fall back to lowest quality\n const optimal = chosen ?? sortedTracks[0];\n if (!optimal) return undefined;\n\n // Apply upgrade hysteresis. No currentTrack → no hysteresis, return\n // optimal. Downgrade (optimal.bandwidth < current) → always apply.\n // Upgrade → only when `optimal.bandwidth >= current * upgradeMargin`,\n // else stay put (return currentTrack so caller's id-compare no-ops).\n if (!currentTrack) return optimal;\n\n if (optimal.bandwidth < currentTrack.bandwidth) return optimal;\n\n if (optimal.bandwidth >= currentTrack.bandwidth * upgradeMargin) return optimal;\n\n return currentTrack;\n}\n\n/**\n * Select the lowest-bandwidth track from the candidate set.\n *\n * Used as a safety-net fallback by callers that need a definitive pick when a primary selection algorithm declines to\n * choose. Pairs naturally with `selectQuality` — when ABR has no good answer (e.g., all candidates exceed available\n * bandwidth and the algorithm doesn't fall back internally), the lowest bitrate is the safest default.\n *\n * @example\n * const fallback = selectLowestQuality(tracks);\n *\n * @param tracks - Candidate tracks (can be unsorted)\n * @returns The lowest-bandwidth track, or `undefined` if `tracks` is empty\n */\nexport function selectLowestQuality<T extends { bandwidth: number }>(tracks: readonly T[]): T | undefined {\n if (tracks.length === 0) return undefined;\n\n return tracks.reduce((min, t) => (t.bandwidth < min.bandwidth ? t : min));\n}\n\n/**\n * Resolution as a total pixel count (`width × height`), the basis for comparing two tracks at the same bitrate. Missing\n * dimensions count as 0, so tracks without resolution metadata (e.g. audio) area-compare equal.\n */\nexport function resolutionArea(track: { width?: number; height?: number }): number {\n return (track.width ?? 0) * (track.height ?? 0);\n}\n\n/**\n * Check if track A has higher resolution than track B. Compares by total pixel count (width × height).\n *\n * @param trackA - First track to compare\n * @param trackB - Second track to compare\n * @returns True if trackA has more pixels than trackB\n */\nfunction hasHigherResolution(\n trackA: PartiallyResolvedVideoTrack | VideoTrack,\n trackB: PartiallyResolvedVideoTrack | VideoTrack\n): boolean {\n return resolutionArea(trackA) > resolutionArea(trackB);\n}\n"],"mappings":";;AAgCA,MAAa,yBAAwC;CACnD,cAAc;CACd,eAAe;AACjB;;;;;AAkHA,SAAgB,eAAe,OAAoD;CACjF,QAAQ,MAAM,SAAS,MAAM,MAAM,UAAU;AAC/C"}
@@ -1,35 +1,31 @@
1
1
  //#region src/media/buffer/back-buffer.ts
2
- /**
3
- * Default back buffer configuration.
4
- */
2
+ /** Default back buffer configuration. */
5
3
  const DEFAULT_BACK_BUFFER_CONFIG = { keepSegments: 2 };
6
4
  /**
7
5
  * Calculate back buffer flush point.
8
6
  *
9
- * Determines where to flush old segments from the back buffer.
10
- * Keeps a fixed number of segments behind the current playback position.
11
- *
12
- * Algorithm:
13
- * 1. Find segments before currentTime
14
- * 2. Count back N segments (keepSegments)
15
- * 3. Return startTime of segment N+1 back (flush everything before this)
7
+ * Determines where to flush old segments from the back buffer. Keeps a fixed number of segments behind the current
8
+ * playback position.
16
9
  *
17
- * @param segments - Available segments (should be sorted by startTime)
18
- * @param currentTime - Current playback position in seconds
19
- * @param config - Optional back buffer configuration
20
- * @returns Time in seconds to flush up to (flush range: [0, flushEnd))
10
+ * Algorithm: 1. Find segments before currentTime 2. Count back N segments (keepSegments) 3. Return startTime of segment
11
+ * N+1 back (flush everything before this)
21
12
  *
22
13
  * @example
23
- * const segments = [
14
+ * const segments = [
24
15
  * { startTime: 0, duration: 6, ... },
25
16
  * { startTime: 6, duration: 6, ... },
26
17
  * { startTime: 12, duration: 6, ... },
27
18
  * { startTime: 18, duration: 6, ... },
28
- * ];
19
+ * ];
20
+ *
21
+ * // Playing at 18s, keep 2 segments
22
+ * const flushEnd = calculateBackBufferFlushPoint(segments, 18);
23
+ * // Returns 6 (flush [0, 6), keep [6-18))
29
24
  *
30
- * // Playing at 18s, keep 2 segments
31
- * const flushEnd = calculateBackBufferFlushPoint(segments, 18);
32
- * // Returns 6 (flush [0, 6), keep [6-18))
25
+ * @param segments - Available segments (should be sorted by startTime)
26
+ * @param currentTime - Current playback position in seconds
27
+ * @param config - Optional back buffer configuration
28
+ * @returns Time in seconds to flush up to (flush range: [0, flushEnd))
33
29
  */
34
30
  function calculateBackBufferFlushPoint(segments, currentTime, config = DEFAULT_BACK_BUFFER_CONFIG) {
35
31
  if (segments.length === 0) return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"back-buffer.js","names":[],"sources":["../../../../src/media/buffer/back-buffer.ts"],"sourcesContent":["/**\n * Back Buffer Strategy (Simple)\n *\n * Calculates flush points for back buffer management.\n * V1 uses simple \"keep N segments\" strategy.\n */\n\nimport type { Segment } from '../types';\n\n/**\n * Back buffer configuration.\n */\nexport interface BackBufferConfig {\n /**\n * Number of segments to keep behind current playback position.\n * Default: 2 segments.\n */\n keepSegments: number;\n}\n\n/**\n * Default back buffer configuration.\n */\nexport const DEFAULT_BACK_BUFFER_CONFIG: BackBufferConfig = {\n keepSegments: 2,\n};\n\n/**\n * Calculate back buffer flush point.\n *\n * Determines where to flush old segments from the back buffer.\n * Keeps a fixed number of segments behind the current playback position.\n *\n * Algorithm:\n * 1. Find segments before currentTime\n * 2. Count back N segments (keepSegments)\n * 3. Return startTime of segment N+1 back (flush everything before this)\n *\n * @param segments - Available segments (should be sorted by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional back buffer configuration\n * @returns Time in seconds to flush up to (flush range: [0, flushEnd))\n *\n * @example\n * const segments = [\n * { startTime: 0, duration: 6, ... },\n * { startTime: 6, duration: 6, ... },\n * { startTime: 12, duration: 6, ... },\n * { startTime: 18, duration: 6, ... },\n * ];\n *\n * // Playing at 18s, keep 2 segments\n * const flushEnd = calculateBackBufferFlushPoint(segments, 18);\n * // Returns 6 (flush [0, 6), keep [6-18))\n */\nexport function calculateBackBufferFlushPoint(\n segments: Segment[],\n currentTime: number,\n config: BackBufferConfig = DEFAULT_BACK_BUFFER_CONFIG\n): number {\n if (segments.length === 0) {\n return 0;\n }\n\n // Find all segments before current time (not including current segment)\n const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);\n\n // If no segments before current time, nothing to flush\n if (segmentsBefore.length === 0) {\n return 0;\n }\n\n // Calculate how many segments to flush\n // Keep last N segments, flush the rest\n const segmentsToFlush = segmentsBefore.length - config.keepSegments;\n\n // If we don't have enough segments to flush, keep everything\n if (segmentsToFlush <= 0) {\n return 0;\n }\n\n // If we want to flush all segments, return currentTime\n // (flush everything before current playback position)\n if (segmentsToFlush >= segmentsBefore.length) {\n return currentTime;\n }\n\n // Return the startTime of the first segment we want to keep\n // Everything before this will be flushed\n return segmentsBefore[segmentsToFlush]!.startTime;\n}\n"],"mappings":";;;;AAuBA,MAAa,6BAA+C,EAC1D,cAAc,EAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,8BACd,UACA,aACA,SAA2B,4BACnB;CACR,IAAI,SAAS,WAAW,GACtB,OAAO;CAIT,MAAM,iBAAiB,SAAS,QAAQ,QAAQ,IAAI,YAAY,WAAW;CAG3E,IAAI,eAAe,WAAW,GAC5B,OAAO;CAKT,MAAM,kBAAkB,eAAe,SAAS,OAAO;CAGvD,IAAI,mBAAmB,GACrB,OAAO;CAKT,IAAI,mBAAmB,eAAe,QACpC,OAAO;CAKT,OAAO,eAAe,gBAAgB,CAAE;AAC1C"}
1
+ {"version":3,"file":"back-buffer.js","names":[],"sources":["../../../../src/media/buffer/back-buffer.ts"],"sourcesContent":["/**\n * Back Buffer Strategy (Simple)\n *\n * Calculates flush points for back buffer management. V1 uses simple \"keep N segments\" strategy.\n */\n\nimport type { Segment } from '../types';\n\n/** Back buffer configuration. */\nexport interface BackBufferConfig {\n /** Number of segments to keep behind current playback position. Default: 2 segments. */\n keepSegments: number;\n}\n\n/** Default back buffer configuration. */\nexport const DEFAULT_BACK_BUFFER_CONFIG: BackBufferConfig = {\n keepSegments: 2,\n};\n\n/**\n * Calculate back buffer flush point.\n *\n * Determines where to flush old segments from the back buffer. Keeps a fixed number of segments behind the current\n * playback position.\n *\n * Algorithm: 1. Find segments before currentTime 2. Count back N segments (keepSegments) 3. Return startTime of segment\n * N+1 back (flush everything before this)\n *\n * @example\n * const segments = [\n * { startTime: 0, duration: 6, ... },\n * { startTime: 6, duration: 6, ... },\n * { startTime: 12, duration: 6, ... },\n * { startTime: 18, duration: 6, ... },\n * ];\n *\n * // Playing at 18s, keep 2 segments\n * const flushEnd = calculateBackBufferFlushPoint(segments, 18);\n * // Returns 6 (flush [0, 6), keep [6-18))\n *\n * @param segments - Available segments (should be sorted by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional back buffer configuration\n * @returns Time in seconds to flush up to (flush range: [0, flushEnd))\n */\nexport function calculateBackBufferFlushPoint(\n segments: Segment[],\n currentTime: number,\n config: BackBufferConfig = DEFAULT_BACK_BUFFER_CONFIG\n): number {\n if (segments.length === 0) {\n return 0;\n }\n\n // Find all segments before current time (not including current segment)\n const segmentsBefore = segments.filter((seg) => seg.startTime < currentTime);\n // If no segments before current time, nothing to flush\n if (segmentsBefore.length === 0) return 0;\n\n // Calculate how many segments to flush\n // Keep last N segments, flush the rest\n const segmentsToFlush = segmentsBefore.length - config.keepSegments;\n // If we don't have enough segments to flush, keep everything\n if (segmentsToFlush <= 0) return 0;\n\n // If we want to flush all segments, return currentTime\n // (flush everything before current playback position)\n if (segmentsToFlush >= segmentsBefore.length) {\n return currentTime;\n }\n\n // Return the startTime of the first segment we want to keep\n // Everything before this will be flushed\n return segmentsBefore[segmentsToFlush]!.startTime;\n}\n"],"mappings":";;AAeA,MAAa,6BAA+C,EAC1D,cAAc,EAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,8BACd,UACA,aACA,SAA2B,4BACnB;CACR,IAAI,SAAS,WAAW,GACtB,OAAO;CAIT,MAAM,iBAAiB,SAAS,QAAQ,QAAQ,IAAI,YAAY,WAAW;CAE3E,IAAI,eAAe,WAAW,GAAG,OAAO;CAIxC,MAAM,kBAAkB,eAAe,SAAS,OAAO;CAEvD,IAAI,mBAAmB,GAAG,OAAO;CAIjC,IAAI,mBAAmB,eAAe,QACpC,OAAO;CAKT,OAAO,eAAe,gBAAgB,CAAE;AAC1C"}
@@ -3,12 +3,12 @@ import { SEGMENT_TIME_EPSILON } from "../types/index.js";
3
3
  /**
4
4
  * Forward Buffer Strategy (Simple)
5
5
  *
6
- * Determines which segments to load for forward buffer management.
7
- * V1 uses simple fixed-duration strategy (buffer N seconds ahead).
6
+ * Determines which segments to load for forward buffer management. V1 uses simple fixed-duration strategy (buffer N
7
+ * seconds ahead).
8
8
  */
9
9
  /**
10
- * Merge intervals into sorted, disjoint ranges; touching or overlapping ranges
11
- * (gap ≤ `epsilon`) are joined. Empty/inverted ranges are dropped.
10
+ * Merge intervals into sorted, disjoint ranges; touching or overlapping ranges (gap ≤ `epsilon`) are joined.
11
+ * Empty/inverted ranges are dropped.
12
12
  */
13
13
  function mergeTimeRanges(ranges, epsilon = SEGMENT_TIME_EPSILON) {
14
14
  const sorted = ranges.filter((r) => r.end > r.start).sort((a, b) => a.start - b.start);
@@ -24,63 +24,55 @@ function mergeTimeRanges(ranges, epsilon = SEGMENT_TIME_EPSILON) {
24
24
  return merged;
25
25
  }
26
26
  /**
27
- * Whether `[start, end)` is fully covered by the union of `merged` ranges,
28
- * tolerating `epsilon` of overhang at each edge. `merged` must be disjoint and
29
- * sorted (as returned by `mergeTimeRanges`), so full coverage means a single
30
- * merged range contains the interval.
27
+ * Whether `[start, end)` is fully covered by the union of `merged` ranges, tolerating `epsilon` of overhang at each
28
+ * edge. `merged` must be disjoint and sorted (as returned by `mergeTimeRanges`), so full coverage means a single merged
29
+ * range contains the interval.
31
30
  */
32
31
  function isTimeRangeCovered(start, end, merged, epsilon = SEGMENT_TIME_EPSILON) {
33
32
  return merged.some((r) => r.start <= start + epsilon && r.end >= end - epsilon);
34
33
  }
35
- /**
36
- * Default forward buffer configuration.
37
- */
34
+ /** Default forward buffer configuration. */
38
35
  const DEFAULT_FORWARD_BUFFER_CONFIG = { bufferDuration: 30 };
39
36
  /**
40
37
  * Get segments that need to be loaded for forward buffer.
41
38
  *
42
- * Determines which segments to load to maintain target buffer duration.
43
- * Handles discontiguous buffering (gaps after seeks).
39
+ * Determines which segments to load to maintain target buffer duration. Handles discontiguous buffering (gaps after
40
+ * seeks).
44
41
  *
45
- * Algorithm:
46
- * 1. Calculate target time: currentTime + bufferDuration
47
- * 2. Find all segments in range [currentTime, targetTime)
48
- * 3. Filter out segments already buffered at that time position
49
- * 4. Return segments to load (fills gaps + extends to target)
42
+ * Algorithm: 1. Calculate target time: currentTime + bufferDuration 2. Find all segments in range [currentTime,
43
+ * targetTime) 3. Filter out segments already buffered at that time position 4. Return segments to load (fills gaps +
44
+ * extends to target)
45
+ *
46
+ * @example
47
+ * // After seek: buffered [0-12, 18-30], playing at 7s
48
+ * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });
49
+ * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)
50
50
  *
51
51
  * @param segments - All available segments from playlist
52
52
  * @param bufferedSegments - Segments already buffered (ordered by startTime)
53
53
  * @param currentTime - Current playback position in seconds
54
54
  * @param config - Optional forward buffer configuration
55
55
  * @returns Array of segments to load (empty if buffer is sufficient)
56
- *
57
- * @example
58
- * // After seek: buffered [0-12, 18-30], playing at 7s
59
- * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });
60
- * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)
61
56
  */
62
57
  /**
63
58
  * Calculate the start time from which to flush forward buffer content.
64
59
  *
65
- * Content that starts at or beyond `currentTime + bufferDuration` is no
66
- * longer needed for the current playback position and should be removed
67
- * from the SourceBuffer. This prevents unbounded accumulation of scattered
68
- * SourceBuffer content after seeks, which can cause QuotaExceededError on
69
- * long-form content.
60
+ * Content that starts at or beyond `currentTime + bufferDuration` is no longer needed for the current playback position
61
+ * and should be removed from the SourceBuffer. This prevents unbounded accumulation of scattered SourceBuffer content
62
+ * after seeks, which can cause QuotaExceededError on long-form content.
63
+ *
64
+ * Returns `Infinity` when nothing needs flushing (no buffered segments exist beyond the threshold).
70
65
  *
71
- * Returns `Infinity` when nothing needs flushing (no buffered segments
72
- * exist beyond the threshold).
66
+ * @example
67
+ * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30
68
+ * const flushStart = calculateForwardFlushPoint(segments, 0);
69
+ * // Returns 30 — flush [30, Infinity), keep [0, 30)
73
70
  *
74
71
  * @param bufferedSegments - Segments currently tracked in the buffer model
75
72
  * @param currentTime - Current playback position in seconds
76
73
  * @param config - Optional forward buffer configuration
77
74
  * @returns Start time to flush from (flush range: [flushStart, Infinity)),
78
- * or Infinity if no flush is needed
79
- *
80
- * @example
81
- * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30
82
- * const flushStart = calculateForwardFlushPoint(segments, 0);
83
- * // Returns 30 — flush [30, Infinity), keep [0, 30)
75
+ * or Infinity if no flush is needed
84
76
  */
85
77
  function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
86
78
  if (bufferedSegments.length === 0) return Infinity;
@@ -90,12 +82,11 @@ function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFA
90
82
  return Math.min(...beyond.map((seg) => seg.startTime));
91
83
  }
92
84
  /**
93
- * Find the start time of the segment containing `currentTime` (or the last
94
- * segment if `currentTime` is past the end). Returns `undefined` when
95
- * `currentTime` is undefined or when no segment matches.
85
+ * Find the start time of the segment containing `currentTime` (or the last segment if `currentTime` is past the end).
86
+ * Returns `undefined` when `currentTime` is undefined or when no segment matches.
96
87
  *
97
- * Used to detect "meaningful currentTime change" — two times that map to the
98
- * same segment start aren't a load-trigger; crossing a segment boundary is.
88
+ * Used to detect "meaningful currentTime change" — two times that map to the same segment start aren't a load-trigger;
89
+ * crossing a segment boundary is.
99
90
  */
100
91
  function segmentStartForTime(currentTime, segments) {
101
92
  if (currentTime == null) return void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"forward-buffer.js","names":[],"sources":["../../../../src/media/buffer/forward-buffer.ts"],"sourcesContent":["/**\n * Forward Buffer Strategy (Simple)\n *\n * Determines which segments to load for forward buffer management.\n * V1 uses simple fixed-duration strategy (buffer N seconds ahead).\n */\n\nimport { SEGMENT_TIME_EPSILON, type Segment } from '../types';\n\n/** A half-open `[start, end)` interval on the presentation timeline. */\nexport interface TimeRange {\n start: number;\n end: number;\n}\n\n/**\n * Merge intervals into sorted, disjoint ranges; touching or overlapping ranges\n * (gap ≤ `epsilon`) are joined. Empty/inverted ranges are dropped.\n */\nexport function mergeTimeRanges(ranges: readonly TimeRange[], epsilon = SEGMENT_TIME_EPSILON): TimeRange[] {\n const sorted = ranges.filter((r) => r.end > r.start).sort((a, b) => a.start - b.start);\n const merged: TimeRange[] = [];\n for (const r of sorted) {\n const last = merged[merged.length - 1];\n if (last && r.start <= last.end + epsilon) {\n last.end = Math.max(last.end, r.end);\n } else {\n merged.push({ start: r.start, end: r.end });\n }\n }\n return merged;\n}\n\n/**\n * Whether `[start, end)` is fully covered by the union of `merged` ranges,\n * tolerating `epsilon` of overhang at each edge. `merged` must be disjoint and\n * sorted (as returned by `mergeTimeRanges`), so full coverage means a single\n * merged range contains the interval.\n */\nexport function isTimeRangeCovered(\n start: number,\n end: number,\n merged: readonly TimeRange[],\n epsilon = SEGMENT_TIME_EPSILON\n): boolean {\n return merged.some((r) => r.start <= start + epsilon && r.end >= end - epsilon);\n}\n\n/**\n * Forward buffer configuration.\n */\nexport interface ForwardBufferConfig {\n /**\n * Duration in seconds to buffer ahead of current playback position.\n * Default: 30 seconds.\n */\n bufferDuration: number;\n}\n\n/**\n * Default forward buffer configuration.\n */\nexport const DEFAULT_FORWARD_BUFFER_CONFIG: ForwardBufferConfig = {\n bufferDuration: 30,\n};\n\n/**\n * Get segments that need to be loaded for forward buffer.\n *\n * Determines which segments to load to maintain target buffer duration.\n * Handles discontiguous buffering (gaps after seeks).\n *\n * Algorithm:\n * 1. Calculate target time: currentTime + bufferDuration\n * 2. Find all segments in range [currentTime, targetTime)\n * 3. Filter out segments already buffered at that time position\n * 4. Return segments to load (fills gaps + extends to target)\n *\n * @param segments - All available segments from playlist\n * @param bufferedSegments - Segments already buffered (ordered by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Array of segments to load (empty if buffer is sufficient)\n *\n * @example\n * // After seek: buffered [0-12, 18-30], playing at 7s\n * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });\n * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)\n */\n/**\n * Calculate the start time from which to flush forward buffer content.\n *\n * Content that starts at or beyond `currentTime + bufferDuration` is no\n * longer needed for the current playback position and should be removed\n * from the SourceBuffer. This prevents unbounded accumulation of scattered\n * SourceBuffer content after seeks, which can cause QuotaExceededError on\n * long-form content.\n *\n * Returns `Infinity` when nothing needs flushing (no buffered segments\n * exist beyond the threshold).\n *\n * @param bufferedSegments - Segments currently tracked in the buffer model\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Start time to flush from (flush range: [flushStart, Infinity)),\n * or Infinity if no flush is needed\n *\n * @example\n * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30\n * const flushStart = calculateForwardFlushPoint(segments, 0);\n * // Returns 30 — flush [30, Infinity), keep [0, 30)\n */\nexport function calculateForwardFlushPoint(\n bufferedSegments: readonly Segment[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): number {\n if (bufferedSegments.length === 0) return Infinity;\n\n const threshold = currentTime + config.bufferDuration;\n\n // Find segments that start at or beyond the threshold\n const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);\n\n if (beyond.length === 0) return Infinity;\n\n // Flush from the earliest such segment onward\n return Math.min(...beyond.map((seg) => seg.startTime));\n}\n\n/**\n * Find the start time of the segment containing `currentTime` (or the last\n * segment if `currentTime` is past the end). Returns `undefined` when\n * `currentTime` is undefined or when no segment matches.\n *\n * Used to detect \"meaningful currentTime change\" — two times that map to the\n * same segment start aren't a load-trigger; crossing a segment boundary is.\n */\nexport function segmentStartForTime(\n currentTime: number | undefined,\n segments: readonly Pick<Segment, 'startTime' | 'duration'>[] | undefined\n): number | undefined {\n if (currentTime == null) return undefined;\n return segments?.find(\n ({ startTime, duration }, i, all) =>\n currentTime >= startTime && (currentTime < startTime + duration || i === all.length - 1)\n )?.startTime;\n}\n\nexport function getSegmentsToLoad(\n segments: readonly Segment[],\n bufferedSegments: readonly Pick<Segment, 'startTime' | 'duration'>[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): Segment[] {\n if (segments.length === 0) {\n return [];\n }\n\n // Calculate target buffer end time\n const targetTime = currentTime + config.bufferDuration;\n\n // Create set of buffered segment start times for fast lookup\n // V1 simple: if ANY segment is buffered at a given time, don't load for that time\n // V2 (future): would compare by startTime + bitrate/track for quality switching\n const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));\n\n // Find segments to load:\n // - Overlaps buffer window [currentTime, targetTime)\n // - Not already buffered at that time position\n const toLoad = segments.filter((seg, i) => {\n const segmentEnd = seg.startTime + seg.duration;\n const isLast = i === segments.length - 1;\n // Interior segments use strict `>` so a segment the playhead just finished\n // (endTime === currentTime at a boundary) isn't reloaded. The terminal\n // segment has no successor and no reachable position past it — the playhead\n // is clamped to the presentation's range — so it needs no upper bound: it's\n // loadable whenever the forward window reaches it. That resolves the\n // exact-end seek (#1828) and the post-loop re-seek — where\n // `MediaSource.duration`, clamped by `endOfStream()` to the true buffered\n // end, has grown a hair past the model's EXTINF-derived end — with no\n // dependency on any duration value.\n //\n // LIVE: for an un-ended live/DVR presentation `isLast` is the sliding edge\n // segment; loading it eagerly here is benign today (forward-window-bounded,\n // deduped by the buffered check), but the live effort should confirm the\n // edge / end-of-stream interaction when it lands.\n const overlapsPlayhead = isLast || segmentEnd > currentTime;\n const isInRange = seg.startTime < targetTime && overlapsPlayhead;\n\n // Must not have a segment buffered at this time position\n const isNotBuffered = !bufferedStartTimes.has(seg.startTime);\n\n return isInRange && isNotBuffered;\n });\n\n return toLoad;\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,SAAgB,gBAAgB,QAA8B,UAAU,sBAAmC;CACzG,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CACrF,MAAM,SAAsB,CAAC;CAC7B,KAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,OAAO,OAAO,OAAO,SAAS;EACpC,IAAI,QAAQ,EAAE,SAAS,KAAK,MAAM,SAChC,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,EAAE,GAAG;OAEnC,OAAO,KAAK;GAAE,OAAO,EAAE;GAAO,KAAK,EAAE;EAAI,CAAC;CAE9C;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,mBACd,OACA,KACA,QACA,UAAU,sBACD;CACT,OAAO,OAAO,MAAM,MAAM,EAAE,SAAS,QAAQ,WAAW,EAAE,OAAO,MAAM,OAAO;AAChF;;;;AAgBA,MAAa,gCAAqD,EAChE,gBAAgB,GAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,2BACd,kBACA,aACA,SAA8B,+BACtB;CACR,IAAI,iBAAiB,WAAW,GAAG,OAAO;CAE1C,MAAM,YAAY,cAAc,OAAO;CAGvC,MAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,aAAa,SAAS;CAE1E,IAAI,OAAO,WAAW,GAAG,OAAO;CAGhC,OAAO,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,IAAI,SAAS,CAAC;AACvD;;;;;;;;;AAUA,SAAgB,oBACd,aACA,UACoB;CACpB,IAAI,eAAe,MAAM,OAAO,KAAA;CAChC,OAAO,UAAU,MACd,EAAE,WAAW,YAAY,GAAG,QAC3B,eAAe,cAAc,cAAc,YAAY,YAAY,MAAM,IAAI,SAAS,EAC1F,CAAC,EAAE;AACL;AAEA,SAAgB,kBACd,UACA,kBACA,aACA,SAA8B,+BACnB;CACX,IAAI,SAAS,WAAW,GACtB,OAAO,CAAC;CAIV,MAAM,aAAa,cAAc,OAAO;CAKxC,MAAM,qBAAqB,IAAI,IAAI,iBAAiB,KAAK,QAAQ,IAAI,SAAS,CAAC;CA+B/E,OA1Be,SAAS,QAAQ,KAAK,MAAM;EACzC,MAAM,aAAa,IAAI,YAAY,IAAI;EAgBvC,MAAM,mBAfS,MAAM,SAAS,SAAS,KAeJ,aAAa;EAChD,MAAM,YAAY,IAAI,YAAY,cAAc;EAGhD,MAAM,gBAAgB,CAAC,mBAAmB,IAAI,IAAI,SAAS;EAE3D,OAAO,aAAa;CACtB,CAEY;AACd"}
1
+ {"version":3,"file":"forward-buffer.js","names":[],"sources":["../../../../src/media/buffer/forward-buffer.ts"],"sourcesContent":["/**\n * Forward Buffer Strategy (Simple)\n *\n * Determines which segments to load for forward buffer management. V1 uses simple fixed-duration strategy (buffer N\n * seconds ahead).\n */\n\nimport { SEGMENT_TIME_EPSILON, type Segment } from '../types';\n\n/** A half-open `[start, end)` interval on the presentation timeline. */\nexport interface TimeRange {\n start: number;\n end: number;\n}\n\n/**\n * Merge intervals into sorted, disjoint ranges; touching or overlapping ranges (gap ≤ `epsilon`) are joined.\n * Empty/inverted ranges are dropped.\n */\nexport function mergeTimeRanges(ranges: readonly TimeRange[], epsilon = SEGMENT_TIME_EPSILON): TimeRange[] {\n const sorted = ranges.filter((r) => r.end > r.start).sort((a, b) => a.start - b.start);\n const merged: TimeRange[] = [];\n\n for (const r of sorted) {\n const last = merged[merged.length - 1];\n\n if (last && r.start <= last.end + epsilon) {\n last.end = Math.max(last.end, r.end);\n } else {\n merged.push({ start: r.start, end: r.end });\n }\n }\n\n return merged;\n}\n\n/**\n * Whether `[start, end)` is fully covered by the union of `merged` ranges, tolerating `epsilon` of overhang at each\n * edge. `merged` must be disjoint and sorted (as returned by `mergeTimeRanges`), so full coverage means a single merged\n * range contains the interval.\n */\nexport function isTimeRangeCovered(\n start: number,\n end: number,\n merged: readonly TimeRange[],\n epsilon = SEGMENT_TIME_EPSILON\n): boolean {\n return merged.some((r) => r.start <= start + epsilon && r.end >= end - epsilon);\n}\n\n/** Forward buffer configuration. */\nexport interface ForwardBufferConfig {\n /** Duration in seconds to buffer ahead of current playback position. Default: 30 seconds. */\n bufferDuration: number;\n}\n\n/** Default forward buffer configuration. */\nexport const DEFAULT_FORWARD_BUFFER_CONFIG: ForwardBufferConfig = {\n bufferDuration: 30,\n};\n\n/**\n * Get segments that need to be loaded for forward buffer.\n *\n * Determines which segments to load to maintain target buffer duration. Handles discontiguous buffering (gaps after\n * seeks).\n *\n * Algorithm: 1. Calculate target time: currentTime + bufferDuration 2. Find all segments in range [currentTime,\n * targetTime) 3. Filter out segments already buffered at that time position 4. Return segments to load (fills gaps +\n * extends to target)\n *\n * @example\n * // After seek: buffered [0-12, 18-30], playing at 7s\n * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });\n * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)\n *\n * @param segments - All available segments from playlist\n * @param bufferedSegments - Segments already buffered (ordered by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Array of segments to load (empty if buffer is sufficient)\n */\n/**\n * Calculate the start time from which to flush forward buffer content.\n *\n * Content that starts at or beyond `currentTime + bufferDuration` is no longer needed for the current playback position\n * and should be removed from the SourceBuffer. This prevents unbounded accumulation of scattered SourceBuffer content\n * after seeks, which can cause QuotaExceededError on long-form content.\n *\n * Returns `Infinity` when nothing needs flushing (no buffered segments exist beyond the threshold).\n *\n * @example\n * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30\n * const flushStart = calculateForwardFlushPoint(segments, 0);\n * // Returns 30 — flush [30, Infinity), keep [0, 30)\n *\n * @param bufferedSegments - Segments currently tracked in the buffer model\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Start time to flush from (flush range: [flushStart, Infinity)),\n * or Infinity if no flush is needed\n */\nexport function calculateForwardFlushPoint(\n bufferedSegments: readonly Segment[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): number {\n if (bufferedSegments.length === 0) return Infinity;\n\n const threshold = currentTime + config.bufferDuration;\n\n // Find segments that start at or beyond the threshold\n const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);\n if (beyond.length === 0) return Infinity;\n\n // Flush from the earliest such segment onward\n return Math.min(...beyond.map((seg) => seg.startTime));\n}\n\n/**\n * Find the start time of the segment containing `currentTime` (or the last segment if `currentTime` is past the end).\n * Returns `undefined` when `currentTime` is undefined or when no segment matches.\n *\n * Used to detect \"meaningful currentTime change\" — two times that map to the same segment start aren't a load-trigger;\n * crossing a segment boundary is.\n */\nexport function segmentStartForTime(\n currentTime: number | undefined,\n segments: readonly Pick<Segment, 'startTime' | 'duration'>[] | undefined\n): number | undefined {\n if (currentTime == null) return undefined;\n\n return segments?.find(\n ({ startTime, duration }, i, all) =>\n currentTime >= startTime && (currentTime < startTime + duration || i === all.length - 1)\n )?.startTime;\n}\n\nexport function getSegmentsToLoad(\n segments: readonly Segment[],\n bufferedSegments: readonly Pick<Segment, 'startTime' | 'duration'>[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): Segment[] {\n if (segments.length === 0) {\n return [];\n }\n\n // Calculate target buffer end time\n const targetTime = currentTime + config.bufferDuration;\n\n // Create set of buffered segment start times for fast lookup\n // V1 simple: if ANY segment is buffered at a given time, don't load for that time\n // V2 (future): would compare by startTime + bitrate/track for quality switching\n const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));\n\n // Find segments to load:\n // - Overlaps buffer window [currentTime, targetTime)\n // - Not already buffered at that time position\n const toLoad = segments.filter((seg, i) => {\n const segmentEnd = seg.startTime + seg.duration;\n const isLast = i === segments.length - 1;\n // Interior segments use strict `>` so a segment the playhead just finished\n // (endTime === currentTime at a boundary) isn't reloaded. The terminal\n // segment has no successor and no reachable position past it — the playhead\n // is clamped to the presentation's range — so it needs no upper bound: it's\n // loadable whenever the forward window reaches it. That resolves the\n // exact-end seek (#1828) and the post-loop re-seek — where\n // `MediaSource.duration`, clamped by `endOfStream()` to the true buffered\n // end, has grown a hair past the model's EXTINF-derived end — with no\n // dependency on any duration value.\n //\n // LIVE: for an un-ended live/DVR presentation `isLast` is the sliding edge\n // segment; loading it eagerly here is benign today (forward-window-bounded,\n // deduped by the buffered check), but the live effort should confirm the\n // edge / end-of-stream interaction when it lands.\n const overlapsPlayhead = isLast || segmentEnd > currentTime;\n const isInRange = seg.startTime < targetTime && overlapsPlayhead;\n\n // Must not have a segment buffered at this time position\n const isNotBuffered = !bufferedStartTimes.has(seg.startTime);\n\n return isInRange && isNotBuffered;\n });\n\n return toLoad;\n}\n"],"mappings":";;;;;;;;;;;;AAmBA,SAAgB,gBAAgB,QAA8B,UAAU,sBAAmC;CACzG,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CACrF,MAAM,SAAsB,CAAC;CAE7B,KAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,OAAO,OAAO,OAAO,SAAS;EAEpC,IAAI,QAAQ,EAAE,SAAS,KAAK,MAAM,SAChC,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,EAAE,GAAG;OAEnC,OAAO,KAAK;GAAE,OAAO,EAAE;GAAO,KAAK,EAAE;EAAI,CAAC;CAE9C;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,mBACd,OACA,KACA,QACA,UAAU,sBACD;CACT,OAAO,OAAO,MAAM,MAAM,EAAE,SAAS,QAAQ,WAAW,EAAE,OAAO,MAAM,OAAO;AAChF;;AASA,MAAa,gCAAqD,EAChE,gBAAgB,GAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,2BACd,kBACA,aACA,SAA8B,+BACtB;CACR,IAAI,iBAAiB,WAAW,GAAG,OAAO;CAE1C,MAAM,YAAY,cAAc,OAAO;CAGvC,MAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,aAAa,SAAS;CAC1E,IAAI,OAAO,WAAW,GAAG,OAAO;CAGhC,OAAO,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,IAAI,SAAS,CAAC;AACvD;;;;;;;;AASA,SAAgB,oBACd,aACA,UACoB;CACpB,IAAI,eAAe,MAAM,OAAO,KAAA;CAEhC,OAAO,UAAU,MACd,EAAE,WAAW,YAAY,GAAG,QAC3B,eAAe,cAAc,cAAc,YAAY,YAAY,MAAM,IAAI,SAAS,EAC1F,CAAC,EAAE;AACL;AAEA,SAAgB,kBACd,UACA,kBACA,aACA,SAA8B,+BACnB;CACX,IAAI,SAAS,WAAW,GACtB,OAAO,CAAC;CAIV,MAAM,aAAa,cAAc,OAAO;CAKxC,MAAM,qBAAqB,IAAI,IAAI,iBAAiB,KAAK,QAAQ,IAAI,SAAS,CAAC;CA+B/E,OA1Be,SAAS,QAAQ,KAAK,MAAM;EACzC,MAAM,aAAa,IAAI,YAAY,IAAI;EAgBvC,MAAM,mBAfS,MAAM,SAAS,SAAS,KAeJ,aAAa;EAChD,MAAM,YAAY,IAAI,YAAY,cAAc;EAGhD,MAAM,gBAAgB,CAAC,mBAAmB,IAAI,IAAI,SAAS;EAE3D,OAAO,aAAa;CACtB,CAEY;AACd"}
@@ -3,52 +3,41 @@ import { NON_FMP4_CONTAINER_MIMES } from "../hls/parse-media-playlist.js";
3
3
  import { buildMimeCodec, isCodecSupported } from "./mse/mediasource-setup.js";
4
4
  //#region src/media/dom/capabilities.ts
5
5
  /**
6
- * Capability probing — the engine's foundation for asking the browser what it
7
- * can actually decode before committing a rendition to the pipeline.
6
+ * Capability probing — the engine's foundation for asking the browser what it can actually decode before committing a
7
+ * rendition to the pipeline.
8
8
  *
9
- * Today this is the synchronous codec half: `canPlayTrack` answers "can this
10
- * environment play this track?" by building the track's MIME codec string and
11
- * passing it to `MediaSource.isTypeSupported` (via `isCodecSupported`). It's the
12
- * DOM implementation of the DOM-free `CanPlayTrack` predicate the
13
- * track-switching hard-constraint pre-pass consumes — injected through engine
14
- * config so the (DOM-free) behavior never imports a DOM API directly.
9
+ * Today this is the synchronous codec half: `canPlayTrack` answers "can this environment play this track?" by building
10
+ * the track's MIME codec string and passing it to `MediaSource.isTypeSupported` (via `isCodecSupported`). It's the DOM
11
+ * implementation of the DOM-free `CanPlayTrack` predicate the track-switching hard-constraint pre-pass consumes —
12
+ * injected through engine config so the (DOM-free) behavior never imports a DOM API directly.
15
13
  *
16
- * Results are memoized by built MIME string: codec support is a pure function
17
- * of (codec, environment) and never changes after load, so probing is lazy
18
- * (per candidate, at constraint-apply time) but each unique MIME is asked once.
14
+ * Results are memoized by built MIME string: codec support is a pure function of (codec, environment) and never changes
15
+ * after load, so probing is lazy (per candidate, at constraint-apply time) but each unique MIME is asked once.
19
16
  *
20
- * Future cluster-D phases (async `requestMediaKeySystemAccess` key-system
21
- * probing, `SourceBuffer.changeType()` availability) extend this surface; the
22
- * async ones land as a state-slot writer behavior rather than a config
17
+ * Future cluster-D phases (async `requestMediaKeySystemAccess` key-system probing, `SourceBuffer.changeType()`
18
+ * availability) extend this surface; the async ones land as a state-slot writer behavior rather than a config
23
19
  * predicate, since their verdict resolves asynchronously.
24
20
  */
25
21
  const codecSupportCache = /* @__PURE__ */ new Map();
26
22
  /**
27
- * Whether the environment can decode `track`, by codec. Builds the track's
28
- * MIME codec string and checks `MediaSource.isTypeSupported`, memoized by MIME.
29
- * A track without enough to probeno `mimeType`, or no declared `codecs`
30
- * (CODECS is optional per the HLS spec) is unprobeable and passes through as
31
- * playable (`true`) rather than being dropped; the late `createSourceBuffer`
32
- * check stays as the backstop for those.
23
+ * Whether the environment can decode `track`, by codec. Builds the track's MIME codec string and checks
24
+ * `MediaSource.isTypeSupported`, memoized by MIME. A track without enough to probe — no `mimeType`, or no declared
25
+ * `codecs` (CODECS is optional per the HLS spec) is unprobeable and passes through as playable (`true`) rather than
26
+ * being dropped; the late `createSourceBuffer` check stays as the backstop for those.
33
27
  *
34
- * Detected non-fMP4 containers (`video/mp2t`, `audio/aac`) are asserted
35
- * unsupported regardless of the probe, so they're pruned before selection
36
- * (the type makes no pick) instead of failing/stalling deep in the pipeline.
37
- * Two different reasons, neither UA-based:
28
+ * Detected non-fMP4 containers (`video/mp2t`, `audio/aac`) are asserted unsupported regardless of the probe, so they're
29
+ * pruned before selection (the type makes no pick) instead of failing/stalling deep in the pipeline. Two different
30
+ * reasons, neither UA-based:
38
31
  *
39
- * - **MPEG-TS** can't be played at all here: `isTypeSupported('video/mp2t…')` is
40
- * a genuine false positive on Chromium (reports `true` but appends produce no
41
- * buffered range), and this engine has no TS transmux pipeline.
42
- * - **Raw ADTS AAC** is a *temporary* limitation. The browser genuinely
43
- * supports it (Chrome/Safari decode `audio/aac`; Firefox doesn't), so it could
44
- * be made playable but our segment actors / loading behaviors / append
45
- * pipeline assume every rendition has an `EXT-X-MAP` init segment (e.g. an
46
- * `append-init` task with an empty URL, fMP4-shaped append handling). Until
47
- * that init-segment assumption is removed, ADTS would fetch but never buffer
48
- * (a silent stall), so we assert it unplayable for now. FOLLOW-UP: drop the
49
- * init-required assumption in the pipeline and switch this to a bare-MIME
50
- * probe (`buildMimeCodec` would project `audio/aac` with no codecs) so it
51
- * plays where the browser supports it.
32
+ * - **MPEG-TS** can't be played at all here: `isTypeSupported('video/mp2t…')` is a genuine false positive on Chromium
33
+ * (reports `true` but appends produce no buffered range), and this engine has no TS transmux pipeline.
34
+ * - **Raw ADTS AAC** is a _temporary_ limitation. The browser genuinely supports it (Chrome/Safari decode `audio/aac`;
35
+ * Firefox doesn't), so it could be made playable but our segment actors / loading behaviors / append pipeline
36
+ * assume every rendition has an `EXT-X-MAP` init segment (e.g. an `append-init` task with an empty URL, fMP4-shaped
37
+ * append handling). Until that init-segment assumption is removed, ADTS would fetch but never buffer (a silent
38
+ * stall), so we assert it unplayable for now. FOLLOW-UP: drop the init-required assumption in the pipeline and switch
39
+ * this to a bare-MIME probe (`buildMimeCodec` would project `audio/aac` with no codecs) so it plays where the browser
40
+ * supports it.
52
41
  *
53
42
  * Override via the engine's `canPlayTrack` config when those pipelines land.
54
43
  */
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.js","names":[],"sources":["../../../../src/media/dom/capabilities.ts"],"sourcesContent":["/**\n * Capability probing — the engine's foundation for asking the browser what it\n * can actually decode before committing a rendition to the pipeline.\n *\n * Today this is the synchronous codec half: `canPlayTrack` answers \"can this\n * environment play this track?\" by building the track's MIME codec string and\n * passing it to `MediaSource.isTypeSupported` (via `isCodecSupported`). It's the\n * DOM implementation of the DOM-free `CanPlayTrack` predicate the\n * track-switching hard-constraint pre-pass consumes injected through engine\n * config so the (DOM-free) behavior never imports a DOM API directly.\n *\n * Results are memoized by built MIME string: codec support is a pure function\n * of (codec, environment) and never changes after load, so probing is lazy\n * (per candidate, at constraint-apply time) but each unique MIME is asked once.\n *\n * Future cluster-D phases (async `requestMediaKeySystemAccess` key-system\n * probing, `SourceBuffer.changeType()` availability) extend this surface; the\n * async ones land as a state-slot writer behavior rather than a config\n * predicate, since their verdict resolves asynchronously.\n */\n\nimport { NON_FMP4_CONTAINER_MIMES } from '../hls/parse-media-playlist';\nimport { type CanPlayTrack, getMediaPlaylistMetadata } from '../types';\nimport { buildMimeCodec, isCodecSupported } from './mse/mediasource-setup';\n\nconst codecSupportCache = new Map<string, boolean>();\n\n/**\n * Whether the environment can decode `track`, by codec. Builds the track's\n * MIME codec string and checks `MediaSource.isTypeSupported`, memoized by MIME.\n * A track without enough to probe — no `mimeType`, or no declared `codecs`\n * (CODECS is optional per the HLS spec) — is unprobeable and passes through as\n * playable (`true`) rather than being dropped; the late `createSourceBuffer`\n * check stays as the backstop for those.\n *\n * Detected non-fMP4 containers (`video/mp2t`, `audio/aac`) are asserted\n * unsupported regardless of the probe, so they're pruned before selection\n * (the type makes no pick) instead of failing/stalling deep in the pipeline.\n * Two different reasons, neither UA-based:\n *\n * - **MPEG-TS** can't be played at all here: `isTypeSupported('video/mp2t…')` is\n * a genuine false positive on Chromium (reports `true` but appends produce no\n * buffered range), and this engine has no TS transmux pipeline.\n * - **Raw ADTS AAC** is a *temporary* limitation. The browser genuinely\n * supports it (Chrome/Safari decode `audio/aac`; Firefox doesn't), so it could\n * be made playable — but our segment actors / loading behaviors / append\n * pipeline assume every rendition has an `EXT-X-MAP` init segment (e.g. an\n * `append-init` task with an empty URL, fMP4-shaped append handling). Until\n * that init-segment assumption is removed, ADTS would fetch but never buffer\n * (a silent stall), so we assert it unplayable for now. FOLLOW-UP: drop the\n * init-required assumption in the pipeline and switch this to a bare-MIME\n * probe (`buildMimeCodec` would project `audio/aac` with no codecs) so it\n * plays where the browser supports it.\n *\n * Override via the engine's `canPlayTrack` config when those pipelines land.\n */\nexport const canPlayTrack: CanPlayTrack = (track) => {\n if (track.mimeType && NON_FMP4_CONTAINER_MIMES.has(track.mimeType)) return false;\n // Encrypted renditions are unplayable *for now* — this engine has no EME /\n // license pipeline, so appending them would fail to decode with nothing to\n // explain it. Pruning them here means a partially-encrypted source still plays\n // its clear renditions, and a fully-encrypted one empties the candidate set\n // (which `track-switching` reports). Remove this when DRM support lands.\n if (getMediaPlaylistMetadata(track)?.encrypted) return false;\n if (!track.mimeType || !track.codecs?.length) return true;\n const mimeCodec = buildMimeCodec({ mimeType: track.mimeType, codecs: track.codecs });\n const cached = codecSupportCache.get(mimeCodec);\n if (cached !== undefined) return cached;\n const supported = isCodecSupported(mimeCodec);\n codecSupportCache.set(mimeCodec, supported);\n return supported;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,oCAAoB,IAAI,IAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BnD,MAAa,gBAA8B,UAAU;CACnD,IAAI,MAAM,YAAY,yBAAyB,IAAI,MAAM,QAAQ,GAAG,OAAO;CAM3E,IAAI,yBAAyB,KAAK,CAAC,EAAE,WAAW,OAAO;CACvD,IAAI,CAAC,MAAM,YAAY,CAAC,MAAM,QAAQ,QAAQ,OAAO;CACrD,MAAM,YAAY,eAAe;EAAE,UAAU,MAAM;EAAU,QAAQ,MAAM;CAAO,CAAC;CACnF,MAAM,SAAS,kBAAkB,IAAI,SAAS;CAC9C,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,MAAM,YAAY,iBAAiB,SAAS;CAC5C,kBAAkB,IAAI,WAAW,SAAS;CAC1C,OAAO;AACT"}
1
+ {"version":3,"file":"capabilities.js","names":[],"sources":["../../../../src/media/dom/capabilities.ts"],"sourcesContent":["/**\n * Capability probing — the engine's foundation for asking the browser what it can actually decode before committing a\n * rendition to the pipeline.\n *\n * Today this is the synchronous codec half: `canPlayTrack` answers \"can this environment play this track?\" by building\n * the track's MIME codec string and passing it to `MediaSource.isTypeSupported` (via `isCodecSupported`). It's the DOM\n * implementation of the DOM-free `CanPlayTrack` predicate the track-switching hard-constraint pre-pass consumes —\n * injected through engine config so the (DOM-free) behavior never imports a DOM API directly.\n *\n * Results are memoized by built MIME string: codec support is a pure function of (codec, environment) and never changes\n * after load, so probing is lazy (per candidate, at constraint-apply time) but each unique MIME is asked once.\n *\n * Future cluster-D phases (async `requestMediaKeySystemAccess` key-system probing, `SourceBuffer.changeType()`\n * availability) extend this surface; the async ones land as a state-slot writer behavior rather than a config\n * predicate, since their verdict resolves asynchronously.\n */\n\nimport { NON_FMP4_CONTAINER_MIMES } from '../hls/parse-media-playlist';\nimport { type CanPlayTrack, getMediaPlaylistMetadata } from '../types';\nimport { buildMimeCodec, isCodecSupported } from './mse/mediasource-setup';\n\nconst codecSupportCache = new Map<string, boolean>();\n\n/**\n * Whether the environment can decode `track`, by codec. Builds the track's MIME codec string and checks\n * `MediaSource.isTypeSupported`, memoized by MIME. A track without enough to probe — no `mimeType`, or no declared\n * `codecs` (CODECS is optional per the HLS spec) — is unprobeable and passes through as playable (`true`) rather than\n * being dropped; the late `createSourceBuffer` check stays as the backstop for those.\n *\n * Detected non-fMP4 containers (`video/mp2t`, `audio/aac`) are asserted unsupported regardless of the probe, so they're\n * pruned before selection (the type makes no pick) instead of failing/stalling deep in the pipeline. Two different\n * reasons, neither UA-based:\n *\n * - **MPEG-TS** can't be played at all here: `isTypeSupported('video/mp2t…')` is a genuine false positive on Chromium\n * (reports `true` but appends produce no buffered range), and this engine has no TS transmux pipeline.\n * - **Raw ADTS AAC** is a _temporary_ limitation. The browser genuinely supports it (Chrome/Safari decode `audio/aac`;\n * Firefox doesn't), so it could be made playable — but our segment actors / loading behaviors / append pipeline\n * assume every rendition has an `EXT-X-MAP` init segment (e.g. an `append-init` task with an empty URL, fMP4-shaped\n * append handling). Until that init-segment assumption is removed, ADTS would fetch but never buffer (a silent\n * stall), so we assert it unplayable for now. FOLLOW-UP: drop the init-required assumption in the pipeline and switch\n * this to a bare-MIME probe (`buildMimeCodec` would project `audio/aac` with no codecs) so it plays where the browser\n * supports it.\n *\n * Override via the engine's `canPlayTrack` config when those pipelines land.\n */\nexport const canPlayTrack: CanPlayTrack = (track) => {\n if (track.mimeType && NON_FMP4_CONTAINER_MIMES.has(track.mimeType)) return false;\n\n // Encrypted renditions are unplayable *for now* — this engine has no EME /\n // license pipeline, so appending them would fail to decode with nothing to\n // explain it. Pruning them here means a partially-encrypted source still plays\n // its clear renditions, and a fully-encrypted one empties the candidate set\n // (which `track-switching` reports). Remove this when DRM support lands.\n if (getMediaPlaylistMetadata(track)?.encrypted) return false;\n\n if (!track.mimeType || !track.codecs?.length) return true;\n\n const mimeCodec = buildMimeCodec({ mimeType: track.mimeType, codecs: track.codecs });\n const cached = codecSupportCache.get(mimeCodec);\n if (cached !== undefined) return cached;\n\n const supported = isCodecSupported(mimeCodec);\n\n codecSupportCache.set(mimeCodec, supported);\n return supported;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,MAAM,oCAAoB,IAAI,IAAqB;;;;;;;;;;;;;;;;;;;;;;;AAwBnD,MAAa,gBAA8B,UAAU;CACnD,IAAI,MAAM,YAAY,yBAAyB,IAAI,MAAM,QAAQ,GAAG,OAAO;CAO3E,IAAI,yBAAyB,KAAK,CAAC,EAAE,WAAW,OAAO;CAEvD,IAAI,CAAC,MAAM,YAAY,CAAC,MAAM,QAAQ,QAAQ,OAAO;CAErD,MAAM,YAAY,eAAe;EAAE,UAAU,MAAM;EAAU,QAAQ,MAAM;CAAO,CAAC;CACnF,MAAM,SAAS,kBAAkB,IAAI,SAAS;CAC9C,IAAI,WAAW,KAAA,GAAW,OAAO;CAEjC,MAAM,YAAY,iBAAiB,SAAS;CAE5C,kBAAkB,IAAI,WAAW,SAAS;CAC1C,OAAO;AACT"}
@@ -2,12 +2,10 @@
2
2
  /**
3
3
  * Append media data to a SourceBuffer.
4
4
  *
5
- * Accepts either a full ArrayBuffer (single append) or an AsyncIterable of
6
- * Uint8Array chunks (one append per chunk, in order). Waits for `updateend`
7
- * between each call so appends are serialized correctly.
5
+ * Accepts either a full ArrayBuffer (single append) or an AsyncIterable of Uint8Array chunks (one append per chunk, in
6
+ * order). Waits for `updateend` between each call so appends are serialized correctly.
8
7
  *
9
- * Errors from the SourceBuffer (`error` event) or from the iterable are
10
- * propagated as rejections.
8
+ * Errors from the SourceBuffer (`error` event) or from the iterable are propagated as rejections.
11
9
  */
12
10
  async function appendSegment(sourceBuffer, data, signal) {
13
11
  if (data instanceof ArrayBuffer) await appendChunk(sourceBuffer, data);
@@ -1 +1 @@
1
- {"version":3,"file":"append-segment.js","names":[],"sources":["../../../../../src/media/dom/mse/append-segment.ts"],"sourcesContent":["/**\n * Segment appender helper.\n *\n * Appends media data (ArrayBuffer or AsyncIterable<Uint8Array> stream) to a\n * SourceBuffer, waiting for `updateend` between calls so the browser can\n * process each append before the next one arrives.\n */\n\nimport type { SegmentData } from '../../types';\n\n/** Data accepted by appendSegment — the MSE-boundary alias of {@link SegmentData}. */\nexport type AppendData = SegmentData;\n\n/**\n * Append media data to a SourceBuffer.\n *\n * Accepts either a full ArrayBuffer (single append) or an AsyncIterable of\n * Uint8Array chunks (one append per chunk, in order). Waits for `updateend`\n * between each call so appends are serialized correctly.\n *\n * Errors from the SourceBuffer (`error` event) or from the iterable are\n * propagated as rejections.\n */\nexport async function appendSegment(sourceBuffer: SourceBuffer, data: AppendData, signal?: AbortSignal): Promise<void> {\n if (data instanceof ArrayBuffer) {\n await appendChunk(sourceBuffer, data);\n } else {\n try {\n for await (const chunk of data) {\n // Check between chunks so an abort can stop streaming before the next\n // appendBuffer call. The current chunk (if any) has already landed in the\n // SourceBuffer; the partial: true flag in the actor model reflects this.\n if (signal?.aborted) throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n await appendChunk(sourceBuffer, chunk);\n }\n } catch (e) {\n // Reset the MSE segment parser on any abort to discard partial fMP4 box\n // data from the SourceBuffer's internal byte buffer. Two paths reach here:\n //\n // 1. Explicit abort check above (signal.aborted between chunks) — the\n // last appended chunk may have left the parser mid-fragment.\n //\n // 2. The fetch was cancelled (signal fired), causing the underlying\n // ReadableStream to error and the for-await loop to throw directly\n // without ever passing through the signal check above. Same cleanup\n // is needed: partial box data must be cleared before the next append.\n //\n // Without this, the next appendBuffer call sees stale partial data and\n // Chrome throws CHUNK_DEMUXER_ERROR_APPEND_FAILED.\n if (e instanceof DOMException && e.name === 'AbortError' && !sourceBuffer.updating) {\n try {\n sourceBuffer.abort();\n } catch {\n // Thrown if the MediaSource is not \"open\" (e.g. during teardown).\n }\n }\n throw e;\n }\n }\n}\n\nasync function appendChunk(sourceBuffer: SourceBuffer, data: ArrayBuffer | Uint8Array<ArrayBufferLike>): Promise<void> {\n if (sourceBuffer.updating) {\n await new Promise<void>((resolve) => {\n const onUpdateEnd = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n resolve();\n };\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n });\n }\n\n return new Promise<void>((resolve, reject) => {\n const onUpdateEnd = () => {\n cleanup();\n resolve();\n };\n\n const onError = (event: Event) => {\n cleanup();\n reject(new Error(`SourceBuffer append error: ${event.type}`));\n };\n\n const cleanup = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n sourceBuffer.removeEventListener('error', onError);\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n sourceBuffer.addEventListener('error', onError);\n\n try {\n sourceBuffer.appendBuffer(data as ArrayBuffer);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;AAuBA,eAAsB,cAAc,cAA4B,MAAkB,QAAqC;CACrH,IAAI,gBAAgB,aAClB,MAAM,YAAY,cAAc,IAAI;MAEpC,IAAI;EACF,WAAW,MAAM,SAAS,MAAM;GAI9B,IAAI,QAAQ,SAAS,MAAM,OAAO,UAAU,IAAI,aAAa,WAAW,YAAY;GACpF,MAAM,YAAY,cAAc,KAAK;EACvC;CACF,SAAS,GAAG;EAcV,IAAI,aAAa,gBAAgB,EAAE,SAAS,gBAAgB,CAAC,aAAa,UACxE,IAAI;GACF,aAAa,MAAM;EACrB,QAAQ,CAER;EAEF,MAAM;CACR;AAEJ;AAEA,eAAe,YAAY,cAA4B,MAAgE;CACrH,IAAI,aAAa,UACf,MAAM,IAAI,SAAe,YAAY;EACnC,MAAM,oBAAoB;GACxB,aAAa,oBAAoB,aAAa,WAAW;GACzD,QAAQ;EACV;EACA,aAAa,iBAAiB,aAAa,WAAW;CACxD,CAAC;CAGH,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,oBAAoB;GACxB,QAAQ;GACR,QAAQ;EACV;EAEA,MAAM,WAAW,UAAiB;GAChC,QAAQ;GACR,uBAAO,IAAI,MAAM,8BAA8B,MAAM,MAAM,CAAC;EAC9D;EAEA,MAAM,gBAAgB;GACpB,aAAa,oBAAoB,aAAa,WAAW;GACzD,aAAa,oBAAoB,SAAS,OAAO;EACnD;EAEA,aAAa,iBAAiB,aAAa,WAAW;EACtD,aAAa,iBAAiB,SAAS,OAAO;EAE9C,IAAI;GACF,aAAa,aAAa,IAAmB;EAC/C,SAAS,OAAO;GACd,QAAQ;GACR,OAAO,KAAK;EACd;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"append-segment.js","names":[],"sources":["../../../../../src/media/dom/mse/append-segment.ts"],"sourcesContent":["/**\n * Segment appender helper.\n *\n * Appends media data (ArrayBuffer or AsyncIterable<Uint8Array> stream) to a SourceBuffer, waiting for `updateend`\n * between calls so the browser can process each append before the next one arrives.\n */\n\nimport type { SegmentData } from '../../types';\n\n/** Data accepted by appendSegment — the MSE-boundary alias of {@link SegmentData}. */\nexport type AppendData = SegmentData;\n\n/**\n * Append media data to a SourceBuffer.\n *\n * Accepts either a full ArrayBuffer (single append) or an AsyncIterable of Uint8Array chunks (one append per chunk, in\n * order). Waits for `updateend` between each call so appends are serialized correctly.\n *\n * Errors from the SourceBuffer (`error` event) or from the iterable are propagated as rejections.\n */\nexport async function appendSegment(sourceBuffer: SourceBuffer, data: AppendData, signal?: AbortSignal): Promise<void> {\n if (data instanceof ArrayBuffer) {\n await appendChunk(sourceBuffer, data);\n } else {\n try {\n for await (const chunk of data) {\n // Check between chunks so an abort can stop streaming before the next\n // appendBuffer call. The current chunk (if any) has already landed in the\n // SourceBuffer; the partial: true flag in the actor model reflects this.\n if (signal?.aborted) throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n\n await appendChunk(sourceBuffer, chunk);\n }\n } catch (e) {\n // Reset the MSE segment parser on any abort to discard partial fMP4 box\n // data from the SourceBuffer's internal byte buffer. Two paths reach here:\n //\n // 1. Explicit abort check above (signal.aborted between chunks) — the\n // last appended chunk may have left the parser mid-fragment.\n //\n // 2. The fetch was cancelled (signal fired), causing the underlying\n // ReadableStream to error and the for-await loop to throw directly\n // without ever passing through the signal check above. Same cleanup\n // is needed: partial box data must be cleared before the next append.\n //\n // Without this, the next appendBuffer call sees stale partial data and\n // Chrome throws CHUNK_DEMUXER_ERROR_APPEND_FAILED.\n if (e instanceof DOMException && e.name === 'AbortError' && !sourceBuffer.updating) {\n try {\n sourceBuffer.abort();\n } catch {\n // Thrown if the MediaSource is not \"open\" (e.g. during teardown).\n }\n }\n\n throw e;\n }\n }\n}\n\nasync function appendChunk(sourceBuffer: SourceBuffer, data: ArrayBuffer | Uint8Array<ArrayBufferLike>): Promise<void> {\n if (sourceBuffer.updating) {\n await new Promise<void>((resolve) => {\n const onUpdateEnd = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n resolve();\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n });\n }\n\n return new Promise<void>((resolve, reject) => {\n const onUpdateEnd = () => {\n cleanup();\n resolve();\n };\n\n const onError = (event: Event) => {\n cleanup();\n reject(new Error(`SourceBuffer append error: ${event.type}`));\n };\n\n const cleanup = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n sourceBuffer.removeEventListener('error', onError);\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n sourceBuffer.addEventListener('error', onError);\n\n try {\n sourceBuffer.appendBuffer(data as ArrayBuffer);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n"],"mappings":";;;;;;;;;AAoBA,eAAsB,cAAc,cAA4B,MAAkB,QAAqC;CACrH,IAAI,gBAAgB,aAClB,MAAM,YAAY,cAAc,IAAI;MAEpC,IAAI;EACF,WAAW,MAAM,SAAS,MAAM;GAI9B,IAAI,QAAQ,SAAS,MAAM,OAAO,UAAU,IAAI,aAAa,WAAW,YAAY;GAEpF,MAAM,YAAY,cAAc,KAAK;EACvC;CACF,SAAS,GAAG;EAcV,IAAI,aAAa,gBAAgB,EAAE,SAAS,gBAAgB,CAAC,aAAa,UACxE,IAAI;GACF,aAAa,MAAM;EACrB,QAAQ,CAER;EAGF,MAAM;CACR;AAEJ;AAEA,eAAe,YAAY,cAA4B,MAAgE;CACrH,IAAI,aAAa,UACf,MAAM,IAAI,SAAe,YAAY;EACnC,MAAM,oBAAoB;GACxB,aAAa,oBAAoB,aAAa,WAAW;GACzD,QAAQ;EACV;EAEA,aAAa,iBAAiB,aAAa,WAAW;CACxD,CAAC;CAGH,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,oBAAoB;GACxB,QAAQ;GACR,QAAQ;EACV;EAEA,MAAM,WAAW,UAAiB;GAChC,QAAQ;GACR,uBAAO,IAAI,MAAM,8BAA8B,MAAM,MAAM,CAAC;EAC9D;EAEA,MAAM,gBAAgB;GACpB,aAAa,oBAAoB,aAAa,WAAW;GACzD,aAAa,oBAAoB,SAAS,OAAO;EACnD;EAEA,aAAa,iBAAiB,aAAa,WAAW;EACtD,aAAa,iBAAiB,SAAS,OAAO;EAE9C,IAAI;GACF,aAAa,aAAa,IAAmB;EAC/C,SAAS,OAAO;GACd,QAAQ;GACR,OAAO,KAAK;EACd;CACF,CAAC;AACH"}
@@ -7,16 +7,16 @@
7
7
  /**
8
8
  * Remove a time range from a SourceBuffer.
9
9
  *
10
- * Waits for the SourceBuffer to be ready (not updating), then removes
11
- * the specified range. Returns a promise that resolves when removal completes.
10
+ * Waits for the SourceBuffer to be ready (not updating), then removes the specified range. Returns a promise that
11
+ * resolves when removal completes.
12
+ *
13
+ * @example
14
+ * await flushBuffer(videoSourceBuffer, 0, 30);
12
15
  *
13
16
  * @param sourceBuffer - The SourceBuffer to remove data from
14
17
  * @param start - Start of the time range to remove (seconds)
15
18
  * @param end - End of the time range to remove (seconds)
16
19
  * @returns Promise that resolves when removal completes
17
- *
18
- * @example
19
- * await flushBuffer(videoSourceBuffer, 0, 30);
20
20
  */
21
21
  async function flushBuffer(sourceBuffer, start, end) {
22
22
  if (sourceBuffer.updating) await new Promise((resolve) => {
@@ -1 +1 @@
1
- {"version":3,"file":"buffer-flusher.js","names":[],"sources":["../../../../../src/media/dom/mse/buffer-flusher.ts"],"sourcesContent":["/**\n * Buffer flusher helper (P12)\n *\n * Removes a time range from a SourceBuffer to manage memory.\n */\n\n/**\n * Remove a time range from a SourceBuffer.\n *\n * Waits for the SourceBuffer to be ready (not updating), then removes\n * the specified range. Returns a promise that resolves when removal completes.\n *\n * @param sourceBuffer - The SourceBuffer to remove data from\n * @param start - Start of the time range to remove (seconds)\n * @param end - End of the time range to remove (seconds)\n * @returns Promise that resolves when removal completes\n *\n * @example\n * await flushBuffer(videoSourceBuffer, 0, 30);\n */\nexport async function flushBuffer(sourceBuffer: SourceBuffer, start: number, end: number): Promise<void> {\n // Wait for SourceBuffer to be ready (not currently updating)\n if (sourceBuffer.updating) {\n await new Promise<void>((resolve) => {\n const onUpdateEnd = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n resolve();\n };\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n });\n }\n\n return new Promise<void>((resolve, reject) => {\n const onUpdateEnd = () => {\n cleanup();\n resolve();\n };\n\n const onError = (event: Event) => {\n cleanup();\n reject(new Error(`SourceBuffer remove error: ${event.type}`));\n };\n\n const cleanup = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n sourceBuffer.removeEventListener('error', onError);\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n sourceBuffer.addEventListener('error', onError);\n\n try {\n sourceBuffer.remove(start, end);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,eAAsB,YAAY,cAA4B,OAAe,KAA4B;CAEvG,IAAI,aAAa,UACf,MAAM,IAAI,SAAe,YAAY;EACnC,MAAM,oBAAoB;GACxB,aAAa,oBAAoB,aAAa,WAAW;GACzD,QAAQ;EACV;EACA,aAAa,iBAAiB,aAAa,WAAW;CACxD,CAAC;CAGH,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,oBAAoB;GACxB,QAAQ;GACR,QAAQ;EACV;EAEA,MAAM,WAAW,UAAiB;GAChC,QAAQ;GACR,uBAAO,IAAI,MAAM,8BAA8B,MAAM,MAAM,CAAC;EAC9D;EAEA,MAAM,gBAAgB;GACpB,aAAa,oBAAoB,aAAa,WAAW;GACzD,aAAa,oBAAoB,SAAS,OAAO;EACnD;EAEA,aAAa,iBAAiB,aAAa,WAAW;EACtD,aAAa,iBAAiB,SAAS,OAAO;EAE9C,IAAI;GACF,aAAa,OAAO,OAAO,GAAG;EAChC,SAAS,OAAO;GACd,QAAQ;GACR,OAAO,KAAK;EACd;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"buffer-flusher.js","names":[],"sources":["../../../../../src/media/dom/mse/buffer-flusher.ts"],"sourcesContent":["/**\n * Buffer flusher helper (P12)\n *\n * Removes a time range from a SourceBuffer to manage memory.\n */\n\n/**\n * Remove a time range from a SourceBuffer.\n *\n * Waits for the SourceBuffer to be ready (not updating), then removes the specified range. Returns a promise that\n * resolves when removal completes.\n *\n * @example\n * await flushBuffer(videoSourceBuffer, 0, 30);\n *\n * @param sourceBuffer - The SourceBuffer to remove data from\n * @param start - Start of the time range to remove (seconds)\n * @param end - End of the time range to remove (seconds)\n * @returns Promise that resolves when removal completes\n */\nexport async function flushBuffer(sourceBuffer: SourceBuffer, start: number, end: number): Promise<void> {\n // Wait for SourceBuffer to be ready (not currently updating)\n if (sourceBuffer.updating) {\n await new Promise<void>((resolve) => {\n const onUpdateEnd = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n resolve();\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n });\n }\n\n return new Promise<void>((resolve, reject) => {\n const onUpdateEnd = () => {\n cleanup();\n resolve();\n };\n\n const onError = (event: Event) => {\n cleanup();\n reject(new Error(`SourceBuffer remove error: ${event.type}`));\n };\n\n const cleanup = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n sourceBuffer.removeEventListener('error', onError);\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n sourceBuffer.addEventListener('error', onError);\n\n try {\n sourceBuffer.remove(start, end);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,eAAsB,YAAY,cAA4B,OAAe,KAA4B;CAEvG,IAAI,aAAa,UACf,MAAM,IAAI,SAAe,YAAY;EACnC,MAAM,oBAAoB;GACxB,aAAa,oBAAoB,aAAa,WAAW;GACzD,QAAQ;EACV;EAEA,aAAa,iBAAiB,aAAa,WAAW;CACxD,CAAC;CAGH,OAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,oBAAoB;GACxB,QAAQ;GACR,QAAQ;EACV;EAEA,MAAM,WAAW,UAAiB;GAChC,QAAQ;GACR,uBAAO,IAAI,MAAM,8BAA8B,MAAM,MAAM,CAAC;EAC9D;EAEA,MAAM,gBAAgB;GACpB,aAAa,oBAAoB,aAAa,WAAW;GACzD,aAAa,oBAAoB,SAAS,OAAO;EACnD;EAEA,aAAa,iBAAiB,aAAa,WAAW;EACtD,aAAa,iBAAiB,SAAS,OAAO;EAE9C,IAAI;GACF,aAAa,OAAO,OAAO,GAAG;EAChC,SAAS,OAAO;GACd,QAAQ;GACR,OAAO,KAAK;EACd;CACF,CAAC;AACH"}
@@ -1,8 +1,8 @@
1
1
  import { hasPresentationDuration } from "../../types/index.js";
2
2
  //#region src/media/dom/mse/duration.ts
3
3
  /**
4
- * Check if we have the basics to update MediaSource duration:
5
- * a `mediaSource` and a `presentation` with a numeric duration.
4
+ * Check if we have the basics to update MediaSource duration: a `mediaSource` and a `presentation` with a numeric
5
+ * duration.
6
6
  */
7
7
  function canUpdateDuration(presentation, mediaSource) {
8
8
  return !!(mediaSource && presentation && hasPresentationDuration(presentation));
@@ -19,42 +19,35 @@ function getBufferedEnd(buffers, isEndMatch) {
19
19
  const isGreaterThan = (x, y) => x > y;
20
20
  const isLessThan = (x, y) => x < y;
21
21
  /**
22
- * Get the maximum buffered end time across an iterable of SourceBuffers
23
- * (typically `mediaSource.sourceBuffers`). Returns `0` when the collection is
24
- * empty or no buffer has any buffered ranges.
22
+ * Get the maximum buffered end time across an iterable of SourceBuffers (typically `mediaSource.sourceBuffers`).
23
+ * Returns `0` when the collection is empty or no buffer has any buffered ranges.
25
24
  */
26
25
  function getMaxBufferedEnd(buffers) {
27
26
  return getBufferedEnd(buffers, isGreaterThan);
28
27
  }
29
28
  /**
30
- * Get the reachable buffered end across an iterable of SourceBuffers (typically
31
- * `mediaSource.sourceBuffers`): the `min` of each buffer's last buffered-range end
32
- * the furthest point every track can play to (the intersection end). Buffers with
33
- * no buffered ranges are skipped. Returns `0` when the collection is empty or no
34
- * buffer has any buffered ranges.
29
+ * Get the reachable buffered end across an iterable of SourceBuffers (typically `mediaSource.sourceBuffers`): the `min`
30
+ * of each buffer's last buffered-range end — the furthest point every track can play to (the intersection end). Buffers
31
+ * with no buffered ranges are skipped. Returns `0` when the collection is empty or no buffer has any buffered ranges.
35
32
  *
36
- * Counterpart to {@link getMaxBufferedEnd}: `max` bounds the overall presentation
37
- * end (e.g. for setting `duration`), `min` bounds where playback can actually reach
38
- * when tracks end at slightly different times (e.g. skewed A/V near end-of-stream).
33
+ * Counterpart to {@link getMaxBufferedEnd}: `max` bounds the overall presentation end (e.g. for setting `duration`),
34
+ * `min` bounds where playback can actually reach when tracks end at slightly different times (e.g. skewed A/V near
35
+ * end-of-stream).
39
36
  */
40
37
  function getMinBufferedEnd(buffers) {
41
38
  return getBufferedEnd(buffers, isLessThan);
42
39
  }
43
40
  /**
44
- * Check if the preconditions are met to *attempt* a `mediaSource.duration`
45
- * write: a `mediaSource` is in scope and the presentation has a valid
46
- * positive duration (or `Infinity` for live).
41
+ * Check if the preconditions are met to _attempt_ a `mediaSource.duration` write: a `mediaSource` is in scope and the
42
+ * presentation has a valid positive duration (or `Infinity` for live).
47
43
  *
48
- * Does **not** check `mediaSource.readyState` or `mediaSource.duration` —
49
- * those are DOM properties the caller resolves at write time (e.g., by
50
- * `await`ing `waitForMediaSourceOpen` and re-checking `readyState` after,
51
- * and guarding on the existing `mediaSource.duration` for idempotency).
52
- * Keeping these off the signal-driven predicate lets callers use this
53
- * inside reactor state derivation without smuggling non-reactive DOM
54
- * reads into `computed(...)`.
44
+ * Does **not** check `mediaSource.readyState` or `mediaSource.duration` — those are DOM properties the caller resolves
45
+ * at write time (e.g., by `await`ing `waitForMediaSourceOpen` and re-checking `readyState` after, and guarding on the
46
+ * existing `mediaSource.duration` for idempotency). Keeping these off the signal-driven predicate lets callers use this
47
+ * inside reactor state derivation without smuggling non-reactive DOM reads into `computed(...)`.
55
48
  *
56
- * `Infinity` is allowed — per the MSE spec, `mediaSource.duration = +Infinity`
57
- * is how live playback signals an indefinite duration.
49
+ * `Infinity` is allowed — per the MSE spec, `mediaSource.duration = +Infinity` is how live playback signals an
50
+ * indefinite duration.
58
51
  */
59
52
  function shouldUpdateDuration(presentation, mediaSource) {
60
53
  if (!canUpdateDuration(presentation, mediaSource)) return false;
@@ -63,13 +56,12 @@ function shouldUpdateDuration(presentation, mediaSource) {
63
56
  return true;
64
57
  }
65
58
  /**
66
- * Wait for all currently-updating SourceBuffers in `buffers` to finish, or
67
- * until `signal` aborts — whichever fires first.
59
+ * Wait for all currently-updating SourceBuffers in `buffers` to finish, or until `signal` aborts — whichever fires
60
+ * first.
68
61
  *
69
- * The MSE spec forbids setting `MediaSource.duration` while any attached
70
- * SourceBuffer has `updating === true`. This defers until all are idle.
71
- * Listeners are registered with `{ signal }` so an abort tears them down
72
- * up-front rather than leaving them dangling until the next `updateend`.
62
+ * The MSE spec forbids setting `MediaSource.duration` while any attached SourceBuffer has `updating === true`. This
63
+ * defers until all are idle. Listeners are registered with `{ signal }` so an abort tears them down up-front rather
64
+ * than leaving them dangling until the next `updateend`.
73
65
  */
74
66
  function waitForSourceBuffersReady(buffers, signal) {
75
67
  if (signal.aborted) return Promise.resolve();