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

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 (461) hide show
  1. package/dist/{dev/core → default/core/actors}/create-machine-actor.js +3 -3
  2. package/dist/default/core/actors/create-machine-actor.js.map +1 -0
  3. package/dist/default/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -0
  5. package/dist/default/core/composition/create-composition.js +85 -0
  6. package/dist/default/core/composition/create-composition.js.map +1 -0
  7. package/dist/default/core/composition/share-signals.js +37 -0
  8. package/dist/default/core/composition/share-signals.js.map +1 -0
  9. package/dist/{dev/core → default/core/reactors}/create-machine-reactor.js +4 -4
  10. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -0
  11. package/dist/default/core/signals/primitives.js +33 -11
  12. package/dist/default/core/signals/primitives.js.map +1 -1
  13. package/dist/default/core/{task.js → tasks/task.js} +2 -2
  14. package/dist/default/core/tasks/task.js.map +1 -0
  15. package/dist/default/dom.js +9 -10
  16. package/dist/default/hls.js +3 -0
  17. package/dist/default/index.js +12 -3
  18. package/dist/default/index.js.map +1 -1
  19. package/dist/default/media/abr/quality-selection.js +83 -0
  20. package/dist/default/media/abr/quality-selection.js.map +1 -0
  21. package/dist/default/{core → media}/buffer/back-buffer.js +2 -2
  22. package/dist/default/media/buffer/back-buffer.js.map +1 -0
  23. package/dist/{dev/core → default/media}/buffer/forward-buffer.js +14 -2
  24. package/dist/default/media/buffer/forward-buffer.js.map +1 -0
  25. package/dist/{dev/dom/media → default/media/dom/mse}/append-segment.js +1 -1
  26. package/dist/default/media/dom/mse/append-segment.js.map +1 -0
  27. package/dist/default/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
  28. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -0
  29. package/dist/default/media/dom/mse/duration.js +78 -0
  30. package/dist/default/media/dom/mse/duration.js.map +1 -0
  31. package/dist/default/media/dom/mse/end-of-stream.js +20 -0
  32. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -0
  33. package/dist/default/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
  34. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -0
  35. package/dist/default/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
  36. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -0
  37. package/dist/default/media/dom/text/text-track-slots.js +69 -0
  38. package/dist/default/media/dom/text/text-track-slots.js.map +1 -0
  39. package/dist/{dev/core → default/media}/hls/parse-attributes.js +1 -1
  40. package/dist/default/media/hls/parse-attributes.js.map +1 -0
  41. package/dist/{dev/core → default/media}/hls/parse-media-playlist.js +1 -1
  42. package/dist/default/media/hls/parse-media-playlist.js.map +1 -0
  43. package/dist/{dev/core → default/media}/hls/parse-multivariant.js +2 -2
  44. package/dist/default/media/hls/parse-multivariant.js.map +1 -0
  45. package/dist/default/{core → media}/hls/resolve-url.js +1 -1
  46. package/dist/default/media/hls/resolve-url.js.map +1 -0
  47. package/dist/default/media/primitives/select-tracks.js +45 -0
  48. package/dist/default/media/primitives/select-tracks.js.map +1 -0
  49. package/dist/default/media/types/index.js +36 -0
  50. package/dist/default/media/types/index.js.map +1 -0
  51. package/dist/default/media/utils/preload.js +22 -0
  52. package/dist/default/media/utils/preload.js.map +1 -0
  53. package/dist/default/media/utils/track-selection.js +47 -0
  54. package/dist/default/media/utils/track-selection.js.map +1 -0
  55. package/dist/default/media/utils/tracks.js +103 -0
  56. package/dist/default/media/utils/tracks.js.map +1 -0
  57. package/dist/default/{core/abr → network}/bandwidth-estimator.js +6 -5
  58. package/dist/default/network/bandwidth-estimator.js.map +1 -0
  59. package/dist/{dev/dom → default}/network/chunked-stream-iterable.js +1 -1
  60. package/dist/default/network/chunked-stream-iterable.js.map +1 -0
  61. package/dist/{dev/core/abr → default/network}/ewma.js +1 -1
  62. package/dist/default/network/ewma.js.map +1 -0
  63. package/dist/default/network/fetch.js +109 -0
  64. package/dist/default/network/fetch.js.map +1 -0
  65. package/dist/{dev/dom/features/segment-loader-actor.js → default/playback/actors/dom/segment-loader.js} +24 -15
  66. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -0
  67. package/dist/{dev/dom/media/source-buffer-actor.js → default/playback/actors/dom/source-buffer.js} +8 -8
  68. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -0
  69. package/dist/{dev/dom/features/text-tracks-actor.js → default/playback/actors/dom/text-tracks.js} +7 -3
  70. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -0
  71. package/dist/default/playback/actors/text-track-segment-loader.js +135 -0
  72. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -0
  73. package/dist/default/playback/behaviors/calculate-presentation-duration.js +58 -0
  74. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -0
  75. package/dist/default/playback/behaviors/dom/end-of-stream.js +178 -0
  76. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -0
  77. package/dist/default/playback/behaviors/dom/load-segments.js +177 -0
  78. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -0
  79. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +173 -0
  80. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
  81. package/dist/default/playback/behaviors/dom/setup-mediasource.js +89 -0
  82. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -0
  83. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +54 -0
  84. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
  85. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +99 -0
  86. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
  87. package/dist/default/playback/behaviors/dom/track-current-time.js +46 -0
  88. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -0
  89. package/dist/default/playback/behaviors/dom/track-load-triggers.js +94 -0
  90. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -0
  91. package/dist/default/playback/behaviors/dom/track-playback-rate.js +37 -0
  92. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -0
  93. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +90 -0
  94. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
  95. package/dist/default/playback/behaviors/quality-switching.js +96 -0
  96. package/dist/default/playback/behaviors/quality-switching.js.map +1 -0
  97. package/dist/default/playback/behaviors/resolve-presentation.js +85 -0
  98. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -0
  99. package/dist/default/playback/behaviors/resolve-track.js +97 -0
  100. package/dist/default/playback/behaviors/resolve-track.js.map +1 -0
  101. package/dist/default/playback/behaviors/select-tracks.js +132 -0
  102. package/dist/default/playback/behaviors/select-tracks.js.map +1 -0
  103. package/dist/default/playback/behaviors/sync-preload.js +59 -0
  104. package/dist/default/playback/behaviors/sync-preload.js.map +1 -0
  105. package/dist/default/playback/behaviors/track-types.js +61 -0
  106. package/dist/default/playback/behaviors/track-types.js.map +1 -0
  107. package/dist/default/{dom/playback-engine → playback/engines/hls}/adapter.js +36 -25
  108. package/dist/default/playback/engines/hls/adapter.js.map +1 -0
  109. package/dist/default/playback/engines/hls/engine.js +102 -0
  110. package/dist/default/playback/engines/hls/engine.js.map +1 -0
  111. package/dist/dev/core/{actor.d.ts → actors/actor.d.ts} +2 -2
  112. package/dist/dev/core/actors/actor.d.ts.map +1 -0
  113. package/dist/dev/core/actors/create-machine-actor.d.ts +125 -0
  114. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -0
  115. package/dist/{default/core → dev/core/actors}/create-machine-actor.js +3 -3
  116. package/dist/dev/core/actors/create-machine-actor.js.map +1 -0
  117. package/dist/dev/core/actors/create-transition-actor.d.ts +42 -0
  118. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -0
  119. package/dist/dev/core/{create-transition-actor.js → actors/create-transition-actor.js} +3 -3
  120. package/dist/dev/core/actors/create-transition-actor.js.map +1 -0
  121. package/dist/dev/core/composition/create-composition.d.ts +274 -0
  122. package/dist/dev/core/composition/create-composition.d.ts.map +1 -0
  123. package/dist/dev/core/composition/create-composition.js +85 -0
  124. package/dist/dev/core/composition/create-composition.js.map +1 -0
  125. package/dist/dev/core/composition/share-signals.d.ts +45 -0
  126. package/dist/dev/core/composition/share-signals.d.ts.map +1 -0
  127. package/dist/dev/core/composition/share-signals.js +37 -0
  128. package/dist/dev/core/composition/share-signals.js.map +1 -0
  129. package/dist/dev/core/machine.d.ts +0 -1
  130. package/dist/dev/core/machine.d.ts.map +1 -1
  131. package/dist/dev/core/reactors/create-machine-reactor.d.ts +93 -0
  132. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -0
  133. package/dist/{default/core → dev/core/reactors}/create-machine-reactor.js +4 -4
  134. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -0
  135. package/dist/dev/core/signals/primitives.d.ts +38 -1
  136. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  137. package/dist/dev/core/signals/primitives.js +33 -11
  138. package/dist/dev/core/signals/primitives.js.map +1 -1
  139. package/dist/dev/core/tasks/task.d.ts +115 -0
  140. package/dist/dev/core/tasks/task.d.ts.map +1 -0
  141. package/dist/dev/core/{task.js → tasks/task.js} +2 -2
  142. package/dist/dev/core/tasks/task.js.map +1 -0
  143. package/dist/dev/dom.d.ts +9 -10
  144. package/dist/dev/dom.js +9 -10
  145. package/dist/dev/hls.d.ts +3 -0
  146. package/dist/dev/hls.js +3 -0
  147. package/dist/dev/index.d.ts +15 -3
  148. package/dist/dev/index.d.ts.map +1 -1
  149. package/dist/dev/index.js +12 -3
  150. package/dist/dev/index.js.map +1 -1
  151. package/dist/dev/media/abr/quality-selection.d.ts +22 -0
  152. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -0
  153. package/dist/dev/media/abr/quality-selection.js +83 -0
  154. package/dist/dev/media/abr/quality-selection.js.map +1 -0
  155. package/dist/dev/media/buffer/back-buffer.d.ts +14 -0
  156. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -0
  157. package/dist/dev/{core → media}/buffer/back-buffer.js +2 -2
  158. package/dist/dev/media/buffer/back-buffer.js.map +1 -0
  159. package/dist/dev/media/buffer/forward-buffer.d.ts +14 -0
  160. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -0
  161. package/dist/{default/core → dev/media}/buffer/forward-buffer.js +14 -2
  162. package/dist/dev/media/buffer/forward-buffer.js.map +1 -0
  163. package/dist/dev/{dom/media → media/dom/mse}/append-segment.d.ts +1 -1
  164. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -0
  165. package/dist/{default/dom/media → dev/media/dom/mse}/append-segment.js +1 -1
  166. package/dist/dev/media/dom/mse/append-segment.js.map +1 -0
  167. package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.d.ts +1 -1
  168. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -0
  169. package/dist/dev/{dom/media → media/dom/mse}/buffer-flusher.js +1 -1
  170. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -0
  171. package/dist/dev/media/dom/mse/duration.js +78 -0
  172. package/dist/dev/media/dom/mse/duration.js.map +1 -0
  173. package/dist/dev/media/dom/mse/end-of-stream.js +20 -0
  174. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -0
  175. package/dist/dev/{dom/media → media/dom/mse}/mediasource-setup.js +63 -18
  176. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -0
  177. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +12 -0
  178. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -0
  179. package/dist/dev/{dom/text/parse-vtt-segment.js → media/dom/text/resolve-vtt-segment.js} +5 -5
  180. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -0
  181. package/dist/dev/media/dom/text/text-track-slots.d.ts +31 -0
  182. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -0
  183. package/dist/dev/media/dom/text/text-track-slots.js +69 -0
  184. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -0
  185. package/dist/{default/core → dev/media}/hls/parse-attributes.js +1 -1
  186. package/dist/dev/media/hls/parse-attributes.js.map +1 -0
  187. package/dist/{default/core → dev/media}/hls/parse-media-playlist.js +1 -1
  188. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -0
  189. package/dist/{default/core → dev/media}/hls/parse-multivariant.js +2 -2
  190. package/dist/dev/media/hls/parse-multivariant.js.map +1 -0
  191. package/dist/dev/{core → media}/hls/resolve-url.js +1 -1
  192. package/dist/dev/media/hls/resolve-url.js.map +1 -0
  193. package/dist/dev/media/primitives/select-tracks.js +45 -0
  194. package/dist/dev/media/primitives/select-tracks.js.map +1 -0
  195. package/dist/dev/{core → media}/types/index.d.ts +30 -2
  196. package/dist/dev/media/types/index.d.ts.map +1 -0
  197. package/dist/dev/media/types/index.js +36 -0
  198. package/dist/dev/media/types/index.js.map +1 -0
  199. package/dist/dev/media/utils/preload.js +22 -0
  200. package/dist/dev/media/utils/preload.js.map +1 -0
  201. package/dist/dev/media/utils/track-selection.js +47 -0
  202. package/dist/dev/media/utils/track-selection.js.map +1 -0
  203. package/dist/dev/media/utils/tracks.js +103 -0
  204. package/dist/dev/media/utils/tracks.js.map +1 -0
  205. package/dist/dev/{core/abr → network}/bandwidth-estimator.d.ts +17 -2
  206. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -0
  207. package/dist/dev/{core/abr → network}/bandwidth-estimator.js +6 -5
  208. package/dist/dev/network/bandwidth-estimator.js.map +1 -0
  209. package/dist/{default/dom → dev}/network/chunked-stream-iterable.js +1 -1
  210. package/dist/dev/network/chunked-stream-iterable.js.map +1 -0
  211. package/dist/{default/core/abr → dev/network}/ewma.js +1 -1
  212. package/dist/dev/network/ewma.js.map +1 -0
  213. package/dist/dev/network/fetch.js +109 -0
  214. package/dist/dev/network/fetch.js.map +1 -0
  215. package/dist/dev/playback/actors/dom/segment-loader.d.ts +36 -0
  216. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -0
  217. package/dist/{default/dom/features/segment-loader-actor.js → dev/playback/actors/dom/segment-loader.js} +24 -15
  218. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -0
  219. package/dist/dev/{dom/media/source-buffer-actor.d.ts → playback/actors/dom/source-buffer.d.ts} +5 -5
  220. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -0
  221. package/dist/{default/dom/media/source-buffer-actor.js → dev/playback/actors/dom/source-buffer.js} +8 -8
  222. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -0
  223. package/dist/dev/playback/actors/dom/text-tracks.d.ts +1 -0
  224. package/dist/{default/dom/features/text-tracks-actor.js → dev/playback/actors/dom/text-tracks.js} +7 -3
  225. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -0
  226. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +59 -0
  227. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -0
  228. package/dist/dev/playback/actors/text-track-segment-loader.js +135 -0
  229. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -0
  230. package/dist/dev/playback/actors/text-tracks.d.ts +42 -0
  231. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -0
  232. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +17 -0
  233. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -0
  234. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +58 -0
  235. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -0
  236. package/dist/dev/playback/behaviors/dom/end-of-stream.js +178 -0
  237. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -0
  238. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +62 -0
  239. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -0
  240. package/dist/dev/playback/behaviors/dom/load-segments.js +177 -0
  241. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -0
  242. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +173 -0
  243. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -0
  244. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +89 -0
  245. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -0
  246. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +31 -0
  247. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -0
  248. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +54 -0
  249. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -0
  250. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +99 -0
  251. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -0
  252. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +34 -0
  253. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -0
  254. package/dist/dev/playback/behaviors/dom/track-current-time.js +46 -0
  255. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -0
  256. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +48 -0
  257. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -0
  258. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +94 -0
  259. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -0
  260. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +34 -0
  261. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -0
  262. package/dist/dev/playback/behaviors/dom/track-playback-rate.js +37 -0
  263. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -0
  264. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +90 -0
  265. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -0
  266. package/dist/dev/playback/behaviors/quality-switching.js +96 -0
  267. package/dist/dev/playback/behaviors/quality-switching.js.map +1 -0
  268. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +7 -0
  269. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -0
  270. package/dist/dev/playback/behaviors/resolve-presentation.js +85 -0
  271. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -0
  272. package/dist/dev/playback/behaviors/resolve-track.js +97 -0
  273. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -0
  274. package/dist/dev/playback/behaviors/select-tracks.js +132 -0
  275. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -0
  276. package/dist/dev/playback/behaviors/sync-preload.js +59 -0
  277. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -0
  278. package/dist/dev/playback/behaviors/track-types.js +61 -0
  279. package/dist/dev/playback/behaviors/track-types.js.map +1 -0
  280. package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.d.ts +14 -10
  281. package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -0
  282. package/dist/dev/{dom/playback-engine → playback/engines/hls}/adapter.js +36 -25
  283. package/dist/dev/playback/engines/hls/adapter.js.map +1 -0
  284. package/dist/dev/playback/engines/hls/engine.d.ts +177 -0
  285. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -0
  286. package/dist/dev/playback/engines/hls/engine.js +102 -0
  287. package/dist/dev/playback/engines/hls/engine.js.map +1 -0
  288. package/package.json +8 -9
  289. package/dist/default/core/abr/bandwidth-estimator.js.map +0 -1
  290. package/dist/default/core/abr/ewma.js.map +0 -1
  291. package/dist/default/core/abr/quality-selection.js +0 -53
  292. package/dist/default/core/abr/quality-selection.js.map +0 -1
  293. package/dist/default/core/buffer/back-buffer.js.map +0 -1
  294. package/dist/default/core/buffer/forward-buffer.js.map +0 -1
  295. package/dist/default/core/create-machine-actor.js.map +0 -1
  296. package/dist/default/core/create-machine-reactor.js.map +0 -1
  297. package/dist/default/core/create-transition-actor.js.map +0 -1
  298. package/dist/default/core/features/calculate-presentation-duration.js +0 -53
  299. package/dist/default/core/features/calculate-presentation-duration.js.map +0 -1
  300. package/dist/default/core/features/quality-switching.js +0 -69
  301. package/dist/default/core/features/quality-switching.js.map +0 -1
  302. package/dist/default/core/features/resolve-presentation.js +0 -73
  303. package/dist/default/core/features/resolve-presentation.js.map +0 -1
  304. package/dist/default/core/features/resolve-track.js +0 -66
  305. package/dist/default/core/features/resolve-track.js.map +0 -1
  306. package/dist/default/core/features/select-tracks.js +0 -130
  307. package/dist/default/core/features/select-tracks.js.map +0 -1
  308. package/dist/default/core/features/sync-preload-attribute.js +0 -26
  309. package/dist/default/core/features/sync-preload-attribute.js.map +0 -1
  310. package/dist/default/core/hls/parse-attributes.js.map +0 -1
  311. package/dist/default/core/hls/parse-media-playlist.js.map +0 -1
  312. package/dist/default/core/hls/parse-multivariant.js.map +0 -1
  313. package/dist/default/core/hls/resolve-url.js.map +0 -1
  314. package/dist/default/core/task.js.map +0 -1
  315. package/dist/default/core/types/index.js +0 -15
  316. package/dist/default/core/types/index.js.map +0 -1
  317. package/dist/default/core/utils/generate-id.js +0 -21
  318. package/dist/default/core/utils/generate-id.js.map +0 -1
  319. package/dist/default/core/utils/track-selection.js +0 -39
  320. package/dist/default/core/utils/track-selection.js.map +0 -1
  321. package/dist/default/dom/features/end-of-stream.js +0 -145
  322. package/dist/default/dom/features/end-of-stream.js.map +0 -1
  323. package/dist/default/dom/features/load-segments.js +0 -192
  324. package/dist/default/dom/features/load-segments.js.map +0 -1
  325. package/dist/default/dom/features/load-text-track-cues.js +0 -104
  326. package/dist/default/dom/features/load-text-track-cues.js.map +0 -1
  327. package/dist/default/dom/features/segment-loader-actor.js.map +0 -1
  328. package/dist/default/dom/features/setup-mediasource.js +0 -48
  329. package/dist/default/dom/features/setup-mediasource.js.map +0 -1
  330. package/dist/default/dom/features/setup-sourcebuffer.js +0 -78
  331. package/dist/default/dom/features/setup-sourcebuffer.js.map +0 -1
  332. package/dist/default/dom/features/sync-text-tracks.js +0 -95
  333. package/dist/default/dom/features/sync-text-tracks.js.map +0 -1
  334. package/dist/default/dom/features/text-track-segment-loader-actor.js +0 -55
  335. package/dist/default/dom/features/text-track-segment-loader-actor.js.map +0 -1
  336. package/dist/default/dom/features/text-tracks-actor.js.map +0 -1
  337. package/dist/default/dom/features/track-current-time.js +0 -49
  338. package/dist/default/dom/features/track-current-time.js.map +0 -1
  339. package/dist/default/dom/features/track-playback-initiated.js +0 -66
  340. package/dist/default/dom/features/track-playback-initiated.js.map +0 -1
  341. package/dist/default/dom/features/track-playback-rate.js +0 -32
  342. package/dist/default/dom/features/track-playback-rate.js.map +0 -1
  343. package/dist/default/dom/features/update-duration.js +0 -78
  344. package/dist/default/dom/features/update-duration.js.map +0 -1
  345. package/dist/default/dom/media/append-segment.js.map +0 -1
  346. package/dist/default/dom/media/buffer-flusher.js.map +0 -1
  347. package/dist/default/dom/media/mediasource-setup.js.map +0 -1
  348. package/dist/default/dom/media/source-buffer-actor.js.map +0 -1
  349. package/dist/default/dom/network/chunked-stream-iterable.js.map +0 -1
  350. package/dist/default/dom/network/fetch.js +0 -54
  351. package/dist/default/dom/network/fetch.js.map +0 -1
  352. package/dist/default/dom/playback-engine/adapter.js.map +0 -1
  353. package/dist/default/dom/playback-engine/engine.js +0 -142
  354. package/dist/default/dom/playback-engine/engine.js.map +0 -1
  355. package/dist/default/dom/playback-engine.js +0 -4
  356. package/dist/default/dom/text/parse-vtt-segment.js.map +0 -1
  357. package/dist/dev/core/abr/bandwidth-estimator.d.ts.map +0 -1
  358. package/dist/dev/core/abr/bandwidth-estimator.js.map +0 -1
  359. package/dist/dev/core/abr/ewma.js.map +0 -1
  360. package/dist/dev/core/abr/quality-selection.js +0 -53
  361. package/dist/dev/core/abr/quality-selection.js.map +0 -1
  362. package/dist/dev/core/actor.d.ts.map +0 -1
  363. package/dist/dev/core/buffer/back-buffer.js.map +0 -1
  364. package/dist/dev/core/buffer/forward-buffer.js.map +0 -1
  365. package/dist/dev/core/create-machine-actor.d.ts +0 -12
  366. package/dist/dev/core/create-machine-actor.d.ts.map +0 -1
  367. package/dist/dev/core/create-machine-actor.js.map +0 -1
  368. package/dist/dev/core/create-machine-reactor.d.ts +0 -8
  369. package/dist/dev/core/create-machine-reactor.d.ts.map +0 -1
  370. package/dist/dev/core/create-machine-reactor.js.map +0 -1
  371. package/dist/dev/core/create-transition-actor.d.ts +0 -23
  372. package/dist/dev/core/create-transition-actor.d.ts.map +0 -1
  373. package/dist/dev/core/create-transition-actor.js.map +0 -1
  374. package/dist/dev/core/features/calculate-presentation-duration.js +0 -53
  375. package/dist/dev/core/features/calculate-presentation-duration.js.map +0 -1
  376. package/dist/dev/core/features/quality-switching.js +0 -69
  377. package/dist/dev/core/features/quality-switching.js.map +0 -1
  378. package/dist/dev/core/features/resolve-presentation.js +0 -73
  379. package/dist/dev/core/features/resolve-presentation.js.map +0 -1
  380. package/dist/dev/core/features/resolve-track.js +0 -66
  381. package/dist/dev/core/features/resolve-track.js.map +0 -1
  382. package/dist/dev/core/features/select-tracks.js +0 -130
  383. package/dist/dev/core/features/select-tracks.js.map +0 -1
  384. package/dist/dev/core/features/sync-preload-attribute.js +0 -26
  385. package/dist/dev/core/features/sync-preload-attribute.js.map +0 -1
  386. package/dist/dev/core/hls/parse-attributes.js.map +0 -1
  387. package/dist/dev/core/hls/parse-media-playlist.js.map +0 -1
  388. package/dist/dev/core/hls/parse-multivariant.js.map +0 -1
  389. package/dist/dev/core/hls/resolve-url.js.map +0 -1
  390. package/dist/dev/core/task.js.map +0 -1
  391. package/dist/dev/core/types/index.d.ts.map +0 -1
  392. package/dist/dev/core/types/index.js +0 -15
  393. package/dist/dev/core/types/index.js.map +0 -1
  394. package/dist/dev/core/utils/generate-id.js +0 -21
  395. package/dist/dev/core/utils/generate-id.js.map +0 -1
  396. package/dist/dev/core/utils/track-selection.d.ts +0 -16
  397. package/dist/dev/core/utils/track-selection.d.ts.map +0 -1
  398. package/dist/dev/core/utils/track-selection.js +0 -39
  399. package/dist/dev/core/utils/track-selection.js.map +0 -1
  400. package/dist/dev/dom/features/end-of-stream.js +0 -145
  401. package/dist/dev/dom/features/end-of-stream.js.map +0 -1
  402. package/dist/dev/dom/features/load-segments.d.ts +0 -72
  403. package/dist/dev/dom/features/load-segments.d.ts.map +0 -1
  404. package/dist/dev/dom/features/load-segments.js +0 -192
  405. package/dist/dev/dom/features/load-segments.js.map +0 -1
  406. package/dist/dev/dom/features/load-text-track-cues.d.ts +0 -85
  407. package/dist/dev/dom/features/load-text-track-cues.d.ts.map +0 -1
  408. package/dist/dev/dom/features/load-text-track-cues.js +0 -104
  409. package/dist/dev/dom/features/load-text-track-cues.js.map +0 -1
  410. package/dist/dev/dom/features/segment-loader-actor.d.ts +0 -1
  411. package/dist/dev/dom/features/segment-loader-actor.js.map +0 -1
  412. package/dist/dev/dom/features/setup-mediasource.js +0 -48
  413. package/dist/dev/dom/features/setup-mediasource.js.map +0 -1
  414. package/dist/dev/dom/features/setup-sourcebuffer.d.ts +0 -9
  415. package/dist/dev/dom/features/setup-sourcebuffer.d.ts.map +0 -1
  416. package/dist/dev/dom/features/setup-sourcebuffer.js +0 -78
  417. package/dist/dev/dom/features/setup-sourcebuffer.js.map +0 -1
  418. package/dist/dev/dom/features/sync-text-tracks.js +0 -95
  419. package/dist/dev/dom/features/sync-text-tracks.js.map +0 -1
  420. package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts +0 -12
  421. package/dist/dev/dom/features/text-track-segment-loader-actor.d.ts.map +0 -1
  422. package/dist/dev/dom/features/text-track-segment-loader-actor.js +0 -55
  423. package/dist/dev/dom/features/text-track-segment-loader-actor.js.map +0 -1
  424. package/dist/dev/dom/features/text-tracks-actor.d.ts +0 -31
  425. package/dist/dev/dom/features/text-tracks-actor.d.ts.map +0 -1
  426. package/dist/dev/dom/features/text-tracks-actor.js.map +0 -1
  427. package/dist/dev/dom/features/track-current-time.d.ts +0 -40
  428. package/dist/dev/dom/features/track-current-time.d.ts.map +0 -1
  429. package/dist/dev/dom/features/track-current-time.js +0 -49
  430. package/dist/dev/dom/features/track-current-time.js.map +0 -1
  431. package/dist/dev/dom/features/track-playback-initiated.d.ts +0 -46
  432. package/dist/dev/dom/features/track-playback-initiated.d.ts.map +0 -1
  433. package/dist/dev/dom/features/track-playback-initiated.js +0 -66
  434. package/dist/dev/dom/features/track-playback-initiated.js.map +0 -1
  435. package/dist/dev/dom/features/track-playback-rate.d.ts +0 -34
  436. package/dist/dev/dom/features/track-playback-rate.d.ts.map +0 -1
  437. package/dist/dev/dom/features/track-playback-rate.js +0 -32
  438. package/dist/dev/dom/features/track-playback-rate.js.map +0 -1
  439. package/dist/dev/dom/features/update-duration.js +0 -78
  440. package/dist/dev/dom/features/update-duration.js.map +0 -1
  441. package/dist/dev/dom/media/append-segment.d.ts.map +0 -1
  442. package/dist/dev/dom/media/append-segment.js.map +0 -1
  443. package/dist/dev/dom/media/buffer-flusher.d.ts.map +0 -1
  444. package/dist/dev/dom/media/buffer-flusher.js.map +0 -1
  445. package/dist/dev/dom/media/mediasource-setup.js.map +0 -1
  446. package/dist/dev/dom/media/source-buffer-actor.d.ts.map +0 -1
  447. package/dist/dev/dom/media/source-buffer-actor.js.map +0 -1
  448. package/dist/dev/dom/network/chunked-stream-iterable.js.map +0 -1
  449. package/dist/dev/dom/network/fetch.js +0 -54
  450. package/dist/dev/dom/network/fetch.js.map +0 -1
  451. package/dist/dev/dom/playback-engine/adapter.d.ts.map +0 -1
  452. package/dist/dev/dom/playback-engine/adapter.js.map +0 -1
  453. package/dist/dev/dom/playback-engine/engine.d.ts +0 -124
  454. package/dist/dev/dom/playback-engine/engine.d.ts.map +0 -1
  455. package/dist/dev/dom/playback-engine/engine.js +0 -142
  456. package/dist/dev/dom/playback-engine/engine.js.map +0 -1
  457. package/dist/dev/dom/playback-engine.d.ts +0 -4
  458. package/dist/dev/dom/playback-engine.js +0 -4
  459. package/dist/dev/dom/text/parse-vtt-segment.d.ts +0 -12
  460. package/dist/dev/dom/text/parse-vtt-segment.d.ts.map +0 -1
  461. package/dist/dev/dom/text/parse-vtt-segment.js.map +0 -1
