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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (489) hide show
  1. package/README.md +5 -0
  2. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  3. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  4. package/dist/default/core/composition/create-composition.js +1 -1
  5. package/dist/default/core/composition/create-composition.js.map +1 -1
  6. package/dist/default/core/composition/share-signals.js.map +1 -1
  7. package/dist/default/core/machine.js.map +1 -1
  8. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  9. package/dist/default/core/signals/effect.js.map +1 -1
  10. package/dist/default/core/signals/primitives.js.map +1 -1
  11. package/dist/default/core/signals/when.js +38 -0
  12. package/dist/default/core/signals/when.js.map +1 -0
  13. package/dist/default/core/tasks/delayed-reschedule.js +31 -0
  14. package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
  15. package/dist/default/core/tasks/task.js +133 -3
  16. package/dist/default/core/tasks/task.js.map +1 -1
  17. package/dist/default/hls-audio.js +3 -0
  18. package/dist/default/hls-background-video.js +3 -0
  19. package/dist/default/hls-video.js +4 -0
  20. package/dist/default/hls.js +7 -5
  21. package/dist/default/media/abr/quality-selection.js.map +1 -1
  22. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  23. package/dist/default/media/buffer/forward-buffer.js +37 -3
  24. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  25. package/dist/default/media/dom/capabilities.js +2 -0
  26. package/dist/default/media/dom/capabilities.js.map +1 -1
  27. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  28. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  29. package/dist/default/media/dom/mse/duration.js +27 -10
  30. package/dist/default/media/dom/mse/duration.js.map +1 -1
  31. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  32. package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
  33. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  34. package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
  35. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  36. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  37. package/dist/default/media/errors.js +62 -0
  38. package/dist/default/media/errors.js.map +1 -0
  39. package/dist/default/media/hls/parse-attributes.js +1 -1
  40. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  41. package/dist/default/media/hls/parse-media-playlist.js +175 -19
  42. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  43. package/dist/default/media/hls/parse-multivariant.js +14 -3
  44. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  45. package/dist/default/media/hls/reload-policy.js +71 -0
  46. package/dist/default/media/hls/reload-policy.js.map +1 -0
  47. package/dist/default/media/hls/resolve-url.js.map +1 -1
  48. package/dist/default/media/live-window.js +36 -0
  49. package/dist/default/media/live-window.js.map +1 -0
  50. package/dist/default/media/media-tracks/media-tracks.js +101 -0
  51. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  52. package/dist/default/media/mp4/box.js +58 -0
  53. package/dist/default/media/mp4/box.js.map +1 -0
  54. package/dist/default/media/mp4/timestamp-origin.js +107 -0
  55. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  56. package/dist/default/media/primitives/select-tracks.js +1 -24
  57. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  58. package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
  59. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  60. package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
  61. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  62. package/dist/default/media/types/index.js +15 -1
  63. package/dist/default/media/types/index.js.map +1 -1
  64. package/dist/default/media/utils/cdn.js.map +1 -1
  65. package/dist/default/media/utils/preload.js.map +1 -1
  66. package/dist/default/media/utils/track-selection.js +1 -1
  67. package/dist/default/media/utils/track-selection.js.map +1 -1
  68. package/dist/default/media/utils/tracks.js +17 -3
  69. package/dist/default/media/utils/tracks.js.map +1 -1
  70. package/dist/default/media-tracks.js +2 -0
  71. package/dist/default/mux-audio.js +3 -0
  72. package/dist/default/mux-background-video.js +3 -0
  73. package/dist/default/mux-video.js +3 -0
  74. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  75. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  76. package/dist/default/network/ewma.js +1 -1
  77. package/dist/default/network/ewma.js.map +1 -1
  78. package/dist/default/network/fetch.js.map +1 -1
  79. package/dist/default/playback/actors/dom/segment-loader.js +61 -95
  80. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  81. package/dist/default/playback/actors/dom/source-buffer.js +1 -0
  82. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  83. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  84. package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
  85. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  86. package/dist/default/playback/adapters/hls-audio/adapter.js +186 -0
  87. package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
  88. package/dist/default/playback/adapters/hls-audio/media.js +9 -0
  89. package/dist/default/playback/adapters/hls-audio/media.js.map +1 -0
  90. package/dist/default/playback/adapters/hls-background-video/adapter.js +123 -0
  91. package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
  92. package/dist/default/playback/adapters/hls-background-video/host.js +60 -0
  93. package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
  94. package/dist/default/playback/adapters/hls-background-video/media.js +21 -0
  95. package/dist/default/playback/adapters/hls-background-video/media.js.map +1 -0
  96. package/dist/default/playback/adapters/hls-video/adapter.js +316 -0
  97. package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
  98. package/dist/default/playback/adapters/hls-video/error-surface.js +51 -0
  99. package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
  100. package/dist/default/playback/adapters/hls-video/media-tracks.js +126 -0
  101. package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
  102. package/dist/default/playback/adapters/hls-video/media.js +11 -0
  103. package/dist/default/playback/adapters/hls-video/media.js.map +1 -0
  104. package/dist/default/playback/adapters/mux-audio/media.js +26 -0
  105. package/dist/default/playback/adapters/mux-audio/media.js.map +1 -0
  106. package/dist/default/playback/adapters/mux-video/adapter.js +92 -0
  107. package/dist/default/playback/adapters/mux-video/adapter.js.map +1 -0
  108. package/dist/default/playback/adapters/mux-video/media.js +23 -0
  109. package/dist/default/playback/adapters/mux-video/media.js.map +1 -0
  110. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  111. package/dist/default/playback/behaviors/collect-errors.js +77 -0
  112. package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
  113. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -1
  114. package/dist/default/playback/behaviors/dom/airplay.js +211 -0
  115. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  116. package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
  117. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  118. package/dist/default/playback/behaviors/dom/end-of-stream.js +13 -1
  119. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  120. package/dist/default/playback/behaviors/dom/load-segments.js +5 -1
  121. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  122. package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
  123. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  124. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +132 -0
  125. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  126. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +17 -4
  127. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  128. package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
  129. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  130. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  131. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  132. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
  133. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  134. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  135. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  136. package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
  137. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  138. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  139. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +31 -8
  140. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  141. package/dist/default/playback/behaviors/establish-start-media-time.js +197 -0
  142. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  143. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  144. package/dist/default/playback/behaviors/resolve-track.js +29 -8
  145. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  146. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  147. package/dist/default/playback/behaviors/setup-failover-monitor.js +1 -1
  148. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -1
  149. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  150. package/dist/default/playback/behaviors/track-switching.js +76 -5
  151. package/dist/default/playback/behaviors/track-switching.js.map +1 -1
  152. package/dist/default/playback/engines/hls/engine-audio-only.js +25 -7
  153. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -1
  154. package/dist/{dev/playback/engines/background-video/engine.js → default/playback/engines/hls/engine-background-video.js} +3 -3
  155. package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
  156. package/dist/default/playback/engines/hls/engine.js +36 -6
  157. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  158. package/dist/default/playback/primitives/error-messages.js +39 -0
  159. package/dist/default/playback/primitives/error-messages.js.map +1 -0
  160. package/dist/default/playback/primitives/failover-fetch.js.map +1 -1
  161. package/dist/default/playback/primitives/head-peek.js +44 -0
  162. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  163. package/dist/default/playback/primitives/live-window.js +66 -0
  164. package/dist/default/playback/primitives/live-window.js.map +1 -0
  165. package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
  166. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  167. package/dist/default/playback/primitives/report-track-conditions.js +86 -0
  168. package/dist/default/playback/primitives/report-track-conditions.js.map +1 -0
  169. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  170. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  171. package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
  172. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  173. package/dist/default/playback/primitives/track-types.js.map +1 -1
  174. package/dist/dev/core/actors/actor.d.ts +0 -1
  175. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  176. package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
  177. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  178. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  179. package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
  180. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  181. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  182. package/dist/dev/core/composition/create-composition.d.ts +10 -11
  183. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  184. package/dist/dev/core/composition/create-composition.js +1 -1
  185. package/dist/dev/core/composition/create-composition.js.map +1 -1
  186. package/dist/dev/core/composition/share-signals.d.ts +0 -1
  187. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  188. package/dist/dev/core/composition/share-signals.js.map +1 -1
  189. package/dist/dev/core/machine.d.ts +1 -0
  190. package/dist/dev/core/machine.d.ts.map +1 -1
  191. package/dist/dev/core/machine.js.map +1 -1
  192. package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
  193. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  194. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  195. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  196. package/dist/dev/core/signals/effect.js.map +1 -1
  197. package/dist/dev/core/signals/primitives.d.ts +1 -2
  198. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  199. package/dist/dev/core/signals/primitives.js.map +1 -1
  200. package/dist/dev/core/signals/when.js +38 -0
  201. package/dist/dev/core/signals/when.js.map +1 -0
  202. package/dist/dev/core/tasks/delayed-reschedule.js +31 -0
  203. package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
  204. package/dist/dev/core/tasks/task.d.ts +53 -2
  205. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  206. package/dist/dev/core/tasks/task.js +133 -3
  207. package/dist/dev/core/tasks/task.js.map +1 -1
  208. package/dist/dev/hls-audio.d.ts +3 -0
  209. package/dist/dev/hls-audio.js +3 -0
  210. package/dist/dev/hls-background-video.d.ts +3 -0
  211. package/dist/dev/hls-background-video.js +3 -0
  212. package/dist/dev/hls-video.d.ts +5 -0
  213. package/dist/dev/hls-video.js +4 -0
  214. package/dist/dev/hls.d.ts +8 -5
  215. package/dist/dev/hls.js +7 -5
  216. package/dist/dev/index.d.ts +0 -1
  217. package/dist/dev/index.d.ts.map +1 -1
  218. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  219. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  220. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  221. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  222. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  223. package/dist/dev/media/buffer/forward-buffer.js +37 -3
  224. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  225. package/dist/dev/media/dom/capabilities.js +2 -0
  226. package/dist/dev/media/dom/capabilities.js.map +1 -1
  227. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
  228. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  229. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  230. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
  231. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  232. package/dist/dev/media/dom/mse/duration.js +27 -10
  233. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  234. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  235. package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
  236. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  237. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
  238. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  239. package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
  240. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  241. package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
  242. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  243. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  244. package/dist/dev/media/errors.d.ts +82 -0
  245. package/dist/dev/media/errors.d.ts.map +1 -0
  246. package/dist/dev/media/errors.js +62 -0
  247. package/dist/dev/media/errors.js.map +1 -0
  248. package/dist/dev/media/hls/parse-attributes.js +1 -1
  249. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  250. package/dist/dev/media/hls/parse-media-playlist.js +175 -19
  251. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  252. package/dist/dev/media/hls/parse-multivariant.js +14 -3
  253. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  254. package/dist/dev/media/hls/reload-policy.js +71 -0
  255. package/dist/dev/media/hls/reload-policy.js.map +1 -0
  256. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  257. package/dist/dev/media/live-window.js +36 -0
  258. package/dist/dev/media/live-window.js.map +1 -0
  259. package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
  260. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  261. package/dist/dev/media/media-tracks/media-tracks.js +101 -0
  262. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  263. package/dist/dev/media/mp4/box.js +58 -0
  264. package/dist/dev/media/mp4/box.js.map +1 -0
  265. package/dist/dev/media/mp4/timestamp-origin.js +107 -0
  266. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  267. package/dist/dev/media/primitives/select-tracks.d.ts +0 -1
  268. package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -1
  269. package/dist/dev/media/primitives/select-tracks.js +1 -24
  270. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  271. package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
  272. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  273. package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
  274. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  275. package/dist/dev/media/types/index.d.ts +163 -4
  276. package/dist/dev/media/types/index.d.ts.map +1 -1
  277. package/dist/dev/media/types/index.js +15 -1
  278. package/dist/dev/media/types/index.js.map +1 -1
  279. package/dist/dev/media/utils/cdn.d.ts.map +1 -1
  280. package/dist/dev/media/utils/cdn.js.map +1 -1
  281. package/dist/dev/media/utils/preload.js.map +1 -1
  282. package/dist/dev/media/utils/track-selection.js +1 -1
  283. package/dist/dev/media/utils/track-selection.js.map +1 -1
  284. package/dist/dev/media/utils/tracks.js +17 -3
  285. package/dist/dev/media/utils/tracks.js.map +1 -1
  286. package/dist/dev/media-tracks.d.ts +3 -0
  287. package/dist/dev/media-tracks.js +2 -0
  288. package/dist/dev/mux-audio.d.ts +4 -0
  289. package/dist/dev/mux-audio.js +3 -0
  290. package/dist/dev/mux-background-video.d.ts +3 -0
  291. package/dist/dev/mux-background-video.js +3 -0
  292. package/dist/dev/mux-video.d.ts +4 -0
  293. package/dist/dev/mux-video.js +3 -0
  294. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  295. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  296. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  297. package/dist/dev/network/ewma.js +1 -1
  298. package/dist/dev/network/ewma.js.map +1 -1
  299. package/dist/dev/network/fetch.js.map +1 -1
  300. package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
  301. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  302. package/dist/dev/playback/actors/dom/segment-loader.js +61 -95
  303. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  304. package/dist/dev/playback/actors/dom/source-buffer.d.ts +3 -33
  305. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  306. package/dist/dev/playback/actors/dom/source-buffer.js +1 -0
  307. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  308. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
  309. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  310. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
  311. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  312. package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
  313. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  314. package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
  315. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  316. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +54 -0
  317. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
  318. package/dist/dev/playback/adapters/hls-audio/adapter.js +186 -0
  319. package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
  320. package/dist/dev/playback/adapters/hls-audio/media.d.ts +10 -0
  321. package/dist/dev/playback/adapters/hls-audio/media.d.ts.map +1 -0
  322. package/dist/dev/playback/adapters/hls-audio/media.js +9 -0
  323. package/dist/dev/playback/adapters/hls-audio/media.js.map +1 -0
  324. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +59 -0
  325. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
  326. package/dist/dev/playback/adapters/hls-background-video/adapter.js +123 -0
  327. package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
  328. package/dist/dev/playback/adapters/hls-background-video/host.d.ts +38 -0
  329. package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
  330. package/dist/dev/playback/adapters/hls-background-video/host.js +60 -0
  331. package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
  332. package/dist/dev/playback/adapters/hls-background-video/media.d.ts +20 -0
  333. package/dist/dev/playback/adapters/hls-background-video/media.d.ts.map +1 -0
  334. package/dist/dev/playback/adapters/hls-background-video/media.js +21 -0
  335. package/dist/dev/playback/adapters/hls-background-video/media.js.map +1 -0
  336. package/dist/dev/playback/adapters/hls-video/adapter.d.ts +63 -0
  337. package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
  338. package/dist/dev/playback/adapters/hls-video/adapter.js +316 -0
  339. package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
  340. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +18 -0
  341. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
  342. package/dist/dev/playback/adapters/hls-video/error-surface.js +51 -0
  343. package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
  344. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +22 -0
  345. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
  346. package/dist/dev/playback/adapters/hls-video/media-tracks.js +126 -0
  347. package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
  348. package/dist/dev/playback/adapters/hls-video/media.d.ts +10 -0
  349. package/dist/dev/playback/adapters/hls-video/media.d.ts.map +1 -0
  350. package/dist/dev/playback/adapters/hls-video/media.js +11 -0
  351. package/dist/dev/playback/adapters/hls-video/media.js.map +1 -0
  352. package/dist/dev/playback/adapters/mux-audio/media.d.ts +28 -0
  353. package/dist/dev/playback/adapters/mux-audio/media.d.ts.map +1 -0
  354. package/dist/dev/playback/adapters/mux-audio/media.js +26 -0
  355. package/dist/dev/playback/adapters/mux-audio/media.js.map +1 -0
  356. package/dist/dev/playback/adapters/mux-video/adapter.d.ts +33 -0
  357. package/dist/dev/playback/adapters/mux-video/adapter.d.ts.map +1 -0
  358. package/dist/dev/playback/adapters/mux-video/adapter.js +92 -0
  359. package/dist/dev/playback/adapters/mux-video/adapter.js.map +1 -0
  360. package/dist/dev/playback/adapters/mux-video/media.d.ts +24 -0
  361. package/dist/dev/playback/adapters/mux-video/media.d.ts.map +1 -0
  362. package/dist/dev/playback/adapters/mux-video/media.js +23 -0
  363. package/dist/dev/playback/adapters/mux-video/media.js.map +1 -0
  364. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +3 -2
  365. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  366. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  367. package/dist/dev/playback/behaviors/collect-errors.js +77 -0
  368. package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
  369. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -1
  370. package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
  371. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  372. package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
  373. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  374. package/dist/dev/playback/behaviors/dom/end-of-stream.js +13 -1
  375. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  376. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
  377. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  378. package/dist/dev/playback/behaviors/dom/load-segments.js +5 -1
  379. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  380. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
  381. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  382. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +132 -0
  383. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  384. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +17 -4
  385. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  386. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
  387. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  388. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
  389. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  390. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  391. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  392. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +26 -0
  393. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  394. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  395. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
  396. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  397. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  398. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
  399. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  400. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
  401. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  402. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
  403. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  404. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  405. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +31 -8
  406. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  407. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +36 -0
  408. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  409. package/dist/dev/playback/behaviors/establish-start-media-time.js +197 -0
  410. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  411. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
  412. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  413. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  414. package/dist/dev/playback/behaviors/resolve-track.js +29 -8
  415. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  416. package/dist/dev/playback/behaviors/select-tracks.d.ts +2 -1
  417. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -1
  418. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  419. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +2 -0
  420. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -1
  421. package/dist/dev/playback/behaviors/setup-failover-monitor.js +1 -1
  422. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -1
  423. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  424. package/dist/dev/playback/behaviors/track-switching.js +76 -5
  425. package/dist/dev/playback/behaviors/track-switching.js.map +1 -1
  426. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +63 -17
  427. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -1
  428. package/dist/dev/playback/engines/hls/engine-audio-only.js +25 -7
  429. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -1
  430. package/dist/dev/playback/engines/{background-video/engine.d.ts → hls/engine-background-video.d.ts} +8 -9
  431. package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
  432. package/dist/{default/playback/engines/background-video/engine.js → dev/playback/engines/hls/engine-background-video.js} +3 -3
  433. package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
  434. package/dist/dev/playback/engines/hls/engine.d.ts +90 -16
  435. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  436. package/dist/dev/playback/engines/hls/engine.js +36 -6
  437. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  438. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  439. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  440. package/dist/dev/playback/primitives/error-messages.js +39 -0
  441. package/dist/dev/playback/primitives/error-messages.js.map +1 -0
  442. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -1
  443. package/dist/dev/playback/primitives/head-peek.js +44 -0
  444. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  445. package/dist/dev/playback/primitives/live-window.js +66 -0
  446. package/dist/dev/playback/primitives/live-window.js.map +1 -0
  447. package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
  448. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  449. package/dist/dev/playback/primitives/report-track-conditions.d.ts +11 -0
  450. package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
  451. package/dist/dev/playback/primitives/report-track-conditions.js +86 -0
  452. package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -0
  453. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  454. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  455. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  456. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
  457. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  458. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
  459. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  460. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
  461. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  462. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  463. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  464. package/dist/dev/playback/primitives/track-types.js.map +1 -1
  465. package/package.json +42 -11
  466. package/dist/default/background-video.js +0 -3
  467. package/dist/default/playback/engines/background-video/adapter.js +0 -158
  468. package/dist/default/playback/engines/background-video/adapter.js.map +0 -1
  469. package/dist/default/playback/engines/background-video/engine.js.map +0 -1
  470. package/dist/default/playback/engines/hls/adapter-audio-only.js +0 -119
  471. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +0 -1
  472. package/dist/default/playback/engines/hls/adapter.js +0 -118
  473. package/dist/default/playback/engines/hls/adapter.js.map +0 -1
  474. package/dist/dev/background-video.d.ts +0 -3
  475. package/dist/dev/background-video.js +0 -3
  476. package/dist/dev/playback/engines/background-video/adapter.d.ts +0 -60
  477. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +0 -1
  478. package/dist/dev/playback/engines/background-video/adapter.js +0 -158
  479. package/dist/dev/playback/engines/background-video/adapter.js.map +0 -1
  480. package/dist/dev/playback/engines/background-video/engine.d.ts.map +0 -1
  481. package/dist/dev/playback/engines/background-video/engine.js.map +0 -1
  482. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +0 -45
  483. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +0 -1
  484. package/dist/dev/playback/engines/hls/adapter-audio-only.js +0 -119
  485. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +0 -1
  486. package/dist/dev/playback/engines/hls/adapter.d.ts +0 -44
  487. package/dist/dev/playback/engines/hls/adapter.d.ts.map +0 -1
  488. package/dist/dev/playback/engines/hls/adapter.js +0 -118
  489. package/dist/dev/playback/engines/hls/adapter.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"track-selection.js","names":[],"sources":["../../../../src/media/utils/track-selection.ts"],"sourcesContent":["import type {\n AudioTrack,\n MaybeResolvedPresentation,\n PartiallyResolvedAudioTrack,\n PartiallyResolvedTextTrack,\n PartiallyResolvedVideoTrack,\n TextTrack,\n TrackType,\n VideoTrack,\n} from '../types';\nimport { isResolvedTrack } from '../types';\n\n/**\n * State shape for track selection.\n * Minimal shape containing presentation and selected track IDs.\n */\nexport interface TrackSelectionState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string | undefined;\n selectedAudioTrackId?: string | undefined;\n selectedTextTrackId?: string | undefined;\n}\n\n/**\n * Map track type to selected track ID property key in state.\n */\nexport const SelectedTrackIdKeyByType = {\n video: 'selectedVideoTrackId',\n audio: 'selectedAudioTrackId',\n text: 'selectedTextTrackId',\n} as const;\n\n/**\n * Get selected track from state by type.\n * Returns properly typed track (partially or fully resolved) or undefined.\n * Type parameter T is inferred from the type argument.\n *\n * @example\n * const videoTrack = getSelectedTrack(state, 'video');\n * if (videoTrack && isResolvedTrack(videoTrack)) {\n * // videoTrack is VideoTrack\n * }\n */\nexport function getSelectedTrack<T extends TrackType>(\n state: TrackSelectionState,\n type: T\n): T extends 'video'\n ? PartiallyResolvedVideoTrack | VideoTrack | undefined\n : T extends 'audio'\n ? PartiallyResolvedAudioTrack | AudioTrack | undefined\n : T extends 'text'\n ? PartiallyResolvedTextTrack | TextTrack | undefined\n : never {\n const { presentation } = state;\n\n if (!presentation?.selectionSets) return undefined as any;\n\n // Get track ID based on type\n const trackIdKey = SelectedTrackIdKeyByType[type];\n const trackId = state[trackIdKey];\n return presentation.selectionSets\n .find(({ type: selectionSetType }) => selectionSetType === type)\n ?.switchingSets[0]?.tracks.find(({ id }) => id === trackId) as any;\n}\n\n/**\n * Returns the duration of the first resolved selected track, preferring\n * video over audio. A track is \"resolved\" once its media playlist has been\n * parsed (per {@link isResolvedTrack}). Returns `undefined` if neither\n * selected track is resolved.\n */\nexport function getResolvedSelectedTrackDuration(state: TrackSelectionState): number | undefined {\n if (state.selectedVideoTrackId) {\n const video = getSelectedTrack(state, 'video');\n if (video && isResolvedTrack(video)) return video.duration;\n }\n if (state.selectedAudioTrackId) {\n const audio = getSelectedTrack(state, 'audio');\n if (audio && isResolvedTrack(audio)) return audio.duration;\n }\n return undefined;\n}\n"],"mappings":";;;;;AA0BA,MAAa,2BAA2B;CACtC,OAAO;CACP,OAAO;CACP,MAAM;CACP;;;;;;;;;;;;AAaD,SAAgB,iBACd,OACA,MAOY;CACZ,MAAM,EAAE,iBAAiB;AAEzB,KAAI,CAAC,cAAc,cAAe,QAAO,KAAA;CAIzC,MAAM,UAAU,MADG,yBAAyB;AAE5C,QAAO,aAAa,cACjB,MAAM,EAAE,MAAM,uBAAuB,qBAAqB,KAAK,EAC9D,cAAc,IAAI,OAAO,MAAM,EAAE,SAAS,OAAO,QAAQ;;;;;;;;AAS/D,SAAgB,iCAAiC,OAAgD;AAC/F,KAAI,MAAM,sBAAsB;EAC9B,MAAM,QAAQ,iBAAiB,OAAO,QAAQ;AAC9C,MAAI,SAAS,gBAAgB,MAAM,CAAE,QAAO,MAAM;;AAEpD,KAAI,MAAM,sBAAsB;EAC9B,MAAM,QAAQ,iBAAiB,OAAO,QAAQ;AAC9C,MAAI,SAAS,gBAAgB,MAAM,CAAE,QAAO,MAAM"}
