@videojs/spf 10.0.0-beta.9 → 10.0.0-rc.2

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 (556) hide show
  1. package/README.md +19 -38
  2. package/dist/default/core/actors/create-machine-actor.js +89 -0
  3. package/dist/default/core/actors/create-machine-actor.js.map +1 -0
  4. package/dist/default/core/actors/create-transition-actor.js +44 -0
  5. package/dist/default/core/actors/create-transition-actor.js.map +1 -0
  6. package/dist/default/core/composition/create-composition.js +82 -0
  7. package/dist/default/core/composition/create-composition.js.map +1 -0
  8. package/dist/default/core/composition/share-signals.js +34 -0
  9. package/dist/default/core/composition/share-signals.js.map +1 -0
  10. package/dist/default/core/machine.js +25 -0
  11. package/dist/default/core/machine.js.map +1 -0
  12. package/dist/default/core/reactors/create-machine-reactor.js +84 -0
  13. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
  14. package/dist/default/core/signals/effect.js +61 -0
  15. package/dist/default/core/signals/effect.js.map +1 -0
  16. package/dist/default/core/signals/primitives.js +50 -0
  17. package/dist/default/core/signals/primitives.js.map +1 -0
  18. package/dist/default/core/signals/when.js +36 -0
  19. package/dist/default/core/signals/when.js.map +1 -0
  20. package/dist/default/core/tasks/delayed-reschedule.js +27 -0
  21. package/dist/default/core/tasks/delayed-reschedule.js.map +1 -0
  22. package/dist/default/core/tasks/task.js +298 -0
  23. package/dist/default/core/tasks/task.js.map +1 -0
  24. package/dist/default/dom.js +9 -0
  25. package/dist/default/hls-audio.js +3 -0
  26. package/dist/default/hls-background-video.js +3 -0
  27. package/dist/default/hls-video.js +4 -0
  28. package/dist/default/hls.js +10 -0
  29. package/dist/default/index.js +22 -0
  30. package/dist/default/index.js.map +1 -0
  31. package/dist/default/media/abr/quality-selection.js +17 -0
  32. package/dist/default/media/abr/quality-selection.js.map +1 -0
  33. package/dist/default/media/buffer/back-buffer.js +42 -0
  34. package/dist/default/media/buffer/back-buffer.js.map +1 -0
  35. package/dist/default/media/buffer/forward-buffer.js +110 -0
  36. package/dist/default/media/buffer/forward-buffer.js.map +1 -0
  37. package/dist/default/media/dom/capabilities.js +61 -0
  38. package/dist/default/media/dom/capabilities.js.map +1 -0
  39. package/dist/default/media/dom/mse/append-segment.js +58 -0
  40. package/dist/default/media/dom/mse/append-segment.js.map +1 -0
  41. package/dist/default/media/dom/mse/buffer-flusher.js +55 -0
  42. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
  43. package/dist/default/media/dom/mse/duration.js +87 -0
  44. package/dist/default/media/dom/mse/duration.js.map +1 -0
  45. package/dist/default/media/dom/mse/end-of-stream.js +17 -0
  46. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
  47. package/dist/default/media/dom/mse/mediasource-setup.js +227 -0
  48. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
  49. package/dist/default/media/dom/screen.js +95 -0
  50. package/dist/default/media/dom/screen.js.map +1 -0
  51. package/dist/default/media/dom/text/resolve-vtt-segment.js +50 -0
  52. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
  53. package/dist/default/media/dom/text/text-track-slots.js +61 -0
  54. package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
  55. package/dist/default/media/errors.js +60 -0
  56. package/dist/default/media/errors.js.map +1 -0
  57. package/dist/default/media/hls/parse-attributes.js +131 -0
  58. package/dist/default/media/hls/parse-attributes.js.map +1 -0
  59. package/dist/default/media/hls/parse-media-playlist.js +253 -0
  60. package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
  61. package/dist/default/media/hls/parse-multivariant.js +234 -0
  62. package/dist/default/media/hls/parse-multivariant.js.map +1 -0
  63. package/dist/default/media/hls/reload-policy.js +63 -0
  64. package/dist/default/media/hls/reload-policy.js.map +1 -0
  65. package/dist/default/media/hls/resolve-url.js +9 -0
  66. package/dist/default/media/hls/resolve-url.js.map +1 -0
  67. package/dist/default/media/live-window.js +32 -0
  68. package/dist/default/media/live-window.js.map +1 -0
  69. package/dist/default/media/media-tracks/media-tracks.js +96 -0
  70. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  71. package/dist/default/media/mp4/box.js +57 -0
  72. package/dist/default/media/mp4/box.js.map +1 -0
  73. package/dist/default/media/mp4/timestamp-origin.js +95 -0
  74. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  75. package/dist/default/media/primitives/resolution.js +26 -0
  76. package/dist/default/media/primitives/resolution.js.map +1 -0
  77. package/dist/default/media/primitives/select-tracks.js +97 -0
  78. package/dist/default/media/primitives/select-tracks.js.map +1 -0
  79. package/dist/default/media/text/parse-vtt-timestamp-map.js +40 -0
  80. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  81. package/dist/default/media/text/resolve-vtt-metadata.js +22 -0
  82. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  83. package/dist/default/media/types/index.js +42 -0
  84. package/dist/default/media/types/index.js.map +1 -0
  85. package/dist/default/media/utils/cdn.js +50 -0
  86. package/dist/default/media/utils/cdn.js.map +1 -0
  87. package/dist/default/media/utils/preload.js +21 -0
  88. package/dist/default/media/utils/preload.js.map +1 -0
  89. package/dist/default/media/utils/track-selection.js +43 -0
  90. package/dist/default/media/utils/track-selection.js.map +1 -0
  91. package/dist/default/media/utils/tracks.js +146 -0
  92. package/dist/default/media/utils/tracks.js.map +1 -0
  93. package/dist/default/media-tracks.js +2 -0
  94. package/dist/default/network/bandwidth-estimator.js +95 -0
  95. package/dist/default/network/bandwidth-estimator.js.map +1 -0
  96. package/dist/default/network/chunked-stream-iterable.js +47 -0
  97. package/dist/default/network/chunked-stream-iterable.js.map +1 -0
  98. package/dist/default/network/ewma.js +68 -0
  99. package/dist/default/network/ewma.js.map +1 -0
  100. package/dist/default/network/fetch.js +108 -0
  101. package/dist/default/network/fetch.js.map +1 -0
  102. package/dist/default/playback/actors/dom/segment-loader.js +233 -0
  103. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
  104. package/dist/default/playback/actors/dom/source-buffer.js +139 -0
  105. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
  106. package/dist/default/playback/actors/dom/text-tracks.js +46 -0
  107. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
  108. package/dist/default/playback/actors/text-track-segment-loader.js +138 -0
  109. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
  110. package/dist/default/playback/adapters/hls-audio/adapter.js +8 -0
  111. package/dist/default/playback/adapters/hls-audio/adapter.js.map +1 -0
  112. package/dist/default/playback/adapters/hls-audio/mixin.js +178 -0
  113. package/dist/default/playback/adapters/hls-audio/mixin.js.map +1 -0
  114. package/dist/default/playback/adapters/hls-background-video/adapter.js +17 -0
  115. package/dist/default/playback/adapters/hls-background-video/adapter.js.map +1 -0
  116. package/dist/default/playback/adapters/hls-background-video/host.js +56 -0
  117. package/dist/default/playback/adapters/hls-background-video/host.js.map +1 -0
  118. package/dist/default/playback/adapters/hls-background-video/mixin.js +191 -0
  119. package/dist/default/playback/adapters/hls-background-video/mixin.js.map +1 -0
  120. package/dist/default/playback/adapters/hls-video/adapter.js +11 -0
  121. package/dist/default/playback/adapters/hls-video/adapter.js.map +1 -0
  122. package/dist/default/playback/adapters/hls-video/error-surface.js +46 -0
  123. package/dist/default/playback/adapters/hls-video/error-surface.js.map +1 -0
  124. package/dist/default/playback/adapters/hls-video/media-tracks.js +125 -0
  125. package/dist/default/playback/adapters/hls-video/media-tracks.js.map +1 -0
  126. package/dist/default/playback/adapters/hls-video/mixin.js +299 -0
  127. package/dist/default/playback/adapters/hls-video/mixin.js.map +1 -0
  128. package/dist/default/playback/behaviors/calculate-presentation-duration.js +33 -0
  129. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
  130. package/dist/default/playback/behaviors/collect-errors.js +98 -0
  131. package/dist/default/playback/behaviors/collect-errors.js.map +1 -0
  132. package/dist/default/playback/behaviors/derive-cdn-priority.js +61 -0
  133. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
  134. package/dist/default/playback/behaviors/dom/airplay.js +188 -0
  135. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  136. package/dist/default/playback/behaviors/dom/apply-start-position.js +81 -0
  137. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  138. package/dist/default/playback/behaviors/dom/end-of-stream.js +97 -0
  139. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
  140. package/dist/default/playback/behaviors/dom/load-segments.js +169 -0
  141. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
  142. package/dist/default/playback/behaviors/dom/recover-end-stall.js +72 -0
  143. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  144. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js +113 -0
  145. package/dist/default/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  146. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +181 -0
  147. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
  148. package/dist/default/playback/behaviors/dom/setup-mediasource.js +100 -0
  149. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
  150. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +40 -0
  151. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
  152. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js +25 -0
  153. package/dist/default/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  154. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +109 -0
  155. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
  156. package/dist/default/playback/behaviors/dom/track-current-time.js +50 -0
  157. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
  158. package/dist/default/playback/behaviors/dom/track-load-triggers.js +86 -0
  159. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
  160. package/dist/default/playback/behaviors/dom/track-playback-rate.js +35 -0
  161. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
  162. package/dist/default/playback/behaviors/dom/track-player-resolution.js +53 -0
  163. package/dist/default/playback/behaviors/dom/track-player-resolution.js.map +1 -0
  164. package/dist/default/playback/behaviors/dom/track-screen-resolution.js +28 -0
  165. package/dist/default/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
  166. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +98 -0
  167. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
  168. package/dist/default/playback/behaviors/establish-start-media-time.js +187 -0
  169. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  170. package/dist/default/playback/behaviors/resolve-presentation.js +76 -0
  171. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
  172. package/dist/default/playback/behaviors/resolve-track.js +134 -0
  173. package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
  174. package/dist/default/playback/behaviors/select-tracks.js +189 -0
  175. package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
  176. package/dist/default/playback/behaviors/setup-failover-monitor.js +61 -0
  177. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
  178. package/dist/default/playback/behaviors/sync-preload.js +59 -0
  179. package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
  180. package/dist/default/playback/behaviors/track-switching.js +407 -0
  181. package/dist/default/playback/behaviors/track-switching.js.map +1 -0
  182. package/dist/default/playback/engines/hls/engine-audio-only.js +94 -0
  183. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
  184. package/dist/default/playback/engines/hls/engine-background-video.js +87 -0
  185. package/dist/default/playback/engines/hls/engine-background-video.js.map +1 -0
  186. package/dist/default/playback/engines/hls/engine.js +144 -0
  187. package/dist/default/playback/engines/hls/engine.js.map +1 -0
  188. package/dist/default/playback/primitives/error-messages.js +42 -0
  189. package/dist/default/playback/primitives/error-messages.js.map +1 -0
  190. package/dist/default/playback/primitives/failover-fetch.js +37 -0
  191. package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
  192. package/dist/default/playback/primitives/head-peek.js +41 -0
  193. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  194. package/dist/default/playback/primitives/live-window.js +63 -0
  195. package/dist/default/playback/primitives/live-window.js.map +1 -0
  196. package/dist/default/playback/primitives/relocation-pipelines.js +191 -0
  197. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  198. package/dist/default/playback/primitives/report-track-conditions.js +74 -0
  199. package/dist/default/playback/primitives/report-track-conditions.js.map +1 -0
  200. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  201. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  202. package/dist/default/playback/primitives/selection-rules.js +115 -0
  203. package/dist/default/playback/primitives/selection-rules.js.map +1 -0
  204. package/dist/default/playback/primitives/text-segment-load-pipeline.js +36 -0
  205. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  206. package/dist/default/playback/primitives/track-types.js +53 -0
  207. package/dist/default/playback/primitives/track-types.js.map +1 -0
  208. package/dist/dev/core/actors/actor.d.ts +26 -0
  209. package/dist/dev/core/actors/actor.d.ts.map +1 -0
  210. package/dist/dev/core/actors/create-machine-actor.d.ts +119 -0
  211. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
  212. package/dist/dev/core/actors/create-machine-actor.js +89 -0
  213. package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
  214. package/dist/dev/core/actors/create-transition-actor.d.ts +37 -0
  215. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
  216. package/dist/dev/core/actors/create-transition-actor.js +44 -0
  217. package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
  218. package/dist/dev/core/composition/create-composition.d.ts +241 -0
  219. package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
  220. package/dist/dev/core/composition/create-composition.js +82 -0
  221. package/dist/dev/core/composition/create-composition.js.map +1 -0
  222. package/dist/dev/core/composition/share-signals.d.ts +39 -0
  223. package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
  224. package/dist/dev/core/composition/share-signals.js +34 -0
  225. package/dist/dev/core/composition/share-signals.js.map +1 -0
  226. package/dist/dev/core/machine.d.ts +21 -0
  227. package/dist/dev/core/machine.d.ts.map +1 -0
  228. package/dist/dev/core/machine.js +25 -0
  229. package/dist/dev/core/machine.js.map +1 -0
  230. package/dist/dev/core/reactors/create-machine-reactor.d.ts +87 -0
  231. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
  232. package/dist/dev/core/reactors/create-machine-reactor.js +84 -0
  233. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
  234. package/dist/dev/core/signals/effect.d.ts +14 -0
  235. package/dist/dev/core/signals/effect.d.ts.map +1 -0
  236. package/dist/dev/core/signals/effect.js +61 -0
  237. package/dist/dev/core/signals/effect.js.map +1 -0
  238. package/dist/dev/core/signals/primitives.d.ts +44 -0
  239. package/dist/dev/core/signals/primitives.d.ts.map +1 -0
  240. package/dist/dev/core/signals/primitives.js +50 -0
  241. package/dist/dev/core/signals/primitives.js.map +1 -0
  242. package/dist/dev/core/signals/when.js +36 -0
  243. package/dist/dev/core/signals/when.js.map +1 -0
  244. package/dist/dev/core/tasks/delayed-reschedule.js +27 -0
  245. package/dist/dev/core/tasks/delayed-reschedule.js.map +1 -0
  246. package/dist/dev/core/tasks/task.d.ts +144 -0
  247. package/dist/dev/core/tasks/task.d.ts.map +1 -0
  248. package/dist/dev/core/tasks/task.js +298 -0
  249. package/dist/dev/core/tasks/task.js.map +1 -0
  250. package/dist/dev/dom.d.ts +9 -0
  251. package/dist/dev/dom.js +9 -0
  252. package/dist/dev/hls-audio.d.ts +3 -0
  253. package/dist/dev/hls-audio.js +3 -0
  254. package/dist/dev/hls-background-video.d.ts +4 -0
  255. package/dist/dev/hls-background-video.js +3 -0
  256. package/dist/dev/hls-video.d.ts +5 -0
  257. package/dist/dev/hls-video.js +4 -0
  258. package/dist/dev/hls.d.ts +11 -0
  259. package/dist/dev/hls.js +10 -0
  260. package/dist/dev/index.d.ts +23 -0
  261. package/dist/dev/index.d.ts.map +1 -0
  262. package/dist/dev/index.js +22 -0
  263. package/dist/dev/index.js.map +1 -0
  264. package/dist/dev/media/abr/quality-selection.d.ts +19 -0
  265. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
  266. package/dist/dev/media/abr/quality-selection.js +17 -0
  267. package/dist/dev/media/abr/quality-selection.js.map +1 -0
  268. package/dist/dev/media/buffer/back-buffer.d.ts +9 -0
  269. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
  270. package/dist/dev/media/buffer/back-buffer.js +42 -0
  271. package/dist/dev/media/buffer/back-buffer.js.map +1 -0
  272. package/dist/dev/media/buffer/forward-buffer.d.ts +9 -0
  273. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
  274. package/dist/dev/media/buffer/forward-buffer.js +110 -0
  275. package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
  276. package/dist/dev/media/dom/capabilities.js +61 -0
  277. package/dist/dev/media/dom/capabilities.js.map +1 -0
  278. package/dist/dev/media/dom/mse/append-segment.d.ts +16 -0
  279. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
  280. package/dist/dev/media/dom/mse/append-segment.js +58 -0
  281. package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
  282. package/dist/dev/media/dom/mse/buffer-flusher.d.ts +24 -0
  283. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
  284. package/dist/dev/media/dom/mse/buffer-flusher.js +55 -0
  285. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
  286. package/dist/dev/media/dom/mse/duration.js +87 -0
  287. package/dist/dev/media/dom/mse/duration.js.map +1 -0
  288. package/dist/dev/media/dom/mse/end-of-stream.js +17 -0
  289. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
  290. package/dist/dev/media/dom/mse/mediasource-setup.js +227 -0
  291. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
  292. package/dist/dev/media/dom/screen.d.ts +7 -0
  293. package/dist/dev/media/dom/screen.d.ts.map +1 -0
  294. package/dist/dev/media/dom/screen.js +95 -0
  295. package/dist/dev/media/dom/screen.js.map +1 -0
  296. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +6 -0
  297. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
  298. package/dist/dev/media/dom/text/resolve-vtt-segment.js +50 -0
  299. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
  300. package/dist/dev/media/dom/text/text-track-slots.d.ts +24 -0
  301. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
  302. package/dist/dev/media/dom/text/text-track-slots.js +61 -0
  303. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
  304. package/dist/dev/media/errors.d.ts +68 -0
  305. package/dist/dev/media/errors.d.ts.map +1 -0
  306. package/dist/dev/media/errors.js +60 -0
  307. package/dist/dev/media/errors.js.map +1 -0
  308. package/dist/dev/media/hls/parse-attributes.js +131 -0
  309. package/dist/dev/media/hls/parse-attributes.js.map +1 -0
  310. package/dist/dev/media/hls/parse-media-playlist.js +253 -0
  311. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
  312. package/dist/dev/media/hls/parse-multivariant.js +234 -0
  313. package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
  314. package/dist/dev/media/hls/reload-policy.js +63 -0
  315. package/dist/dev/media/hls/reload-policy.js.map +1 -0
  316. package/dist/dev/media/hls/resolve-url.js +9 -0
  317. package/dist/dev/media/hls/resolve-url.js.map +1 -0
  318. package/dist/dev/media/live-window.js +32 -0
  319. package/dist/dev/media/live-window.js.map +1 -0
  320. package/dist/dev/media/media-tracks/media-tracks.d.ts +46 -0
  321. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  322. package/dist/dev/media/media-tracks/media-tracks.js +96 -0
  323. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  324. package/dist/dev/media/mp4/box.js +57 -0
  325. package/dist/dev/media/mp4/box.js.map +1 -0
  326. package/dist/dev/media/mp4/timestamp-origin.js +95 -0
  327. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  328. package/dist/dev/media/primitives/resolution.d.ts +22 -0
  329. package/dist/dev/media/primitives/resolution.d.ts.map +1 -0
  330. package/dist/dev/media/primitives/resolution.js +26 -0
  331. package/dist/dev/media/primitives/resolution.js.map +1 -0
  332. package/dist/dev/media/primitives/select-tracks.js +97 -0
  333. package/dist/dev/media/primitives/select-tracks.js.map +1 -0
  334. package/dist/dev/media/text/parse-vtt-timestamp-map.js +40 -0
  335. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  336. package/dist/dev/media/text/resolve-vtt-metadata.js +22 -0
  337. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  338. package/dist/dev/media/types/index.d.ts +306 -0
  339. package/dist/dev/media/types/index.d.ts.map +1 -0
  340. package/dist/dev/media/types/index.js +42 -0
  341. package/dist/dev/media/types/index.js.map +1 -0
  342. package/dist/dev/media/utils/cdn.d.ts +11 -0
  343. package/dist/dev/media/utils/cdn.d.ts.map +1 -0
  344. package/dist/dev/media/utils/cdn.js +50 -0
  345. package/dist/dev/media/utils/cdn.js.map +1 -0
  346. package/dist/dev/media/utils/preload.js +21 -0
  347. package/dist/dev/media/utils/preload.js.map +1 -0
  348. package/dist/dev/media/utils/track-selection.js +43 -0
  349. package/dist/dev/media/utils/track-selection.js.map +1 -0
  350. package/dist/dev/media/utils/tracks.js +146 -0
  351. package/dist/dev/media/utils/tracks.js.map +1 -0
  352. package/dist/dev/media-tracks.d.ts +3 -0
  353. package/dist/dev/media-tracks.js +2 -0
  354. package/dist/dev/network/bandwidth-estimator.d.ts +48 -0
  355. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
  356. package/dist/dev/network/bandwidth-estimator.js +95 -0
  357. package/dist/dev/network/bandwidth-estimator.js.map +1 -0
  358. package/dist/dev/network/chunked-stream-iterable.js +47 -0
  359. package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
  360. package/dist/dev/network/ewma.js +68 -0
  361. package/dist/dev/network/ewma.js.map +1 -0
  362. package/dist/dev/network/fetch.js +108 -0
  363. package/dist/dev/network/fetch.js.map +1 -0
  364. package/dist/dev/playback/actors/dom/segment-loader.d.ts +44 -0
  365. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
  366. package/dist/dev/playback/actors/dom/segment-loader.js +233 -0
  367. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
  368. package/dist/dev/playback/actors/dom/source-buffer.d.ts +45 -0
  369. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
  370. package/dist/dev/playback/actors/dom/source-buffer.js +139 -0
  371. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
  372. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -0
  373. package/dist/dev/playback/actors/dom/text-tracks.js +46 -0
  374. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
  375. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +48 -0
  376. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
  377. package/dist/dev/playback/actors/text-track-segment-loader.js +138 -0
  378. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
  379. package/dist/dev/playback/actors/text-tracks.d.ts +30 -0
  380. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
  381. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts +11 -0
  382. package/dist/dev/playback/adapters/hls-audio/adapter.d.ts.map +1 -0
  383. package/dist/dev/playback/adapters/hls-audio/adapter.js +8 -0
  384. package/dist/dev/playback/adapters/hls-audio/adapter.js.map +1 -0
  385. package/dist/dev/playback/adapters/hls-audio/mixin.d.ts +52 -0
  386. package/dist/dev/playback/adapters/hls-audio/mixin.d.ts.map +1 -0
  387. package/dist/dev/playback/adapters/hls-audio/mixin.js +178 -0
  388. package/dist/dev/playback/adapters/hls-audio/mixin.js.map +1 -0
  389. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts +19 -0
  390. package/dist/dev/playback/adapters/hls-background-video/adapter.d.ts.map +1 -0
  391. package/dist/dev/playback/adapters/hls-background-video/adapter.js +17 -0
  392. package/dist/dev/playback/adapters/hls-background-video/adapter.js.map +1 -0
  393. package/dist/dev/playback/adapters/hls-background-video/host.d.ts +34 -0
  394. package/dist/dev/playback/adapters/hls-background-video/host.d.ts.map +1 -0
  395. package/dist/dev/playback/adapters/hls-background-video/host.js +56 -0
  396. package/dist/dev/playback/adapters/hls-background-video/host.js.map +1 -0
  397. package/dist/dev/playback/adapters/hls-background-video/mixin.d.ts +69 -0
  398. package/dist/dev/playback/adapters/hls-background-video/mixin.d.ts.map +1 -0
  399. package/dist/dev/playback/adapters/hls-background-video/mixin.js +191 -0
  400. package/dist/dev/playback/adapters/hls-background-video/mixin.js.map +1 -0
  401. package/dist/dev/playback/adapters/hls-video/adapter.d.ts +11 -0
  402. package/dist/dev/playback/adapters/hls-video/adapter.d.ts.map +1 -0
  403. package/dist/dev/playback/adapters/hls-video/adapter.js +11 -0
  404. package/dist/dev/playback/adapters/hls-video/adapter.js.map +1 -0
  405. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts +17 -0
  406. package/dist/dev/playback/adapters/hls-video/error-surface.d.ts.map +1 -0
  407. package/dist/dev/playback/adapters/hls-video/error-surface.js +46 -0
  408. package/dist/dev/playback/adapters/hls-video/error-surface.js.map +1 -0
  409. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts +21 -0
  410. package/dist/dev/playback/adapters/hls-video/media-tracks.d.ts.map +1 -0
  411. package/dist/dev/playback/adapters/hls-video/media-tracks.js +125 -0
  412. package/dist/dev/playback/adapters/hls-video/media-tracks.js.map +1 -0
  413. package/dist/dev/playback/adapters/hls-video/mixin.d.ts +62 -0
  414. package/dist/dev/playback/adapters/hls-video/mixin.d.ts.map +1 -0
  415. package/dist/dev/playback/adapters/hls-video/mixin.js +299 -0
  416. package/dist/dev/playback/adapters/hls-video/mixin.js.map +1 -0
  417. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +23 -0
  418. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
  419. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +33 -0
  420. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
  421. package/dist/dev/playback/behaviors/collect-errors.d.ts +2 -0
  422. package/dist/dev/playback/behaviors/collect-errors.js +98 -0
  423. package/dist/dev/playback/behaviors/collect-errors.js.map +1 -0
  424. package/dist/dev/playback/behaviors/derive-cdn-priority.js +61 -0
  425. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
  426. package/dist/dev/playback/behaviors/dom/airplay.js +188 -0
  427. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  428. package/dist/dev/playback/behaviors/dom/apply-start-position.js +81 -0
  429. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  430. package/dist/dev/playback/behaviors/dom/end-of-stream.js +97 -0
  431. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
  432. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +69 -0
  433. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
  434. package/dist/dev/playback/behaviors/dom/load-segments.js +169 -0
  435. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
  436. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +72 -0
  437. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  438. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js +113 -0
  439. package/dist/dev/playback/behaviors/dom/seek-to-live-edge.js.map +1 -0
  440. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +181 -0
  441. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
  442. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +100 -0
  443. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
  444. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +28 -0
  445. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
  446. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +40 -0
  447. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
  448. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js +25 -0
  449. package/dist/dev/playback/behaviors/dom/sync-live-seekable-range.js.map +1 -0
  450. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +109 -0
  451. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
  452. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +33 -0
  453. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
  454. package/dist/dev/playback/behaviors/dom/track-current-time.js +50 -0
  455. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
  456. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +41 -0
  457. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
  458. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +86 -0
  459. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
  460. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +33 -0
  461. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
  462. package/dist/dev/playback/behaviors/dom/track-playback-rate.js +35 -0
  463. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
  464. package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts +8 -0
  465. package/dist/dev/playback/behaviors/dom/track-player-resolution.d.ts.map +1 -0
  466. package/dist/dev/playback/behaviors/dom/track-player-resolution.js +53 -0
  467. package/dist/dev/playback/behaviors/dom/track-player-resolution.js.map +1 -0
  468. package/dist/dev/playback/behaviors/dom/track-screen-resolution.js +28 -0
  469. package/dist/dev/playback/behaviors/dom/track-screen-resolution.js.map +1 -0
  470. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +98 -0
  471. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
  472. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +32 -0
  473. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  474. package/dist/dev/playback/behaviors/establish-start-media-time.js +187 -0
  475. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  476. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +8 -0
  477. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
  478. package/dist/dev/playback/behaviors/resolve-presentation.js +76 -0
  479. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
  480. package/dist/dev/playback/behaviors/resolve-track.js +134 -0
  481. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
  482. package/dist/dev/playback/behaviors/select-tracks.d.ts +61 -0
  483. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
  484. package/dist/dev/playback/behaviors/select-tracks.js +189 -0
  485. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
  486. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +11 -0
  487. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
  488. package/dist/dev/playback/behaviors/setup-failover-monitor.js +61 -0
  489. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
  490. package/dist/dev/playback/behaviors/sync-preload.js +59 -0
  491. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
  492. package/dist/dev/playback/behaviors/track-switching.d.ts +110 -0
  493. package/dist/dev/playback/behaviors/track-switching.d.ts.map +1 -0
  494. package/dist/dev/playback/behaviors/track-switching.js +407 -0
  495. package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
  496. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +160 -0
  497. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
  498. package/dist/dev/playback/engines/hls/engine-audio-only.js +94 -0
  499. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
  500. package/dist/dev/playback/engines/hls/engine-background-video.d.ts +131 -0
  501. package/dist/dev/playback/engines/hls/engine-background-video.d.ts.map +1 -0
  502. package/dist/dev/playback/engines/hls/engine-background-video.js +87 -0
  503. package/dist/dev/playback/engines/hls/engine-background-video.js.map +1 -0
  504. package/dist/dev/playback/engines/hls/engine.d.ts +289 -0
  505. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
  506. package/dist/dev/playback/engines/hls/engine.js +144 -0
  507. package/dist/dev/playback/engines/hls/engine.js.map +1 -0
  508. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  509. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  510. package/dist/dev/playback/primitives/error-messages.js +42 -0
  511. package/dist/dev/playback/primitives/error-messages.js.map +1 -0
  512. package/dist/dev/playback/primitives/failover-fetch.js +37 -0
  513. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
  514. package/dist/dev/playback/primitives/head-peek.js +41 -0
  515. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  516. package/dist/dev/playback/primitives/live-window.js +63 -0
  517. package/dist/dev/playback/primitives/live-window.js.map +1 -0
  518. package/dist/dev/playback/primitives/relocation-pipelines.js +191 -0
  519. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  520. package/dist/dev/playback/primitives/report-track-conditions.d.ts +8 -0
  521. package/dist/dev/playback/primitives/report-track-conditions.d.ts.map +1 -0
  522. package/dist/dev/playback/primitives/report-track-conditions.js +74 -0
  523. package/dist/dev/playback/primitives/report-track-conditions.js.map +1 -0
  524. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  525. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  526. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  527. package/dist/dev/playback/primitives/selection-rules.d.ts +66 -0
  528. package/dist/dev/playback/primitives/selection-rules.d.ts.map +1 -0
  529. package/dist/dev/playback/primitives/selection-rules.js +115 -0
  530. package/dist/dev/playback/primitives/selection-rules.js.map +1 -0
  531. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +40 -0
  532. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  533. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +56 -0
  534. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  535. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +36 -0
  536. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  537. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  538. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  539. package/dist/dev/playback/primitives/track-types.js +53 -0
  540. package/dist/dev/playback/primitives/track-types.js.map +1 -0
  541. package/package.json +60 -41
  542. package/dist/adapter-25AtMN8s.d.ts +0 -719
  543. package/dist/adapter-25AtMN8s.d.ts.map +0 -1
  544. package/dist/adapter-CflgYzjF.js +0 -3556
  545. package/dist/adapter-CflgYzjF.js.map +0 -1
  546. package/dist/dom/index.d.ts +0 -221
  547. package/dist/dom/index.d.ts.map +0 -1
  548. package/dist/dom/index.js +0 -38
  549. package/dist/dom/index.js.map +0 -1
  550. package/dist/dom/playback-engine/index.d.ts +0 -2
  551. package/dist/dom/playback-engine/index.js +0 -4
  552. package/dist/index.d.ts +0 -13
  553. package/dist/index.d.ts.map +0 -1
  554. package/dist/index.js +0 -3
  555. package/dist/src-DRsS_H80.js +0 -14
  556. package/dist/src-DRsS_H80.js.map +0 -1