@@ -1,4 +1,4 @@
1
- //#region src/core/buffer/forward-buffer.ts
1
+ //#region src/media/buffer/forward-buffer.ts
2
2
  /**
3
3
  * Default forward buffer configuration.
4
4
  */
@@ -56,6 +56,18 @@ function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFA
56
56
  if (beyond.length === 0) return Infinity;
57
57
  return Math.min(...beyond.map((seg) => seg.startTime));
58
58
  }
59
+ /**
60
+ * Find the start time of the segment containing `currentTime` (or the last
61
+ * segment if `currentTime` is past the end). Returns `undefined` when
62
+ * `currentTime` is undefined or when no segment matches.
63
+ *
64
+ * Used to detect "meaningful currentTime change" — two times that map to the
65
+ * same segment start aren't a load-trigger; crossing a segment boundary is.
66
+ */
67
+ function segmentStartForTime(currentTime, segments) {
68
+ if (currentTime == null) return void 0;
69
+ return segments?.find(({ startTime, duration }, i, all) => currentTime >= startTime && (currentTime < startTime + duration || i === all.length - 1))?.startTime;
70
+ }
59
71
  function getSegmentsToLoad(segments, bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
60
72
  if (segments.length === 0) return [];
61
73
  const targetTime = currentTime + config.bufferDuration;
@@ -68,6 +80,6 @@ function getSegmentsToLoad(segments, bufferedSegments, currentTime, config = DEF
68
80
  });