1
+ {"version":3,"file":"track-selection.js","names":[],"sources":["../../../../src/media/utils/track-selection.ts"],"sourcesContent":["import type {\n AudioTrack,\n MaybeResolvedPresentation,\n PartiallyResolvedAudioTrack,\n PartiallyResolvedTextTrack,\n PartiallyResolvedVideoTrack,\n TextTrack,\n TrackType,\n VideoTrack,\n} from '../types';\nimport { isResolvedTrack } from '../types';\n\n/**\n * State shape for track selection.\n * Minimal shape containing presentation and selected track IDs.\n */\nexport interface TrackSelectionState {\n presentation?: MaybeResolvedPresentation;\n selectedVideoTrackId?: string | undefined;\n selectedAudioTrackId?: string | undefined;\n selectedTextTrackId?: string | undefined;\n}\n\n/**\n * Map track type to selected track ID property key in state.\n */\nexport const SelectedTrackIdKeyByType = {\n video: 'selectedVideoTrackId',\n audio: 'selectedAudioTrackId',\n text: 'selectedTextTrackId',\n} as const;\n\n/**\n * Get selected track from state by type.\n * Returns properly typed track (partially or fully resolved) or undefined.\n * Type parameter T is inferred from the type argument.\n *\n * @example\n * const videoTrack = getSelectedTrack(state, 'video');\n * if (videoTrack && isResolvedTrack(videoTrack)) {\n * // videoTrack is VideoTrack\n * }\n */\nexport function getSelectedTrack<T extends TrackType>(\n state: TrackSelectionState,\n type: T\n): T extends 'video'\n ? PartiallyResolvedVideoTrack | VideoTrack | undefined\n : T extends 'audio'\n ? PartiallyResolvedAudioTrack | AudioTrack | undefined\n : T extends 'text'\n ? PartiallyResolvedTextTrack | TextTrack | undefined\n : never {\n const { presentation } = state;\n\n if (!presentation?.selectionSets) return undefined as any;\n\n // Get track ID based on type\n const trackIdKey = SelectedTrackIdKeyByType[type];\n const trackId = state[trackIdKey];\n return presentation.selectionSets\n .find(({ type: selectionSetType }) => selectionSetType === type)\n ?.switchingSets[0]?.tracks.find(({ id }) => id === trackId) as any;\n}\n\n/**\n * Returns the duration of the first resolved selected track, preferring\n * video over audio. A track is \"resolved\" once its media playlist has been\n * parsed (per {@link isResolvedTrack}). Returns `undefined` if neither\n * selected track is resolved.\n */\nexport function getResolvedSelectedTrackDuration(state: TrackSelectionState): number | undefined {\n if (state.selectedVideoTrackId) {\n const video = getSelectedTrack(state, 'video');\n if (video && isResolvedTrack(video)) return video.duration;\n }\n if (state.selectedAudioTrackId) {\n const audio = getSelectedTrack(state, 'audio');\n if (audio && isResolvedTrack(audio)) return audio.duration;\n }\n return undefined;\n}\n"],"mappings":";;;;;AA0BA,MAAa,2BAA2B;CACtC,OAAO;CACP,OAAO;CACP,MAAM;AACR;;;;;;;;;;;;AAaA,SAAgB,iBACd,OACA,MAOY;CACZ,MAAM,EAAE,iBAAiB;CAEzB,IAAI,CAAC,cAAc,eAAe,OAAO,KAAA;CAIzC,MAAM,UAAU,MADG,yBAAyB;CAE5C,OAAO,aAAa,cACjB,MAAM,EAAE,MAAM,uBAAuB,qBAAqB,IAAI,CAAC,EAC9D,cAAc,EAAE,EAAE,OAAO,MAAM,EAAE,SAAS,OAAO,OAAO;AAC9D;;;;;;;AAQA,SAAgB,iCAAiC,OAAgD;CAC/F,IAAI,MAAM,sBAAsB;EAC9B,MAAM,QAAQ,iBAAiB,OAAO,OAAO;EAC7C,IAAI,SAAS,gBAAgB,KAAK,GAAG,OAAO,MAAM;CACpD;CACA,IAAI,MAAM,sBAAsB;EAC9B,MAAM,QAAQ,iBAAiB,OAAO,OAAO;EAC7C,IAAI,SAAS,gBAAgB,KAAK,GAAG,OAAO,MAAM;CACpD;AAEF"}
@@ -53,19 +53,19 @@ function findTrackById(presentation, trackId) {
53
53
  function findResolvedTextTrack(presentation, trackId) {
54
54
  if (!presentation || !trackId) return void 0;
55
55
  const track = findTrack(presentation, "text", trackId);
56
- if (!track || track.type !== "text" || !isResolvedTrack(track)) return void 0;
56
+ if (track?.type !== "text" || !isResolvedTrack(track)) return void 0;
57
57
  return track;
58
58
  }
59
59
  function findResolvedVideoTrack(presentation, trackId) {
60
60
  if (!presentation || !trackId) return void 0;
61
61
  const track = findTrack(presentation, "video", trackId);
62
- if (!track || track.type !== "video" || !isResolvedTrack(track)) return void 0;
62
+ if (track?.type !== "video" || !isResolvedTrack(track)) return void 0;
63
63
  return track;
64
64
  }
65
65
  function findResolvedAudioTrack(presentation, trackId) {
66
66
  if (!presentation || !trackId) return void 0;
67
67
  const track = findTrack(presentation, "audio", trackId);
68
- if (!track || track.type !== "audio" || !isResolvedTrack(track)) return void 0;
68
+ if (track?.type !== "audio" || !isResolvedTrack(track)) return void 0;
69
69
  return track;
70
70
  }
71
71
  /**
@@ -91,6 +91,20 @@ function hasCodecs(track) {
91
91
  * for mixed-container sources (e.g. muxed-TS video + raw-`.aac` audio) and races
92
92
  * concurrent per-type resolution. Same-type writes are disjoint and safe.
93
93
  * Idempotent — tracks already at `mimeType` are left as-is.
94
+ *
95
+ * **Assumes one container per type, which the HLS specs don't guarantee.** Apple's
96
+ * HLS Authoring Specification not only permits a mixed ladder, it produces one:
97
+ * §1.5 requires HEVC in fMP4, while §9.22 says a 192 kbit/s H.264 variant
98
+ * *packaged in a transport stream* SHOULD be provided for cellular — so a
99
+ * conformant HEVC ladder with that fallback is necessarily mixed. RFC 8216 is
100
+ * silent too (§6.2.4 constrains variant *content*, not container).
101
+ *
102
+ * Where that happens, resolving the TS variant first relabels the whole type,
103
+ * capability probing prunes all of it, and a source whose fMP4 variants were fine
104
+ * reports as unplayable. Accepted because the current target is CMAF-compliant
105
+ * single-container delivery (plus Apple's official fMP4 example); revisit by
106
+ * narrowing the relabel to the resolved track, or gating propagation on config,
107
+ * if mixed ladders come into scope.
94
108
  */
95
109
  function applyContainerMimeType(presentation, type, mimeType) {
96
110
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"tracks.js","names":[],"sources":["../../../../src/media/utils/tracks.ts"],"sourcesContent":["import type {\n AudioTrack,\n MaybeResolvedPresentation,\n PartiallyResolvedTrack,\n Presentation,\n ResolvedTrack,\n TextTrack,\n TrackType,\n VideoTrack,\n} from '../types';\nimport { isResolvedTrack } from '../types';\n\n/**\n * Get the tracks of the given type from a presentation's first switching set.\n *\n * Returns `[]` when the presentation is unresolved, when no selection set of\n * `type` exists, or when its first switching set is empty. Returned tracks may\n * be partially resolved (URL only) or fully resolved (with segments) — callers\n * narrow as needed.\n *\n * The \"first switching set\" assumption matches the rest of the codebase\n * (HLS typically has one switching set per type); multi-group / multi-period\n * support would generalize this.\n */\nexport function getTracksByType(\n presentation: MaybeResolvedPresentation,\n type: TrackType\n): readonly (PartiallyResolvedTrack | ResolvedTrack)[] {\n return presentation.selectionSets?.find(({ type: t }) => t === type)?.switchingSets[0]?.tracks ?? [];\n}\n\n/**\n * Find a track of the given type and id within a presentation.\n *\n * Returns the matching track from the first switching set of the matching\n * selection set, or `undefined` if either is missing. The returned track may\n * be partially resolved (URL only) or fully resolved (with segments) — callers\n * narrow as needed.\n */\nexport function findTrack(\n presentation: MaybeResolvedPresentation,\n type: TrackType,\n trackId: string\n): PartiallyResolvedTrack | ResolvedTrack | undefined {\n return getTracksByType(presentation, type).find(({ id }) => id === trackId);\n}\n\n/**\n * Find a track by id across all selection sets in a presentation, without\n * knowing its type up front. Used when the caller has a track id obtained\n * from a downstream consumer (e.g. `SourceBufferActor.initTrackId`) and\n * needs to locate the corresponding track in the presentation.\n *\n * Track ids are unique within a presentation per the HLS spec; the first\n * match wins.\n */\nexport function findTrackById(\n presentation: MaybeResolvedPresentation,\n trackId: string\n): PartiallyResolvedTrack | ResolvedTrack | undefined {\n for (const selectionSet of presentation.selectionSets ?? []) {\n const track = selectionSet.switchingSets[0]?.tracks.find(({ id }) => id === trackId);\n if (track) return track;\n }\n return undefined;\n}\n\n/**\n * Find a text track of the given id within a presentation and narrow it to\n * the fully-resolved `TextTrack` shape (segments populated). Returns\n * `undefined` if no track matches the id, the matching track isn't a text\n * track, or it hasn't been resolved yet.\n *\n * The segments-non-empty check stays at the call site — a resolved track\n * with zero segments is a valid state, distinct from \"ready to load.\"\n */\nexport function findResolvedTextTrack(\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n): TextTrack | undefined {\n if (!presentation || !trackId) return undefined;\n const track = findTrack(presentation, 'text', trackId);\n // `findTrack` returns the wide union; narrow via discriminant before\n // applying `isResolvedTrack`'s text-specific overload.\n if (!track || track.type !== 'text' || !isResolvedTrack(track)) return undefined;\n return track;\n}\n\nexport function findResolvedVideoTrack(\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n): VideoTrack | undefined {\n if (!presentation || !trackId) return undefined;\n const track = findTrack(presentation, 'video', trackId);\n if (!track || track.type !== 'video' || !isResolvedTrack(track)) return undefined;\n return track;\n}\n\nexport function findResolvedAudioTrack(\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n): AudioTrack | undefined {\n if (!presentation || !trackId) return undefined;\n const track = findTrack(presentation, 'audio', trackId);\n if (!track || track.type !== 'audio' || !isResolvedTrack(track)) return undefined;\n return track;\n}\n\n/**\n * Whether a track carries a non-empty `codecs` array. Both partially-\n * resolved and fully-resolved tracks may carry codecs — they come from\n * the multivariant playlist's `EXT-X-STREAM-INF` line, not from the\n * per-type media playlist — so this works at either resolution stage.\n *\n * `TextTrack` doesn't declare a `codecs` field; the `'codecs' in track`\n * check narrows it out for the false branch.\n */\nexport function hasCodecs(track: PartiallyResolvedTrack | ResolvedTrack | undefined): boolean {\n return !!track && 'codecs' in track && !!track.codecs?.length;\n}\n\n/**\n * Set `mimeType` on every track of one `type` (immutably). Used to propagate a\n * detected container across a type's renditions: an ABR ladder is the same\n * content at different bitrates, so one rendition's container holds for all of\n * them — capability probing + SourceBuffer setup then get the right MIME for the\n * whole type from a single resolved media playlist, without fetching the rest.\n *\n * Scoped to one type on purpose: propagating *across* audio/video would be wrong\n * for mixed-container sources (e.g. muxed-TS video + raw-`.aac` audio) and races\n * concurrent per-type resolution. Same-type writes are disjoint and safe.\n * Idempotent — tracks already at `mimeType` are left as-is.\n */\nexport function applyContainerMimeType(presentation: Presentation, type: TrackType, mimeType: string): Presentation {\n return {\n ...presentation,\n selectionSets: presentation.selectionSets.map((selectionSet) =>\n selectionSet.type === type\n ? {\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) =>\n track.mimeType === mimeType ? track : { ...track, mimeType }\n ),\n })),\n }\n : selectionSet\n ),\n } as Presentation;\n}\n\n/**\n * Updates a track within a presentation (immutably). Generic — works for\n * video, audio, or text tracks.\n */\nexport function updateTrackInPresentation<T extends ResolvedTrack>(\n presentation: Presentation,\n resolvedTrack: T\n): Presentation {\n const trackId = resolvedTrack.id;\n return {\n ...presentation,\n selectionSets: presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => (track.id === trackId ? resolvedTrack : track)),\n })),\n })),\n } as Presentation;\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,SAAgB,gBACd,cACA,MACqD;AACrD,QAAO,aAAa,eAAe,MAAM,EAAE,MAAM,QAAQ,MAAM,KAAK,EAAE,cAAc,IAAI,UAAU,EAAE;;;;;;;;;;AAWtG,SAAgB,UACd,cACA,MACA,SACoD;AACpD,QAAO,gBAAgB,cAAc,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,QAAQ;;;;;;;;;;;AAY7E,SAAgB,cACd,cACA,SACoD;AACpD,MAAK,MAAM,gBAAgB,aAAa,iBAAiB,EAAE,EAAE;EAC3D,MAAM,QAAQ,aAAa,cAAc,IAAI,OAAO,MAAM,EAAE,SAAS,OAAO,QAAQ;AACpF,MAAI,MAAO,QAAO;;;;;;;;;;;;AActB,SAAgB,sBACd,cACA,SACuB;AACvB,KAAI,CAAC,gBAAgB,CAAC,QAAS,QAAO,KAAA;CACtC,MAAM,QAAQ,UAAU,cAAc,QAAQ,QAAQ;AAGtD,KAAI,CAAC,SAAS,MAAM,SAAS,UAAU,CAAC,gBAAgB,MAAM,CAAE,QAAO,KAAA;AACvE,QAAO;;AAGT,SAAgB,uBACd,cACA,SACwB;AACxB,KAAI,CAAC,gBAAgB,CAAC,QAAS,QAAO,KAAA;CACtC,MAAM,QAAQ,UAAU,cAAc,SAAS,QAAQ;AACvD,KAAI,CAAC,SAAS,MAAM,SAAS,WAAW,CAAC,gBAAgB,MAAM,CAAE,QAAO,KAAA;AACxE,QAAO;;AAGT,SAAgB,uBACd,cACA,SACwB;AACxB,KAAI,CAAC,gBAAgB,CAAC,QAAS,QAAO,KAAA;CACtC,MAAM,QAAQ,UAAU,cAAc,SAAS,QAAQ;AACvD,KAAI,CAAC,SAAS,MAAM,SAAS,WAAW,CAAC,gBAAgB,MAAM,CAAE,QAAO,KAAA;AACxE,QAAO;;;;;;;;;;;AAYT,SAAgB,UAAU,OAAoE;AAC5F,QAAO,CAAC,CAAC,SAAS,YAAY,SAAS,CAAC,CAAC,MAAM,QAAQ;;;;;;;;;;;;;;AAezD,SAAgB,uBAAuB,cAA4B,MAAiB,UAAgC;AAClH,QAAO;EACL,GAAG;EACH,eAAe,aAAa,cAAc,KAAK,iBAC7C,aAAa,SAAS,OAClB;GACE,GAAG;GACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;IAC/D,GAAG;IACH,QAAQ,aAAa,OAAO,KAAK,UAC/B,MAAM,aAAa,WAAW,QAAQ;KAAE,GAAG;KAAO;KAAU,CAC7D;IACF,EAAE;GACJ,GACD,aACL;EACF;;;;;;AAOH,SAAgB,0BACd,cACA,eACc;CACd,MAAM,UAAU,cAAc;AAC9B,QAAO;EACL,GAAG;EACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;GAC/D,GAAG;GACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;IAC/D,GAAG;IACH,QAAQ,aAAa,OAAO,KAAK,UAAW,MAAM,OAAO,UAAU,gBAAgB,MAAO;IAC3F,EAAE;GACJ,EAAE;EACJ"}
1
+ {"version":3,"file":"tracks.js","names":[],"sources":["../../../../src/media/utils/tracks.ts"],"sourcesContent":["import type {\n AudioTrack,\n MaybeResolvedPresentation,\n PartiallyResolvedTrack,\n Presentation,\n ResolvedTrack,\n TextTrack,\n TrackType,\n VideoTrack,\n} from '../types';\nimport { isResolvedTrack } from '../types';\n\n/**\n * Get the tracks of the given type from a presentation's first switching set.\n *\n * Returns `[]` when the presentation is unresolved, when no selection set of\n * `type` exists, or when its first switching set is empty. Returned tracks may\n * be partially resolved (URL only) or fully resolved (with segments) — callers\n * narrow as needed.\n *\n * The \"first switching set\" assumption matches the rest of the codebase\n * (HLS typically has one switching set per type); multi-group / multi-period\n * support would generalize this.\n */\nexport function getTracksByType(\n presentation: MaybeResolvedPresentation,\n type: TrackType\n): readonly (PartiallyResolvedTrack | ResolvedTrack)[] {\n return presentation.selectionSets?.find(({ type: t }) => t === type)?.switchingSets[0]?.tracks ?? [];\n}\n\n/**\n * Find a track of the given type and id within a presentation.\n *\n * Returns the matching track from the first switching set of the matching\n * selection set, or `undefined` if either is missing. The returned track may\n * be partially resolved (URL only) or fully resolved (with segments) — callers\n * narrow as needed.\n */\nexport function findTrack(\n presentation: MaybeResolvedPresentation,\n type: TrackType,\n trackId: string\n): PartiallyResolvedTrack | ResolvedTrack | undefined {\n return getTracksByType(presentation, type).find(({ id }) => id === trackId);\n}\n\n/**\n * Find a track by id across all selection sets in a presentation, without\n * knowing its type up front. Used when the caller has a track id obtained\n * from a downstream consumer (e.g. `SourceBufferActor.initTrackId`) and\n * needs to locate the corresponding track in the presentation.\n *\n * Track ids are unique within a presentation per the HLS spec; the first\n * match wins.\n */\nexport function findTrackById(\n presentation: MaybeResolvedPresentation,\n trackId: string\n): PartiallyResolvedTrack | ResolvedTrack | undefined {\n for (const selectionSet of presentation.selectionSets ?? []) {\n const track = selectionSet.switchingSets[0]?.tracks.find(({ id }) => id === trackId);\n if (track) return track;\n }\n return undefined;\n}\n\n/**\n * Find a text track of the given id within a presentation and narrow it to\n * the fully-resolved `TextTrack` shape (segments populated). Returns\n * `undefined` if no track matches the id, the matching track isn't a text\n * track, or it hasn't been resolved yet.\n *\n * The segments-non-empty check stays at the call site — a resolved track\n * with zero segments is a valid state, distinct from \"ready to load.\"\n */\nexport function findResolvedTextTrack(\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n): TextTrack | undefined {\n if (!presentation || !trackId) return undefined;\n const track = findTrack(presentation, 'text', trackId);\n // `findTrack` returns the wide union; narrow via discriminant before\n // applying `isResolvedTrack`'s text-specific overload.\n if (track?.type !== 'text' || !isResolvedTrack(track)) return undefined;\n return track;\n}\n\nexport function findResolvedVideoTrack(\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n): VideoTrack | undefined {\n if (!presentation || !trackId) return undefined;\n const track = findTrack(presentation, 'video', trackId);\n if (track?.type !== 'video' || !isResolvedTrack(track)) return undefined;\n return track;\n}\n\nexport function findResolvedAudioTrack(\n presentation: MaybeResolvedPresentation | undefined,\n trackId: string | undefined\n): AudioTrack | undefined {\n if (!presentation || !trackId) return undefined;\n const track = findTrack(presentation, 'audio', trackId);\n if (track?.type !== 'audio' || !isResolvedTrack(track)) return undefined;\n return track;\n}\n\n/**\n * Whether a track carries a non-empty `codecs` array. Both partially-\n * resolved and fully-resolved tracks may carry codecs — they come from\n * the multivariant playlist's `EXT-X-STREAM-INF` line, not from the\n * per-type media playlist — so this works at either resolution stage.\n *\n * `TextTrack` doesn't declare a `codecs` field; the `'codecs' in track`\n * check narrows it out for the false branch.\n */\nexport function hasCodecs(track: PartiallyResolvedTrack | ResolvedTrack | undefined): boolean {\n return !!track && 'codecs' in track && !!track.codecs?.length;\n}\n\n/**\n * Set `mimeType` on every track of one `type` (immutably). Used to propagate a\n * detected container across a type's renditions: an ABR ladder is the same\n * content at different bitrates, so one rendition's container holds for all of\n * them — capability probing + SourceBuffer setup then get the right MIME for the\n * whole type from a single resolved media playlist, without fetching the rest.\n *\n * Scoped to one type on purpose: propagating *across* audio/video would be wrong\n * for mixed-container sources (e.g. muxed-TS video + raw-`.aac` audio) and races\n * concurrent per-type resolution. Same-type writes are disjoint and safe.\n * Idempotent — tracks already at `mimeType` are left as-is.\n *\n * **Assumes one container per type, which the HLS specs don't guarantee.** Apple's\n * HLS Authoring Specification not only permits a mixed ladder, it produces one:\n * §1.5 requires HEVC in fMP4, while §9.22 says a 192 kbit/s H.264 variant\n * *packaged in a transport stream* SHOULD be provided for cellular — so a\n * conformant HEVC ladder with that fallback is necessarily mixed. RFC 8216 is\n * silent too (§6.2.4 constrains variant *content*, not container).\n *\n * Where that happens, resolving the TS variant first relabels the whole type,\n * capability probing prunes all of it, and a source whose fMP4 variants were fine\n * reports as unplayable. Accepted because the current target is CMAF-compliant\n * single-container delivery (plus Apple's official fMP4 example); revisit by\n * narrowing the relabel to the resolved track, or gating propagation on config,\n * if mixed ladders come into scope.\n */\nexport function applyContainerMimeType(presentation: Presentation, type: TrackType, mimeType: string): Presentation {\n return {\n ...presentation,\n selectionSets: presentation.selectionSets.map((selectionSet) =>\n selectionSet.type === type\n ? {\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) =>\n track.mimeType === mimeType ? track : { ...track, mimeType }\n ),\n })),\n }\n : selectionSet\n ),\n } as Presentation;\n}\n\n/**\n * Updates a track within a presentation (immutably). Generic — works for\n * video, audio, or text tracks.\n */\nexport function updateTrackInPresentation<T extends ResolvedTrack>(\n presentation: Presentation,\n resolvedTrack: T\n): Presentation {\n const trackId = resolvedTrack.id;\n return {\n ...presentation,\n selectionSets: presentation.selectionSets.map((selectionSet) => ({\n ...selectionSet,\n switchingSets: selectionSet.switchingSets.map((switchingSet) => ({\n ...switchingSet,\n tracks: switchingSet.tracks.map((track) => (track.id === trackId ? resolvedTrack : track)),\n })),\n })),\n } as Presentation;\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,SAAgB,gBACd,cACA,MACqD;CACrD,OAAO,aAAa,eAAe,MAAM,EAAE,MAAM,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,EAAE,EAAE,UAAU,CAAC;AACrG;;;;;;;;;AAUA,SAAgB,UACd,cACA,MACA,SACoD;CACpD,OAAO,gBAAgB,cAAc,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,OAAO,OAAO;AAC5E;;;;;;;;;;AAWA,SAAgB,cACd,cACA,SACoD;CACpD,KAAK,MAAM,gBAAgB,aAAa,iBAAiB,CAAC,GAAG;EAC3D,MAAM,QAAQ,aAAa,cAAc,EAAE,EAAE,OAAO,MAAM,EAAE,SAAS,OAAO,OAAO;EACnF,IAAI,OAAO,OAAO;CACpB;AAEF;;;;;;;;;;AAWA,SAAgB,sBACd,cACA,SACuB;CACvB,IAAI,CAAC,gBAAgB,CAAC,SAAS,OAAO,KAAA;CACtC,MAAM,QAAQ,UAAU,cAAc,QAAQ,OAAO;CAGrD,IAAI,OAAO,SAAS,UAAU,CAAC,gBAAgB,KAAK,GAAG,OAAO,KAAA;CAC9D,OAAO;AACT;AAEA,SAAgB,uBACd,cACA,SACwB;CACxB,IAAI,CAAC,gBAAgB,CAAC,SAAS,OAAO,KAAA;CACtC,MAAM,QAAQ,UAAU,cAAc,SAAS,OAAO;CACtD,IAAI,OAAO,SAAS,WAAW,CAAC,gBAAgB,KAAK,GAAG,OAAO,KAAA;CAC/D,OAAO;AACT;AAEA,SAAgB,uBACd,cACA,SACwB;CACxB,IAAI,CAAC,gBAAgB,CAAC,SAAS,OAAO,KAAA;CACtC,MAAM,QAAQ,UAAU,cAAc,SAAS,OAAO;CACtD,IAAI,OAAO,SAAS,WAAW,CAAC,gBAAgB,KAAK,GAAG,OAAO,KAAA;CAC/D,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,UAAU,OAAoE;CAC5F,OAAO,CAAC,CAAC,SAAS,YAAY,SAAS,CAAC,CAAC,MAAM,QAAQ;AACzD;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,uBAAuB,cAA4B,MAAiB,UAAgC;CAClH,OAAO;EACL,GAAG;EACH,eAAe,aAAa,cAAc,KAAK,iBAC7C,aAAa,SAAS,OAClB;GACE,GAAG;GACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;IAC/D,GAAG;IACH,QAAQ,aAAa,OAAO,KAAK,UAC/B,MAAM,aAAa,WAAW,QAAQ;KAAE,GAAG;KAAO;IAAS,CAC7D;GACF,EAAE;EACJ,IACA,YACN;CACF;AACF;;;;;AAMA,SAAgB,0BACd,cACA,eACc;CACd,MAAM,UAAU,cAAc;CAC9B,OAAO;EACL,GAAG;EACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;GAC/D,GAAG;GACH,eAAe,aAAa,cAAc,KAAK,kBAAkB;IAC/D,GAAG;IACH,QAAQ,aAAa,OAAO,KAAK,UAAW,MAAM,OAAO,UAAU,gBAAgB,KAAM;GAC3F,EAAE;EACJ,EAAE;CACJ;AACF"}
@@ -0,0 +1,3 @@
1
+ import { AudioTrack, VideoTrack } from "./media/types/index.js";
2
+ import { AudioDedupeKey, VideoDedupeKey, dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection } from "./media/media-tracks/media-tracks.js";
3
+ export { type AudioDedupeKey, type AudioTrack, type VideoDedupeKey, type VideoTrack, dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection };
@@ -0,0 +1,2 @@
1
+ import { dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection } from "./media/media-tracks/media-tracks.js";
2
+ export { dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection };
@@ -0,0 +1,4 @@
1
+ import { MuxMediaAPI, MuxMediaProps, muxMediaDefaultProps } from "./playback/adapters/mux-video/adapter.js";
2
+ import { MuxAudioMedia } from "./playback/adapters/mux-audio/media.js";
3
+ import { MuxContentData, MuxSourceBase } from "@videojs/media/dom/mux/source";
4
+ export { MuxAudioMedia, type MuxContentData, type MuxMediaAPI, type MuxMediaProps, type MuxSourceBase, muxMediaDefaultProps };
@@ -0,0 +1,3 @@
1
+ import { muxMediaDefaultProps } from "./playback/adapters/mux-video/adapter.js";
2
+ import { MuxAudioMedia } from "./playback/adapters/mux-audio/media.js";
3
+ export { MuxAudioMedia, muxMediaDefaultProps };
@@ -0,0 +1,3 @@
1
+ import { HlsBackgroundVideoMediaAPI, HlsBackgroundVideoMediaElement, HlsBackgroundVideoMediaMixin, HlsBackgroundVideoMediaProps, hlsBackgroundVideoMediaDefaultProps } from "./playback/adapters/hls-background-video/adapter.js";
2
+ import { HlsBackgroundVideoMedia } from "./playback/adapters/hls-background-video/media.js";
3
+ export { HlsBackgroundVideoMedia as MuxBackgroundVideoMedia, type HlsBackgroundVideoMediaAPI as MuxBackgroundVideoMediaAPI, HlsBackgroundVideoMediaElement as MuxBackgroundVideoMediaElement, HlsBackgroundVideoMediaMixin as MuxBackgroundVideoMediaMixin, type HlsBackgroundVideoMediaProps as MuxBackgroundVideoMediaProps, hlsBackgroundVideoMediaDefaultProps as muxBackgroundVideoMediaDefaultProps };
@@ -0,0 +1,3 @@
1
+ import { HlsBackgroundVideoMediaElement, HlsBackgroundVideoMediaMixin, hlsBackgroundVideoMediaDefaultProps } from "./playback/adapters/hls-background-video/adapter.js";
2
+ import { HlsBackgroundVideoMedia } from "./playback/adapters/hls-background-video/media.js";
3
+ export { HlsBackgroundVideoMedia as MuxBackgroundVideoMedia, HlsBackgroundVideoMediaElement as MuxBackgroundVideoMediaElement, HlsBackgroundVideoMediaMixin as MuxBackgroundVideoMediaMixin, hlsBackgroundVideoMediaDefaultProps as muxBackgroundVideoMediaDefaultProps };
@@ -0,0 +1,4 @@
1
+ import { MuxMediaAPI, MuxMediaMixin, MuxMediaProps, muxMediaDefaultProps } from "./playback/adapters/mux-video/adapter.js";
2
+ import { MuxVideoMedia } from "./playback/adapters/mux-video/media.js";
3
+ import { MuxContentData, MuxSourceBase } from "@videojs/media/dom/mux/source";
4
+ export { type MuxContentData, type MuxMediaAPI, MuxMediaMixin, type MuxMediaProps, type MuxSourceBase, MuxVideoMedia, muxMediaDefaultProps };
@@ -0,0 +1,3 @@
1
+ import { MuxMediaMixin, muxMediaDefaultProps } from "./playback/adapters/mux-video/adapter.js";
2
+ import { MuxVideoMedia } from "./playback/adapters/mux-video/media.js";
3
+ export { MuxMediaMixin, MuxVideoMedia, muxMediaDefaultProps };
@@ -1 +1 @@
1
- {"version":3,"file":"bandwidth-estimator.d.ts","names":[],"sources":["../../../src/network/bandwidth-estimator.ts"],"mappings":";;AAsBA;;;;;;;;;;;AAgBA;;;;;;;UAhBiB,cAAA;EA0Bf;EAxBA,YAAA;EAwBW;EAtBX,eAAA;;EAEA,YAAA;;EAEA,eAAA;;EAEA,YAAA;AAAA;;;;UAMe,eAAA;;EAEf,YAAA;;EAEA,YAAA;;EAEA,aAAA;;EAEA,QAAA;;EAEA,WAAA;AAAA"}
1
+ {"version":3,"file":"bandwidth-estimator.d.ts","names":[],"sources":["../../../src/network/bandwidth-estimator.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;UAsBiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;UAMe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA"}
@@ -1 +1 @@
1
- {"version":3,"file":"bandwidth-estimator.js","names":[],"sources":["../../../src/network/bandwidth-estimator.ts"],"sourcesContent":["/**\n * Dual EWMA Bandwidth Estimator\n *\n * Estimates available bandwidth using two EWMA calculations with different\n * half-lives, taking the minimum of both. This approach (from Shaka Player):\n *\n * - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops\n * - **Slow EWMA** (5s half-life): Provides stability during fluctuations\n * - **min(fast, slow)**: Adapts down quickly, up slowly\n *\n * This naturally provides asymmetric behavior needed for good QoE:\n * avoiding stalls (quick downgrade) while preventing oscillation (slow upgrade).\n */\n\nimport { applyZeroFactor, calculateEwma } from './ewma';\n\n/**\n * Bandwidth estimator state.\n *\n * This state structure will be managed by O1 (State Container).\n * Functions in this module operate on this state immutably.\n */\nexport interface BandwidthState {\n /** Fast-moving EWMA estimate (raw, uncorrected). */\n fastEstimate: number;\n /** Total weight accumulated in fast EWMA. */\n fastTotalWeight: number;\n /** Slow-moving EWMA estimate (raw, uncorrected). */\n slowEstimate: number;\n /** Total weight accumulated in slow EWMA. */\n slowTotalWeight: number;\n /** Total bytes sampled across all valid samples. */\n bytesSampled: number;\n}\n\n/**\n * Configuration for bandwidth estimation.\n */\nexport interface BandwidthConfig {\n /** Half-life for fast EWMA in seconds. */\n fastHalfLife: number;\n /** Half-life for slow EWMA in seconds. */\n slowHalfLife: number;\n /** Minimum total bytes before trusting the estimate. */\n minTotalBytes: number;\n /** Minimum bytes per sample to count (filters TTFB-dominated samples). */\n minBytes: number;\n /** Minimum sample duration in ms (filters cached responses). */\n minDuration: number;\n}\n\n/**\n * Default bandwidth estimator configuration.\n *\n * Values match Shaka Player defaults based on experimentation.\n */\nexport const DEFAULT_BANDWIDTH_CONFIG: BandwidthConfig = {\n fastHalfLife: 2, // 2 seconds\n slowHalfLife: 5, // 5 seconds\n minTotalBytes: 128_000, // 128 KB\n minBytes: 16_000, // 16 KB\n minDuration: 5, // 5 ms\n};\n\n/**\n * Add a bandwidth sample from a segment download.\n *\n * Samples are filtered based on:\n * - Minimum bytes (filters TTFB-dominated small segments)\n * - Minimum duration (filters cached responses)\n *\n * Valid samples update both fast and slow EWMA estimates.\n *\n * @param state - Current estimator state\n * @param durationMs - Download duration in milliseconds\n * @param numBytes - Number of bytes downloaded\n * @param config - Optional estimator configuration (uses defaults if not provided)\n * @returns New estimator state with sample incorporated (or unchanged if filtered)\n *\n * @example\n * let state = { fastEstimate: 0, fastTotalWeight: 0, ... };\n * // Sample: 1MB in 1 second\n * state = sampleBandwidth(state, 1000, 1_000_000);\n */\nexport function sampleBandwidth(\n state: BandwidthState,\n durationMs: number,\n numBytes: number,\n config: BandwidthConfig = DEFAULT_BANDWIDTH_CONFIG\n): BandwidthState {\n // Always track bytes for startup phase calculation\n const updatedBytesSampled = state.bytesSampled + numBytes;\n\n // Filter: Ignore samples below minimum bytes (TTFB-dominated)\n // Small segments' download time is mostly connection setup latency,\n // which would artificially lower our bandwidth estimate\n if (numBytes < config.minBytes) {\n return { ...state, bytesSampled: updatedBytesSampled };\n }\n\n // Filter: Ignore samples faster than minimum duration (cached responses)\n // Cached responses load nearly instantly and would artificially inflate\n // our bandwidth estimate\n if (durationMs < config.minDuration) {\n return { ...state, bytesSampled: updatedBytesSampled };\n }\n\n // Calculate bandwidth in bits per second\n // Formula: (bytes * 8 bits/byte * 1000 ms/s) / ms = bits/s\n const bandwidth = (8000 * numBytes) / durationMs;\n\n // Weight by duration in seconds\n // Longer downloads are more reliable indicators of true bandwidth\n const weight = durationMs / 1000;\n\n return {\n fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),\n fastTotalWeight: state.fastTotalWeight + weight,\n slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),\n slowTotalWeight: state.slowTotalWeight + weight,\n bytesSampled: updatedBytesSampled,\n };\n}\n\n/**\n * Get the current bandwidth estimate.\n *\n * Returns the **minimum** of the fast and slow EWMA estimates.\n * This provides the key asymmetric behavior:\n * - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)\n * - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)\n *\n * Uses default estimate until enough data has been sampled — and when no\n * estimator state exists at all (`state === undefined`).\n *\n * @param state - Current estimator state, or `undefined` before any samples have been collected\n * @param defaultEstimate - Fallback estimate before sufficient samples (bps)\n * @param config - Optional estimator configuration (uses defaults if not provided)\n * @returns Bandwidth estimate in bits per second\n *\n * @example\n * const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default\n */\nexport function getBandwidthEstimate(\n state: BandwidthState | undefined,\n defaultEstimate: number,\n config: BandwidthConfig = DEFAULT_BANDWIDTH_CONFIG\n): number {\n // Use default until we have enough samples to trust our estimate\n if (!state || state.bytesSampled < config.minTotalBytes) {\n return defaultEstimate;\n }\n\n // Apply zero-factor correction to both estimates\n const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);\n\n const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);\n\n // Take the minimum - this is the key insight from Shaka Player\n // It naturally provides \"down quickly, up slowly\" behavior\n return Math.min(fastEstimate, slowEstimate);\n}\n\n/**\n * Check if the estimator has enough data to provide a reliable estimate.\n *\n * Requires both:\n * - Enough total bytes sampled (minTotalBytes threshold)\n * - At least one valid EWMA sample (totalWeight > 0)\n *\n * @param state - Current estimator state\n * @param config - Optional estimator configuration (uses defaults if not provided)\n * @returns True if we've sampled enough bytes to trust the estimate\n *\n * @example\n * if (hasGoodEstimate(state)) {\n * const estimate = getBandwidthEstimate(state, 5_000_000);\n * }\n */\nexport function hasGoodEstimate(state: BandwidthState, config: BandwidthConfig = DEFAULT_BANDWIDTH_CONFIG): boolean {\n // Need enough total bytes AND at least one valid EWMA sample\n return state.bytesSampled >= config.minTotalBytes && state.fastTotalWeight > 0 && state.slowTotalWeight > 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,2BAA4C;CACvD,cAAc;CACd,cAAc;CACd,eAAe;CACf,UAAU;CACV,aAAa;CACd;;;;;;;;;;;;;;;;;;;;;AAsBD,SAAgB,gBACd,OACA,YACA,UACA,SAA0B,0BACV;CAEhB,MAAM,sBAAsB,MAAM,eAAe;AAKjD,KAAI,WAAW,OAAO,SACpB,QAAO;EAAE,GAAG;EAAO,cAAc;EAAqB;AAMxD,KAAI,aAAa,OAAO,YACtB,QAAO;EAAE,GAAG;EAAO,cAAc;EAAqB;CAKxD,MAAM,YAAa,MAAO,WAAY;CAItC,MAAM,SAAS,aAAa;AAE5B,QAAO;EACL,cAAc,cAAc,MAAM,cAAc,WAAW,QAAQ,OAAO,aAAa;EACvF,iBAAiB,MAAM,kBAAkB;EACzC,cAAc,cAAc,MAAM,cAAc,WAAW,QAAQ,OAAO,aAAa;EACvF,iBAAiB,MAAM,kBAAkB;EACzC,cAAc;EACf;;;;;;;;;;;;;;;;;;;;;AAsBH,SAAgB,qBACd,OACA,iBACA,SAA0B,0BAClB;AAER,KAAI,CAAC,SAAS,MAAM,eAAe,OAAO,cACxC,QAAO;CAIT,MAAM,eAAe,gBAAgB,MAAM,cAAc,MAAM,iBAAiB,OAAO,aAAa;CAEpG,MAAM,eAAe,gBAAgB,MAAM,cAAc,MAAM,iBAAiB,OAAO,aAAa;AAIpG,QAAO,KAAK,IAAI,cAAc,aAAa"}
1
+ {"version":3,"file":"bandwidth-estimator.js","names":[],"sources":["../../../src/network/bandwidth-estimator.ts"],"sourcesContent":["/**\n * Dual EWMA Bandwidth Estimator\n *\n * Estimates available bandwidth using two EWMA calculations with different\n * half-lives, taking the minimum of both. This approach (from Shaka Player):\n *\n * - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops\n * - **Slow EWMA** (5s half-life): Provides stability during fluctuations\n * - **min(fast, slow)**: Adapts down quickly, up slowly\n *\n * This naturally provides asymmetric behavior needed for good QoE:\n * avoiding stalls (quick downgrade) while preventing oscillation (slow upgrade).\n */\n\nimport { applyZeroFactor, calculateEwma } from './ewma';\n\n/**\n * Bandwidth estimator state.\n *\n * This state structure will be managed by O1 (State Container).\n * Functions in this module operate on this state immutably.\n */\nexport interface BandwidthState {\n /** Fast-moving EWMA estimate (raw, uncorrected). */\n fastEstimate: number;\n /** Total weight accumulated in fast EWMA. */\n fastTotalWeight: number;\n /** Slow-moving EWMA estimate (raw, uncorrected). */\n slowEstimate: number;\n /** Total weight accumulated in slow EWMA. */\n slowTotalWeight: number;\n /** Total bytes sampled across all valid samples. */\n bytesSampled: number;\n}\n\n/**\n * Configuration for bandwidth estimation.\n */\nexport interface BandwidthConfig {\n /** Half-life for fast EWMA in seconds. */\n fastHalfLife: number;\n /** Half-life for slow EWMA in seconds. */\n slowHalfLife: number;\n /** Minimum total bytes before trusting the estimate. */\n minTotalBytes: number;\n /** Minimum bytes per sample to count (filters TTFB-dominated samples). */\n minBytes: number;\n /** Minimum sample duration in ms (filters cached responses). */\n minDuration: number;\n}\n\n/**\n * Default bandwidth estimator configuration.\n *\n * Values match Shaka Player defaults based on experimentation.\n */\nexport const DEFAULT_BANDWIDTH_CONFIG: BandwidthConfig = {\n fastHalfLife: 2, // 2 seconds\n slowHalfLife: 5, // 5 seconds\n minTotalBytes: 128_000, // 128 KB\n minBytes: 16_000, // 16 KB\n minDuration: 5, // 5 ms\n};\n\n/**\n * Add a bandwidth sample from a segment download.\n *\n * Samples are filtered based on:\n * - Minimum bytes (filters TTFB-dominated small segments)\n * - Minimum duration (filters cached responses)\n *\n * Valid samples update both fast and slow EWMA estimates.\n *\n * @param state - Current estimator state\n * @param durationMs - Download duration in milliseconds\n * @param numBytes - Number of bytes downloaded\n * @param config - Optional estimator configuration (uses defaults if not provided)\n * @returns New estimator state with sample incorporated (or unchanged if filtered)\n *\n * @example\n * let state = { fastEstimate: 0, fastTotalWeight: 0, ... };\n * // Sample: 1MB in 1 second\n * state = sampleBandwidth(state, 1000, 1_000_000);\n */\nexport function sampleBandwidth(\n state: BandwidthState,\n durationMs: number,\n numBytes: number,\n config: BandwidthConfig = DEFAULT_BANDWIDTH_CONFIG\n): BandwidthState {\n // Always track bytes for startup phase calculation\n const updatedBytesSampled = state.bytesSampled + numBytes;\n\n // Filter: Ignore samples below minimum bytes (TTFB-dominated)\n // Small segments' download time is mostly connection setup latency,\n // which would artificially lower our bandwidth estimate\n if (numBytes < config.minBytes) {\n return { ...state, bytesSampled: updatedBytesSampled };\n }\n\n // Filter: Ignore samples faster than minimum duration (cached responses)\n // Cached responses load nearly instantly and would artificially inflate\n // our bandwidth estimate\n if (durationMs < config.minDuration) {\n return { ...state, bytesSampled: updatedBytesSampled };\n }\n\n // Calculate bandwidth in bits per second\n // Formula: (bytes * 8 bits/byte * 1000 ms/s) / ms = bits/s\n const bandwidth = (8000 * numBytes) / durationMs;\n\n // Weight by duration in seconds\n // Longer downloads are more reliable indicators of true bandwidth\n const weight = durationMs / 1000;\n\n return {\n fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),\n fastTotalWeight: state.fastTotalWeight + weight,\n slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),\n slowTotalWeight: state.slowTotalWeight + weight,\n bytesSampled: updatedBytesSampled,\n };\n}\n\n/**\n * Get the current bandwidth estimate.\n *\n * Returns the **minimum** of the fast and slow EWMA estimates.\n * This provides the key asymmetric behavior:\n * - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)\n * - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)\n *\n * Uses default estimate until enough data has been sampled — and when no\n * estimator state exists at all (`state === undefined`).\n *\n * @param state - Current estimator state, or `undefined` before any samples have been collected\n * @param defaultEstimate - Fallback estimate before sufficient samples (bps)\n * @param config - Optional estimator configuration (uses defaults if not provided)\n * @returns Bandwidth estimate in bits per second\n *\n * @example\n * const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default\n */\nexport function getBandwidthEstimate(\n state: BandwidthState | undefined,\n defaultEstimate: number,\n config: BandwidthConfig = DEFAULT_BANDWIDTH_CONFIG\n): number {\n // Use default until we have enough samples to trust our estimate\n if (!state || state.bytesSampled < config.minTotalBytes) {\n return defaultEstimate;\n }\n\n // Apply zero-factor correction to both estimates\n const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);\n\n const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);\n\n // Take the minimum - this is the key insight from Shaka Player\n // It naturally provides \"down quickly, up slowly\" behavior\n return Math.min(fastEstimate, slowEstimate);\n}\n\n/**\n * Check if the estimator has enough data to provide a reliable estimate.\n *\n * Requires both:\n * - Enough total bytes sampled (minTotalBytes threshold)\n * - At least one valid EWMA sample (totalWeight > 0)\n *\n * @param state - Current estimator state\n * @param config - Optional estimator configuration (uses defaults if not provided)\n * @returns True if we've sampled enough bytes to trust the estimate\n *\n * @example\n * if (hasGoodEstimate(state)) {\n * const estimate = getBandwidthEstimate(state, 5_000_000);\n * }\n */\nexport function hasGoodEstimate(state: BandwidthState, config: BandwidthConfig = DEFAULT_BANDWIDTH_CONFIG): boolean {\n // Need enough total bytes AND at least one valid EWMA sample\n return state.bytesSampled >= config.minTotalBytes && state.fastTotalWeight > 0 && state.slowTotalWeight > 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,2BAA4C;CACvD,cAAc;CACd,cAAc;CACd,eAAe;CACf,UAAU;CACV,aAAa;AACf;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,gBACd,OACA,YACA,UACA,SAA0B,0BACV;CAEhB,MAAM,sBAAsB,MAAM,eAAe;CAKjD,IAAI,WAAW,OAAO,UACpB,OAAO;EAAE,GAAG;EAAO,cAAc;CAAoB;CAMvD,IAAI,aAAa,OAAO,aACtB,OAAO;EAAE,GAAG;EAAO,cAAc;CAAoB;CAKvD,MAAM,YAAa,MAAO,WAAY;CAItC,MAAM,SAAS,aAAa;CAE5B,OAAO;EACL,cAAc,cAAc,MAAM,cAAc,WAAW,QAAQ,OAAO,YAAY;EACtF,iBAAiB,MAAM,kBAAkB;EACzC,cAAc,cAAc,MAAM,cAAc,WAAW,QAAQ,OAAO,YAAY;EACtF,iBAAiB,MAAM,kBAAkB;EACzC,cAAc;CAChB;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,qBACd,OACA,iBACA,SAA0B,0BAClB;CAER,IAAI,CAAC,SAAS,MAAM,eAAe,OAAO,eACxC,OAAO;CAIT,MAAM,eAAe,gBAAgB,MAAM,cAAc,MAAM,iBAAiB,OAAO,YAAY;CAEnG,MAAM,eAAe,gBAAgB,MAAM,cAAc,MAAM,iBAAiB,OAAO,YAAY;CAInG,OAAO,KAAK,IAAI,cAAc,YAAY;AAC5C"}
@@ -1 +1 @@
1
- {"version":3,"file":"chunked-stream-iterable.js","names":["#readableStream"],"sources":["../../../src/network/chunked-stream-iterable.ts"],"sourcesContent":["const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17; // 128 KB\n\nexport interface ChunkedStreamIterableOptions {\n minChunkSize?: number;\n}\n\n/**\n * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an\n * `AsyncIterable<Uint8Array>` that yields chunks no smaller than\n * `minChunkSize` bytes. Smaller network chunks are accumulated and yielded\n * together once the threshold is met. Any remainder is flushed on stream end.\n *\n * Errors from the underlying stream propagate naturally — the reader lock is\n * always released via `finally`.\n */\nexport class ChunkedStreamIterable implements AsyncIterable<Uint8Array> {\n readonly minChunkSize: number;\n #readableStream: ReadableStream<Uint8Array>;\n\n constructor(\n readableStream: ReadableStream<Uint8Array>,\n { minChunkSize = DEFAULT_MIN_CHUNK_SIZE }: ChunkedStreamIterableOptions = {}\n ) {\n this.#readableStream = readableStream;\n this.minChunkSize = minChunkSize;\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<Uint8Array> {\n let pending: Uint8Array | undefined;\n const reader = this.#readableStream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n if (pending) yield pending;\n break;\n }\n\n pending = pending ? concat(pending, value) : value;\n\n if (pending.length >= this.minChunkSize) {\n yield pending;\n pending = undefined;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const result = new Uint8Array(a.length + b.length);\n result.set(a);\n result.set(b, a.length);\n return result;\n}\n"],"mappings":";AAAA,MAAM,yBAAyB,KAAK;;;;;;;;;;AAepC,IAAa,wBAAb,MAAwE;CACtE;CACA;CAEA,YACE,gBACA,EAAE,eAAe,2BAAyD,EAAE,EAC5E;AACA,QAAA,iBAAuB;AACvB,OAAK,eAAe;;CAGtB,QAAQ,OAAO,iBAA6C;EAC1D,IAAI;EACJ,MAAM,SAAS,MAAA,eAAqB,WAAW;AAC/C,MAAI;AACF,UAAO,MAAM;IACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,QAAI,MAAM;AACR,SAAI,QAAS,OAAM;AACnB;;AAGF,cAAU,UAAU,OAAO,SAAS,MAAM,GAAG;AAE7C,QAAI,QAAQ,UAAU,KAAK,cAAc;AACvC,WAAM;AACN,eAAU,KAAA;;;YAGN;AACR,UAAO,aAAa;;;;AAK1B,SAAS,OAAO,GAAe,GAA2B;CACxD,MAAM,SAAS,IAAI,WAAW,EAAE,SAAS,EAAE,OAAO;AAClD,QAAO,IAAI,EAAE;AACb,QAAO,IAAI,GAAG,EAAE,OAAO;AACvB,QAAO"}
1
+ {"version":3,"file":"chunked-stream-iterable.js","names":["#readableStream"],"sources":["../../../src/network/chunked-stream-iterable.ts"],"sourcesContent":["const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17; // 128 KB\n\nexport interface ChunkedStreamIterableOptions {\n minChunkSize?: number;\n}\n\n/**\n * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an\n * `AsyncIterable<Uint8Array>` that yields chunks no smaller than\n * `minChunkSize` bytes. Smaller network chunks are accumulated and yielded\n * together once the threshold is met. Any remainder is flushed on stream end.\n *\n * Errors from the underlying stream propagate naturally — the reader lock is\n * always released via `finally`.\n */\nexport class ChunkedStreamIterable implements AsyncIterable<Uint8Array> {\n readonly minChunkSize: number;\n #readableStream: ReadableStream<Uint8Array>;\n\n constructor(\n readableStream: ReadableStream<Uint8Array>,\n { minChunkSize = DEFAULT_MIN_CHUNK_SIZE }: ChunkedStreamIterableOptions = {}\n ) {\n this.#readableStream = readableStream;\n this.minChunkSize = minChunkSize;\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<Uint8Array> {\n let pending: Uint8Array | undefined;\n const reader = this.#readableStream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n if (pending) yield pending;\n break;\n }\n\n pending = pending ? concat(pending, value) : value;\n\n if (pending.length >= this.minChunkSize) {\n yield pending;\n pending = undefined;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const result = new Uint8Array(a.length + b.length);\n result.set(a);\n result.set(b, a.length);\n return result;\n}\n"],"mappings":";AAAA,MAAM,yBAAyB,KAAK;;;;;;;;;;AAepC,IAAa,wBAAb,MAAwE;CACtE;CACA;CAEA,YACE,gBACA,EAAE,eAAe,2BAAyD,CAAC,GAC3E;EACA,KAAKA,kBAAkB;EACvB,KAAK,eAAe;CACtB;CAEA,QAAQ,OAAO,iBAA6C;EAC1D,IAAI;EACJ,MAAM,SAAS,KAAKA,gBAAgB,UAAU;EAC9C,IAAI;GACF,OAAO,MAAM;IACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;IAC1C,IAAI,MAAM;KACR,IAAI,SAAS,MAAM;KACnB;IACF;IAEA,UAAU,UAAU,OAAO,SAAS,KAAK,IAAI;IAE7C,IAAI,QAAQ,UAAU,KAAK,cAAc;KACvC,MAAM;KACN,UAAU,KAAA;IACZ;GACF;EACF,UAAU;GACR,OAAO,YAAY;EACrB;CACF;AACF;AAEA,SAAS,OAAO,GAAe,GAA2B;CACxD,MAAM,SAAS,IAAI,WAAW,EAAE,SAAS,EAAE,MAAM;CACjD,OAAO,IAAI,CAAC;CACZ,OAAO,IAAI,GAAG,EAAE,MAAM;CACtB,OAAO;AACT"}
@@ -66,6 +66,6 @@ function applyZeroFactor(estimate, totalWeight, halfLife) {
66
66
  return estimate / (1 - calculateAlpha(halfLife) ** totalWeight);
67
67
  }
68
68
  //#endregion
69
- export { applyZeroFactor, calculateEwma };
69
+ export { applyZeroFactor, calculateAlpha, calculateEwma };
70
70
 
71
71
  //# sourceMappingURL=ewma.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ewma.js","names":[],"sources":["../../../src/network/ewma.ts"],"sourcesContent":["/**\n * Exponentially Weighted Moving Average (EWMA)\n *\n * Pure functional implementation of EWMA calculations.\n * Based on Shaka Player's EWMA algorithm.\n */\n\n/**\n * Calculate alpha (decay factor) from half-life.\n *\n * Alpha determines how quickly old data \"expires\":\n * - alpha close to 1 = slow decay (long memory)\n * - alpha close to 0 = fast decay (short memory)\n *\n * @param halfLife - The quantity of prior samples (by weight) that make up\n * half of the new estimate. Must be positive.\n * @returns Alpha value between 0 and 1\n *\n * @example\n * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life\n */\nexport function calculateAlpha(halfLife: number): number {\n return Math.exp(Math.log(0.5) / halfLife);\n}\n\n/**\n * Calculate exponentially weighted moving average.\n *\n * Updates an estimate by blending a new value with the previous estimate,\n * weighted by the sample duration. Longer samples have more influence.\n *\n * @param prevEstimate - Previous EWMA estimate\n * @param value - New sample value to incorporate\n * @param weight - Sample weight (typically duration in seconds)\n * @param halfLife - Half-life for decay (typically 2-5 seconds)\n * @returns Updated EWMA estimate\n *\n * @example\n * let estimate = 0;\n * estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample\n * estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample\n */\nexport function calculateEwma(prevEstimate: number, value: number, weight: number, halfLife: number): number {\n const alpha = calculateAlpha(halfLife);\n const adjAlpha = alpha ** weight;\n return value * (1 - adjAlpha) + adjAlpha * prevEstimate;\n}\n\n/**\n * Apply zero-factor correction to EWMA estimate.\n *\n * The zero-factor correction compensates for bias when starting from zero.\n * Without this correction, early estimates would be artificially low.\n *\n * As totalWeight increases, the correction factor approaches 1, meaning\n * the estimate becomes more reliable and needs less correction.\n *\n * @param estimate - Raw EWMA estimate (uncorrected)\n * @param totalWeight - Accumulated weight from all samples\n * @param halfLife - Half-life used in EWMA calculation\n * @returns Corrected estimate, or 0 if totalWeight is 0\n *\n * @example\n * const raw = calculateEwma(0, 1_000_000, 1, 2);\n * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000\n */\nexport function applyZeroFactor(estimate: number, totalWeight: number, halfLife: number): number {\n if (totalWeight === 0) {\n return 0;\n }\n\n const alpha = calculateAlpha(halfLife);\n const zeroFactor = 1 - alpha ** totalWeight;\n return estimate / zeroFactor;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eAAe,UAA0B;AACvD,QAAO,KAAK,IAAI,KAAK,IAAI,GAAI,GAAG,SAAS;;;;;;;;;;;;;;;;;;;AAoB3C,SAAgB,cAAc,cAAsB,OAAe,QAAgB,UAA0B;CAE3G,MAAM,WADQ,eAAe,SAAS,IACZ;AAC1B,QAAO,SAAS,IAAI,YAAY,WAAW;;;;;;;;;;;;;;;;;;;;AAqB7C,SAAgB,gBAAgB,UAAkB,aAAqB,UAA0B;AAC/F,KAAI,gBAAgB,EAClB,QAAO;AAKT,QAAO,YADY,IADL,eAAe,SAAS,IACN"}
1
+ {"version":3,"file":"ewma.js","names":[],"sources":["../../../src/network/ewma.ts"],"sourcesContent":["/**\n * Exponentially Weighted Moving Average (EWMA)\n *\n * Pure functional implementation of EWMA calculations.\n * Based on Shaka Player's EWMA algorithm.\n */\n\n/**\n * Calculate alpha (decay factor) from half-life.\n *\n * Alpha determines how quickly old data \"expires\":\n * - alpha close to 1 = slow decay (long memory)\n * - alpha close to 0 = fast decay (short memory)\n *\n * @param halfLife - The quantity of prior samples (by weight) that make up\n * half of the new estimate. Must be positive.\n * @returns Alpha value between 0 and 1\n *\n * @example\n * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life\n */\nexport function calculateAlpha(halfLife: number): number {\n return Math.exp(Math.log(0.5) / halfLife);\n}\n\n/**\n * Calculate exponentially weighted moving average.\n *\n * Updates an estimate by blending a new value with the previous estimate,\n * weighted by the sample duration. Longer samples have more influence.\n *\n * @param prevEstimate - Previous EWMA estimate\n * @param value - New sample value to incorporate\n * @param weight - Sample weight (typically duration in seconds)\n * @param halfLife - Half-life for decay (typically 2-5 seconds)\n * @returns Updated EWMA estimate\n *\n * @example\n * let estimate = 0;\n * estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample\n * estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample\n */\nexport function calculateEwma(prevEstimate: number, value: number, weight: number, halfLife: number): number {\n const alpha = calculateAlpha(halfLife);\n const adjAlpha = alpha ** weight;\n return value * (1 - adjAlpha) + adjAlpha * prevEstimate;\n}\n\n/**\n * Apply zero-factor correction to EWMA estimate.\n *\n * The zero-factor correction compensates for bias when starting from zero.\n * Without this correction, early estimates would be artificially low.\n *\n * As totalWeight increases, the correction factor approaches 1, meaning\n * the estimate becomes more reliable and needs less correction.\n *\n * @param estimate - Raw EWMA estimate (uncorrected)\n * @param totalWeight - Accumulated weight from all samples\n * @param halfLife - Half-life used in EWMA calculation\n * @returns Corrected estimate, or 0 if totalWeight is 0\n *\n * @example\n * const raw = calculateEwma(0, 1_000_000, 1, 2);\n * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000\n */\nexport function applyZeroFactor(estimate: number, totalWeight: number, halfLife: number): number {\n if (totalWeight === 0) {\n return 0;\n }\n\n const alpha = calculateAlpha(halfLife);\n const zeroFactor = 1 - alpha ** totalWeight;\n return estimate / zeroFactor;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eAAe,UAA0B;CACvD,OAAO,KAAK,IAAI,KAAK,IAAI,EAAG,IAAI,QAAQ;AAC1C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,cAAsB,OAAe,QAAgB,UAA0B;CAE3G,MAAM,WADQ,eAAe,QACR,KAAK;CAC1B,OAAO,SAAS,IAAI,YAAY,WAAW;AAC7C;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,gBAAgB,UAAkB,aAAqB,UAA0B;CAC/F,IAAI,gBAAgB,GAClB,OAAO;CAKT,OAAO,YADY,IADL,eAAe,QACF,KAAK;AAElC"}
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","names":[],"sources":["../../../src/network/fetch.ts"],"sourcesContent":["/**\n * HTTP Fetch Wrapper\n *\n * Composable building blocks:\n * - fetchResolvable() — fetch a Resource (handles byte ranges); returns Response\n * - getResponseText() — extract text from Response\n * - fetchResolvableStream() — single-stage async generator over body chunks\n * - fetchStream() — two-stage: await connection establishment, then lazily\n * iterate body chunks. Use when timing the connection start independently\n * of body consumption matters (e.g., observable fetch timing for ABR).\n * - createTrackedFetch() — factory for a fetchStream-shape function that\n * samples bandwidth (via EWMA) per chunk and notifies via callback.\n */\n\nimport { type BandwidthState, sampleBandwidth } from './bandwidth-estimator';\nimport { ChunkedStreamIterable, type ChunkedStreamIterableOptions } from './chunked-stream-iterable';\n\n/**\n * Minimal Response-like interface for text extraction.\n * Allows testing without full Response object.\n */\nexport interface ResponseLike {\n text(): Promise<string>;\n}\n\n/**\n * An HTTP-addressable resource — URL plus optional byte range.\n * Media's `AddressableObject` (and anything else with the same shape)\n * is structurally compatible; kept local so this module stays\n * domain-agnostic.\n */\nexport interface Resource {\n url: string;\n byteRange?: {\n start: number;\n end: number;\n };\n}\n\n/**\n * Fetch resolvable from a Resource.\n *\n * Handles byte range requests if byteRange is present.\n * Returns native fetch Response for composability (can extract text, stream, etc.).\n *\n * @param addressable - Resource to fetch (url + optional byteRange)\n * @returns Promise resolving to Response\n *\n * @example\n * const response = await fetchResolvable({ url: 'https://example.com/segment.m4s' });\n * const text = await getResponseText(response);\n *\n * @example\n * // With byte range\n * const response = await fetchResolvable({\n * url: 'https://example.com/file.mp4',\n * byteRange: { start: 1000, end: 1999 }\n * });\n */\nexport async function fetchResolvable(addressable: Resource, options?: RequestInit): Promise<Response> {\n const headers = new Headers(options?.headers);\n\n // Add Range header for byte range requests\n if (addressable.byteRange) {\n const { start, end } = addressable.byteRange;\n headers.set('Range', `bytes=${start}-${end}`);\n }\n\n const request = new Request(addressable.url, {\n method: 'GET',\n headers,\n ...options,\n });\n\n return fetch(request);\n}\n\n/**\n * Fetch resolvable as bytes.\n *\n * Convenience wrapper around fetchResolvable that resolves the body as an\n * ArrayBuffer. Use this when you need the raw bytes (e.g. segment appends).\n * For text or streaming consumption, use fetchResolvable directly.\n */\nexport async function fetchResolvableBytes(addressable: Resource, options?: RequestInit): Promise<ArrayBuffer> {\n const response = await fetchResolvable(addressable, options);\n return response.arrayBuffer();\n}\n\n/**\n * Fetch resolvable as a stream of Uint8Array chunks.\n *\n * Convenience wrapper around fetchResolvable that yields the body as chunks\n * via ChunkedStreamIterable. Headers are awaited before the first chunk is\n * yielded (TTFB is accounted for before iteration begins).\n *\n * Throws if the response body is null (e.g. non-body HTTP status).\n * Errors from the underlying stream propagate naturally as thrown errors.\n */\nexport async function* fetchResolvableStream(\n addressable: Resource,\n options?: RequestInit & ChunkedStreamIterableOptions\n): AsyncGenerator<Uint8Array> {\n const { minChunkSize, ...fetchOptions } = options ?? {};\n const response = await fetchResolvable(addressable, fetchOptions);\n if (!response.body) throw new Error('Response has no body');\n yield* new ChunkedStreamIterable(response.body, ...(minChunkSize !== undefined ? [{ minChunkSize }] : []));\n}\n\n/**\n * Extract text from Response.\n *\n * Accepts minimal Response-like object (just needs text() method).\n * Returns promise from response.text().\n *\n * @param response - Response-like object with text() method\n * @returns Promise resolving to text content\n *\n * @example\n * const response = await fetchResolvable(addressable);\n * const text = await getResponseText(response);\n */\nexport function getResponseText(response: ResponseLike): Promise<string> {\n return response.text();\n}\n\n/**\n * Fetch a resource and resolve its text body — the text analog of\n * {@link FetchBytes}. A non-OK status rejects, so HTTP failures surface as\n * rejections that callers (and decorators like the failover tracker) handle\n * uniformly with network errors.\n */\nexport type FetchText = (addressable: Resource, options?: RequestInit) => Promise<string>;\n\n/** Default {@link FetchText}: fetch the resource, reject on non-OK, return text. */\nexport const fetchResolvableText: FetchText = async (addressable, options) => {\n const response = await fetchResolvable(addressable, options);\n if (!response.ok) {\n throw new Error(`fetchResolvableText: ${response.status} ${response.statusText} for ${addressable.url}`);\n }\n return getResponseText(response);\n};\n\n/**\n * Two-stage fetch helper: eagerly starts the HTTP request (TTFB is awaited),\n * then returns a lazy iterable over the response body. Separating connection\n * start from body iteration makes fetch timing predictable and observable\n * regardless of when downstream consumers begin pulling chunks.\n *\n * Sibling to {@link fetchResolvableStream}, which is single-stage (calls\n * `fetch` only when iteration starts). Pick `fetchStream` when \"when did the\n * fetch start\" needs to be observable separately from \"when did the body\n * begin arriving.\"\n */\nexport type FetchOptions = RequestInit & ChunkedStreamIterableOptions;\n\nexport type FetchBytes = (addressable: Resource, options?: FetchOptions) => Promise<AsyncIterable<Uint8Array>>;\n\nexport async function fetchStream(addressable: Resource, options?: FetchOptions): Promise<AsyncIterable<Uint8Array>> {\n const { minChunkSize, ...fetchOptions } = options ?? {};\n const response = await fetchResolvable(addressable, fetchOptions);\n if (!response.body) throw new Error('Response has no body');\n return new ChunkedStreamIterable(response.body, ...(minChunkSize !== undefined ? [{ minChunkSize }] : []));\n}\n\n/**\n * Returns a {@link FetchBytes} function that samples bandwidth via EWMA\n * per body chunk. The factory captures the running bandwidth state\n * internally; per chunk it computes the next state and notifies the\n * supplied `onSample` callback.\n *\n * The factory's internal accumulator is seeded from `initial` and updated\n * on every chunk; callers don't need to thread it back in. `onSample`\n * receives the *new* state after each chunk — typical use is to bridge\n * samples back into engine state for ABR consumers.\n *\n * @param initial - Starting `BandwidthState` (commonly zeros or the\n * engine's current accumulator).\n * @param onSample - Called with the new `BandwidthState` after each chunk.\n */\nexport function createTrackedFetch(initial: BandwidthState, onSample: (next: BandwidthState) => void): FetchBytes {\n let state = initial;\n return async (addressable, options) => {\n const { minChunkSize, ...fetchOptions } = options ?? {};\n const response = await fetchResolvable(addressable, fetchOptions);\n if (!response.body) throw new Error('Response has no body');\n const body = response.body;\n return {\n [Symbol.asyncIterator]: async function* () {\n let chunkStart = performance.now();\n for await (const chunk of new ChunkedStreamIterable(\n body,\n ...(minChunkSize !== undefined ? [{ minChunkSize }] : [])\n )) {\n const elapsed = performance.now() - chunkStart;\n state = sampleBandwidth(state, elapsed, chunk.byteLength);\n onSample(state);\n yield chunk;\n chunkStart = performance.now();\n }\n },\n };\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,eAAsB,gBAAgB,aAAuB,SAA0C;CACrG,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;AAG7C,KAAI,YAAY,WAAW;EACzB,MAAM,EAAE,OAAO,QAAQ,YAAY;AACnC,UAAQ,IAAI,SAAS,SAAS,MAAM,GAAG,MAAM;;CAG/C,MAAM,UAAU,IAAI,QAAQ,YAAY,KAAK;EAC3C,QAAQ;EACR;EACA,GAAG;EACJ,CAAC;AAEF,QAAO,MAAM,QAAQ;;;;;;;;;;;;;;;AAgDvB,SAAgB,gBAAgB,UAAyC;AACvE,QAAO,SAAS,MAAM;;;AAYxB,MAAa,sBAAiC,OAAO,aAAa,YAAY;CAC5E,MAAM,WAAW,MAAM,gBAAgB,aAAa,QAAQ;AAC5D,KAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG,SAAS,WAAW,OAAO,YAAY,MAAM;AAE1G,QAAO,gBAAgB,SAAS;;AAkBlC,eAAsB,YAAY,aAAuB,SAA4D;CACnH,MAAM,EAAE,cAAc,GAAG,iBAAiB,WAAW,EAAE;CACvD,MAAM,WAAW,MAAM,gBAAgB,aAAa,aAAa;AACjE,KAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,uBAAuB;AAC3D,QAAO,IAAI,sBAAsB,SAAS,MAAM,GAAI,iBAAiB,KAAA,IAAY,CAAC,EAAE,cAAc,CAAC,GAAG,EAAE,CAAE;;;;;;;;;;;;;;;;;AAkB5G,SAAgB,mBAAmB,SAAyB,UAAsD;CAChH,IAAI,QAAQ;AACZ,QAAO,OAAO,aAAa,YAAY;EACrC,MAAM,EAAE,cAAc,GAAG,iBAAiB,WAAW,EAAE;EACvD,MAAM,WAAW,MAAM,gBAAgB,aAAa,aAAa;AACjE,MAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,uBAAuB;EAC3D,MAAM,OAAO,SAAS;AACtB,SAAO,EACL,CAAC,OAAO,gBAAgB,mBAAmB;GACzC,IAAI,aAAa,YAAY,KAAK;AAClC,cAAW,MAAM,SAAS,IAAI,sBAC5B,MACA,GAAI,iBAAiB,KAAA,IAAY,CAAC,EAAE,cAAc,CAAC,GAAG,EAAE,CACzD,EAAE;IACD,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,YAAQ,gBAAgB,OAAO,SAAS,MAAM,WAAW;AACzD,aAAS,MAAM;AACf,UAAM;AACN,iBAAa,YAAY,KAAK;;KAGnC"}
1
+ {"version":3,"file":"fetch.js","names":[],"sources":["../../../src/network/fetch.ts"],"sourcesContent":["/**\n * HTTP Fetch Wrapper\n *\n * Composable building blocks:\n * - fetchResolvable() — fetch a Resource (handles byte ranges); returns Response\n * - getResponseText() — extract text from Response\n * - fetchResolvableStream() — single-stage async generator over body chunks\n * - fetchStream() — two-stage: await connection establishment, then lazily\n * iterate body chunks. Use when timing the connection start independently\n * of body consumption matters (e.g., observable fetch timing for ABR).\n * - createTrackedFetch() — factory for a fetchStream-shape function that\n * samples bandwidth (via EWMA) per chunk and notifies via callback.\n */\n\nimport { type BandwidthState, sampleBandwidth } from './bandwidth-estimator';\nimport { ChunkedStreamIterable, type ChunkedStreamIterableOptions } from './chunked-stream-iterable';\n\n/**\n * Minimal Response-like interface for text extraction.\n * Allows testing without full Response object.\n */\nexport interface ResponseLike {\n text(): Promise<string>;\n}\n\n/**\n * An HTTP-addressable resource — URL plus optional byte range.\n * Media's `AddressableObject` (and anything else with the same shape)\n * is structurally compatible; kept local so this module stays\n * domain-agnostic.\n */\nexport interface Resource {\n url: string;\n byteRange?: {\n start: number;\n end: number;\n };\n}\n\n/**\n * Fetch resolvable from a Resource.\n *\n * Handles byte range requests if byteRange is present.\n * Returns native fetch Response for composability (can extract text, stream, etc.).\n *\n * @param addressable - Resource to fetch (url + optional byteRange)\n * @returns Promise resolving to Response\n *\n * @example\n * const response = await fetchResolvable({ url: 'https://example.com/segment.m4s' });\n * const text = await getResponseText(response);\n *\n * @example\n * // With byte range\n * const response = await fetchResolvable({\n * url: 'https://example.com/file.mp4',\n * byteRange: { start: 1000, end: 1999 }\n * });\n */\nexport async function fetchResolvable(addressable: Resource, options?: RequestInit): Promise<Response> {\n const headers = new Headers(options?.headers);\n\n // Add Range header for byte range requests\n if (addressable.byteRange) {\n const { start, end } = addressable.byteRange;\n headers.set('Range', `bytes=${start}-${end}`);\n }\n\n const request = new Request(addressable.url, {\n method: 'GET',\n headers,\n ...options,\n });\n\n return fetch(request);\n}\n\n/**\n * Fetch resolvable as bytes.\n *\n * Convenience wrapper around fetchResolvable that resolves the body as an\n * ArrayBuffer. Use this when you need the raw bytes (e.g. segment appends).\n * For text or streaming consumption, use fetchResolvable directly.\n */\nexport async function fetchResolvableBytes(addressable: Resource, options?: RequestInit): Promise<ArrayBuffer> {\n const response = await fetchResolvable(addressable, options);\n return response.arrayBuffer();\n}\n\n/**\n * Fetch resolvable as a stream of Uint8Array chunks.\n *\n * Convenience wrapper around fetchResolvable that yields the body as chunks\n * via ChunkedStreamIterable. Headers are awaited before the first chunk is\n * yielded (TTFB is accounted for before iteration begins).\n *\n * Throws if the response body is null (e.g. non-body HTTP status).\n * Errors from the underlying stream propagate naturally as thrown errors.\n */\nexport async function* fetchResolvableStream(\n addressable: Resource,\n options?: RequestInit & ChunkedStreamIterableOptions\n): AsyncGenerator<Uint8Array> {\n const { minChunkSize, ...fetchOptions } = options ?? {};\n const response = await fetchResolvable(addressable, fetchOptions);\n if (!response.body) throw new Error('Response has no body');\n yield* new ChunkedStreamIterable(response.body, ...(minChunkSize !== undefined ? [{ minChunkSize }] : []));\n}\n\n/**\n * Extract text from Response.\n *\n * Accepts minimal Response-like object (just needs text() method).\n * Returns promise from response.text().\n *\n * @param response - Response-like object with text() method\n * @returns Promise resolving to text content\n *\n * @example\n * const response = await fetchResolvable(addressable);\n * const text = await getResponseText(response);\n */\nexport function getResponseText(response: ResponseLike): Promise<string> {\n return response.text();\n}\n\n/**\n * Fetch a resource and resolve its text body — the text analog of\n * {@link FetchBytes}. A non-OK status rejects, so HTTP failures surface as\n * rejections that callers (and decorators like the failover tracker) handle\n * uniformly with network errors.\n */\nexport type FetchText = (addressable: Resource, options?: RequestInit) => Promise<string>;\n\n/** Default {@link FetchText}: fetch the resource, reject on non-OK, return text. */\nexport const fetchResolvableText: FetchText = async (addressable, options) => {\n const response = await fetchResolvable(addressable, options);\n if (!response.ok) {\n throw new Error(`fetchResolvableText: ${response.status} ${response.statusText} for ${addressable.url}`);\n }\n return getResponseText(response);\n};\n\n/**\n * Two-stage fetch helper: eagerly starts the HTTP request (TTFB is awaited),\n * then returns a lazy iterable over the response body. Separating connection\n * start from body iteration makes fetch timing predictable and observable\n * regardless of when downstream consumers begin pulling chunks.\n *\n * Sibling to {@link fetchResolvableStream}, which is single-stage (calls\n * `fetch` only when iteration starts). Pick `fetchStream` when \"when did the\n * fetch start\" needs to be observable separately from \"when did the body\n * begin arriving.\"\n */\nexport type FetchOptions = RequestInit & ChunkedStreamIterableOptions;\n\nexport type FetchBytes = (addressable: Resource, options?: FetchOptions) => Promise<AsyncIterable<Uint8Array>>;\n\nexport async function fetchStream(addressable: Resource, options?: FetchOptions): Promise<AsyncIterable<Uint8Array>> {\n const { minChunkSize, ...fetchOptions } = options ?? {};\n const response = await fetchResolvable(addressable, fetchOptions);\n if (!response.body) throw new Error('Response has no body');\n return new ChunkedStreamIterable(response.body, ...(minChunkSize !== undefined ? [{ minChunkSize }] : []));\n}\n\n/**\n * Returns a {@link FetchBytes} function that samples bandwidth via EWMA\n * per body chunk. The factory captures the running bandwidth state\n * internally; per chunk it computes the next state and notifies the\n * supplied `onSample` callback.\n *\n * The factory's internal accumulator is seeded from `initial` and updated\n * on every chunk; callers don't need to thread it back in. `onSample`\n * receives the *new* state after each chunk — typical use is to bridge\n * samples back into engine state for ABR consumers.\n *\n * @param initial - Starting `BandwidthState` (commonly zeros or the\n * engine's current accumulator).\n * @param onSample - Called with the new `BandwidthState` after each chunk.\n */\nexport function createTrackedFetch(initial: BandwidthState, onSample: (next: BandwidthState) => void): FetchBytes {\n let state = initial;\n return async (addressable, options) => {\n const { minChunkSize, ...fetchOptions } = options ?? {};\n const response = await fetchResolvable(addressable, fetchOptions);\n if (!response.body) throw new Error('Response has no body');\n const body = response.body;\n return {\n [Symbol.asyncIterator]: async function* () {\n let chunkStart = performance.now();\n for await (const chunk of new ChunkedStreamIterable(\n body,\n ...(minChunkSize !== undefined ? [{ minChunkSize }] : [])\n )) {\n const elapsed = performance.now() - chunkStart;\n state = sampleBandwidth(state, elapsed, chunk.byteLength);\n onSample(state);\n yield chunk;\n chunkStart = performance.now();\n }\n },\n };\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,eAAsB,gBAAgB,aAAuB,SAA0C;CACrG,MAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;CAG5C,IAAI,YAAY,WAAW;EACzB,MAAM,EAAE,OAAO,QAAQ,YAAY;EACnC,QAAQ,IAAI,SAAS,SAAS,MAAM,GAAG,KAAK;CAC9C;CAEA,MAAM,UAAU,IAAI,QAAQ,YAAY,KAAK;EAC3C,QAAQ;EACR;EACA,GAAG;CACL,CAAC;CAED,OAAO,MAAM,OAAO;AACtB;;;;;;;;;;;;;;AA+CA,SAAgB,gBAAgB,UAAyC;CACvE,OAAO,SAAS,KAAK;AACvB;;AAWA,MAAa,sBAAiC,OAAO,aAAa,YAAY;CAC5E,MAAM,WAAW,MAAM,gBAAgB,aAAa,OAAO;CAC3D,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG,SAAS,WAAW,OAAO,YAAY,KAAK;CAEzG,OAAO,gBAAgB,QAAQ;AACjC;AAiBA,eAAsB,YAAY,aAAuB,SAA4D;CACnH,MAAM,EAAE,cAAc,GAAG,iBAAiB,WAAW,CAAC;CACtD,MAAM,WAAW,MAAM,gBAAgB,aAAa,YAAY;CAChE,IAAI,CAAC,SAAS,MAAM,MAAM,IAAI,MAAM,sBAAsB;CAC1D,OAAO,IAAI,sBAAsB,SAAS,MAAM,GAAI,iBAAiB,KAAA,IAAY,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAE;AAC3G;;;;;;;;;;;;;;;;AAiBA,SAAgB,mBAAmB,SAAyB,UAAsD;CAChH,IAAI,QAAQ;CACZ,OAAO,OAAO,aAAa,YAAY;EACrC,MAAM,EAAE,cAAc,GAAG,iBAAiB,WAAW,CAAC;EACtD,MAAM,WAAW,MAAM,gBAAgB,aAAa,YAAY;EAChE,IAAI,CAAC,SAAS,MAAM,MAAM,IAAI,MAAM,sBAAsB;EAC1D,MAAM,OAAO,SAAS;EACtB,OAAO,EACL,CAAC,OAAO,gBAAgB,mBAAmB;GACzC,IAAI,aAAa,YAAY,IAAI;GACjC,WAAW,MAAM,SAAS,IAAI,sBAC5B,MACA,GAAI,iBAAiB,KAAA,IAAY,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CACzD,GAAG;IACD,MAAM,UAAU,YAAY,IAAI,IAAI;IACpC,QAAQ,gBAAgB,OAAO,SAAS,MAAM,UAAU;IACxD,SAAS,KAAK;IACd,MAAM;IACN,aAAa,YAAY,IAAI;GAC/B;EACF,EACF;CACF;AACF"}
@@ -1,5 +1,7 @@
1
- import { MessageActor } from "../../../core/actors/create-machine-actor.js";
2
1
  import { AudioTrack, VideoTrack } from "../../../media/types/index.js";
2
+ import { MessageActor } from "../../../core/actors/create-machine-actor.js";
3
+ import "../../primitives/segment-load-pipeline.js";
4
+ import "./source-buffer.js";
3
5
  //#region src/playback/actors/dom/segment-loader.d.ts
4
6
  /** Track types that have SourceBuffers (video and audio only). */
5
7
  type SegmentLoaderTrack = VideoTrack | AudioTrack;
@@ -27,10 +29,20 @@ type SegmentLoaderActorState = 'idle' | 'loading' | 'destroyed';
27
29
  interface SegmentLoaderActorContext {
28
30
  /** Track ID of the init segment currently being fetched/appended, or null. */
29
31
  inFlightInitTrackId: string | null;
30
- /** Segment ID currently being fetched/appended, or null. */
31
- inFlightSegmentId: string | null;
32
+ /**
33
+ * Identity of the segment currently being fetched/appended, or null. Tracks
34
+ * the `trackId` alongside the positional `id` because renditions number
35
+ * segments independently (`segment-N`): an in-flight LOW `segment-0` must not
36
+ * be mistaken for a needed HIGH `segment-0` on a switch (see the loading
37
+ * handler's continue/preempt decision). Mirrors the text-track loader, which
38
+ * already matches its in-flight segment on `(inFlightTrackId, inFlightSegmentId)`.
39
+ */
40
+ inFlightSegment: {
41
+ id: string;
42
+ trackId: string;
43
+ } | null;
32
44
  }
33
45
  type SegmentLoaderActor = MessageActor<SegmentLoaderActorState, SegmentLoaderActorContext, SegmentLoaderMessage>;
34
46
  //#endregion
35
- export { SegmentLoaderActor };
47
+ export { SegmentLoaderActor, SegmentLoaderActorContext, SegmentLoaderActorState, SegmentLoaderMessage, SegmentLoaderTrack };
36
48
  //# sourceMappingURL=segment-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"segment-loader.d.ts","names":[],"sources":["../../../../../src/playback/actors/dom/segment-loader.ts"],"mappings":";;;;KAoDY,kBAAA,GAAqB,UAAA,GAAa,UAAA;AA+C9C;;;;;AAOA;;;;;AAPA,KAnCY,oBAAA;EACV,IAAA;EACA,KAAA,EAAO,kBAAA;EACP,KAAA;IAAU,KAAA;IAAe,GAAA;EAAA;AAAA;;KA6Bf,uBAAA;;UAGK,yBAAA;;EAEf,mBAAA;;EAEA,iBAAA;AAAA;AAAA,KAGU,kBAAA,GAAqB,YAAA,CAAa,uBAAA,EAAyB,yBAAA,EAA2B,oBAAA"}
1
+ {"version":3,"file":"segment-loader.d.ts","names":[],"sources":["../../../../../src/playback/actors/dom/segment-loader.ts"],"mappings":";;;;;;KAwDY,qBAAqB,aAAa;;;;;;;;;;;KAYlC;EACV;EACA,OAAO;EACP;IAAU;IAAe;;;;KAQf;;UAGK;;EAEf;;;;;;;;;EASA;IAAmB;IAAY;;;KAGrB,qBAAqB,aAAa,yBAAyB,2BAA2B"}
@@ -1,106 +1,49 @@
1
1
  import { peek } from "../../../core/signals/primitives.js";
2
- import { effect } from "../../../core/signals/effect.js";
3
2
  import { SerialRunner, Task } from "../../../core/tasks/task.js";
4
3
  import { createMachineActor } from "../../../core/actors/create-machine-actor.js";
5
- import { DEFAULT_FORWARD_BUFFER_CONFIG, calculateForwardFlushPoint, getSegmentsToLoad } from "../../../media/buffer/forward-buffer.js";
6
4
  import "../../../media/types/index.js";
5
+ import { DEFAULT_FORWARD_BUFFER_CONFIG, calculateForwardFlushPoint, getSegmentsToLoad, isTimeRangeCovered, mergeTimeRanges } from "../../../media/buffer/forward-buffer.js";
7
6
  import { DEFAULT_BACK_BUFFER_CONFIG, calculateBackBufferFlushPoint } from "../../../media/buffer/back-buffer.js";
7
+ import { DEFAULT_MESSAGE_PIPELINES } from "../../primitives/segment-load-pipeline.js";
8
8
  //#region src/playback/actors/dom/segment-loader.ts
9
9
  /**
10
- * Resolves when the SourceBufferActor snapshot reaches 'idle'.
11
- * Rejects if the signal is aborted or the actor is destroyed.
12
- *
13
- * Used to sequence SourceBufferActor operations without awaiting send()
14
- * directly send() is fire-and-forget; callers observe completion via
15
- * state transition.
10
+ * Wraps a LoadTask descriptor into a Task that runs the op's message pipeline
11
+ * (fetch/discover/stamp/dispatch, per the composition's `messagePipelines`).
12
+ * Updates in-flight context around the async region so the loading handler can
13
+ * make accurate continue/preempt decisions at any point, and checks the abort
14
+ * signal before each step.
16
15
  */
17
- function waitForIdle(snapshot, signal) {
18
- return new Promise((resolve, reject) => {
19
- if (snapshot.get().value === "idle") {
20
- resolve();
21
- return;
22
- }
23
- if (snapshot.get().value === "destroyed") {
24
- reject(new DOMException("Aborted", "AbortError"));
25
- return;
26
- }
27
- if (signal.aborted) {
28
- reject(signal.reason);
29
- return;
30
- }
31
- let stop;
32
- const cleanup = (fn) => {
33
- stop?.();
34
- signal.removeEventListener("abort", onAbort);
35
- fn();
36
- };
37
- const onAbort = () => cleanup(() => reject(signal.reason));
38
- stop = effect(() => {
39
- const value = snapshot.get().value;
40
- if (value === "idle") cleanup(resolve);
41
- else if (value === "destroyed") cleanup(() => reject(new DOMException("Aborted", "AbortError")));
42
- });
43
- signal.addEventListener("abort", onAbort, { once: true });
44
- });
45
- }
46
- /**
47
- * Wraps a LoadTask descriptor into a Task that fetches (if needed) and
48
- * forwards to SourceBufferActor. Updates in-flight context around async
49
- * operations so the loading handler can make accurate continue/preempt
50
- * decisions at any point.
51
- */
52
- function makeLoadTask(op, { getContext, setContext, fetchBytes, sourceBufferActor }) {
16
+ function makeLoadTask(op, { getContext, setContext, pipelines, deps }) {
53
17
  return new Task(async (taskSignal) => {
54
18
  if (taskSignal.aborted) return;
55
- if (op.type === "remove") {
56
- sourceBufferActor.send(op);
57
- await waitForIdle(sourceBufferActor.snapshot, taskSignal);
58
- return;
59
- }
60
- if (op.type === "append-init") {
61
- setContext({
19
+ const frame = op.type === "append-segment" ? {
20
+ op,
21
+ meta: op.meta
22
+ } : { op };
23
+ try {
24
+ if (op.type === "append-init") setContext({
62
25
  ...getContext(),
63
26
  inFlightInitTrackId: op.meta.trackId
64
27
  });
65
- try {
66
- const data = await fetchBytes(op, {
67
- signal: taskSignal,
68
- minChunkSize: Infinity
69
- });
70
- if (!taskSignal.aborted) {
71
- sourceBufferActor.send({
72
- type: "append-init",
73
- data,
74
- meta: op.meta
75
- });
76
- await waitForIdle(sourceBufferActor.snapshot, taskSignal);
28
+ else if (op.type === "append-segment") setContext({
29
+ ...getContext(),
30
+ inFlightSegment: {
31
+ id: op.meta.id,
32
+ trackId: op.meta.trackId
77
33
  }
78
- } finally {
79
- setContext({
80
- ...getContext(),
81
- inFlightInitTrackId: null
82
- });
83
- }
84
- return;
85
- }
86
- setContext({
87
- ...getContext(),
88
- inFlightSegmentId: op.meta.id
89
- });
90
- try {
91
- const stream = await fetchBytes(op, { signal: taskSignal });
92
- if (!taskSignal.aborted) {
93
- sourceBufferActor.send({
94
- type: "append-segment",
95
- data: stream,
96
- meta: op.meta
97
- });
98
- await waitForIdle(sourceBufferActor.snapshot, taskSignal);
34
+ });
35
+ for (const step of pipelines[op.type]) {
36
+ if (taskSignal.aborted) return;
37
+ await step(frame, taskSignal, deps);
99
38
  }
100
39
  } finally {
101
- setContext({
40
+ if (op.type === "append-init") setContext({
41
+ ...getContext(),
42
+ inFlightInitTrackId: null
43
+ });
44
+ else if (op.type === "append-segment") setContext({
102
45
  ...getContext(),
103
- inFlightSegmentId: null
46
+ inFlightSegment: null
104
47
  });
105
48
  }
106
49
  });
@@ -123,8 +66,16 @@ function makeLoadTask(op, { getContext, setContext, fetchBytes, sourceBufferActo
123
66
  * @param fetchBytes - Tracked fetch closure (owns throughput sampling for segments).
124
67
  * Accepts an optional `minChunkSize` in options; init segments pass `Infinity`
125
68
  * so the entire body accumulates as one chunk before appending.
69
+ * @param compositionDeps - The composition's `state`/`context`/`config`, threaded
70
+ * opaquely into each step's {@link StepDeps} (the loader never reads them). Lets
71
+ * injected steps (relocation) read composition signals at call time. Defaults to
72
+ * empty for standalone / base-pipeline use.
126
73
  */
127
- function createSegmentLoaderActor(sourceBufferActor, fetchBytes, config = {}) {
74
+ function createSegmentLoaderActor(sourceBufferActor, fetchBytes, config = {}, compositionDeps = {
75
+ state: {},
76
+ context: {},
77
+ config: {}
78
+ }) {
128
79
  const forwardBufferConfig = {
129
80
  ...DEFAULT_FORWARD_BUFFER_CONFIG,
130
81
  ...config.forwardBuffer
@@ -133,9 +84,22 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes, config = {}) {
133
84
  ...DEFAULT_BACK_BUFFER_CONFIG,
134
85
  ...config.backBuffer
135
86
  };
87
+ const deps = {
88
+ state: compositionDeps.state,
89
+ context: compositionDeps.context,
90
+ config: {
91
+ ...compositionDeps.config,
92
+ sourceBufferActor,
93
+ fetch: fetchBytes
94
+ }
95
+ };
96
+ const pipelines = (config.messagePipelines ?? DEFAULT_MESSAGE_PIPELINES)();
136
97
  const getBufferedSegments = (allSegments) => {
137
- const bufferedIds = new Set(peek(sourceBufferActor.snapshot).context.segments.filter((s) => !s.partial).map((s) => s.id));
138
- return allSegments.filter((s) => bufferedIds.has(s.id));
98
+ const merged = mergeTimeRanges(peek(sourceBufferActor.snapshot).context.segments.filter((s) => !s.partial).map((s) => ({
99
+ start: s.startTime,
100
+ end: s.startTime + s.duration
101
+ })));
102
+ return allSegments.filter((s) => isTimeRangeCovered(s.startTime, s.startTime + s.duration, merged));
139
103
  };
140
104
  /**
141
105
  * Translate a load message into an ordered LoadTask list based on committed
@@ -236,8 +200,8 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes, config = {}) {
236
200
  runner.schedule(makeLoadTask(op, {
237
201
  getContext,
238
202
  setContext,
239
- fetchBytes,
240
- sourceBufferActor
203
+ pipelines,
204
+ deps
241
205
  })).then(void 0, (e) => {
242
206
  if (e instanceof Error && e.name === "AbortError") return;
243
207
  console.error("Unexpected error in segment loader:", e);
@@ -250,7 +214,7 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes, config = {}) {
250
214
  initial: "idle",
251
215
  context: {
252
216
  inFlightInitTrackId: null,
253
- inFlightSegmentId: null
217
+ inFlightSegment: null
254
218
  },
255
219
  states: {
256
220
  idle: { on: { load: (msg, ctx) => {
@@ -264,12 +228,14 @@ function createSegmentLoaderActor(sourceBufferActor, fetchBytes, config = {}) {
264
228
  on: { load: (msg, ctx) => {
265
229
  const { context, runner } = ctx;
266
230
  const allTasks = planTasks(msg);
267
- if (context.inFlightSegmentId !== null && allTasks.some((t) => t.type === "append-segment" && t.meta.id === context.inFlightSegmentId) || context.inFlightInitTrackId !== null && allTasks.some((t) => t.type === "append-init" && t.meta.trackId === context.inFlightInitTrackId)) {
231
+ const inFlight = context.inFlightSegment;
232
+ const segmentInFlightStillNeeded = (t) => t.type === "append-segment" && inFlight !== null && t.meta.id === inFlight.id && t.meta.trackId === inFlight.trackId;
233
+ if (inFlight !== null && allTasks.some(segmentInFlightStillNeeded) || context.inFlightInitTrackId !== null && allTasks.some((t) => t.type === "append-init" && t.meta.trackId === context.inFlightInitTrackId)) {
268
234
  runner.abortPending();
269
- scheduleAll(allTasks.filter((t) => !(t.type === "append-segment" && t.meta.id === context.inFlightSegmentId) && !(t.type === "append-init" && t.meta.trackId === context.inFlightInitTrackId)), ctx);
235
+ scheduleAll(allTasks.filter((t) => !segmentInFlightStillNeeded(t) && !(t.type === "append-init" && t.meta.trackId === context.inFlightInitTrackId)), ctx);
270
236
  } else {
271
237
  runner.abortAll();
272
- if (context.inFlightSegmentId !== null || context.inFlightInitTrackId !== null && allTasks.some((t) => t.type === "append-init" && t.meta.trackId !== context.inFlightInitTrackId)) sourceBufferActor.send({ type: "cancel" });
238
+ if (context.inFlightSegment !== null || context.inFlightInitTrackId !== null && allTasks.some((t) => t.type === "append-init" && t.meta.trackId !== context.inFlightInitTrackId)) sourceBufferActor.send({ type: "cancel" });
273
239
  scheduleAll(allTasks, ctx);
274
240
  }
275
241
  } }