@@ -0,0 +1,146 @@
1
+ import { isResolvedTrack } from "../types/index.js";
2
+ //#region src/media/utils/tracks.ts
3
+ /**
4
+ * Get the tracks of the given type from a presentation's first switching set.
5
+ *
6
+ * Returns `[]` when the presentation is unresolved, when no selection set of `type` exists, or when its first switching
7
+ * set is empty. Returned tracks may be partially resolved (URL only) or fully resolved (with segments) — callers narrow
8
+ * as needed.
9
+ *
10
+ * The "first switching set" assumption matches the rest of the codebase (HLS typically has one switching set per type);
11
+ * multi-group / multi-period support would generalize this.
12
+ */
13
+ function getTracksByType(presentation, type) {
14
+ return presentation.selectionSets?.find(({ type: t }) => t === type)?.switchingSets[0]?.tracks ?? [];
15
+ }
16
+ /**
17
+ * Find a track of the given type and id within a presentation.
18
+ *
19
+ * Returns the matching track from the first switching set of the matching selection set, or `undefined` if either is
20
+ * missing. The returned track may be partially resolved (URL only) or fully resolved (with segments) — callers narrow
21
+ * as needed.
22
+ */
23
+ function findTrack(presentation, type, trackId) {
24
+ return getTracksByType(presentation, type).find(({ id }) => id === trackId);
25
+ }
26
+ /**
27
+ * Find a track by id across all selection sets in a presentation, without knowing its type up front. Used when the
28
+ * caller has a track id obtained from a downstream consumer (e.g. `SourceBufferActor.initTrackId`) and needs to locate
29
+ * the corresponding track in the presentation.
30
+ *
31
+ * Track ids are unique within a presentation per the HLS spec; the first match wins.
32
+ */
33
+ function findTrackById(presentation, trackId) {
34
+ for (const selectionSet of presentation.selectionSets ?? []) {
35
+ const track = selectionSet.switchingSets[0]?.tracks.find(({ id }) => id === trackId);
36
+ if (track) return track;
37
+ }
38
+ }
39
+ /**
40
+ * Find a text track of the given id within a presentation and narrow it to the fully-resolved `TextTrack` shape
41
+ * (segments populated). Returns `undefined` if no track matches the id, the matching track isn't a text track, or it
42
+ * hasn't been resolved yet.
43
+ *
44
+ * The segments-non-empty check stays at the call site — a resolved track with zero segments is a valid state, distinct
45
+ * from "ready to load."
46
+ */
47
+ function findResolvedTextTrack(presentation, trackId) {
48
+ if (!presentation || !trackId) return void 0;
49
+ const track = findTrack(presentation, "text", trackId);
50
+ if (track?.type !== "text" || !isResolvedTrack(track)) return void 0;
51
+ return track;
52
+ }
53
+ function findResolvedVideoTrack(presentation, trackId) {
54
+ if (!presentation || !trackId) return void 0;
55
+ const track = findTrack(presentation, "video", trackId);
56
+ if (track?.type !== "video" || !isResolvedTrack(track)) return void 0;
57
+ return track;
58
+ }
59
+ function findResolvedAudioTrack(presentation, trackId) {
60
+ if (!presentation || !trackId) return void 0;
61
+ const track = findTrack(presentation, "audio", trackId);
62
+ if (track?.type !== "audio" || !isResolvedTrack(track)) return void 0;
63
+ return track;
64
+ }
65
+ /**
66
+ * Whether a track carries a non-empty `codecs` array. Both partially- resolved and fully-resolved tracks may carry
67
+ * codecs — they come from the multivariant playlist's `EXT-X-STREAM-INF` line, not from the per-type media playlist —
68
+ * so this works at either resolution stage.
69
+ *
70
+ * `TextTrack` doesn't declare a `codecs` field; the `'codecs' in track` check narrows it out for the false branch.
71
+ */
72
+ function hasCodecs(track) {
73
+ return !!track && "codecs" in track && !!track.codecs?.length;
74
+ }
75
+ /**
76
+ * The family (RFC 6381 4CC) of one codec string: `'avc1.640028'` → `'avc1'`, `'mp4a.40.2'` → `'mp4a'`, dotless strings
77
+ * (`'ec-3'`) pass through whole. Lowercased so families compare regardless of manifest casing.
78
+ *
79
+ * The family is what a `SourceBuffer`'s bytestream is keyed on: renditions in one family swap via a new init segment,
80
+ * while crossing families needs `SourceBuffer.changeType()` (which SPF doesn't implement).
81
+ */
82
+ function getCodecFamily(codec) {
83
+ return codec.trim().split(".", 1)[0].toLowerCase();
84
+ }
85
+ /**
86
+ * The distinct codec families a track carries, or `undefined` when it carries no codecs (unknowable, per `hasCodecs` —
87
+ * not "none"). A demuxed rendition has one family; a muxed one (bipbop's `CODECS="hvc1…,mp4a…"`) has one per elementary
88
+ * stream.
89
+ */
90
+ function getCodecFamilies(track) {
91
+ if (!track.codecs?.length) return void 0;
92
+ return [...new Set(track.codecs.map(getCodecFamily))];
93
+ }
94
+ /**
95
+ * Set `mimeType` on every track of one `type` (immutably). Used to propagate a detected container across a type's
96
+ * renditions: an ABR ladder is the same content at different bitrates, so one rendition's container holds for all of
97
+ * them — capability probing + SourceBuffer setup then get the right MIME for the whole type from a single resolved
98
+ * media playlist, without fetching the rest.
99
+ *
100
+ * Scoped to one type on purpose: propagating _across_ audio/video would be wrong for mixed-container sources (e.g.
101
+ * muxed-TS video + raw-`.aac` audio) and races concurrent per-type resolution. Same-type writes are disjoint and safe.
102
+ * Idempotent — tracks already at `mimeType` are left as-is.
103
+ *
104
+ * **Assumes one container per type, which the HLS specs don't guarantee.** Apple's HLS Authoring Specification not only
105
+ * permits a mixed ladder, it produces one: §1.5 requires HEVC in fMP4, while §9.22 says a 192 kbit/s H.264 variant
106
+ * _packaged in a transport stream_ SHOULD be provided for cellular — so a conformant HEVC ladder with that fallback is
107
+ * necessarily mixed. RFC 8216 is silent too (§6.2.4 constrains variant _content_, not container).
108
+ *
109
+ * Where that happens, resolving the TS variant first relabels the whole type, capability probing prunes all of it, and
110
+ * a source whose fMP4 variants were fine reports as unplayable. Accepted because the current target is CMAF-compliant
111
+ * single-container delivery (plus Apple's official fMP4 example); revisit by narrowing the relabel to the resolved
112
+ * track, or gating propagation on config, if mixed ladders come into scope.
113
+ */
114
+ function applyContainerMimeType(presentation, type, mimeType) {
115
+ return {
116
+ ...presentation,
117
+ selectionSets: presentation.selectionSets.map((selectionSet) => selectionSet.type === type ? {
118
+ ...selectionSet,
119
+ switchingSets: selectionSet.switchingSets.map((switchingSet) => ({
120
+ ...switchingSet,
121
+ tracks: switchingSet.tracks.map((track) => track.mimeType === mimeType ? track : {
122
+ ...track,
123
+ mimeType
124
+ })
125
+ }))
126
+ } : selectionSet)
127
+ };
128
+ }
129
+ /** Updates a track within a presentation (immutably). Generic — works for video, audio, or text tracks. */
130
+ function updateTrackInPresentation(presentation, resolvedTrack) {
131
+ const trackId = resolvedTrack.id;
132
+ return {
133
+ ...presentation,
134
+ selectionSets: presentation.selectionSets.map((selectionSet) => ({
135
+ ...selectionSet,
136
+ switchingSets: selectionSet.switchingSets.map((switchingSet) => ({
137
+ ...switchingSet,
138
+ tracks: switchingSet.tracks.map((track) => track.id === trackId ? resolvedTrack : track)
139
+ }))
140
+ }))
141
+ };
142
+ }
143
+ //#endregion
144
+ export { applyContainerMimeType, findResolvedAudioTrack, findResolvedTextTrack, findResolvedVideoTrack, findTrack, findTrackById, getCodecFamilies, getCodecFamily, getTracksByType, hasCodecs, updateTrackInPresentation };
145
+
146
+ //# sourceMappingURL=tracks.js.map
@@ -0,0 +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 `type` exists, or when its first switching\n * set is empty. Returned tracks may be partially resolved (URL only) or fully resolved (with segments) — callers narrow\n * as needed.\n *\n * The \"first switching set\" assumption matches the rest of the codebase (HLS typically has one switching set per type);\n * multi-group / multi-period 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 selection set, or `undefined` if either is\n * missing. The returned track may be partially resolved (URL only) or fully resolved (with segments) — callers narrow\n * 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 knowing its type up front. Used when the\n * caller has a track id obtained from a downstream consumer (e.g. `SourceBufferActor.initTrackId`) and needs to locate\n * the corresponding track in the presentation.\n *\n * Track ids are unique within a presentation per the HLS spec; the first 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\n return undefined;\n}\n\n/**\n * Find a text track of the given id within a presentation and narrow it to the fully-resolved `TextTrack` shape\n * (segments populated). Returns `undefined` if no track matches the id, the matching track isn't a text track, or it\n * hasn't been resolved yet.\n *\n * The segments-non-empty check stays at the call site — a resolved track with zero segments is a valid state, distinct\n * 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\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\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\n const track = findTrack(presentation, 'video', trackId);\n if (track?.type !== 'video' || !isResolvedTrack(track)) return undefined;\n\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\n const track = findTrack(presentation, 'audio', trackId);\n if (track?.type !== 'audio' || !isResolvedTrack(track)) return undefined;\n\n return track;\n}\n\n/**\n * Whether a track carries a non-empty `codecs` array. Both partially- resolved and fully-resolved tracks may carry\n * codecs — they come from the multivariant playlist's `EXT-X-STREAM-INF` line, not from the per-type media playlist —\n * so this works at either resolution stage.\n *\n * `TextTrack` doesn't declare a `codecs` field; the `'codecs' in track` 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 * The family (RFC 6381 4CC) of one codec string: `'avc1.640028'` → `'avc1'`, `'mp4a.40.2'` → `'mp4a'`, dotless strings\n * (`'ec-3'`) pass through whole. Lowercased so families compare regardless of manifest casing.\n *\n * The family is what a `SourceBuffer`'s bytestream is keyed on: renditions in one family swap via a new init segment,\n * while crossing families needs `SourceBuffer.changeType()` (which SPF doesn't implement).\n */\nexport function getCodecFamily(codec: string): string {\n return codec.trim().split('.', 1)[0]!.toLowerCase();\n}\n\n/**\n * The distinct codec families a track carries, or `undefined` when it carries no codecs (unknowable, per `hasCodecs` —\n * not \"none\"). A demuxed rendition has one family; a muxed one (bipbop's `CODECS=\"hvc1…,mp4a…\"`) has one per elementary\n * stream.\n */\nexport function getCodecFamilies(track: { codecs?: string[] }): readonly string[] | undefined {\n if (!track.codecs?.length) return undefined;\n\n return [...new Set(track.codecs.map(getCodecFamily))];\n}\n\n/**\n * Set `mimeType` on every track of one `type` (immutably). Used to propagate a detected container across a type's\n * renditions: an ABR ladder is the same 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 whole type from a single resolved\n * media playlist, without fetching the rest.\n *\n * Scoped to one type on purpose: propagating _across_ audio/video would be wrong for mixed-container sources (e.g.\n * muxed-TS video + raw-`.aac` audio) and races 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 HLS Authoring Specification not only\n * permits a mixed ladder, it produces one: §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 conformant HEVC ladder with that fallback is\n * necessarily mixed. RFC 8216 is silent too (§6.2.4 constrains variant _content_, not container).\n *\n * Where that happens, resolving the TS variant first relabels the whole type, capability probing prunes all of it, and\n * a source whose fMP4 variants were fine reports as unplayable. Accepted because the current target is CMAF-compliant\n * single-container delivery (plus Apple's official fMP4 example); revisit by narrowing the relabel to the resolved\n * track, or gating propagation on config, 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/** Updates a track within a presentation (immutably). Generic — works for video, audio, or text tracks. */\nexport function updateTrackInPresentation<T extends ResolvedTrack>(\n presentation: Presentation,\n resolvedTrack: T\n): Presentation {\n const trackId = resolvedTrack.id;\n\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":";;;;;;;;;;;;AAsBA,SAAgB,gBACd,cACA,MACqD;CACrD,OAAO,aAAa,eAAe,MAAM,EAAE,MAAM,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,EAAE,EAAE,UAAU,CAAC;AACrG;;;;;;;;AASA,SAAgB,UACd,cACA,MACA,SACoD;CACpD,OAAO,gBAAgB,cAAc,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,OAAO,OAAO;AAC5E;;;;;;;;AASA,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;AAGF;;;;;;;;;AAUA,SAAgB,sBACd,cACA,SACuB;CACvB,IAAI,CAAC,gBAAgB,CAAC,SAAS,OAAO,KAAA;CAEtC,MAAM,QAAQ,UAAU,cAAc,QAAQ,OAAO;CAGrD,IAAI,OAAO,SAAS,UAAU,CAAC,gBAAgB,KAAK,GAAG,OAAO,KAAA;CAE9D,OAAO;AACT;AAEA,SAAgB,uBACd,cACA,SACwB;CACxB,IAAI,CAAC,gBAAgB,CAAC,SAAS,OAAO,KAAA;CAEtC,MAAM,QAAQ,UAAU,cAAc,SAAS,OAAO;CACtD,IAAI,OAAO,SAAS,WAAW,CAAC,gBAAgB,KAAK,GAAG,OAAO,KAAA;CAE/D,OAAO;AACT;AAEA,SAAgB,uBACd,cACA,SACwB;CACxB,IAAI,CAAC,gBAAgB,CAAC,SAAS,OAAO,KAAA;CAEtC,MAAM,QAAQ,UAAU,cAAc,SAAS,OAAO;CACtD,IAAI,OAAO,SAAS,WAAW,CAAC,gBAAgB,KAAK,GAAG,OAAO,KAAA;CAE/D,OAAO;AACT;;;;;;;;AASA,SAAgB,UAAU,OAAoE;CAC5F,OAAO,CAAC,CAAC,SAAS,YAAY,SAAS,CAAC,CAAC,MAAM,QAAQ;AACzD;;;;;;;;AASA,SAAgB,eAAe,OAAuB;CACpD,OAAO,MAAM,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,CAAE,YAAY;AACpD;;;;;;AAOA,SAAgB,iBAAiB,OAA6D;CAC5F,IAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,KAAA;CAElC,OAAO,CAAC,GAAG,IAAI,IAAI,MAAM,OAAO,IAAI,cAAc,CAAC,CAAC;AACtD;;;;;;;;;;;;;;;;;;;;;AAsBA,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;;AAGA,SAAgB,0BACd,cACA,eACc;CACd,MAAM,UAAU,cAAc;CAE9B,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,48 @@
1
+ //#region src/network/bandwidth-estimator.d.ts
2
+ /**
3
+ * Dual EWMA Bandwidth Estimator
4
+ *
5
+ * Estimates available bandwidth using two EWMA calculations with different half-lives, taking the minimum of both. This
6
+ * approach (from Shaka Player):
7
+ *
8
+ * - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops
9
+ * - **Slow EWMA** (5s half-life): Provides stability during fluctuations
10
+ * - **min(fast, slow)**: Adapts down quickly, up slowly
11
+ *
12
+ * This naturally provides asymmetric behavior needed for good QoE: avoiding stalls (quick downgrade) while preventing
13
+ * oscillation (slow upgrade).
14
+ */
15
+ /**
16
+ * Bandwidth estimator state.
17
+ *
18
+ * This state structure will be managed by O1 (State Container). Functions in this module operate on this state
19
+ * immutably.
20
+ */
21
+ interface BandwidthState {
22
+ /** Fast-moving EWMA estimate (raw, uncorrected). */
23
+ fastEstimate: number;
24
+ /** Total weight accumulated in fast EWMA. */
25
+ fastTotalWeight: number;
26
+ /** Slow-moving EWMA estimate (raw, uncorrected). */
27
+ slowEstimate: number;
28
+ /** Total weight accumulated in slow EWMA. */
29
+ slowTotalWeight: number;
30
+ /** Total bytes sampled across all valid samples. */
31
+ bytesSampled: number;
32
+ }
33
+ /** Configuration for bandwidth estimation. */
34
+ interface BandwidthConfig {
35
+ /** Half-life for fast EWMA in seconds. */
36
+ fastHalfLife: number;
37
+ /** Half-life for slow EWMA in seconds. */
38
+ slowHalfLife: number;
39
+ /** Minimum total bytes before trusting the estimate. */
40
+ minTotalBytes: number;
41
+ /** Minimum bytes per sample to count (filters TTFB-dominated samples). */
42
+ minBytes: number;
43
+ /** Minimum sample duration in ms (filters cached responses). */
44
+ minDuration: number;
45
+ }
46
+ //#endregion
47
+ export { BandwidthConfig, BandwidthState };
48
+ //# sourceMappingURL=bandwidth-estimator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bandwidth-estimator.d.ts","names":[],"sources":["../../../src/network/bandwidth-estimator.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;UAsBiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA"}
@@ -0,0 +1,95 @@
1
+ import { applyZeroFactor, calculateEwma } from "./ewma.js";
2
+ //#region src/network/bandwidth-estimator.ts
3
+ /**
4
+ * Dual EWMA Bandwidth Estimator
5
+ *
6
+ * Estimates available bandwidth using two EWMA calculations with different half-lives, taking the minimum of both. This
7
+ * approach (from Shaka Player):
8
+ *
9
+ * - **Fast EWMA** (2s half-life): Reacts quickly to bandwidth drops
10
+ * - **Slow EWMA** (5s half-life): Provides stability during fluctuations
11
+ * - **min(fast, slow)**: Adapts down quickly, up slowly
12
+ *
13
+ * This naturally provides asymmetric behavior needed for good QoE: avoiding stalls (quick downgrade) while preventing
14
+ * oscillation (slow upgrade).
15
+ */
16
+ /**
17
+ * Default bandwidth estimator configuration.
18
+ *
19
+ * Values match Shaka Player defaults based on experimentation.
20
+ */
21
+ const DEFAULT_BANDWIDTH_CONFIG = {
22
+ fastHalfLife: 2,
23
+ slowHalfLife: 5,
24
+ minTotalBytes: 128e3,
25
+ minBytes: 16e3,
26
+ minDuration: 5
27
+ };
28
+ /**
29
+ * Add a bandwidth sample from a segment download.
30
+ *
31
+ * Samples are filtered based on: - Minimum bytes (filters TTFB-dominated small segments) - Minimum duration (filters
32
+ * cached responses)
33
+ *
34
+ * Valid samples update both fast and slow EWMA estimates.
35
+ *
36
+ * @example
37
+ * let state = { fastEstimate: 0, fastTotalWeight: 0, ... };
38
+ * // Sample: 1MB in 1 second
39
+ * state = sampleBandwidth(state, 1000, 1_000_000);
40
+ *
41
+ * @param state - Current estimator state
42
+ * @param durationMs - Download duration in milliseconds
43
+ * @param numBytes - Number of bytes downloaded
44
+ * @param config - Optional estimator configuration (uses defaults if not provided)
45
+ * @returns New estimator state with sample incorporated (or unchanged if filtered)
46
+ */
47
+ function sampleBandwidth(state, durationMs, numBytes, config = DEFAULT_BANDWIDTH_CONFIG) {
48
+ const updatedBytesSampled = state.bytesSampled + numBytes;
49
+ if (numBytes < config.minBytes) return {
50
+ ...state,
51
+ bytesSampled: updatedBytesSampled
52
+ };
53
+ if (durationMs < config.minDuration) return {
54
+ ...state,
55
+ bytesSampled: updatedBytesSampled
56
+ };
57
+ const bandwidth = 8e3 * numBytes / durationMs;
58
+ const weight = durationMs / 1e3;
59
+ return {
60
+ fastEstimate: calculateEwma(state.fastEstimate, bandwidth, weight, config.fastHalfLife),
61
+ fastTotalWeight: state.fastTotalWeight + weight,
62
+ slowEstimate: calculateEwma(state.slowEstimate, bandwidth, weight, config.slowHalfLife),
63
+ slowTotalWeight: state.slowTotalWeight + weight,
64
+ bytesSampled: updatedBytesSampled
65
+ };
66
+ }
67
+ /**
68
+ * Get the current bandwidth estimate.
69
+ *
70
+ * Returns the **minimum** of the fast and slow EWMA estimates. This provides the key asymmetric behavior:
71
+ *
72
+ * - When bandwidth drops, fast EWMA reacts first and dominates (quick adaptation)
73
+ * - When bandwidth rises, slow EWMA lags behind and dominates (slow adaptation)
74
+ *
75
+ * Uses default estimate until enough data has been sampled — and when no estimator state exists at all (`state ===
76
+ * undefined`).
77
+ *
78
+ * @example
79
+ * const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default
80
+ *
81
+ * @param state - Current estimator state, or `undefined` before any samples have been collected
82
+ * @param defaultEstimate - Fallback estimate before sufficient samples (bps)
83
+ * @param config - Optional estimator configuration (uses defaults if not provided)
84
+ * @returns Bandwidth estimate in bits per second
85
+ */
86
+ function getBandwidthEstimate(state, defaultEstimate, config = DEFAULT_BANDWIDTH_CONFIG) {
87
+ if (!state || state.bytesSampled < config.minTotalBytes) return defaultEstimate;
88
+ const fastEstimate = applyZeroFactor(state.fastEstimate, state.fastTotalWeight, config.fastHalfLife);
89
+ const slowEstimate = applyZeroFactor(state.slowEstimate, state.slowTotalWeight, config.slowHalfLife);
90
+ return Math.min(fastEstimate, slowEstimate);
91
+ }
92
+ //#endregion
93
+ export { DEFAULT_BANDWIDTH_CONFIG, getBandwidthEstimate, sampleBandwidth };
94
+
95
+ //# sourceMappingURL=bandwidth-estimator.js.map
@@ -0,0 +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 half-lives, taking the minimum of both. This\n * 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: avoiding stalls (quick downgrade) while preventing\n * 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). Functions in this module operate on this state\n * 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/** Configuration for bandwidth estimation. */\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: - Minimum bytes (filters TTFB-dominated small segments) - Minimum duration (filters\n * cached responses)\n *\n * Valid samples update both fast and slow EWMA estimates.\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 *\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 */\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. This provides the key asymmetric behavior:\n *\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 estimator state exists at all (`state ===\n * undefined`).\n *\n * @example\n * const estimate = getBandwidthEstimate(state, 5_000_000); // 5 Mbps default\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 */\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: - Enough total bytes sampled (minTotalBytes threshold) - At least one valid EWMA sample (totalWeight >\n * 0)\n *\n * @example\n * if (hasGoodEstimate(state)) {\n * const estimate = getBandwidthEstimate(state, 5_000_000);\n * }\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 */\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":";;;;;;;;;;;;;;;;;;;;AAsDA,MAAa,2BAA4C;CACvD,cAAc;CACd,cAAc;CACd,eAAe;CACf,UAAU;CACV,aAAa;AACf;;;;;;;;;;;;;;;;;;;;AAqBA,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"}
@@ -0,0 +1,47 @@
1
+ //#region src/network/chunked-stream-iterable.ts
2
+ const DEFAULT_MIN_CHUNK_SIZE = 2 ** 17;
3
+ /**
4
+ * Adapts a `ReadableStream<Uint8Array>` (e.g. `response.body`) into an `AsyncIterable<Uint8Array>` that yields chunks
5
+ * no smaller than `minChunkSize` bytes. Smaller network chunks are accumulated and yielded together once the threshold
6
+ * is met. Any remainder is flushed on stream end.
7
+ *
8
+ * Errors from the underlying stream propagate naturally — the reader lock is always released via `finally`.
9
+ */
10
+ var ChunkedStreamIterable = class {
11
+ minChunkSize;
12
+ #readableStream;
13
+ constructor(readableStream, { minChunkSize = DEFAULT_MIN_CHUNK_SIZE } = {}) {
14
+ this.#readableStream = readableStream;
15
+ this.minChunkSize = minChunkSize;
16
+ }
17
+ async *[Symbol.asyncIterator]() {
18
+ let pending;
19
+ const reader = this.#readableStream.getReader();
20
+ try {
21
+ while (true) {
22
+ const { done, value } = await reader.read();
23
+ if (done) {
24
+ if (pending) yield pending;
25
+ break;
26
+ }
27
+ pending = pending ? concat(pending, value) : value;
28
+ if (pending.length >= this.minChunkSize) {
29
+ yield pending;
30
+ pending = void 0;
31
+ }
32
+ }
33
+ } finally {
34
+ reader.releaseLock();
35
+ }
36
+ }
37
+ };
38
+ function concat(a, b) {
39
+ const result = new Uint8Array(a.length + b.length);
40
+ result.set(a);
41
+ result.set(b, a.length);
42
+ return result;
43
+ }
44
+ //#endregion
45
+ export { ChunkedStreamIterable };
46
+
47
+ //# sourceMappingURL=chunked-stream-iterable.js.map
@@ -0,0 +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 `AsyncIterable<Uint8Array>` that yields chunks\n * no smaller than `minChunkSize` bytes. Smaller network chunks are accumulated and yielded together once the threshold\n * is met. Any remainder is flushed on stream end.\n *\n * Errors from the underlying stream propagate naturally — the reader lock is 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\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n if (pending) yield pending;\n\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\n result.set(a);\n result.set(b, a.length);\n return result;\n}\n"],"mappings":";AAAA,MAAM,yBAAyB,KAAK;;;;;;;;AAapC,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;EAE9C,IAAI;GACF,OAAO,MAAM;IACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;IAE1C,IAAI,MAAM;KACR,IAAI,SAAS,MAAM;KAEnB;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;CAEjD,OAAO,IAAI,CAAC;CACZ,OAAO,IAAI,GAAG,EAAE,MAAM;CACtB,OAAO;AACT"}
@@ -0,0 +1,68 @@
1
+ //#region src/network/ewma.ts
2
+ /**
3
+ * Exponentially Weighted Moving Average (EWMA)
4
+ *
5
+ * Pure functional implementation of EWMA calculations. Based on Shaka Player's EWMA algorithm.
6
+ */
7
+ /**
8
+ * Calculate alpha (decay factor) from half-life.
9
+ *
10
+ * Alpha determines how quickly old data "expires": - alpha close to 1 = slow decay (long memory) - alpha close to 0 =
11
+ * fast decay (short memory)
12
+ *
13
+ * @example
14
+ * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life
15
+ *
16
+ * @param halfLife - The quantity of prior samples (by weight) that make up half of the new estimate. Must be positive.
17
+ * @returns Alpha value between 0 and 1
18
+ */
19
+ function calculateAlpha(halfLife) {
20
+ return Math.exp(Math.log(.5) / halfLife);
21
+ }
22
+ /**
23
+ * Calculate exponentially weighted moving average.
24
+ *
25
+ * Updates an estimate by blending a new value with the previous estimate, weighted by the sample duration. Longer
26
+ * samples have more influence.
27
+ *
28
+ * @example
29
+ * let estimate = 0;
30
+ * estimate = calculateEwma(estimate, 1_000_000, 1, 2); // First sample
31
+ * estimate = calculateEwma(estimate, 2_000_000, 1, 2); // Second sample
32
+ *
33
+ * @param prevEstimate - Previous EWMA estimate
34
+ * @param value - New sample value to incorporate
35
+ * @param weight - Sample weight (typically duration in seconds)
36
+ * @param halfLife - Half-life for decay (typically 2-5 seconds)
37
+ * @returns Updated EWMA estimate
38
+ */
39
+ function calculateEwma(prevEstimate, value, weight, halfLife) {
40
+ const adjAlpha = calculateAlpha(halfLife) ** weight;
41
+ return value * (1 - adjAlpha) + adjAlpha * prevEstimate;
42
+ }
43
+ /**
44
+ * Apply zero-factor correction to EWMA estimate.
45
+ *
46
+ * The zero-factor correction compensates for bias when starting from zero. Without this correction, early estimates
47
+ * would be artificially low.
48
+ *
49
+ * As totalWeight increases, the correction factor approaches 1, meaning the estimate becomes more reliable and needs
50
+ * less correction.
51
+ *
52
+ * @example
53
+ * const raw = calculateEwma(0, 1_000_000, 1, 2);
54
+ * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000
55
+ *
56
+ * @param estimate - Raw EWMA estimate (uncorrected)
57
+ * @param totalWeight - Accumulated weight from all samples
58
+ * @param halfLife - Half-life used in EWMA calculation
59
+ * @returns Corrected estimate, or 0 if totalWeight is 0
60
+ */
61
+ function applyZeroFactor(estimate, totalWeight, halfLife) {
62
+ if (totalWeight === 0) return 0;
63
+ return estimate / (1 - calculateAlpha(halfLife) ** totalWeight);
64
+ }
65
+ //#endregion
66
+ export { applyZeroFactor, calculateAlpha, calculateEwma };
67
+
68
+ //# sourceMappingURL=ewma.js.map
@@ -0,0 +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. 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\": - alpha close to 1 = slow decay (long memory) - alpha close to 0 =\n * fast decay (short memory)\n *\n * @example\n * const alpha = calculateAlpha(2); // ≈ 0.7071 for 2-second half-life\n *\n * @param halfLife - The quantity of prior samples (by weight) that make up half of the new estimate. Must be positive.\n * @returns Alpha value between 0 and 1\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, weighted by the sample duration. Longer\n * samples have more influence.\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 *\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 */\nexport function calculateEwma(prevEstimate: number, value: number, weight: number, halfLife: number): number {\n const alpha = calculateAlpha(halfLife);\n const adjAlpha = alpha ** weight;\n\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. Without this correction, early estimates\n * would be artificially low.\n *\n * As totalWeight increases, the correction factor approaches 1, meaning the estimate becomes more reliable and needs\n * less correction.\n *\n * @example\n * const raw = calculateEwma(0, 1_000_000, 1, 2);\n * const corrected = applyZeroFactor(raw, 1, 2); // ≈ 1_000_000\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 */\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\n return estimate / zeroFactor;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,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;CAE1B,OAAO,SAAS,IAAI,YAAY,WAAW;AAC7C;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,gBAAgB,UAAkB,aAAqB,UAA0B;CAC/F,IAAI,gBAAgB,GAClB,OAAO;CAMT,OAAO,YAFY,IADL,eAAe,QACF,KAAK;AAGlC"}
@@ -0,0 +1,108 @@
1
+ import { sampleBandwidth } from "./bandwidth-estimator.js";
2
+ import { ChunkedStreamIterable } from "./chunked-stream-iterable.js";
3
+ //#region src/network/fetch.ts
4
+ /**
5
+ * HTTP Fetch Wrapper
6
+ *
7
+ * Composable building blocks: - fetchResolvable() — fetch a Resource (handles byte ranges); returns Response -
8
+ * getResponseText() — extract text from Response - fetchResolvableStream() — single-stage async generator over body
9
+ * chunks - fetchStream() — two-stage: await connection establishment, then lazily iterate body chunks. Use when timing
10
+ * the connection start independently of body consumption matters (e.g., observable fetch timing for ABR). -
11
+ * createTrackedFetch() — factory for a fetchStream-shape function that samples bandwidth (via EWMA) per chunk and
12
+ * notifies via callback.
13
+ */
14
+ /**
15
+ * Fetch resolvable from a Resource.
16
+ *
17
+ * Handles byte range requests if byteRange is present. Returns native fetch Response for composability (can extract
18
+ * text, stream, etc.).
19
+ *
20
+ * @example
21
+ * const response = await fetchResolvable({ url: 'https://example.com/segment.m4s' });
22
+ * const text = await getResponseText(response);
23
+ *
24
+ * @example
25
+ * // With byte range
26
+ * const response = await fetchResolvable({
27
+ * url: 'https://example.com/file.mp4',
28
+ * byteRange: { start: 1000, end: 1999 },
29
+ * });
30
+ *
31
+ * @param addressable - Resource to fetch (url + optional byteRange)
32
+ * @returns Promise resolving to Response
33
+ */
34
+ async function fetchResolvable(addressable, options) {
35
+ const headers = new Headers(options?.headers);
36
+ if (addressable.byteRange) {
37
+ const { start, end } = addressable.byteRange;
38
+ headers.set("Range", `bytes=${start}-${end}`);
39
+ }
40
+ const request = new Request(addressable.url, {
41
+ method: "GET",
42
+ headers,
43
+ ...options
44
+ });
45
+ return fetch(request);
46
+ }
47
+ /**
48
+ * Extract text from Response.
49
+ *
50
+ * Accepts minimal Response-like object (just needs text() method). Returns promise from response.text().
51
+ *
52
+ * @example
53
+ * const response = await fetchResolvable(addressable);
54
+ * const text = await getResponseText(response);
55
+ *
56
+ * @param response - Response-like object with text() method
57
+ * @returns Promise resolving to text content
58
+ */
59
+ function getResponseText(response) {
60
+ return response.text();
61
+ }
62
+ /** Default {@link FetchText}: fetch the resource, reject on non-OK, return text. */
63
+ const fetchResolvableText = async (addressable, options) => {
64
+ const response = await fetchResolvable(addressable, options);
65
+ if (!response.ok) throw new Error(`fetchResolvableText: ${response.status} ${response.statusText} for ${addressable.url}`);
66
+ return getResponseText(response);
67
+ };
68
+ async function fetchStream(addressable, options) {
69
+ const { minChunkSize, ...fetchOptions } = options ?? {};
70
+ const response = await fetchResolvable(addressable, fetchOptions);
71
+ if (!response.body) throw new Error("Response has no body");
72
+ return new ChunkedStreamIterable(response.body, ...minChunkSize !== void 0 ? [{ minChunkSize }] : []);
73
+ }
74
+ /**
75
+ * Returns a {@link FetchBytes} function that samples bandwidth via EWMA per body chunk. The factory captures the
76
+ * running bandwidth state internally; per chunk it computes the next state and notifies the supplied `onSample`
77
+ * callback.
78
+ *
79
+ * The factory's internal accumulator is seeded from `initial` and updated on every chunk; callers don't need to thread
80
+ * it back in. `onSample` receives the _new_ state after each chunk — typical use is to bridge samples back into engine
81
+ * state for ABR consumers.
82
+ *
83
+ * @param initial - Starting `BandwidthState` (commonly zeros or the engine's current accumulator).
84
+ * @param onSample - Called with the new `BandwidthState` after each chunk.
85
+ */
86
+ function createTrackedFetch(initial, onSample) {
87
+ let state = initial;
88
+ return async (addressable, options) => {
89
+ const { minChunkSize, ...fetchOptions } = options ?? {};
90
+ const response = await fetchResolvable(addressable, fetchOptions);
91
+ if (!response.body) throw new Error("Response has no body");
92
+ const body = response.body;
93
+ return { [Symbol.asyncIterator]: async function* () {
94
+ let chunkStart = performance.now();
95
+ for await (const chunk of new ChunkedStreamIterable(body, ...minChunkSize !== void 0 ? [{ minChunkSize }] : [])) {
96
+ const elapsed = performance.now() - chunkStart;
97
+ state = sampleBandwidth(state, elapsed, chunk.byteLength);
98
+ onSample(state);
99
+ yield chunk;
100
+ chunkStart = performance.now();
101
+ }
102
+ } };
103
+ };
104
+ }
105
+ //#endregion
106
+ export { createTrackedFetch, fetchResolvable, fetchResolvableText, fetchStream, getResponseText };
107
+
108
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","names":[],"sources":["../../../src/network/fetch.ts"],"sourcesContent":["/**\n * HTTP Fetch Wrapper\n *\n * Composable building blocks: - fetchResolvable() — fetch a Resource (handles byte ranges); returns Response -\n * getResponseText() — extract text from Response - fetchResolvableStream() — single-stage async generator over body\n * chunks - fetchStream() — two-stage: await connection establishment, then lazily iterate body chunks. Use when timing\n * the connection start independently of body consumption matters (e.g., observable fetch timing for ABR). -\n * createTrackedFetch() — factory for a fetchStream-shape function that samples bandwidth (via EWMA) per chunk and\n * notifies via callback.\n */\n\nimport { type BandwidthState, sampleBandwidth } from './bandwidth-estimator';\nimport { ChunkedStreamIterable, type ChunkedStreamIterableOptions } from './chunked-stream-iterable';\n\n/** Minimal Response-like interface for text extraction. Allows testing without full Response object. */\nexport interface ResponseLike {\n text(): Promise<string>;\n}\n\n/**\n * An HTTP-addressable resource — URL plus optional byte range. Media's `AddressableObject` (and anything else with the\n * same shape) is structurally compatible; kept local so this module stays 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. Returns native fetch Response for composability (can extract\n * text, stream, etc.).\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 *\n * @param addressable - Resource to fetch (url + optional byteRange)\n * @returns Promise resolving to Response\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\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 ArrayBuffer. Use this when you need the raw\n * bytes (e.g. segment appends). 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\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 via ChunkedStreamIterable. Headers are\n * awaited before the first chunk is yielded (TTFB is accounted for before iteration begins).\n *\n * Throws if the response body is null (e.g. non-body HTTP status). Errors from the underlying stream propagate\n * 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\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). Returns promise from response.text().\n *\n * @example\n * const response = await fetchResolvable(addressable);\n * const text = await getResponseText(response);\n *\n * @param response - Response-like object with text() method\n * @returns Promise resolving to text content\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 {@link FetchBytes}. A non-OK status rejects, so HTTP\n * failures surface as rejections that callers (and decorators like the failover tracker) handle uniformly with network\n * 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\n if (!response.ok) {\n throw new Error(`fetchResolvableText: ${response.status} ${response.statusText} for ${addressable.url}`);\n }\n\n return getResponseText(response);\n};\n\n/**\n * Two-stage fetch helper: eagerly starts the HTTP request (TTFB is awaited), then returns a lazy iterable over the\n * response body. Separating connection 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 `fetch` only when iteration starts). Pick\n * `fetchStream` when \"when did the fetch start\" needs to be observable separately from \"when did the body begin\n * 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\n return new ChunkedStreamIterable(response.body, ...(minChunkSize !== undefined ? [{ minChunkSize }] : []));\n}\n\n/**\n * Returns a {@link FetchBytes} function that samples bandwidth via EWMA per body chunk. The factory captures the\n * running bandwidth state internally; per chunk it computes the next state and notifies the supplied `onSample`\n * callback.\n *\n * The factory's internal accumulator is seeded from `initial` and updated on every chunk; callers don't need to thread\n * it back in. `onSample` receives the _new_ state after each chunk — typical use is to bridge samples back into engine\n * state for ABR consumers.\n *\n * @param initial - Starting `BandwidthState` (commonly zeros or the 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\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\n const body = response.body;\n\n return {\n [Symbol.asyncIterator]: async function* () {\n let chunkStart = performance.now();\n\n for await (const chunk of new ChunkedStreamIterable(\n body,\n ...(minChunkSize !== undefined ? [{ minChunkSize }] : [])\n )) {\n const elapsed = performance.now() - chunkStart;\n\n state = sampleBandwidth(state, elapsed, chunk.byteLength);\n onSample(state);\n yield chunk;\n chunkStart = performance.now();\n }\n },\n };\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,eAAsB,gBAAgB,aAAuB,SAA0C;CACrG,MAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;CAG5C,IAAI,YAAY,WAAW;EACzB,MAAM,EAAE,OAAO,QAAQ,YAAY;EAEnC,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;;;;;;;;;;;;;AA8CA,SAAgB,gBAAgB,UAAyC;CACvE,OAAO,SAAS,KAAK;AACvB;;AAUA,MAAa,sBAAiC,OAAO,aAAa,YAAY;CAC5E,MAAM,WAAW,MAAM,gBAAgB,aAAa,OAAO;CAE3D,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG,SAAS,WAAW,OAAO,YAAY,KAAK;CAGzG,OAAO,gBAAgB,QAAQ;AACjC;AAeA,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;CAE1D,OAAO,IAAI,sBAAsB,SAAS,MAAM,GAAI,iBAAiB,KAAA,IAAY,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAE;AAC3G;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,SAAyB,UAAsD;CAChH,IAAI,QAAQ;CAEZ,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;EAE1D,MAAM,OAAO,SAAS;EAEtB,OAAO,EACL,CAAC,OAAO,gBAAgB,mBAAmB;GACzC,IAAI,aAAa,YAAY,IAAI;GAEjC,WAAW,MAAM,SAAS,IAAI,sBAC5B,MACA,GAAI,iBAAiB,KAAA,IAAY,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CACzD,GAAG;IACD,MAAM,UAAU,YAAY,IAAI,IAAI;IAEpC,QAAQ,gBAAgB,OAAO,SAAS,MAAM,UAAU;IACxD,SAAS,KAAK;IACd,MAAM;IACN,aAAa,YAAY,IAAI;GAC/B;EACF,EACF;CACF;AACF"}