69
81
  }
70
82
  //#endregion
71
- export { DEFAULT_FORWARD_BUFFER_CONFIG, calculateForwardFlushPoint, getSegmentsToLoad };
83
+ export { DEFAULT_FORWARD_BUFFER_CONFIG, calculateForwardFlushPoint, getSegmentsToLoad, segmentStartForTime };
72
84
 
73
85
  //# sourceMappingURL=forward-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-buffer.js","names":[],"sources":["../../../../src/media/buffer/forward-buffer.ts"],"sourcesContent":["/**\n * Forward Buffer Strategy (Simple)\n *\n * Determines which segments to load for forward buffer management.\n * V1 uses simple fixed-duration strategy (buffer N seconds ahead).\n */\n\nimport type { Segment } from '../types';\n\n/**\n * Forward buffer configuration.\n */\nexport interface ForwardBufferConfig {\n /**\n * Duration in seconds to buffer ahead of current playback position.\n * Default: 30 seconds.\n */\n bufferDuration: number;\n}\n\n/**\n * Default forward buffer configuration.\n */\nexport const DEFAULT_FORWARD_BUFFER_CONFIG: ForwardBufferConfig = {\n bufferDuration: 30,\n};\n\n/**\n * Get segments that need to be loaded for forward buffer.\n *\n * Determines which segments to load to maintain target buffer duration.\n * Handles discontiguous buffering (gaps after seeks).\n *\n * Algorithm:\n * 1. Calculate target time: currentTime + bufferDuration\n * 2. Find all segments in range [currentTime, targetTime)\n * 3. Filter out segments already buffered at that time position\n * 4. Return segments to load (fills gaps + extends to target)\n *\n * @param segments - All available segments from playlist\n * @param bufferedSegments - Segments already buffered (ordered by startTime)\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Array of segments to load (empty if buffer is sufficient)\n *\n * @example\n * // After seek: buffered [0-12, 18-30], playing at 7s\n * const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });\n * // Returns [seg-12, seg-30] (fills gap, extends to target 31s)\n */\n/**\n * Calculate the start time from which to flush forward buffer content.\n *\n * Content that starts at or beyond `currentTime + bufferDuration` is no\n * longer needed for the current playback position and should be removed\n * from the SourceBuffer. This prevents unbounded accumulation of scattered\n * SourceBuffer content after seeks, which can cause QuotaExceededError on\n * long-form content.\n *\n * Returns `Infinity` when nothing needs flushing (no buffered segments\n * exist beyond the threshold).\n *\n * @param bufferedSegments - Segments currently tracked in the buffer model\n * @param currentTime - Current playback position in seconds\n * @param config - Optional forward buffer configuration\n * @returns Start time to flush from (flush range: [flushStart, Infinity)),\n * or Infinity if no flush is needed\n *\n * @example\n * // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30\n * const flushStart = calculateForwardFlushPoint(segments, 0);\n * // Returns 30 — flush [30, Infinity), keep [0, 30)\n */\nexport function calculateForwardFlushPoint(\n bufferedSegments: readonly Segment[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): number {\n if (bufferedSegments.length === 0) return Infinity;\n\n const threshold = currentTime + config.bufferDuration;\n\n // Find segments that start at or beyond the threshold\n const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);\n\n if (beyond.length === 0) return Infinity;\n\n // Flush from the earliest such segment onward\n return Math.min(...beyond.map((seg) => seg.startTime));\n}\n\n/**\n * Find the start time of the segment containing `currentTime` (or the last\n * segment if `currentTime` is past the end). Returns `undefined` when\n * `currentTime` is undefined or when no segment matches.\n *\n * Used to detect \"meaningful currentTime change\" — two times that map to the\n * same segment start aren't a load-trigger; crossing a segment boundary is.\n */\nexport function segmentStartForTime(\n currentTime: number | undefined,\n segments: readonly Pick<Segment, 'startTime' | 'duration'>[] | undefined\n): number | undefined {\n if (currentTime == null) return undefined;\n return segments?.find(\n ({ startTime, duration }, i, all) =>\n currentTime >= startTime && (currentTime < startTime + duration || i === all.length - 1)\n )?.startTime;\n}\n\nexport function getSegmentsToLoad(\n segments: readonly Segment[],\n bufferedSegments: readonly Pick<Segment, 'startTime' | 'duration'>[],\n currentTime: number,\n config: ForwardBufferConfig = DEFAULT_FORWARD_BUFFER_CONFIG\n): Segment[] {\n if (segments.length === 0) {\n return [];\n }\n\n // Calculate target buffer end time\n const targetTime = currentTime + config.bufferDuration;\n\n // Create set of buffered segment start times for fast lookup\n // V1 simple: if ANY segment is buffered at a given time, don't load for that time\n // V2 (future): would compare by startTime + bitrate/track for quality switching\n const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));\n\n // Find segments to load:\n // - Overlaps buffer window [currentTime, targetTime)\n // - Not already buffered at that time position\n const toLoad = segments.filter((seg) => {\n // Segment must overlap the buffer window\n const segmentEnd = seg.startTime + seg.duration;\n const isInRange = seg.startTime < targetTime && segmentEnd > currentTime;\n\n // Must not have a segment buffered at this time position\n const isNotBuffered = !bufferedStartTimes.has(seg.startTime);\n\n return isInRange && isNotBuffered;\n });\n\n return toLoad;\n}\n"],"mappings":";;;;AAuBA,MAAa,gCAAqD,EAChE,gBAAgB,IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDD,SAAgB,2BACd,kBACA,aACA,SAA8B,+BACtB;AACR,KAAI,iBAAiB,WAAW,EAAG,QAAO;CAE1C,MAAM,YAAY,cAAc,OAAO;CAGvC,MAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,aAAa,UAAU;AAE3E,KAAI,OAAO,WAAW,EAAG,QAAO;AAGhC,QAAO,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,IAAI,UAAU,CAAC;;;;;;;;;;AAWxD,SAAgB,oBACd,aACA,UACoB;AACpB,KAAI,eAAe,KAAM,QAAO,KAAA;AAChC,QAAO,UAAU,MACd,EAAE,WAAW,YAAY,GAAG,QAC3B,eAAe,cAAc,cAAc,YAAY,YAAY,MAAM,IAAI,SAAS,GACzF,EAAE;;AAGL,SAAgB,kBACd,UACA,kBACA,aACA,SAA8B,+BACnB;AACX,KAAI,SAAS,WAAW,EACtB,QAAO,EAAE;CAIX,MAAM,aAAa,cAAc,OAAO;CAKxC,MAAM,qBAAqB,IAAI,IAAI,iBAAiB,KAAK,QAAQ,IAAI,UAAU,CAAC;AAgBhF,QAXe,SAAS,QAAQ,QAAQ;EAEtC,MAAM,aAAa,IAAI,YAAY,IAAI;EACvC,MAAM,YAAY,IAAI,YAAY,cAAc,aAAa;EAG7D,MAAM,gBAAgB,CAAC,mBAAmB,IAAI,IAAI,UAAU;AAE5D,SAAO,aAAa;GACpB"}
@@ -1,4 +1,4 @@
1
- //#region src/dom/media/append-segment.ts
1
+ //#region src/media/dom/mse/append-segment.ts
2
2
  /**
3
3
  * Append media data to a SourceBuffer.
4
4
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append-segment.js","names":[],"sources":["../../../../../src/media/dom/mse/append-segment.ts"],"sourcesContent":["/**\n * Segment appender helper.\n *\n * Appends media data (ArrayBuffer or AsyncIterable<Uint8Array> stream) to a\n * SourceBuffer, waiting for `updateend` between calls so the browser can\n * process each append before the next one arrives.\n */\n\n/** Data accepted by appendSegment — a full buffer or an async chunk stream. */\nexport type AppendData = ArrayBuffer | AsyncIterable<Uint8Array>;\n\n/**\n * Append media data to a SourceBuffer.\n *\n * Accepts either a full ArrayBuffer (single append) or an AsyncIterable of\n * Uint8Array chunks (one append per chunk, in order). Waits for `updateend`\n * between each call so appends are serialized correctly.\n *\n * Errors from the SourceBuffer (`error` event) or from the iterable are\n * propagated as rejections.\n */\nexport async function appendSegment(sourceBuffer: SourceBuffer, data: AppendData, signal?: AbortSignal): Promise<void> {\n if (data instanceof ArrayBuffer) {\n await appendChunk(sourceBuffer, data);\n } else {\n try {\n for await (const chunk of data) {\n // Check between chunks so an abort can stop streaming before the next\n // appendBuffer call. The current chunk (if any) has already landed in the\n // SourceBuffer; the partial: true flag in the actor model reflects this.\n if (signal?.aborted) throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n await appendChunk(sourceBuffer, chunk);\n }\n } catch (e) {\n // Reset the MSE segment parser on any abort to discard partial fMP4 box\n // data from the SourceBuffer's internal byte buffer. Two paths reach here:\n //\n // 1. Explicit abort check above (signal.aborted between chunks) — the\n // last appended chunk may have left the parser mid-fragment.\n //\n // 2. The fetch was cancelled (signal fired), causing the underlying\n // ReadableStream to error and the for-await loop to throw directly\n // without ever passing through the signal check above. Same cleanup\n // is needed: partial box data must be cleared before the next append.\n //\n // Without this, the next appendBuffer call sees stale partial data and\n // Chrome throws CHUNK_DEMUXER_ERROR_APPEND_FAILED.\n if (e instanceof DOMException && e.name === 'AbortError' && !sourceBuffer.updating) {\n try {\n sourceBuffer.abort();\n } catch {\n // Thrown if the MediaSource is not \"open\" (e.g. during teardown).\n }\n }\n throw e;\n }\n }\n}\n\nasync function appendChunk(sourceBuffer: SourceBuffer, data: ArrayBuffer | Uint8Array<ArrayBufferLike>): Promise<void> {\n if (sourceBuffer.updating) {\n await new Promise<void>((resolve) => {\n const onUpdateEnd = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n resolve();\n };\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n });\n }\n\n return new Promise<void>((resolve, reject) => {\n const onUpdateEnd = () => {\n cleanup();\n resolve();\n };\n\n const onError = (event: Event) => {\n cleanup();\n reject(new Error(`SourceBuffer append error: ${event.type}`));\n };\n\n const cleanup = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n sourceBuffer.removeEventListener('error', onError);\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n sourceBuffer.addEventListener('error', onError);\n\n try {\n sourceBuffer.appendBuffer(data as ArrayBuffer);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;AAqBA,eAAsB,cAAc,cAA4B,MAAkB,QAAqC;AACrH,KAAI,gBAAgB,YAClB,OAAM,YAAY,cAAc,KAAK;KAErC,KAAI;AACF,aAAW,MAAM,SAAS,MAAM;AAI9B,OAAI,QAAQ,QAAS,OAAM,OAAO,UAAU,IAAI,aAAa,WAAW,aAAa;AACrF,SAAM,YAAY,cAAc,MAAM;;UAEjC,GAAG;AAcV,MAAI,aAAa,gBAAgB,EAAE,SAAS,gBAAgB,CAAC,aAAa,SACxE,KAAI;AACF,gBAAa,OAAO;UACd;AAIV,QAAM;;;AAKZ,eAAe,YAAY,cAA4B,MAAgE;AACrH,KAAI,aAAa,SACf,OAAM,IAAI,SAAe,YAAY;EACnC,MAAM,oBAAoB;AACxB,gBAAa,oBAAoB,aAAa,YAAY;AAC1D,YAAS;;AAEX,eAAa,iBAAiB,aAAa,YAAY;GACvD;AAGJ,QAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,oBAAoB;AACxB,YAAS;AACT,YAAS;;EAGX,MAAM,WAAW,UAAiB;AAChC,YAAS;AACT,0BAAO,IAAI,MAAM,8BAA8B,MAAM,OAAO,CAAC;;EAG/D,MAAM,gBAAgB;AACpB,gBAAa,oBAAoB,aAAa,YAAY;AAC1D,gBAAa,oBAAoB,SAAS,QAAQ;;AAGpD,eAAa,iBAAiB,aAAa,YAAY;AACvD,eAAa,iBAAiB,SAAS,QAAQ;AAE/C,MAAI;AACF,gBAAa,aAAa,KAAoB;WACvC,OAAO;AACd,YAAS;AACT,UAAO,MAAM;;GAEf"}
@@ -1,4 +1,4 @@
1
- //#region src/dom/media/buffer-flusher.ts
1
+ //#region src/media/dom/mse/buffer-flusher.ts
2
2
  /**
3
3
  * Buffer flusher helper (P12)
4
4
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer-flusher.js","names":[],"sources":["../../../../../src/media/dom/mse/buffer-flusher.ts"],"sourcesContent":["/**\n * Buffer flusher helper (P12)\n *\n * Removes a time range from a SourceBuffer to manage memory.\n */\n\n/**\n * Remove a time range from a SourceBuffer.\n *\n * Waits for the SourceBuffer to be ready (not updating), then removes\n * the specified range. Returns a promise that resolves when removal completes.\n *\n * @param sourceBuffer - The SourceBuffer to remove data from\n * @param start - Start of the time range to remove (seconds)\n * @param end - End of the time range to remove (seconds)\n * @returns Promise that resolves when removal completes\n *\n * @example\n * await flushBuffer(videoSourceBuffer, 0, 30);\n */\nexport async function flushBuffer(sourceBuffer: SourceBuffer, start: number, end: number): Promise<void> {\n // Wait for SourceBuffer to be ready (not currently updating)\n if (sourceBuffer.updating) {\n await new Promise<void>((resolve) => {\n const onUpdateEnd = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n resolve();\n };\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n });\n }\n\n return new Promise<void>((resolve, reject) => {\n const onUpdateEnd = () => {\n cleanup();\n resolve();\n };\n\n const onError = (event: Event) => {\n cleanup();\n reject(new Error(`SourceBuffer remove error: ${event.type}`));\n };\n\n const cleanup = () => {\n sourceBuffer.removeEventListener('updateend', onUpdateEnd);\n sourceBuffer.removeEventListener('error', onError);\n };\n\n sourceBuffer.addEventListener('updateend', onUpdateEnd);\n sourceBuffer.addEventListener('error', onError);\n\n try {\n sourceBuffer.remove(start, end);\n } catch (error) {\n cleanup();\n reject(error);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,eAAsB,YAAY,cAA4B,OAAe,KAA4B;AAEvG,KAAI,aAAa,SACf,OAAM,IAAI,SAAe,YAAY;EACnC,MAAM,oBAAoB;AACxB,gBAAa,oBAAoB,aAAa,YAAY;AAC1D,YAAS;;AAEX,eAAa,iBAAiB,aAAa,YAAY;GACvD;AAGJ,QAAO,IAAI,SAAe,SAAS,WAAW;EAC5C,MAAM,oBAAoB;AACxB,YAAS;AACT,YAAS;;EAGX,MAAM,WAAW,UAAiB;AAChC,YAAS;AACT,0BAAO,IAAI,MAAM,8BAA8B,MAAM,OAAO,CAAC;;EAG/D,MAAM,gBAAgB;AACpB,gBAAa,oBAAoB,aAAa,YAAY;AAC1D,gBAAa,oBAAoB,SAAS,QAAQ;;AAGpD,eAAa,iBAAiB,aAAa,YAAY;AACvD,eAAa,iBAAiB,SAAS,QAAQ;AAE/C,MAAI;AACF,gBAAa,OAAO,OAAO,IAAI;WACxB,OAAO;AACd,YAAS;AACT,UAAO,MAAM;;GAEf"}
@@ -0,0 +1,78 @@
1
+ import { hasPresentationDuration } from "../../types/index.js";
2
+ //#region src/media/dom/mse/duration.ts
3
+ /**
4
+ * Check if we have the basics to update MediaSource duration:
5
+ * a `mediaSource` and a `presentation` with a numeric duration.
6
+ */
7
+ function canUpdateDuration(presentation, mediaSource) {
8
+ return !!(mediaSource && presentation && hasPresentationDuration(presentation));
9
+ }
10
+ /**
11
+ * Get the maximum buffered end time across an iterable of SourceBuffers
12
+ * (typically `mediaSource.sourceBuffers`). Returns `0` when the collection is
13
+ * empty or no buffer has any buffered ranges.
14
+ */
15
+ function getMaxBufferedEnd(buffers) {
16
+ let maxEnd = 0;
17
+ for (const buffer of buffers) {
18
+ const { buffered } = buffer;
19
+ if (buffered.length > 0) {
20
+ const end = buffered.end(buffered.length - 1);
21
+ if (end > maxEnd) maxEnd = end;
22
+ }
23
+ }
24
+ return maxEnd;
25
+ }
26
+ /**
27
+ * Check if the preconditions are met to *attempt* a `mediaSource.duration`
28
+ * write: a `mediaSource` is in scope and the presentation has a valid
29
+ * positive duration (or `Infinity` for live).
30
+ *
31
+ * Does **not** check `mediaSource.readyState` or `mediaSource.duration` —
32
+ * those are DOM properties the caller resolves at write time (e.g., by
33
+ * `await`ing `waitForMediaSourceOpen` and re-checking `readyState` after,
34
+ * and guarding on the existing `mediaSource.duration` for idempotency).
35
+ * Keeping these off the signal-driven predicate lets callers use this
36
+ * inside reactor state derivation without smuggling non-reactive DOM
37
+ * reads into `computed(...)`.
38
+ *
39
+ * `Infinity` is allowed — per the MSE spec, `mediaSource.duration = +Infinity`
40
+ * is how live playback signals an indefinite duration.
41
+ */
42
+ function shouldUpdateDuration(presentation, mediaSource) {
43
+ if (!canUpdateDuration(presentation, mediaSource)) return false;
44
+ const duration = presentation.duration;
45
+ if (Number.isNaN(duration) || duration <= 0) return false;
46
+ return true;
47
+ }
48
+ /**
49
+ * Wait for all currently-updating SourceBuffers in `buffers` to finish, or
50
+ * until `signal` aborts — whichever fires first.
51
+ *
52
+ * The MSE spec forbids setting `MediaSource.duration` while any attached
53
+ * SourceBuffer has `updating === true`. This defers until all are idle.
54
+ * Listeners are registered with `{ signal }` so an abort tears them down
55
+ * up-front rather than leaving them dangling until the next `updateend`.
56
+ */
57
+ function waitForSourceBuffersReady(buffers, signal) {
58
+ if (signal.aborted) return Promise.resolve();
59
+ const updating = [];
60
+ for (const buf of buffers) if (buf.updating) updating.push(buf);
61
+ if (updating.length === 0) return Promise.resolve();
62
+ return new Promise((resolve) => {
63
+ let remaining = updating.length;
64
+ const onUpdateEnd = () => {
65
+ remaining--;
66
+ if (remaining === 0) resolve();
67
+ };
68
+ for (const buf of updating) buf.addEventListener("updateend", onUpdateEnd, {
69
+ once: true,
70
+ signal
71
+ });
72
+ signal.addEventListener("abort", () => resolve(), { once: true });
73
+ });
74
+ }
75
+ //#endregion
76
+ export { getMaxBufferedEnd, shouldUpdateDuration, waitForSourceBuffersReady };
77
+
78
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.js","names":[],"sources":["../../../../../src/media/dom/mse/duration.ts"],"sourcesContent":["/**\n * MediaSource duration helpers.\n *\n * Predicates and async primitives for propagating a presentation's duration\n * to `mediaSource.duration` under the MSE spec's constraints:\n *\n * - `duration` cannot be set while any attached SourceBuffer is `updating`.\n * - `duration` cannot be less than any buffered range's end time.\n *\n * The buffer-set helpers take a `SourceBufferList` (or any iterable of\n * `SourceBuffer`) — they operate uniformly across whatever buffers are\n * attached, so callers in audio-only, video-only, and mixed configurations\n * compose them without per-type plumbing. `mediaSource.sourceBuffers` is the\n * canonical aggregate.\n *\n * Consumed by `updateMediaSourceDuration` (DOM behavior) — kept here so the layering\n * stays clean: the predicates and wait helper are pure DOM/MSE primitives\n * with no `core/` reactivity.\n */\n\nimport type { MaybeResolvedPresentation } from '../../types';\nimport { hasPresentationDuration } from '../../types';\n\ntype SourceBufferIterable = SourceBufferList | Iterable<SourceBuffer>;\n\n/**\n * Check if we have the basics to update MediaSource duration:\n * a `mediaSource` and a `presentation` with a numeric duration.\n */\nexport function canUpdateDuration(\n presentation: MaybeResolvedPresentation | undefined,\n mediaSource: MediaSource | undefined\n): boolean {\n return !!(mediaSource && presentation && hasPresentationDuration(presentation));\n}\n\n/**\n * Get the maximum buffered end time across an iterable of SourceBuffers\n * (typically `mediaSource.sourceBuffers`). Returns `0` when the collection is\n * empty or no buffer has any buffered ranges.\n */\nexport function getMaxBufferedEnd(buffers: SourceBufferIterable): number {\n let maxEnd = 0;\n\n for (const buffer of buffers) {\n const { buffered } = buffer;\n if (buffered.length > 0) {\n const end = buffered.end(buffered.length - 1);\n if (end > maxEnd) {\n maxEnd = end;\n }\n }\n }\n\n return maxEnd;\n}\n\n/**\n * Check if the preconditions are met to *attempt* a `mediaSource.duration`\n * write: a `mediaSource` is in scope and the presentation has a valid\n * positive duration (or `Infinity` for live).\n *\n * Does **not** check `mediaSource.readyState` or `mediaSource.duration` —\n * those are DOM properties the caller resolves at write time (e.g., by\n * `await`ing `waitForMediaSourceOpen` and re-checking `readyState` after,\n * and guarding on the existing `mediaSource.duration` for idempotency).\n * Keeping these off the signal-driven predicate lets callers use this\n * inside reactor state derivation without smuggling non-reactive DOM\n * reads into `computed(...)`.\n *\n * `Infinity` is allowed — per the MSE spec, `mediaSource.duration = +Infinity`\n * is how live playback signals an indefinite duration.\n */\nexport function shouldUpdateDuration(\n presentation: MaybeResolvedPresentation | undefined,\n mediaSource: MediaSource | undefined\n): boolean {\n if (!canUpdateDuration(presentation, mediaSource)) return false;\n\n const duration = presentation!.duration!;\n\n if (Number.isNaN(duration) || duration <= 0) return false;\n\n return true;\n}\n\n/**\n * Wait for all currently-updating SourceBuffers in `buffers` to finish, or\n * until `signal` aborts — whichever fires first.\n *\n * The MSE spec forbids setting `MediaSource.duration` while any attached\n * SourceBuffer has `updating === true`. This defers until all are idle.\n * Listeners are registered with `{ signal }` so an abort tears them down\n * up-front rather than leaving them dangling until the next `updateend`.\n */\nexport function waitForSourceBuffersReady(buffers: SourceBufferIterable, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.resolve();\n\n const updating: SourceBuffer[] = [];\n for (const buf of buffers) {\n if (buf.updating) updating.push(buf);\n }\n\n if (updating.length === 0) return Promise.resolve();\n\n return new Promise<void>((resolve) => {\n let remaining = updating.length;\n const onUpdateEnd = () => {\n remaining--;\n if (remaining === 0) resolve();\n };\n\n for (const buf of updating) {\n buf.addEventListener('updateend', onUpdateEnd, { once: true, signal });\n }\n\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,kBACd,cACA,aACS;AACT,QAAO,CAAC,EAAE,eAAe,gBAAgB,wBAAwB,aAAa;;;;;;;AAQhF,SAAgB,kBAAkB,SAAuC;CACvE,IAAI,SAAS;AAEb,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,EAAE,aAAa;AACrB,MAAI,SAAS,SAAS,GAAG;GACvB,MAAM,MAAM,SAAS,IAAI,SAAS,SAAS,EAAE;AAC7C,OAAI,MAAM,OACR,UAAS;;;AAKf,QAAO;;;;;;;;;;;;;;;;;;AAmBT,SAAgB,qBACd,cACA,aACS;AACT,KAAI,CAAC,kBAAkB,cAAc,YAAY,CAAE,QAAO;CAE1D,MAAM,WAAW,aAAc;AAE/B,KAAI,OAAO,MAAM,SAAS,IAAI,YAAY,EAAG,QAAO;AAEpD,QAAO;;;;;;;;;;;AAYT,SAAgB,0BAA0B,SAA+B,QAAoC;AAC3G,KAAI,OAAO,QAAS,QAAO,QAAQ,SAAS;CAE5C,MAAM,WAA2B,EAAE;AACnC,MAAK,MAAM,OAAO,QAChB,KAAI,IAAI,SAAU,UAAS,KAAK,IAAI;AAGtC,KAAI,SAAS,WAAW,EAAG,QAAO,QAAQ,SAAS;AAEnD,QAAO,IAAI,SAAe,YAAY;EACpC,IAAI,YAAY,SAAS;EACzB,MAAM,oBAAoB;AACxB;AACA,OAAI,cAAc,EAAG,UAAS;;AAGhC,OAAK,MAAM,OAAO,SAChB,KAAI,iBAAiB,aAAa,aAAa;GAAE,MAAM;GAAM;GAAQ,CAAC;AAGxE,SAAO,iBAAiB,eAAe,SAAS,EAAE,EAAE,MAAM,MAAM,CAAC;GACjE"}
@@ -0,0 +1,20 @@
1
+ //#region src/media/dom/mse/end-of-stream.ts
2
+ /**
3
+ * Check if the temporally last segment of `expectedSegments` is present in
4
+ * `appendedSegments` and not marked partial.
5
+ *
6
+ * Compares by segment ID rather than by a pipeline flag, so the result
7
+ * stays correct across quality switches (different tracks have different
8
+ * segment IDs) and back-buffer flushes (flushed segment IDs are removed
9
+ * from the appended list).
10
+ */
11
+ function isLastSegmentAppended(expectedSegments, appendedSegments) {
12
+ if (expectedSegments.length === 0) return true;
13
+ const lastSeg = expectedSegments[expectedSegments.length - 1];
14
+ if (!lastSeg) return false;
15
+ return appendedSegments?.some((s) => s.id === lastSeg.id && !s.partial) ?? false;
16
+ }
17
+ //#endregion
18
+ export { isLastSegmentAppended };
19
+
20
+ //# sourceMappingURL=end-of-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"end-of-stream.js","names":[],"sources":["../../../../../src/media/dom/mse/end-of-stream.ts"],"sourcesContent":["/**\n * End-of-stream detection helpers.\n *\n * Pure predicate that compares a track's expected segment list against the\n * appended-segment list from an MSE SourceBufferActor's snapshot. Consumed\n * by the `endOfStream` playback behavior to decide when to call\n * `MediaSource.endOfStream()`.\n *\n * Kept here so the layering stays clean: predicate has no `core/`\n * reactivity and operates on plain segment lists extracted by the caller.\n */\n\n/**\n * Minimum information about an appended segment needed to decide whether\n * it counts toward end-of-stream readiness.\n *\n * Matches the shape of `SourceBufferActor`'s context segments — callers\n * typically pass `actor.snapshot.get().context.segments` directly.\n */\nexport interface AppendedSegment {\n id: string;\n /**\n * True while a streaming append is in progress for this segment. A\n * partial segment is still streaming — it does not count as the last\n * segment being ready.\n */\n partial?: boolean;\n}\n\n/**\n * Check if the temporally last segment of `expectedSegments` is present in\n * `appendedSegments` and not marked partial.\n *\n * Compares by segment ID rather than by a pipeline flag, so the result\n * stays correct across quality switches (different tracks have different\n * segment IDs) and back-buffer flushes (flushed segment IDs are removed\n * from the appended list).\n */\nexport function isLastSegmentAppended(\n expectedSegments: readonly { id: string }[],\n appendedSegments: readonly AppendedSegment[] | undefined\n): boolean {\n if (expectedSegments.length === 0) return true;\n const lastSeg = expectedSegments[expectedSegments.length - 1];\n if (!lastSeg) return false;\n return appendedSegments?.some((s) => s.id === lastSeg.id && !s.partial) ?? false;\n}\n"],"mappings":";;;;;;;;;;AAsCA,SAAgB,sBACd,kBACA,kBACS;AACT,KAAI,iBAAiB,WAAW,EAAG,QAAO;CAC1C,MAAM,UAAU,iBAAiB,iBAAiB,SAAS;AAC3D,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,kBAAkB,MAAM,MAAM,EAAE,OAAO,QAAQ,MAAM,CAAC,EAAE,QAAQ,IAAI"}
@@ -1,5 +1,4 @@
1
- import { signal } from "../../core/signals/primitives.js";
2
- //#region src/dom/media/mediasource-setup.ts
1
+ //#region src/media/dom/mse/mediasource-setup.ts
3
2
  /**
4
3
  * MediaSource Setup
5
4
  *
@@ -97,6 +96,22 @@ function createSourceBuffer(mediaSource, mimeCodec) {
97
96
  return mediaSource.addSourceBuffer(mimeCodec);
98
97
  }
99
98
  /**
99
+ * Build a MIME codec string from a track's `mimeType` + `codecs`. Works
100
+ * on partially-resolved tracks — both fields come from the multivariant
101
+ * playlist and are available before media-playlist resolution.
102
+ *
103
+ * @param track - Track carrying `mimeType` and `codecs`
104
+ * @returns MIME codec string suitable for `MediaSource.addSourceBuffer`
105
+ *
106
+ * @example
107
+ * buildMimeCodec({ mimeType: 'video/mp4', codecs: ['avc1.42E01E'] })
108
+ * // => 'video/mp4; codecs="avc1.42E01E"'
109
+ */
110
+ function buildMimeCodec(track) {
111
+ const codecString = track.codecs?.join(",") ?? "";
112
+ return `${track.mimeType}; codecs="${codecString}"`;
113
+ }
114
+ /**
100
115
  * Check if a codec is supported.
101
116
  *
102
117
  * @param mimeCodec - MIME type with codecs string
@@ -112,35 +127,65 @@ function isCodecSupported(mimeCodec) {
112
127
  return MediaSource.isTypeSupported(mimeCodec);
113
128
  }
114
129
  /**
115
- * Create a reactive signal that mirrors `mediaSource.readyState`.
130
+ * Observe `mediaSource.readyState` changes via DOM events.
116
131
  *
117
- * Listens to `sourceopen`, `sourceended`, and `sourceclose` events and updates
118
- * the signal accordingly, making readyState visible to the TC39 signal graph.
119
- * Listeners are automatically removed when `signal` is aborted.
132
+ * Listens to `sourceopen`, `sourceended`, and `sourceclose` and invokes
133
+ * `onChange` with the current `readyState` after each event. Listeners
134
+ * are automatically removed when `abortSignal` is aborted.
120
135
  *
121
136
  * @param mediaSource - The MediaSource to observe
122
- * @param signal - AbortSignal that controls listener lifetime
123
- * @returns A `Signal.ReadonlyState` that reflects the current `readyState`
137
+ * @param abortSignal - AbortSignal that controls listener lifetime
138
+ * @param onChange - Called with the current readyState after each change
124
139
  *
125
140
  * @example
126
141
  * const controller = new AbortController();
127
- * const readyState = observeMediaSourceReadyState(mediaSource, controller.signal);
128
- * effect(() => {
129
- * if (readyState.get() === 'open') { ... }
142
+ * onMediaSourceReadyStateChange(mediaSource, controller.signal, (state) => {
143
+ * if (state === 'open') { ... }
130
144
  * });
131
- * // Later:
132
- * controller.abort();
145
+ * // Later: controller.abort();
133
146
  */
134
- function observeMediaSourceReadyState(mediaSource, abortSignal) {
135
- const readyState = signal(mediaSource.readyState);
136
- const update = () => readyState.set(mediaSource.readyState);
147
+ function onMediaSourceReadyStateChange(mediaSource, abortSignal, onChange) {
148
+ const update = () => onChange(mediaSource.readyState);
137
149
  const options = { signal: abortSignal };
138
150
  mediaSource.addEventListener("sourceopen", update, options);
139
151
  mediaSource.addEventListener("sourceended", update, options);
140
152
  mediaSource.addEventListener("sourceclose", update, options);
141
- return readyState;
153
+ }
154
+ /**
155
+ * Wait until `mediaSource.readyState` transitions away from `'closed'`
156
+ * (the next `sourceopen`/`sourceended`/`sourceclose` event), or until
157
+ * `signal` aborts — whichever fires first.
158
+ *
159
+ * Resolves immediately when `readyState` is already `'open'` or `'ended'`
160
+ * (no further transition is coming, so there's nothing to wait for). The
161
+ * caller is expected to re-check `readyState` after the await to
162
+ * distinguish `'open'` from terminal states.
163
+ *
164
+ * Companion to `onMediaSourceReadyStateChange` for one-shot use in async
165
+ * sequences (e.g., a behavior's reactor entry that needs to wait for the
166
+ * MediaSource to attach before performing a spec-conforming mutation).
167
+ *
168
+ * @example
169
+ * await waitForMediaSourceOpen(mediaSource, signal);
170
+ * if (signal.aborted || mediaSource.readyState !== 'open') return;
171
+ * // safe to perform 'open'-state-only work
172
+ */
173
+ function waitForMediaSourceOpen(mediaSource, signal) {
174
+ if (signal.aborted) return Promise.resolve();
175
+ if (mediaSource.readyState !== "closed") return Promise.resolve();
176
+ return new Promise((resolve) => {
177
+ const done = () => resolve();
178
+ const options = {
179
+ once: true,
180
+ signal
181
+ };
182
+ mediaSource.addEventListener("sourceopen", done, options);
183
+ mediaSource.addEventListener("sourceended", done, options);
184
+ mediaSource.addEventListener("sourceclose", done, options);
185
+ signal.addEventListener("abort", done, { once: true });
186
+ });
142
187
  }
143
188
  //#endregion
144
- export { attachMediaSource, createMediaSource, createSourceBuffer, observeMediaSourceReadyState };
189
+ export { attachMediaSource, buildMimeCodec, createMediaSource, createSourceBuffer, onMediaSourceReadyStateChange, waitForMediaSourceOpen };
145
190
 
146
191
  //# sourceMappingURL=mediasource-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediasource-setup.js","names":[],"sources":["../../../../../src/media/dom/mse/mediasource-setup.ts"],"sourcesContent":["/**\n * MediaSource Setup\n *\n * Utilities for creating and configuring MediaSource/ManagedMediaSource\n * for MSE (Media Source Extensions) playback.\n *\n * Global ManagedMediaSource types are defined in ./mediasource.d.ts\n */\n\n/**\n * Check if MediaSource API is supported.\n */\nexport function supportsMediaSource(): boolean {\n return typeof MediaSource !== 'undefined';\n}\n\n/**\n * Check if ManagedMediaSource API is supported.\n * ManagedMediaSource is a newer Safari API with better lifecycle management.\n */\nexport function supportsManagedMediaSource(): boolean {\n return typeof ManagedMediaSource !== 'undefined';\n}\n\n/**\n * Options for creating a MediaSource.\n */\nexport interface CreateMediaSourceOptions {\n /** Prefer ManagedMediaSource when available (default: false for broader compatibility). */\n preferManaged?: boolean;\n}\n\n/**\n * Create a MediaSource or ManagedMediaSource instance.\n *\n * @param options - Creation options\n * @returns A MediaSource or ManagedMediaSource instance\n * @throws Error if no MediaSource API is available\n *\n * @example\n * const mediaSource = createMediaSource();\n * const mediaElement = document.querySelector('video');\n * attachMediaSource(mediaSource, mediaElement);\n */\nexport function createMediaSource(options: CreateMediaSourceOptions = {}): MediaSource {\n const { preferManaged = false } = options;\n\n if (preferManaged && supportsManagedMediaSource()) {\n return new ManagedMediaSource!();\n }\n\n if (supportsMediaSource()) {\n return new MediaSource();\n }\n\n throw new Error('MediaSource API is not supported');\n}\n\n/**\n * Result of attaching a MediaSource to a media element.\n */\nexport interface AttachMediaSourceResult {\n /** The object URL created for the MediaSource (empty string for ManagedMediaSource). */\n url: string;\n /** Detach the MediaSource and clean up resources. */\n detach: () => void;\n}\n\n/**\n * Attach a MediaSource to an HTMLMediaElement.\n *\n * Uses srcObject for ManagedMediaSource (Safari), or createObjectURL for regular MediaSource.\n *\n * @param mediaSource - The MediaSource to attach\n * @param mediaElement - The media element to attach to\n * @returns Object with URL and detach function\n *\n * @example\n * const mediaSource = createMediaSource();\n * const { detach } = attachMediaSource(mediaSource, videoElement);\n * // Use mediaSource...\n * // Later, to clean up:\n * detach();\n */\nexport function attachMediaSource(mediaSource: MediaSource, mediaElement: HTMLMediaElement): AttachMediaSourceResult {\n // ManagedMediaSource requires srcObject instead of createObjectURL\n const isManagedMediaSource = supportsManagedMediaSource() && mediaSource instanceof ManagedMediaSource!;\n\n if (isManagedMediaSource) {\n // ManagedMediaSource requires disableRemotePlayback — without it Safari\n // will not fire sourceopen.\n (mediaElement as HTMLMediaElement & { disableRemotePlayback: boolean }).disableRemotePlayback = true;\n\n // Use srcObject for ManagedMediaSource\n (mediaElement as HTMLMediaElement & { srcObject: MediaSource | null }).srcObject = mediaSource;\n\n const detach = (): void => {\n (mediaElement as HTMLMediaElement & { srcObject: MediaSource | null }).srcObject = null;\n mediaElement.load(); // Reset the element\n };\n\n return { url: '', detach };\n }\n\n // Use createObjectURL for regular MediaSource\n const url = URL.createObjectURL(mediaSource);\n mediaElement.src = url;\n\n const detach = (): void => {\n mediaElement.removeAttribute('src');\n mediaElement.load(); // Reset the element\n URL.revokeObjectURL(url);\n };\n\n return { url, detach };\n}\n\n/**\n * Create a SourceBuffer on a MediaSource.\n *\n * @param mediaSource - The MediaSource (must be in 'open' state)\n * @param mimeCodec - MIME type with codecs (e.g., 'video/mp4; codecs=\"avc1.42E01E\"')\n * @returns The created SourceBuffer\n * @throws Error if MediaSource is not open or codec is unsupported\n *\n * @example\n * const buffer = createSourceBuffer(mediaSource, 'video/mp4; codecs=\"avc1.42E01E\"');\n */\nexport function createSourceBuffer(mediaSource: MediaSource, mimeCodec: string): SourceBuffer {\n if (mediaSource.readyState !== 'open') {\n throw new Error('MediaSource is not open');\n }\n\n if (!isCodecSupported(mimeCodec)) {\n throw new Error(`Codec not supported: ${mimeCodec}`);\n }\n\n return mediaSource.addSourceBuffer(mimeCodec);\n}\n\n/**\n * Build a MIME codec string from a track's `mimeType` + `codecs`. Works\n * on partially-resolved tracks — both fields come from the multivariant\n * playlist and are available before media-playlist resolution.\n *\n * @param track - Track carrying `mimeType` and `codecs`\n * @returns MIME codec string suitable for `MediaSource.addSourceBuffer`\n *\n * @example\n * buildMimeCodec({ mimeType: 'video/mp4', codecs: ['avc1.42E01E'] })\n * // => 'video/mp4; codecs=\"avc1.42E01E\"'\n */\nexport function buildMimeCodec(track: { mimeType: string; codecs?: string[] }): string {\n const codecString = track.codecs?.join(',') ?? '';\n return `${track.mimeType}; codecs=\"${codecString}\"`;\n}\n\n/**\n * Check if a codec is supported.\n *\n * @param mimeCodec - MIME type with codecs string\n * @returns True if the codec is supported\n *\n * @example\n * if (isCodecSupported('video/mp4; codecs=\"avc1.42E01E\"')) {\n * // Create source buffer\n * }\n */\nexport function isCodecSupported(mimeCodec: string): boolean {\n if (!supportsMediaSource()) {\n return false;\n }\n\n return MediaSource.isTypeSupported(mimeCodec);\n}\n\n/**\n * Observe `mediaSource.readyState` changes via DOM events.\n *\n * Listens to `sourceopen`, `sourceended`, and `sourceclose` and invokes\n * `onChange` with the current `readyState` after each event. Listeners\n * are automatically removed when `abortSignal` is aborted.\n *\n * @param mediaSource - The MediaSource to observe\n * @param abortSignal - AbortSignal that controls listener lifetime\n * @param onChange - Called with the current readyState after each change\n *\n * @example\n * const controller = new AbortController();\n * onMediaSourceReadyStateChange(mediaSource, controller.signal, (state) => {\n * if (state === 'open') { ... }\n * });\n * // Later: controller.abort();\n */\nexport function onMediaSourceReadyStateChange(\n mediaSource: MediaSource,\n abortSignal: AbortSignal,\n onChange: (readyState: MediaSource['readyState']) => void\n): void {\n const update = () => onChange(mediaSource.readyState);\n const options = { signal: abortSignal };\n mediaSource.addEventListener('sourceopen', update, options);\n mediaSource.addEventListener('sourceended', update, options);\n mediaSource.addEventListener('sourceclose', update, options);\n}\n\n/**\n * Wait until `mediaSource.readyState` transitions away from `'closed'`\n * (the next `sourceopen`/`sourceended`/`sourceclose` event), or until\n * `signal` aborts — whichever fires first.\n *\n * Resolves immediately when `readyState` is already `'open'` or `'ended'`\n * (no further transition is coming, so there's nothing to wait for). The\n * caller is expected to re-check `readyState` after the await to\n * distinguish `'open'` from terminal states.\n *\n * Companion to `onMediaSourceReadyStateChange` for one-shot use in async\n * sequences (e.g., a behavior's reactor entry that needs to wait for the\n * MediaSource to attach before performing a spec-conforming mutation).\n *\n * @example\n * await waitForMediaSourceOpen(mediaSource, signal);\n * if (signal.aborted || mediaSource.readyState !== 'open') return;\n * // safe to perform 'open'-state-only work\n */\nexport function waitForMediaSourceOpen(mediaSource: MediaSource, signal: AbortSignal): Promise<void> {\n if (signal.aborted) return Promise.resolve();\n if (mediaSource.readyState !== 'closed') return Promise.resolve();\n\n return new Promise<void>((resolve) => {\n const done = () => resolve();\n const options = { once: true, signal };\n mediaSource.addEventListener('sourceopen', done, options);\n mediaSource.addEventListener('sourceended', done, options);\n mediaSource.addEventListener('sourceclose', done, options);\n signal.addEventListener('abort', done, { once: true });\n });\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,sBAA+B;AAC7C,QAAO,OAAO,gBAAgB;;;;;;AAOhC,SAAgB,6BAAsC;AACpD,QAAO,OAAO,uBAAuB;;;;;;;;;;;;;;AAuBvC,SAAgB,kBAAkB,UAAoC,EAAE,EAAe;CACrF,MAAM,EAAE,gBAAgB,UAAU;AAElC,KAAI,iBAAiB,4BAA4B,CAC/C,QAAO,IAAI,oBAAqB;AAGlC,KAAI,qBAAqB,CACvB,QAAO,IAAI,aAAa;AAG1B,OAAM,IAAI,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;AA6BrD,SAAgB,kBAAkB,aAA0B,cAAyD;AAInH,KAF6B,4BAA4B,IAAI,uBAAuB,oBAE1D;AAGvB,eAAuE,wBAAwB;AAG/F,eAAsE,YAAY;EAEnF,MAAM,eAAqB;AACxB,gBAAsE,YAAY;AACnF,gBAAa,MAAM;;AAGrB,SAAO;GAAE,KAAK;GAAI;GAAQ;;CAI5B,MAAM,MAAM,IAAI,gBAAgB,YAAY;AAC5C,cAAa,MAAM;CAEnB,MAAM,eAAqB;AACzB,eAAa,gBAAgB,MAAM;AACnC,eAAa,MAAM;AACnB,MAAI,gBAAgB,IAAI;;AAG1B,QAAO;EAAE;EAAK;EAAQ;;;;;;;;;;;;;AAcxB,SAAgB,mBAAmB,aAA0B,WAAiC;AAC5F,KAAI,YAAY,eAAe,OAC7B,OAAM,IAAI,MAAM,0BAA0B;AAG5C,KAAI,CAAC,iBAAiB,UAAU,CAC9B,OAAM,IAAI,MAAM,wBAAwB,YAAY;AAGtD,QAAO,YAAY,gBAAgB,UAAU;;;;;;;;;;;;;;AAe/C,SAAgB,eAAe,OAAwD;CACrF,MAAM,cAAc,MAAM,QAAQ,KAAK,IAAI,IAAI;AAC/C,QAAO,GAAG,MAAM,SAAS,YAAY,YAAY;;;;;;;;;;;;;AAcnD,SAAgB,iBAAiB,WAA4B;AAC3D,KAAI,CAAC,qBAAqB,CACxB,QAAO;AAGT,QAAO,YAAY,gBAAgB,UAAU;;;;;;;;;;;;;;;;;;;;AAqB/C,SAAgB,8BACd,aACA,aACA,UACM;CACN,MAAM,eAAe,SAAS,YAAY,WAAW;CACrD,MAAM,UAAU,EAAE,QAAQ,aAAa;AACvC,aAAY,iBAAiB,cAAc,QAAQ,QAAQ;AAC3D,aAAY,iBAAiB,eAAe,QAAQ,QAAQ;AAC5D,aAAY,iBAAiB,eAAe,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;AAsB9D,SAAgB,uBAAuB,aAA0B,QAAoC;AACnG,KAAI,OAAO,QAAS,QAAO,QAAQ,SAAS;AAC5C,KAAI,YAAY,eAAe,SAAU,QAAO,QAAQ,SAAS;AAEjE,QAAO,IAAI,SAAe,YAAY;EACpC,MAAM,aAAa,SAAS;EAC5B,MAAM,UAAU;GAAE,MAAM;GAAM;GAAQ;AACtC,cAAY,iBAAiB,cAAc,MAAM,QAAQ;AACzD,cAAY,iBAAiB,eAAe,MAAM,QAAQ;AAC1D,cAAY,iBAAiB,eAAe,MAAM,QAAQ;AAC1D,SAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,MAAM,CAAC;GACtD"}
@@ -1,4 +1,4 @@
1
- //#region src/dom/text/parse-vtt-segment.ts
1
+ //#region src/media/dom/text/resolve-vtt-segment.ts
2
2
  /**
3
3
  * Parse a VTT segment using browser's native parser.
4
4
  *
@@ -16,7 +16,7 @@ function ensureDummyVideo() {
16
16
  }
17
17
  return dummyVideo;
18
18
  }
19
- function parseVttSegment(url) {
19
+ function resolveVttSegment(url) {
20
20
  const video = ensureDummyVideo();
21
21
  const track = document.createElement("track");
22
22
  track.kind = "subtitles";
@@ -47,10 +47,10 @@ function parseVttSegment(url) {
47
47
  track.src = url;
48
48
  });
49
49
  }
50
- function destroyVttParser() {
50
+ function destroyVttResolver() {
51
51
  dummyVideo = null;
52
52
  }
53
53
  //#endregion
54
- export { destroyVttParser, parseVttSegment };
54
+ export { destroyVttResolver, resolveVttSegment };
55
55
 
56
- //# sourceMappingURL=parse-vtt-segment.js.map
56
+ //# sourceMappingURL=resolve-vtt-segment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-vtt-segment.js","names":[],"sources":["../../../../../src/media/dom/text/resolve-vtt-segment.ts"],"sourcesContent":["/**\n * Parse a VTT segment using browser's native parser.\n *\n * Creates a dummy video element with a track element to leverage\n * the browser's optimized VTT parsing. Returns parsed VTTCue objects.\n */\n\n// Singleton dummy video (reused across all parsing)\nlet dummyVideo: HTMLVideoElement | null = null;\n\nfunction ensureDummyVideo(): HTMLVideoElement {\n if (!dummyVideo) {\n dummyVideo = document.createElement('video');\n dummyVideo.muted = true;\n dummyVideo.preload = 'none';\n dummyVideo.style.display = 'none';\n dummyVideo.crossOrigin = 'anonymous';\n }\n return dummyVideo;\n}\n\nexport function resolveVttSegment(url: string): Promise<VTTCue[]> {\n const video = ensureDummyVideo();\n const track = document.createElement('track');\n track.kind = 'subtitles';\n track.default = true;\n\n return new Promise((resolve, reject) => {\n const onLoad = (): void => {\n const cues: VTTCue[] = [];\n const textTrack = track.track;\n\n if (textTrack.cues) {\n for (let i = 0; i < textTrack.cues.length; i++) {\n const cue = textTrack.cues[i];\n if (cue) {\n cues.push(cue as VTTCue);\n }\n }\n }\n\n cleanup();\n resolve(cues);\n };\n\n const onError = (): void => {\n cleanup();\n reject(new Error(`Failed to load VTT segment: ${url}`));\n };\n\n const cleanup = (): void => {\n track.removeEventListener('load', onLoad);\n track.removeEventListener('error', onError);\n video.removeChild(track);\n };\n\n track.addEventListener('load', onLoad);\n track.addEventListener('error', onError);\n video.appendChild(track);\n track.src = url;\n });\n}\n\nexport function destroyVttResolver(): void {\n dummyVideo = null;\n}\n"],"mappings":";;;;;;;AAQA,IAAI,aAAsC;AAE1C,SAAS,mBAAqC;AAC5C,KAAI,CAAC,YAAY;AACf,eAAa,SAAS,cAAc,QAAQ;AAC5C,aAAW,QAAQ;AACnB,aAAW,UAAU;AACrB,aAAW,MAAM,UAAU;AAC3B,aAAW,cAAc;;AAE3B,QAAO;;AAGT,SAAgB,kBAAkB,KAAgC;CAChE,MAAM,QAAQ,kBAAkB;CAChC,MAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,OAAM,OAAO;AACb,OAAM,UAAU;AAEhB,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,eAAqB;GACzB,MAAM,OAAiB,EAAE;GACzB,MAAM,YAAY,MAAM;AAExB,OAAI,UAAU,KACZ,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK,QAAQ,KAAK;IAC9C,MAAM,MAAM,UAAU,KAAK;AAC3B,QAAI,IACF,MAAK,KAAK,IAAc;;AAK9B,YAAS;AACT,WAAQ,KAAK;;EAGf,MAAM,gBAAsB;AAC1B,YAAS;AACT,0BAAO,IAAI,MAAM,+BAA+B,MAAM,CAAC;;EAGzD,MAAM,gBAAsB;AAC1B,SAAM,oBAAoB,QAAQ,OAAO;AACzC,SAAM,oBAAoB,SAAS,QAAQ;AAC3C,SAAM,YAAY,MAAM;;AAG1B,QAAM,iBAAiB,QAAQ,OAAO;AACtC,QAAM,iBAAiB,SAAS,QAAQ;AACxC,QAAM,YAAY,MAAM;AACxB,QAAM,MAAM;GACZ;;AAGJ,SAAgB,qBAA2B;AACzC,cAAa"}
@@ -0,0 +1,69 @@
1
+ //#region src/media/dom/text/text-track-slots.ts
2
+ /**
3
+ * SPF-owned `<track>` selector. Each slot created by
4
+ * `addSubtitlesTracksToMedia` carries this attribute so reads and removals can
5
+ * filter SPF-owned tracks from host-page-owned ones.
6
+ */
7
+ const SPF_TRACK_SELECTOR = "track[data-src-track]";
8
+ /**
9
+ * Allocate text-track slots on `mediaElement` for each model track by creating
10
+ * and appending `<track>` children. Marks each element with `data-src-track`
11
+ * so it can be distinguished from `<track>` children the host page added
12
+ * directly — used by `getShowingSubtitlesTrackFromMedia` and
13
+ * `removeAllSubtitlesTracksFromMedia` to scope their reads/removals to
14
+ * SPF-owned slots. The spec has no `removeTextTrack` API, so creating
15
+ * `<track>` elements is the only mechanism for adding *and* removing entries
16
+ * to `mediaElement.textTracks`.
17
+ */
18
+ function addSubtitlesTracksToMedia(mediaElement, modelTextTracks) {
19
+ for (const modelTrack of modelTextTracks) {
20
+ const el = document.createElement("track");
21
+ el.id = modelTrack.id;
22
+ el.kind = modelTrack.kind;
23
+ el.label = modelTrack.label;
24
+ el.toggleAttribute("data-src-track", true);
25
+ if (modelTrack.language) el.srclang = modelTrack.language;
26
+ if (modelTrack.default) el.default = true;
27
+ mediaElement.appendChild(el);
28
+ }
29
+ }
30
+ /**
31
+ * Return the SPF-owned subtitle/caption `TextTrack` currently in `'showing'`
32
+ * mode, or `undefined` if none. Restricts the search to slots created by
33
+ * `addSubtitlesTracksToMedia` (via the `data-src-track` selector) so a showing
34
+ * track that the host page added directly is ignored — SPF selection only
35
+ * mirrors tracks it owns.
36
+ */
37
+ function getShowingSubtitlesTrackFromMedia(mediaElement) {
38
+ const elements = mediaElement.querySelectorAll(SPF_TRACK_SELECTOR);
39
+ for (const el of elements) {
40
+ const track = el.track;
41
+ if (track.mode === "showing" && (track.kind === "subtitles" || track.kind === "captions")) return track;
42
+ }
43
+ }
44
+ /**
45
+ * Remove every SPF-owned `<track>` child from `mediaElement` (those tagged
46
+ * with `data-src-track` by `addSubtitlesTracksToMedia`). `<track>` elements
47
+ * the host page added directly are left in place.
48
+ */
49
+ function removeAllSubtitlesTracksFromMedia(mediaElement) {
50
+ const elements = mediaElement.querySelectorAll(SPF_TRACK_SELECTOR);
51
+ for (const el of elements) el.remove();
52
+ }
53
+ /**
54
+ * Apply a selection to a `TextTrackList` by setting each subtitle/caption
55
+ * track's `mode` to `'showing'` if its `id` matches `selectedId` and
56
+ * `'disabled'` otherwise. Tracks of other kinds (chapters, metadata,
57
+ * descriptions) are left untouched — they may be owned by the host page.
58
+ */
59
+ function syncTextTrackModes(textTracks, selectedId) {
60
+ for (let i = 0; i < textTracks.length; i++) {
61
+ const track = textTracks[i];
62
+ if (track.kind !== "subtitles" && track.kind !== "captions") continue;
63
+ track.mode = track.id === selectedId ? "showing" : "disabled";
64
+ }
65
+ }
66
+ //#endregion
67
+ export { addSubtitlesTracksToMedia, getShowingSubtitlesTrackFromMedia, removeAllSubtitlesTracksFromMedia, syncTextTrackModes };
68
+
69
+ //# sourceMappingURL=text-track-slots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-track-slots.js","names":[],"sources":["../../../../../src/media/dom/text/text-track-slots.ts"],"sourcesContent":["import type { PartiallyResolvedTextTrack, TextTrack } from '../../types';\n\n/**\n * SPF-owned `<track>` selector. Each slot created by\n * `addSubtitlesTracksToMedia` carries this attribute so reads and removals can\n * filter SPF-owned tracks from host-page-owned ones.\n */\nconst SPF_TRACK_SELECTOR = 'track[data-src-track]';\n\n/**\n * Allocate text-track slots on `mediaElement` for each model track by creating\n * and appending `<track>` children. Marks each element with `data-src-track`\n * so it can be distinguished from `<track>` children the host page added\n * directly — used by `getShowingSubtitlesTrackFromMedia` and\n * `removeAllSubtitlesTracksFromMedia` to scope their reads/removals to\n * SPF-owned slots. The spec has no `removeTextTrack` API, so creating\n * `<track>` elements is the only mechanism for adding *and* removing entries\n * to `mediaElement.textTracks`.\n */\nexport function addSubtitlesTracksToMedia(\n mediaElement: HTMLMediaElement,\n modelTextTracks: readonly (PartiallyResolvedTextTrack | TextTrack)[]\n): void {\n for (const modelTrack of modelTextTracks) {\n const el = document.createElement('track');\n el.id = modelTrack.id;\n el.kind = modelTrack.kind;\n el.label = modelTrack.label;\n el.toggleAttribute('data-src-track', true);\n if (modelTrack.language) el.srclang = modelTrack.language;\n if (modelTrack.default) el.default = true;\n mediaElement.appendChild(el);\n }\n}\n\n/**\n * Return the SPF-owned subtitle/caption `TextTrack` currently in `'showing'`\n * mode, or `undefined` if none. Restricts the search to slots created by\n * `addSubtitlesTracksToMedia` (via the `data-src-track` selector) so a showing\n * track that the host page added directly is ignored — SPF selection only\n * mirrors tracks it owns.\n */\nexport function getShowingSubtitlesTrackFromMedia(mediaElement: HTMLMediaElement): globalThis.TextTrack | undefined {\n const elements = mediaElement.querySelectorAll<HTMLTrackElement>(SPF_TRACK_SELECTOR);\n for (const el of elements) {\n const track = el.track;\n if (track.mode === 'showing' && (track.kind === 'subtitles' || track.kind === 'captions')) {\n return track;\n }\n }\n return undefined;\n}\n\n/**\n * Remove every SPF-owned `<track>` child from `mediaElement` (those tagged\n * with `data-src-track` by `addSubtitlesTracksToMedia`). `<track>` elements\n * the host page added directly are left in place.\n */\nexport function removeAllSubtitlesTracksFromMedia(mediaElement: HTMLMediaElement): void {\n const elements = mediaElement.querySelectorAll<HTMLTrackElement>(SPF_TRACK_SELECTOR);\n for (const el of elements) {\n el.remove();\n }\n}\n\n/**\n * Apply a selection to a `TextTrackList` by setting each subtitle/caption\n * track's `mode` to `'showing'` if its `id` matches `selectedId` and\n * `'disabled'` otherwise. Tracks of other kinds (chapters, metadata,\n * descriptions) are left untouched — they may be owned by the host page.\n */\nexport function syncTextTrackModes(textTracks: TextTrackList, selectedId: string | undefined): void {\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i]!;\n if (track.kind !== 'subtitles' && track.kind !== 'captions') continue;\n track.mode = track.id === selectedId ? 'showing' : 'disabled';\n }\n}\n"],"mappings":";;;;;;AAOA,MAAM,qBAAqB;;;;;;;;;;;AAY3B,SAAgB,0BACd,cACA,iBACM;AACN,MAAK,MAAM,cAAc,iBAAiB;EACxC,MAAM,KAAK,SAAS,cAAc,QAAQ;AAC1C,KAAG,KAAK,WAAW;AACnB,KAAG,OAAO,WAAW;AACrB,KAAG,QAAQ,WAAW;AACtB,KAAG,gBAAgB,kBAAkB,KAAK;AAC1C,MAAI,WAAW,SAAU,IAAG,UAAU,WAAW;AACjD,MAAI,WAAW,QAAS,IAAG,UAAU;AACrC,eAAa,YAAY,GAAG;;;;;;;;;;AAWhC,SAAgB,kCAAkC,cAAkE;CAClH,MAAM,WAAW,aAAa,iBAAmC,mBAAmB;AACpF,MAAK,MAAM,MAAM,UAAU;EACzB,MAAM,QAAQ,GAAG;AACjB,MAAI,MAAM,SAAS,cAAc,MAAM,SAAS,eAAe,MAAM,SAAS,YAC5E,QAAO;;;;;;;;AAWb,SAAgB,kCAAkC,cAAsC;CACtF,MAAM,WAAW,aAAa,iBAAmC,mBAAmB;AACpF,MAAK,MAAM,MAAM,SACf,IAAG,QAAQ;;;;;;;;AAUf,SAAgB,mBAAmB,YAA2B,YAAsC;AAClG,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;EAC1C,MAAM,QAAQ,WAAW;AACzB,MAAI,MAAM,SAAS,eAAe,MAAM,SAAS,WAAY;AAC7D,QAAM,OAAO,MAAM,OAAO,aAAa,YAAY"}
@@ -1,4 +1,4 @@
1
- //#region src/core/hls/parse-attributes.ts
1
+ //#region src/media/hls/parse-attributes.ts
2
2
  /**
3
3
  * Parse HLS attribute list from a tag line.
4
4
  * Handles both quoted and unquoted values.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-attributes.js","names":[],"sources":["../../../../src/media/hls/parse-attributes.ts"],"sourcesContent":["import type { FrameRate } from '../types';\n\n/**\n * Parse HLS attribute list from a tag line.\n * Handles both quoted and unquoted values.\n */\nexport function parseAttributeList(line: string): Map<string, string> {\n const attributes = new Map<string, string>();\n const regex = /([A-Z0-9-]+)=(?:\"([^\"]*)\"|([^,]*))/g;\n\n for (const match of line.matchAll(regex)) {\n const key = match[1];\n const value = match[2] ?? match[3] ?? '';\n if (key) {\n attributes.set(key, value);\n }\n }\n\n return attributes;\n}\n\n/**\n * Parse RESOLUTION attribute value (WIDTHxHEIGHT).\n */\nexport function parseResolution(value: string): { width: number; height: number } | null {\n const match = /^(\\d+)x(\\d+)$/.exec(value);\n if (!match) return null;\n\n const width = Number.parseInt(match[1]!, 10);\n const height = Number.parseInt(match[2]!, 10);\n\n return { width, height };\n}\n\n/**\n * Parse FRAME-RATE attribute to rational frame rate.\n */\nexport function parseFrameRate(value: string): FrameRate | undefined {\n const fps = Number.parseFloat(value);\n if (Number.isNaN(fps) || fps <= 0) return undefined;\n\n // Common frame rates with tolerance for floating point precision\n if (Math.abs(fps - 23.976) < 0.01) {\n return { frameRateNumerator: 24000, frameRateDenominator: 1001 };\n }\n if (Math.abs(fps - 29.97) < 0.01) {\n return { frameRateNumerator: 30000, frameRateDenominator: 1001 };\n }\n if (Math.abs(fps - 59.94) < 0.01) {\n return { frameRateNumerator: 60000, frameRateDenominator: 1001 };\n }\n\n // Integer frame rates\n if (fps % 1 === 0) {\n return { frameRateNumerator: Math.round(fps) };\n }\n\n // Default: use rounded value\n return { frameRateNumerator: Math.round(fps) };\n}\n\n/**\n * Parse CODECS attribute into separate video and audio codecs.\n */\nexport function parseCodecs(codecs: string): { video?: string; audio?: string } {\n const parts = codecs.split(',').map((s) => s.trim());\n const result: { video?: string; audio?: string } = {};\n\n for (const codec of parts) {\n if (codec.startsWith('avc1.') || codec.startsWith('hvc1.') || codec.startsWith('hev1.')) {\n result.video = codec;\n } else if (codec.startsWith('mp4a.')) {\n result.audio = codec;\n }\n }\n\n return result;\n}\n\n/**\n * Parse #EXTINF duration value.\n */\nexport function parseExtInfDuration(value: string): number {\n const durationPart = value.split(',')[0] ?? value;\n const duration = Number.parseFloat(durationPart);\n return Number.isNaN(duration) ? 0 : duration;\n}\n\n/**\n * Parse BYTERANGE attribute value.\n * Format: \"length[@offset]\"\n * If offset is omitted, it continues from the previous byte range end.\n */\nexport function parseByteRange(value: string, previousEnd?: number): { start: number; end: number } | null {\n const match = /^(\\d+)(?:@(\\d+))?$/.exec(value);\n if (!match) return null;\n\n const length = Number.parseInt(match[1]!, 10);\n if (Number.isNaN(length)) return null;\n\n let start: number;\n if (match[2] !== undefined) {\n start = Number.parseInt(match[2], 10);\n if (Number.isNaN(start)) return null;\n } else if (previousEnd !== undefined) {\n start = previousEnd;\n } else {\n return null;\n }\n\n return { start, end: start + length - 1 };\n}\n\n/**\n * AttributeList - Typed attribute access wrapper.\n */\nexport interface AttributeList {\n get: (key: string) => string | undefined;\n getInt: (key: string, defaultValue?: number) => number | undefined;\n getFloat: (key: string, defaultValue?: number) => number | undefined;\n getBool: (key: string) => boolean;\n getResolution: (key: string) => { width: number; height: number } | undefined;\n getFrameRate: (key: string) => FrameRate | undefined;\n}\n\n/**\n * Create AttributeList from raw attribute string.\n */\nexport function createAttributeList(line: string): AttributeList {\n const map = parseAttributeList(line);\n\n return {\n get(key: string): string | undefined {\n return map.get(key);\n },\n\n getInt(key: string, defaultValue?: number): number | undefined {\n const value = map.get(key);\n if (value === undefined) return defaultValue;\n const parsed = Number.parseInt(value, 10);\n return Number.isNaN(parsed) ? defaultValue : parsed;\n },\n\n getFloat(key: string, defaultValue?: number): number | undefined {\n const value = map.get(key);\n if (value === undefined) return defaultValue;\n const parsed = Number.parseFloat(value);\n return Number.isNaN(parsed) ? defaultValue : parsed;\n },\n\n getBool(key: string): boolean {\n return map.get(key) === 'YES';\n },\n\n getResolution(key: string): { width: number; height: number } | undefined {\n const value = map.get(key);\n if (!value) return undefined;\n return parseResolution(value) ?? undefined;\n },\n\n getFrameRate(key: string): FrameRate | undefined {\n const value = map.get(key);\n if (!value) return undefined;\n return parseFrameRate(value);\n },\n };\n}\n\n/**\n * Match a tag and extract its attributes.\n * Returns null if the line doesn't match the tag.\n */\nexport function matchTag(line: string, tag: string): AttributeList | null {\n const prefix = `#${tag}:`;\n if (!line.startsWith(prefix)) return null;\n return createAttributeList(line.slice(prefix.length));\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { matchTag, parseByteRange, parseExtInfDuration } from "./parse-attributes.js";
2
2
  import { resolveUrl } from "./resolve-url.js";
3
- //#region src/core/hls/parse-media-playlist.ts
3
+ //#region src/media/hls/parse-media-playlist.ts
4
4
  /**
5
5
  * Parse HLS media playlist and resolve track with segments.
6
6
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-media-playlist.js","names":[],"sources":["../../../../src/media/hls/parse-media-playlist.ts"],"sourcesContent":["import type {\n AudioTrack,\n PartiallyResolvedAudioTrack,\n PartiallyResolvedTextTrack,\n PartiallyResolvedTrack,\n PartiallyResolvedVideoTrack,\n Segment,\n TextTrack,\n VideoTrack,\n} from '../types';\nimport { matchTag, parseByteRange, parseExtInfDuration } from './parse-attributes';\nimport { resolveUrl } from './resolve-url';\n\n/**\n * Resolve unresolved track type to its resolved equivalent.\n */\ntype ResolveTrack<T> = T extends PartiallyResolvedVideoTrack\n ? VideoTrack\n : T extends PartiallyResolvedAudioTrack\n ? AudioTrack\n : T extends PartiallyResolvedTextTrack\n ? TextTrack\n : never;\n\n/**\n * Parse HLS media playlist and resolve track with segments.\n *\n * Takes an unresolved track (from multivariant playlist) and media playlist text,\n * returns a HAM-compliant resolved track with segments.\n *\n * @param text - Media playlist text content\n * @param unresolved - Unresolved track from parseMultivariantPlaylist\n * @returns Resolved track with segments (type inferred from input)\n */\nexport function parseMediaPlaylist<T extends PartiallyResolvedTrack>(\n text: string,\n unresolved: T | ResolveTrack<T>\n): ResolveTrack<T> {\n const lines = text.split(/\\r?\\n/);\n\n // Segments and resources resolve relative to media playlist URL (per HLS spec)\n const baseUrl = unresolved.url;\n\n // Parse playlist\n const segments: Segment[] = [];\n let initSegmentUrl: string | undefined;\n let initSegmentByteRange: { start: number; end: number } | undefined;\n\n let currentDuration = 0;\n let currentByteRange: { start: number; end: number } | undefined;\n let currentTime = 0;\n let segmentIndex = 0;\n let previousByteRangeEnd: number | undefined;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n if (!trimmed || (trimmed.startsWith('#') && !trimmed.startsWith('#EXT'))) {\n continue;\n }\n\n if (\n trimmed === '#EXTM3U' ||\n trimmed.startsWith('#EXT-X-VERSION:') ||\n trimmed.startsWith('#EXT-X-TARGETDURATION:') ||\n trimmed.startsWith('#EXT-X-PLAYLIST-TYPE:') ||\n trimmed.startsWith('#EXT-X-INDEPENDENT-SEGMENTS')\n ) {\n continue;\n }\n\n // #EXT-X-MAP - Init segment\n const mapAttrs = matchTag(trimmed, 'EXT-X-MAP');\n if (mapAttrs) {\n const uri = mapAttrs.get('URI');\n if (uri) {\n initSegmentUrl = resolveUrl(uri, baseUrl);\n const byteRangeStr = mapAttrs.get('BYTERANGE');\n if (byteRangeStr) {\n initSegmentByteRange = parseByteRange(byteRangeStr, 0) ?? undefined;\n }\n }\n continue;\n }\n\n // #EXTINF - Segment duration\n if (trimmed.startsWith('#EXTINF:')) {\n currentDuration = parseExtInfDuration(trimmed.slice(8));\n continue;\n }\n\n // #EXT-X-BYTERANGE - Segment byte range\n if (trimmed.startsWith('#EXT-X-BYTERANGE:')) {\n currentByteRange = parseByteRange(trimmed.slice(17), previousByteRangeEnd) ?? undefined;\n continue;\n }\n\n if (trimmed === '#EXT-X-ENDLIST') {\n continue;\n }\n\n // Segment URI\n if (!trimmed.startsWith('#') && currentDuration > 0) {\n const segment: Segment = {\n id: `segment-${segmentIndex}`,\n url: resolveUrl(trimmed, baseUrl),\n duration: currentDuration,\n startTime: currentTime,\n };\n\n if (currentByteRange) {\n segment.byteRange = currentByteRange;\n previousByteRangeEnd = currentByteRange.end + 1;\n } else {\n previousByteRangeEnd = undefined;\n }\n\n segments.push(segment);\n currentTime += currentDuration;\n segmentIndex++;\n\n currentDuration = 0;\n currentByteRange = undefined;\n }\n }\n\n const totalDuration = currentTime;\n\n // Build initialization (VTT may not have init segment)\n const initialization =\n unresolved.type === 'text' && !initSegmentUrl\n ? undefined\n : initSegmentUrl\n ? { url: initSegmentUrl, ...(initSegmentByteRange ? { byteRange: initSegmentByteRange } : {}) }\n : { url: '' };\n\n // Generic resolution: All type-specific fields already on unresolved track from P1\n // Just add parsed properties (startTime, duration, segments, initialization)\n return {\n ...unresolved,\n startTime: 0,\n duration: totalDuration,\n segments,\n initialization,\n } as unknown as ResolveTrack<T>;\n}\n"],"mappings":";;;;;;;;;;;;;AAkCA,SAAgB,mBACd,MACA,YACiB;CACjB,MAAM,QAAQ,KAAK,MAAM,QAAQ;CAGjC,MAAM,UAAU,WAAW;CAG3B,MAAM,WAAsB,EAAE;CAC9B,IAAI;CACJ,IAAI;CAEJ,IAAI,kBAAkB;CACtB,IAAI;CACJ,IAAI,cAAc;CAClB,IAAI,eAAe;CACnB,IAAI;AAEJ,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAE3B,MAAI,CAAC,WAAY,QAAQ,WAAW,IAAI,IAAI,CAAC,QAAQ,WAAW,OAAO,CACrE;AAGF,MACE,YAAY,aACZ,QAAQ,WAAW,kBAAkB,IACrC,QAAQ,WAAW,yBAAyB,IAC5C,QAAQ,WAAW,wBAAwB,IAC3C,QAAQ,WAAW,8BAA8B,CAEjD;EAIF,MAAM,WAAW,SAAS,SAAS,YAAY;AAC/C,MAAI,UAAU;GACZ,MAAM,MAAM,SAAS,IAAI,MAAM;AAC/B,OAAI,KAAK;AACP,qBAAiB,WAAW,KAAK,QAAQ;IACzC,MAAM,eAAe,SAAS,IAAI,YAAY;AAC9C,QAAI,aACF,wBAAuB,eAAe,cAAc,EAAE,IAAI,KAAA;;AAG9D;;AAIF,MAAI,QAAQ,WAAW,WAAW,EAAE;AAClC,qBAAkB,oBAAoB,QAAQ,MAAM,EAAE,CAAC;AACvD;;AAIF,MAAI,QAAQ,WAAW,oBAAoB,EAAE;AAC3C,sBAAmB,eAAe,QAAQ,MAAM,GAAG,EAAE,qBAAqB,IAAI,KAAA;AAC9E;;AAGF,MAAI,YAAY,iBACd;AAIF,MAAI,CAAC,QAAQ,WAAW,IAAI,IAAI,kBAAkB,GAAG;GACnD,MAAM,UAAmB;IACvB,IAAI,WAAW;IACf,KAAK,WAAW,SAAS,QAAQ;IACjC,UAAU;IACV,WAAW;IACZ;AAED,OAAI,kBAAkB;AACpB,YAAQ,YAAY;AACpB,2BAAuB,iBAAiB,MAAM;SAE9C,wBAAuB,KAAA;AAGzB,YAAS,KAAK,QAAQ;AACtB,kBAAe;AACf;AAEA,qBAAkB;AAClB,sBAAmB,KAAA;;;CAIvB,MAAM,gBAAgB;CAGtB,MAAM,iBACJ,WAAW,SAAS,UAAU,CAAC,iBAC3B,KAAA,IACA,iBACE;EAAE,KAAK;EAAgB,GAAI,uBAAuB,EAAE,WAAW,sBAAsB,GAAG,EAAE;EAAG,GAC7F,EAAE,KAAK,IAAI;AAInB,QAAO;EACL,GAAG;EACH,WAAW;EACX,UAAU;EACV;EACA;EACD"}
@@ -1,7 +1,7 @@
1
- import { generateId } from "../utils/generate-id.js";
2
1
  import { matchTag, parseCodecs } from "./parse-attributes.js";
3
2
  import { resolveUrl } from "./resolve-url.js";
4
- //#region src/core/hls/parse-multivariant.ts
3
+ import { generateId } from "@videojs/utils/string";
4
+ //#region src/media/hls/parse-multivariant.ts
5
5
  /**
6
6
  * Parse HLS multivariant playlist into a Presentation.
7
7
  *
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-multivariant.js","names":[],"sources":["../../../../src/media/hls/parse-multivariant.ts"],"sourcesContent":["import { generateId } from '@videojs/utils/string';\nimport type {\n AddressableObject,\n AudioSelectionSet,\n AudioSwitchingSet,\n FrameRate,\n PartiallyResolvedAudioTrack,\n PartiallyResolvedTextTrack,\n PartiallyResolvedVideoTrack,\n Presentation,\n SelectionSet,\n TextSelectionSet,\n TextSwitchingSet,\n VideoSelectionSet,\n VideoSwitchingSet,\n} from '../types';\nimport { matchTag, parseCodecs } from './parse-attributes';\nimport { resolveUrl } from './resolve-url';\n\n/**\n * Parse HLS multivariant playlist into a Presentation.\n *\n * Returns Presentation with partially resolved tracks (no segment information).\n * Tracks contain metadata from multivariant playlist (bandwidth, resolution, codecs)\n * but segment information is added when media playlists are fetched.\n *\n * @param text - Raw playlist text content\n * @param unresolved - Unresolved presentation (contains URL for base URL resolution)\n * @returns Presentation with partially resolved tracks (duration is undefined)\n */\nexport function parseMultivariantPlaylist(text: string, unresolved: AddressableObject): Presentation {\n const baseUrl = unresolved.url;\n const lines = text.split(/\\r?\\n/);\n\n // Intermediate parsing structures\n interface StreamInfo {\n uri: string;\n bandwidth: number;\n resolution?: { width: number; height: number } | undefined;\n codecs?: string | undefined;\n frameRate?: FrameRate | undefined;\n audioGroupId?: string | undefined;\n }\n\n interface AudioRenditionInfo {\n groupId: string;\n name: string;\n language?: string | undefined;\n uri?: string | undefined;\n default?: boolean | undefined;\n autoselect?: boolean | undefined;\n }\n\n interface SubtitleRenditionInfo {\n groupId: string;\n name: string;\n language?: string | undefined;\n uri: string;\n default?: boolean | undefined;\n autoselect?: boolean | undefined;\n forced?: boolean | undefined;\n }\n\n const streams: StreamInfo[] = [];\n const audioRenditions: AudioRenditionInfo[] = [];\n const subtitleRenditions: SubtitleRenditionInfo[] = [];\n\n // State for STREAM-INF parsing (URI follows on next line)\n let pendingStreamInfo: Omit<StreamInfo, 'uri'> | null = null;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Skip empty lines and comments\n if (!trimmed || (trimmed.startsWith('#') && !trimmed.startsWith('#EXT'))) {\n continue;\n }\n\n // Skip tags not used in Presentation model\n if (\n trimmed === '#EXTM3U' ||\n trimmed.startsWith('#EXT-X-VERSION:') ||\n trimmed.startsWith('#EXT-X-INDEPENDENT-SEGMENTS')\n ) {\n continue;\n }\n\n // #EXT-X-MEDIA:TYPE=AUDIO/SUBTITLES\n const mediaAttrs = matchTag(trimmed, 'EXT-X-MEDIA');\n if (mediaAttrs) {\n const type = mediaAttrs.get('TYPE');\n const groupId = mediaAttrs.get('GROUP-ID');\n const name = mediaAttrs.get('NAME');\n\n if (type === 'AUDIO' && groupId && name) {\n const uri = mediaAttrs.get('URI');\n audioRenditions.push({\n groupId,\n name,\n language: mediaAttrs.get('LANGUAGE'),\n uri: uri ? resolveUrl(uri, baseUrl) : undefined,\n default: mediaAttrs.getBool('DEFAULT'),\n autoselect: mediaAttrs.getBool('AUTOSELECT'),\n });\n }\n\n if (type === 'SUBTITLES' && groupId && name) {\n const uri = mediaAttrs.get('URI');\n // URI is required for subtitle tracks\n if (uri) {\n subtitleRenditions.push({\n groupId,\n name,\n language: mediaAttrs.get('LANGUAGE'),\n uri: resolveUrl(uri, baseUrl),\n default: mediaAttrs.getBool('DEFAULT'),\n autoselect: mediaAttrs.getBool('AUTOSELECT'),\n forced: mediaAttrs.getBool('FORCED'),\n });\n }\n }\n continue;\n }\n\n // #EXT-X-STREAM-INF:BANDWIDTH=...\n const streamInfAttrs = matchTag(trimmed, 'EXT-X-STREAM-INF');\n if (streamInfAttrs) {\n pendingStreamInfo = {\n bandwidth: streamInfAttrs.getInt('BANDWIDTH', 0)!,\n resolution: streamInfAttrs.getResolution('RESOLUTION'),\n codecs: streamInfAttrs.get('CODECS'),\n frameRate: streamInfAttrs.getFrameRate('FRAME-RATE'),\n audioGroupId: streamInfAttrs.get('AUDIO'),\n };\n continue;\n }\n\n // URI line following STREAM-INF\n if (!trimmed.startsWith('#') && pendingStreamInfo) {\n streams.push({\n ...pendingStreamInfo,\n uri: resolveUrl(trimmed, baseUrl),\n });\n pendingStreamInfo = null;\n }\n }\n\n // Separate streams into video and audio based on codecs\n // If CODECS has single codec, use parseCodecs to determine type\n const videoStreams: typeof streams = [];\n const audioOnlyStreams: typeof streams = [];\n\n for (const stream of streams) {\n if (!stream.codecs) {\n // No codecs - assume video (default behavior)\n videoStreams.push(stream);\n continue;\n }\n\n const parsedCodecs = parseCodecs(stream.codecs);\n const codecCount = stream.codecs.split(',').length;\n\n // Single codec - determine type from parseCodecs result\n if (codecCount === 1) {\n if (parsedCodecs.audio && !parsedCodecs.video) {\n // Audio-only stream\n audioOnlyStreams.push(stream);\n } else {\n // Video stream (or unknown - default to video)\n videoStreams.push(stream);\n }\n } else {\n // Multiple codecs - video stream with muxed audio\n videoStreams.push(stream);\n }\n }\n\n // Build PartiallyResolvedVideoTracks from video streams\n const videoTracks: PartiallyResolvedVideoTrack[] = videoStreams.map((stream) => {\n const codecs = stream.codecs ? parseCodecs(stream.codecs) : undefined;\n\n const track: PartiallyResolvedVideoTrack = {\n type: 'video' as const,\n id: generateId(),\n url: stream.uri,\n bandwidth: stream.bandwidth,\n // Type-specific defaults (CMAF video)\n mimeType: 'video/mp4',\n codecs: [],\n };\n\n if (stream.resolution?.width !== undefined) {\n track.width = stream.resolution.width;\n }\n if (stream.resolution?.height !== undefined) {\n track.height = stream.resolution.height;\n }\n if (codecs?.video) {\n track.codecs = [codecs.video];\n }\n if (stream.frameRate) {\n track.frameRate = stream.frameRate;\n }\n if (stream.audioGroupId) {\n track.audioGroupId = stream.audioGroupId;\n }\n\n return track;\n });\n\n // Build PartiallyResolvedAudioTracks from audio-only streams\n const audioOnlyTracks: PartiallyResolvedAudioTrack[] = audioOnlyStreams.map((stream) => {\n const codecs = stream.codecs ? parseCodecs(stream.codecs) : undefined;\n\n const track: PartiallyResolvedAudioTrack = {\n type: 'audio' as const,\n id: generateId(),\n url: stream.uri,\n bandwidth: stream.bandwidth,\n mimeType: 'audio/mp4',\n codecs: codecs?.audio ? [codecs.audio] : [],\n groupId: stream.audioGroupId || 'default',\n name: 'Default',\n sampleRate: 48000, // Default - will be in media playlist if available\n channels: 2, // Default - will be in media playlist if available\n };\n\n return track;\n });\n\n // Build PartiallyResolvedAudioTracks from audio renditions (EXT-X-MEDIA)\n // Extract audio codecs from referencing streams\n const audioRenditionTracks: PartiallyResolvedAudioTrack[] = audioRenditions.map((rendition) => {\n let audioCodecs: string[] | undefined;\n for (const stream of streams) {\n if (stream.audioGroupId === rendition.groupId && stream.codecs) {\n const codecs = parseCodecs(stream.codecs);\n if (codecs.audio) {\n audioCodecs = [codecs.audio];\n break;\n }\n }\n }\n\n const track: PartiallyResolvedAudioTrack = {\n type: 'audio' as const,\n id: generateId(),\n url: rendition.uri ?? '',\n groupId: rendition.groupId,\n name: rendition.name,\n // Type-specific defaults (CMAF audio)\n mimeType: 'audio/mp4',\n bandwidth: 0, // Not available in multivariant for demuxed audio\n sampleRate: 48000, // CMAF default\n channels: 2, // Stereo default\n codecs: [],\n };\n\n if (rendition.language) {\n track.language = rendition.language;\n }\n if (audioCodecs) {\n track.codecs = audioCodecs;\n }\n if (rendition.default) {\n track.default = rendition.default;\n }\n if (rendition.autoselect) {\n track.autoselect = rendition.autoselect;\n }\n\n return track;\n });\n\n // Combine audio tracks from both EXT-X-MEDIA renditions and audio-only STREAM-INF\n const audioTracks = [...audioRenditionTracks, ...audioOnlyTracks];\n\n // Build PartiallyResolvedTextTracks from subtitle renditions\n const textTracks: PartiallyResolvedTextTrack[] = subtitleRenditions.map((rendition) => {\n const track: PartiallyResolvedTextTrack = {\n type: 'text' as const,\n id: generateId(),\n url: rendition.uri,\n groupId: rendition.groupId,\n label: rendition.name,\n kind: 'subtitles' as const,\n // Type-specific defaults (VTT)\n mimeType: 'text/vtt',\n bandwidth: 0, // Text tracks don't consume bandwidth\n };\n\n if (rendition.language) {\n track.language = rendition.language;\n }\n // Match hls.js/http-streaming: only set default=true when BOTH DEFAULT=YES AND AUTOSELECT=YES\n if (rendition.default && rendition.autoselect) {\n track.default = true;\n }\n if (rendition.autoselect) {\n track.autoselect = rendition.autoselect;\n }\n if (rendition.forced) {\n track.forced = rendition.forced;\n }\n\n return track;\n });\n\n // Build selection sets\n const selectionSets: SelectionSet[] = [];\n\n if (videoTracks.length > 0) {\n const videoSwitchingSet: VideoSwitchingSet = {\n id: generateId(),\n type: 'video',\n tracks: videoTracks,\n };\n\n const videoSelectionSet: VideoSelectionSet = {\n id: generateId(),\n type: 'video',\n switchingSets: [videoSwitchingSet],\n };\n\n selectionSets.push(videoSelectionSet);\n }\n\n if (audioTracks.length > 0) {\n const audioSwitchingSet: AudioSwitchingSet = {\n id: generateId(),\n type: 'audio',\n tracks: audioTracks,\n };\n\n const audioSelectionSet: AudioSelectionSet = {\n id: generateId(),\n type: 'audio',\n switchingSets: [audioSwitchingSet],\n };\n\n selectionSets.push(audioSelectionSet);\n }\n\n if (textTracks.length > 0) {\n const textSwitchingSet: TextSwitchingSet = {\n id: generateId(),\n type: 'text',\n tracks: textTracks,\n };\n\n const textSelectionSet: TextSelectionSet = {\n id: generateId(),\n type: 'text',\n switchingSets: [textSwitchingSet],\n };\n\n selectionSets.push(textSelectionSet);\n }\n\n // Build presentation (duration is undefined until tracks are resolved)\n return {\n id: generateId(),\n url: unresolved.url,\n startTime: 0,\n // duration: undefined, // Won't be known until after at least one media playlist is fetched + parsed\n selectionSets,\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region src/core/hls/resolve-url.ts
1
+ //#region src/media/hls/resolve-url.ts
2
2
  /**
3
3
  * Resolve a potentially relative URL against a base URL using native URL API.
4
4
  */
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-url.js","names":[],"sources":["../../../../src/media/hls/resolve-url.ts"],"sourcesContent":["/**\n * Resolve a potentially relative URL against a base URL using native URL API.\n */\nexport function resolveUrl(url: string, baseUrl: string): string {\n return new URL(url, baseUrl).href;\n}\n"],"mappings":";;;;AAGA,SAAgB,WAAW,KAAa,SAAyB;AAC/D,QAAO,IAAI,IAAI,KAAK,QAAQ,CAAC"}