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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/README.md +5 -0
  2. package/dist/default/background-video.js +3 -0
  3. package/dist/default/core/actors/create-machine-actor.js.map +1 -1
  4. package/dist/default/core/actors/create-transition-actor.js.map +1 -1
  5. package/dist/default/core/composition/create-composition.js +2 -2
  6. package/dist/default/core/composition/create-composition.js.map +1 -1
  7. package/dist/default/core/composition/share-signals.js +13 -11
  8. package/dist/default/core/composition/share-signals.js.map +1 -1
  9. package/dist/default/core/machine.js.map +1 -1
  10. package/dist/default/core/reactors/create-machine-reactor.js.map +1 -1
  11. package/dist/default/core/signals/effect.js.map +1 -1
  12. package/dist/default/core/signals/primitives.js.map +1 -1
  13. package/dist/default/core/tasks/task.js.map +1 -1
  14. package/dist/default/hls.js +4 -1
  15. package/dist/default/media/abr/quality-selection.js +6 -68
  16. package/dist/default/media/abr/quality-selection.js.map +1 -1
  17. package/dist/default/media/buffer/back-buffer.js.map +1 -1
  18. package/dist/default/media/buffer/forward-buffer.js +37 -3
  19. package/dist/default/media/buffer/forward-buffer.js.map +1 -1
  20. package/dist/default/media/dom/capabilities.js +70 -0
  21. package/dist/default/media/dom/capabilities.js.map +1 -0
  22. package/dist/default/media/dom/mse/append-segment.js.map +1 -1
  23. package/dist/default/media/dom/mse/buffer-flusher.js.map +1 -1
  24. package/dist/default/media/dom/mse/duration.js +27 -10
  25. package/dist/default/media/dom/mse/duration.js.map +1 -1
  26. package/dist/default/media/dom/mse/end-of-stream.js.map +1 -1
  27. package/dist/default/media/dom/mse/mediasource-setup.js +68 -17
  28. package/dist/default/media/dom/mse/mediasource-setup.js.map +1 -1
  29. package/dist/default/media/dom/text/resolve-vtt-segment.js +1 -7
  30. package/dist/default/media/dom/text/resolve-vtt-segment.js.map +1 -1
  31. package/dist/default/media/dom/text/text-track-slots.js +3 -3
  32. package/dist/default/media/dom/text/text-track-slots.js.map +1 -1
  33. package/dist/default/media/hls/parse-attributes.js +17 -3
  34. package/dist/default/media/hls/parse-attributes.js.map +1 -1
  35. package/dist/default/media/hls/parse-media-playlist.js +29 -1
  36. package/dist/default/media/hls/parse-media-playlist.js.map +1 -1
  37. package/dist/default/media/hls/parse-multivariant.js +15 -6
  38. package/dist/default/media/hls/parse-multivariant.js.map +1 -1
  39. package/dist/default/media/hls/resolve-url.js.map +1 -1
  40. package/dist/default/media/media-tracks/media-tracks.js +101 -0
  41. package/dist/default/media/media-tracks/media-tracks.js.map +1 -0
  42. package/dist/default/media/mp4/box.js +58 -0
  43. package/dist/default/media/mp4/box.js.map +1 -0
  44. package/dist/default/media/mp4/timestamp-origin.js +107 -0
  45. package/dist/default/media/mp4/timestamp-origin.js.map +1 -0
  46. package/dist/default/media/primitives/select-tracks.js +98 -13
  47. package/dist/default/media/primitives/select-tracks.js.map +1 -1
  48. package/dist/default/media/text/parse-vtt-timestamp-map.js +42 -0
  49. package/dist/default/media/text/parse-vtt-timestamp-map.js.map +1 -0
  50. package/dist/default/media/text/resolve-vtt-metadata.js +23 -0
  51. package/dist/default/media/text/resolve-vtt-metadata.js.map +1 -0
  52. package/dist/default/media/types/index.js.map +1 -1
  53. package/dist/default/media/utils/cdn.js +56 -0
  54. package/dist/default/media/utils/cdn.js.map +1 -0
  55. package/dist/default/media/utils/preload.js.map +1 -1
  56. package/dist/default/media/utils/track-selection.js +1 -1
  57. package/dist/default/media/utils/track-selection.js.map +1 -1
  58. package/dist/default/media/utils/tracks.js +31 -4
  59. package/dist/default/media/utils/tracks.js.map +1 -1
  60. package/dist/default/media-tracks.js +2 -0
  61. package/dist/default/network/bandwidth-estimator.js.map +1 -1
  62. package/dist/default/network/chunked-stream-iterable.js.map +1 -1
  63. package/dist/default/network/ewma.js +1 -1
  64. package/dist/default/network/ewma.js.map +1 -1
  65. package/dist/default/network/fetch.js +7 -1
  66. package/dist/default/network/fetch.js.map +1 -1
  67. package/dist/default/playback/actors/dom/segment-loader.js +103 -106
  68. package/dist/default/playback/actors/dom/segment-loader.js.map +1 -1
  69. package/dist/default/playback/actors/dom/source-buffer.js +3 -1
  70. package/dist/default/playback/actors/dom/source-buffer.js.map +1 -1
  71. package/dist/default/playback/actors/dom/text-tracks.js.map +1 -1
  72. package/dist/default/playback/actors/text-track-segment-loader.js +30 -16
  73. package/dist/default/playback/actors/text-track-segment-loader.js.map +1 -1
  74. package/dist/default/playback/behaviors/calculate-presentation-duration.js +21 -43
  75. package/dist/default/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  76. package/dist/default/playback/behaviors/derive-cdn-priority.js +68 -0
  77. package/dist/default/playback/behaviors/derive-cdn-priority.js.map +1 -0
  78. package/dist/default/playback/behaviors/dom/airplay.js +211 -0
  79. package/dist/default/playback/behaviors/dom/airplay.js.map +1 -0
  80. package/dist/default/playback/behaviors/dom/apply-start-position.js +93 -0
  81. package/dist/default/playback/behaviors/dom/apply-start-position.js.map +1 -0
  82. package/dist/default/playback/behaviors/dom/end-of-stream.js +25 -102
  83. package/dist/default/playback/behaviors/dom/end-of-stream.js.map +1 -1
  84. package/dist/default/playback/behaviors/dom/load-segments.js +6 -2
  85. package/dist/default/playback/behaviors/dom/load-segments.js.map +1 -1
  86. package/dist/default/playback/behaviors/dom/recover-end-stall.js +77 -0
  87. package/dist/default/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  88. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js +50 -18
  89. package/dist/default/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  90. package/dist/default/playback/behaviors/dom/setup-mediasource.js +36 -11
  91. package/dist/default/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  92. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  93. package/dist/default/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  94. package/dist/default/playback/behaviors/dom/sync-text-tracks.js +43 -18
  95. package/dist/default/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  96. package/dist/default/playback/behaviors/dom/track-current-time.js +6 -0
  97. package/dist/default/playback/behaviors/dom/track-current-time.js.map +1 -1
  98. package/dist/default/playback/behaviors/dom/track-load-triggers.js +4 -4
  99. package/dist/default/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  100. package/dist/default/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  101. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js +3 -2
  102. package/dist/default/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  103. package/dist/default/playback/behaviors/establish-start-media-time.js +123 -0
  104. package/dist/default/playback/behaviors/establish-start-media-time.js.map +1 -0
  105. package/dist/default/playback/behaviors/resolve-presentation.js.map +1 -1
  106. package/dist/default/playback/behaviors/resolve-track.js +45 -22
  107. package/dist/default/playback/behaviors/resolve-track.js.map +1 -1
  108. package/dist/default/playback/behaviors/select-tracks.js +46 -77
  109. package/dist/default/playback/behaviors/select-tracks.js.map +1 -1
  110. package/dist/default/playback/behaviors/setup-failover-monitor.js +66 -0
  111. package/dist/default/playback/behaviors/setup-failover-monitor.js.map +1 -0
  112. package/dist/default/playback/behaviors/sync-preload.js.map +1 -1
  113. package/dist/default/playback/behaviors/track-switching.js +403 -0
  114. package/dist/default/playback/behaviors/track-switching.js.map +1 -0
  115. package/dist/default/playback/engines/background-video/adapter.js +158 -0
  116. package/dist/default/playback/engines/background-video/adapter.js.map +1 -0
  117. package/dist/default/playback/engines/background-video/engine.js +75 -0
  118. package/dist/default/playback/engines/background-video/engine.js.map +1 -0
  119. package/dist/default/playback/engines/hls/adapter-audio-only.js +124 -0
  120. package/dist/default/playback/engines/hls/adapter-audio-only.js.map +1 -0
  121. package/dist/default/playback/engines/hls/adapter.js +20 -11
  122. package/dist/default/playback/engines/hls/adapter.js.map +1 -1
  123. package/dist/default/playback/engines/hls/engine-audio-only.js +94 -0
  124. package/dist/default/playback/engines/hls/engine-audio-only.js.map +1 -0
  125. package/dist/default/playback/engines/hls/engine.js +38 -8
  126. package/dist/default/playback/engines/hls/engine.js.map +1 -1
  127. package/dist/default/playback/primitives/failover-fetch.js +42 -0
  128. package/dist/default/playback/primitives/failover-fetch.js.map +1 -0
  129. package/dist/default/playback/primitives/head-peek.js +44 -0
  130. package/dist/default/playback/primitives/head-peek.js.map +1 -0
  131. package/dist/default/playback/primitives/relocation-pipelines.js +203 -0
  132. package/dist/default/playback/primitives/relocation-pipelines.js.map +1 -0
  133. package/dist/default/playback/primitives/segment-load-pipeline.js +96 -0
  134. package/dist/default/playback/primitives/segment-load-pipeline.js.map +1 -0
  135. package/dist/default/playback/primitives/text-segment-load-pipeline.js +37 -0
  136. package/dist/default/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  137. package/dist/{dev/playback/behaviors → default/playback/primitives}/track-types.js +1 -1
  138. package/dist/default/playback/primitives/track-types.js.map +1 -0
  139. package/dist/dev/background-video.d.ts +3 -0
  140. package/dist/dev/background-video.js +3 -0
  141. package/dist/dev/core/actors/actor.d.ts +0 -1
  142. package/dist/dev/core/actors/actor.d.ts.map +1 -1
  143. package/dist/dev/core/actors/create-machine-actor.d.ts +9 -6
  144. package/dist/dev/core/actors/create-machine-actor.d.ts.map +1 -1
  145. package/dist/dev/core/actors/create-machine-actor.js.map +1 -1
  146. package/dist/dev/core/actors/create-transition-actor.d.ts +0 -1
  147. package/dist/dev/core/actors/create-transition-actor.d.ts.map +1 -1
  148. package/dist/dev/core/actors/create-transition-actor.js.map +1 -1
  149. package/dist/dev/core/composition/create-composition.d.ts +10 -11
  150. package/dist/dev/core/composition/create-composition.d.ts.map +1 -1
  151. package/dist/dev/core/composition/create-composition.js +2 -2
  152. package/dist/dev/core/composition/create-composition.js.map +1 -1
  153. package/dist/dev/core/composition/share-signals.d.ts +11 -10
  154. package/dist/dev/core/composition/share-signals.d.ts.map +1 -1
  155. package/dist/dev/core/composition/share-signals.js +13 -11
  156. package/dist/dev/core/composition/share-signals.js.map +1 -1
  157. package/dist/dev/core/machine.d.ts +1 -0
  158. package/dist/dev/core/machine.d.ts.map +1 -1
  159. package/dist/dev/core/machine.js.map +1 -1
  160. package/dist/dev/core/reactors/create-machine-reactor.d.ts +2 -2
  161. package/dist/dev/core/reactors/create-machine-reactor.d.ts.map +1 -1
  162. package/dist/dev/core/reactors/create-machine-reactor.js.map +1 -1
  163. package/dist/dev/core/signals/effect.d.ts.map +1 -1
  164. package/dist/dev/core/signals/effect.js.map +1 -1
  165. package/dist/dev/core/signals/primitives.d.ts +1 -2
  166. package/dist/dev/core/signals/primitives.d.ts.map +1 -1
  167. package/dist/dev/core/signals/primitives.js.map +1 -1
  168. package/dist/dev/core/tasks/task.d.ts +2 -2
  169. package/dist/dev/core/tasks/task.d.ts.map +1 -1
  170. package/dist/dev/core/tasks/task.js.map +1 -1
  171. package/dist/dev/hls.d.ts +5 -1
  172. package/dist/dev/hls.js +4 -1
  173. package/dist/dev/index.d.ts +0 -1
  174. package/dist/dev/index.d.ts.map +1 -1
  175. package/dist/dev/media/abr/quality-selection.d.ts.map +1 -1
  176. package/dist/dev/media/abr/quality-selection.js +6 -68
  177. package/dist/dev/media/abr/quality-selection.js.map +1 -1
  178. package/dist/dev/media/buffer/back-buffer.d.ts.map +1 -1
  179. package/dist/dev/media/buffer/back-buffer.js.map +1 -1
  180. package/dist/dev/media/buffer/forward-buffer.d.ts.map +1 -1
  181. package/dist/dev/media/buffer/forward-buffer.js +37 -3
  182. package/dist/dev/media/buffer/forward-buffer.js.map +1 -1
  183. package/dist/dev/media/dom/capabilities.js +70 -0
  184. package/dist/dev/media/dom/capabilities.js.map +1 -0
  185. package/dist/dev/media/dom/mse/append-segment.d.ts +3 -9
  186. package/dist/dev/media/dom/mse/append-segment.d.ts.map +1 -1
  187. package/dist/dev/media/dom/mse/append-segment.js.map +1 -1
  188. package/dist/dev/media/dom/mse/buffer-flusher.d.ts.map +1 -1
  189. package/dist/dev/media/dom/mse/buffer-flusher.js.map +1 -1
  190. package/dist/dev/media/dom/mse/duration.js +27 -10
  191. package/dist/dev/media/dom/mse/duration.js.map +1 -1
  192. package/dist/dev/media/dom/mse/end-of-stream.js.map +1 -1
  193. package/dist/dev/media/dom/mse/mediasource-setup.js +68 -17
  194. package/dist/dev/media/dom/mse/mediasource-setup.js.map +1 -1
  195. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts +0 -6
  196. package/dist/dev/media/dom/text/resolve-vtt-segment.d.ts.map +1 -1
  197. package/dist/dev/media/dom/text/resolve-vtt-segment.js +1 -7
  198. package/dist/dev/media/dom/text/resolve-vtt-segment.js.map +1 -1
  199. package/dist/dev/media/dom/text/text-track-slots.d.ts +0 -1
  200. package/dist/dev/media/dom/text/text-track-slots.d.ts.map +1 -1
  201. package/dist/dev/media/dom/text/text-track-slots.js +3 -3
  202. package/dist/dev/media/dom/text/text-track-slots.js.map +1 -1
  203. package/dist/dev/media/hls/parse-attributes.js +17 -3
  204. package/dist/dev/media/hls/parse-attributes.js.map +1 -1
  205. package/dist/dev/media/hls/parse-media-playlist.js +29 -1
  206. package/dist/dev/media/hls/parse-media-playlist.js.map +1 -1
  207. package/dist/dev/media/hls/parse-multivariant.js +15 -6
  208. package/dist/dev/media/hls/parse-multivariant.js.map +1 -1
  209. package/dist/dev/media/hls/resolve-url.js.map +1 -1
  210. package/dist/dev/media/media-tracks/media-tracks.d.ts +51 -0
  211. package/dist/dev/media/media-tracks/media-tracks.d.ts.map +1 -0
  212. package/dist/dev/media/media-tracks/media-tracks.js +101 -0
  213. package/dist/dev/media/media-tracks/media-tracks.js.map +1 -0
  214. package/dist/dev/media/mp4/box.js +58 -0
  215. package/dist/dev/media/mp4/box.js.map +1 -0
  216. package/dist/dev/media/mp4/timestamp-origin.js +107 -0
  217. package/dist/dev/media/mp4/timestamp-origin.js.map +1 -0
  218. package/dist/dev/media/primitives/select-tracks.d.ts +35 -0
  219. package/dist/dev/media/primitives/select-tracks.d.ts.map +1 -0
  220. package/dist/dev/media/primitives/select-tracks.js +98 -13
  221. package/dist/dev/media/primitives/select-tracks.js.map +1 -1
  222. package/dist/dev/media/text/parse-vtt-timestamp-map.js +42 -0
  223. package/dist/dev/media/text/parse-vtt-timestamp-map.js.map +1 -0
  224. package/dist/dev/media/text/resolve-vtt-metadata.js +23 -0
  225. package/dist/dev/media/text/resolve-vtt-metadata.js.map +1 -0
  226. package/dist/dev/media/types/index.d.ts +69 -2
  227. package/dist/dev/media/types/index.d.ts.map +1 -1
  228. package/dist/dev/media/types/index.js.map +1 -1
  229. package/dist/dev/media/utils/cdn.d.ts +13 -0
  230. package/dist/dev/media/utils/cdn.d.ts.map +1 -0
  231. package/dist/dev/media/utils/cdn.js +56 -0
  232. package/dist/dev/media/utils/cdn.js.map +1 -0
  233. package/dist/dev/media/utils/preload.js.map +1 -1
  234. package/dist/dev/media/utils/track-selection.js +1 -1
  235. package/dist/dev/media/utils/track-selection.js.map +1 -1
  236. package/dist/dev/media/utils/tracks.js +31 -4
  237. package/dist/dev/media/utils/tracks.js.map +1 -1
  238. package/dist/dev/media-tracks.d.ts +3 -0
  239. package/dist/dev/media-tracks.js +2 -0
  240. package/dist/dev/network/bandwidth-estimator.d.ts.map +1 -1
  241. package/dist/dev/network/bandwidth-estimator.js.map +1 -1
  242. package/dist/dev/network/chunked-stream-iterable.js.map +1 -1
  243. package/dist/dev/network/ewma.js +1 -1
  244. package/dist/dev/network/ewma.js.map +1 -1
  245. package/dist/dev/network/fetch.js +7 -1
  246. package/dist/dev/network/fetch.js.map +1 -1
  247. package/dist/dev/playback/actors/dom/segment-loader.d.ts +16 -4
  248. package/dist/dev/playback/actors/dom/segment-loader.d.ts.map +1 -1
  249. package/dist/dev/playback/actors/dom/segment-loader.js +103 -106
  250. package/dist/dev/playback/actors/dom/segment-loader.js.map +1 -1
  251. package/dist/dev/playback/actors/dom/source-buffer.d.ts +10 -25
  252. package/dist/dev/playback/actors/dom/source-buffer.d.ts.map +1 -1
  253. package/dist/dev/playback/actors/dom/source-buffer.js +3 -1
  254. package/dist/dev/playback/actors/dom/source-buffer.js.map +1 -1
  255. package/dist/dev/playback/actors/dom/text-tracks.d.ts +3 -1
  256. package/dist/dev/playback/actors/dom/text-tracks.js.map +1 -1
  257. package/dist/dev/playback/actors/text-track-segment-loader.d.ts +7 -12
  258. package/dist/dev/playback/actors/text-track-segment-loader.d.ts.map +1 -1
  259. package/dist/dev/playback/actors/text-track-segment-loader.js +30 -16
  260. package/dist/dev/playback/actors/text-track-segment-loader.js.map +1 -1
  261. package/dist/dev/playback/actors/text-tracks.d.ts +3 -12
  262. package/dist/dev/playback/actors/text-tracks.d.ts.map +1 -1
  263. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts +11 -2
  264. package/dist/dev/playback/behaviors/calculate-presentation-duration.d.ts.map +1 -1
  265. package/dist/dev/playback/behaviors/calculate-presentation-duration.js +21 -43
  266. package/dist/dev/playback/behaviors/calculate-presentation-duration.js.map +1 -1
  267. package/dist/dev/playback/behaviors/derive-cdn-priority.js +68 -0
  268. package/dist/dev/playback/behaviors/derive-cdn-priority.js.map +1 -0
  269. package/dist/dev/playback/behaviors/dom/airplay.js +211 -0
  270. package/dist/dev/playback/behaviors/dom/airplay.js.map +1 -0
  271. package/dist/dev/playback/behaviors/dom/apply-start-position.js +93 -0
  272. package/dist/dev/playback/behaviors/dom/apply-start-position.js.map +1 -0
  273. package/dist/dev/playback/behaviors/dom/end-of-stream.js +25 -102
  274. package/dist/dev/playback/behaviors/dom/end-of-stream.js.map +1 -1
  275. package/dist/dev/playback/behaviors/dom/load-segments.d.ts +14 -4
  276. package/dist/dev/playback/behaviors/dom/load-segments.d.ts.map +1 -1
  277. package/dist/dev/playback/behaviors/dom/load-segments.js +6 -2
  278. package/dist/dev/playback/behaviors/dom/load-segments.js.map +1 -1
  279. package/dist/dev/playback/behaviors/dom/recover-end-stall.js +77 -0
  280. package/dist/dev/playback/behaviors/dom/recover-end-stall.js.map +1 -0
  281. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js +50 -18
  282. package/dist/dev/playback/behaviors/dom/setup-buffer-actors.js.map +1 -1
  283. package/dist/dev/playback/behaviors/dom/setup-mediasource.js +36 -11
  284. package/dist/dev/playback/behaviors/dom/setup-mediasource.js.map +1 -1
  285. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts +16 -19
  286. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.d.ts.map +1 -1
  287. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js +11 -25
  288. package/dist/dev/playback/behaviors/dom/setup-text-track-actors.js.map +1 -1
  289. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js +43 -18
  290. package/dist/dev/playback/behaviors/dom/sync-text-tracks.js.map +1 -1
  291. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts +1 -2
  292. package/dist/dev/playback/behaviors/dom/track-current-time.d.ts.map +1 -1
  293. package/dist/dev/playback/behaviors/dom/track-current-time.js +6 -0
  294. package/dist/dev/playback/behaviors/dom/track-current-time.js.map +1 -1
  295. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts +1 -2
  296. package/dist/dev/playback/behaviors/dom/track-load-triggers.d.ts.map +1 -1
  297. package/dist/dev/playback/behaviors/dom/track-load-triggers.js +4 -4
  298. package/dist/dev/playback/behaviors/dom/track-load-triggers.js.map +1 -1
  299. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts +1 -2
  300. package/dist/dev/playback/behaviors/dom/track-playback-rate.d.ts.map +1 -1
  301. package/dist/dev/playback/behaviors/dom/track-playback-rate.js.map +1 -1
  302. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js +3 -2
  303. package/dist/dev/playback/behaviors/dom/update-mediasource-duration.js.map +1 -1
  304. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts +31 -0
  305. package/dist/dev/playback/behaviors/establish-start-media-time.d.ts.map +1 -0
  306. package/dist/dev/playback/behaviors/establish-start-media-time.js +123 -0
  307. package/dist/dev/playback/behaviors/establish-start-media-time.js.map +1 -0
  308. package/dist/dev/playback/behaviors/resolve-presentation.d.ts +2 -1
  309. package/dist/dev/playback/behaviors/resolve-presentation.d.ts.map +1 -1
  310. package/dist/dev/playback/behaviors/resolve-presentation.js.map +1 -1
  311. package/dist/dev/playback/behaviors/resolve-track.js +45 -22
  312. package/dist/dev/playback/behaviors/resolve-track.js.map +1 -1
  313. package/dist/dev/playback/behaviors/select-tracks.d.ts +14 -0
  314. package/dist/dev/playback/behaviors/select-tracks.d.ts.map +1 -0
  315. package/dist/dev/playback/behaviors/select-tracks.js +46 -77
  316. package/dist/dev/playback/behaviors/select-tracks.js.map +1 -1
  317. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts +14 -0
  318. package/dist/dev/playback/behaviors/setup-failover-monitor.d.ts.map +1 -0
  319. package/dist/dev/playback/behaviors/setup-failover-monitor.js +66 -0
  320. package/dist/dev/playback/behaviors/setup-failover-monitor.js.map +1 -0
  321. package/dist/dev/playback/behaviors/sync-preload.js.map +1 -1
  322. package/dist/dev/playback/behaviors/track-switching.js +403 -0
  323. package/dist/dev/playback/behaviors/track-switching.js.map +1 -0
  324. package/dist/dev/playback/engines/background-video/adapter.d.ts +59 -0
  325. package/dist/dev/playback/engines/background-video/adapter.d.ts.map +1 -0
  326. package/dist/dev/playback/engines/background-video/adapter.js +158 -0
  327. package/dist/dev/playback/engines/background-video/adapter.js.map +1 -0
  328. package/dist/dev/playback/engines/background-video/engine.d.ts +106 -0
  329. package/dist/dev/playback/engines/background-video/engine.d.ts.map +1 -0
  330. package/dist/dev/playback/engines/background-video/engine.js +75 -0
  331. package/dist/dev/playback/engines/background-video/engine.js.map +1 -0
  332. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts +46 -0
  333. package/dist/dev/playback/engines/hls/adapter-audio-only.d.ts.map +1 -0
  334. package/dist/dev/playback/engines/hls/adapter-audio-only.js +124 -0
  335. package/dist/dev/playback/engines/hls/adapter-audio-only.js.map +1 -0
  336. package/dist/dev/playback/engines/hls/adapter.d.ts +3 -5
  337. package/dist/dev/playback/engines/hls/adapter.d.ts.map +1 -1
  338. package/dist/dev/playback/engines/hls/adapter.js +20 -11
  339. package/dist/dev/playback/engines/hls/adapter.js.map +1 -1
  340. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts +156 -0
  341. package/dist/dev/playback/engines/hls/engine-audio-only.d.ts.map +1 -0
  342. package/dist/dev/playback/engines/hls/engine-audio-only.js +94 -0
  343. package/dist/dev/playback/engines/hls/engine-audio-only.js.map +1 -0
  344. package/dist/dev/playback/engines/hls/engine.d.ts +109 -4
  345. package/dist/dev/playback/engines/hls/engine.d.ts.map +1 -1
  346. package/dist/dev/playback/engines/hls/engine.js +38 -8
  347. package/dist/dev/playback/engines/hls/engine.js.map +1 -1
  348. package/dist/dev/playback/primitives/derive-start-media-time.d.ts +16 -0
  349. package/dist/dev/playback/primitives/derive-start-media-time.d.ts.map +1 -0
  350. package/dist/dev/playback/primitives/failover-fetch.js +42 -0
  351. package/dist/dev/playback/primitives/failover-fetch.js.map +1 -0
  352. package/dist/dev/playback/primitives/head-peek.js +44 -0
  353. package/dist/dev/playback/primitives/head-peek.js.map +1 -0
  354. package/dist/dev/playback/primitives/relocation-pipelines.js +203 -0
  355. package/dist/dev/playback/primitives/relocation-pipelines.js.map +1 -0
  356. package/dist/dev/playback/primitives/segment-load-pipeline.d.ts +2 -0
  357. package/dist/dev/playback/primitives/segment-load-pipeline.js +96 -0
  358. package/dist/dev/playback/primitives/segment-load-pipeline.js.map +1 -0
  359. package/dist/dev/playback/primitives/source-buffer-messages.d.ts +43 -0
  360. package/dist/dev/playback/primitives/source-buffer-messages.d.ts.map +1 -0
  361. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts +65 -0
  362. package/dist/dev/playback/primitives/text-segment-load-pipeline.d.ts.map +1 -0
  363. package/dist/dev/playback/primitives/text-segment-load-pipeline.js +37 -0
  364. package/dist/dev/playback/primitives/text-segment-load-pipeline.js.map +1 -0
  365. package/dist/dev/playback/primitives/text-track-messages.d.ts +14 -0
  366. package/dist/dev/playback/primitives/text-track-messages.d.ts.map +1 -0
  367. package/dist/{default/playback/behaviors → dev/playback/primitives}/track-types.js +1 -1
  368. package/dist/dev/playback/primitives/track-types.js.map +1 -0
  369. package/package.json +17 -7
  370. package/dist/default/playback/behaviors/quality-switching.js +0 -96
  371. package/dist/default/playback/behaviors/quality-switching.js.map +0 -1
  372. package/dist/default/playback/behaviors/track-types.js.map +0 -1
  373. package/dist/dev/playback/behaviors/quality-switching.js +0 -96
  374. package/dist/dev/playback/behaviors/quality-switching.js.map +0 -1
  375. package/dist/dev/playback/behaviors/track-types.js.map +0 -1
@@ -1 +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
+ {"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 channels?: number | 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 // CHANNELS is a quoted string whose first parameter is the channel\n // count (\"6\", or \"16/JOC\" for spatial audio); getInt reads the\n // leading integer.\n channels: mediaAttrs.getInt('CHANNELS'),\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, de-duplicating the\n // HLS cross-product: one video rendition is listed across several\n // `EXT-X-STREAM-INF` entries — one per audio group it can pair with, all\n // sharing the same media-playlist URI. Collapse them to one track per URI,\n // accumulating every advertised audio group. (Redundant-stream renditions\n // live at *distinct* per-CDN URIs, so they stay separate — only the same-URI\n // cross-product merges.)\n const videoTracksByUrl = new Map<string, PartiallyResolvedVideoTrack>();\n for (const stream of videoStreams) {\n const existing = videoTracksByUrl.get(stream.uri);\n if (existing) {\n if (stream.audioGroupId && !existing.audioGroupIds?.includes(stream.audioGroupId)) {\n existing.audioGroupIds = [...(existing.audioGroupIds ?? []), stream.audioGroupId];\n }\n // BANDWIDTH is video + audio combined; the duplicates differ only in the\n // paired audio. Keep the lowest as the closest proxy to video-only, which\n // is what ABR should rank on.\n if (stream.bandwidth < existing.bandwidth) {\n existing.bandwidth = stream.bandwidth;\n }\n continue;\n }\n\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.audioGroupIds = [stream.audioGroupId];\n }\n\n videoTracksByUrl.set(stream.uri, track);\n }\n const videoTracks: PartiallyResolvedVideoTrack[] = [...videoTracksByUrl.values()];\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: rendition.channels ?? 2, // From EXT-X-MEDIA CHANNELS; 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":";;;;;;;;;;;;;;;AA8BA,SAAgB,0BAA0B,MAAc,YAA6C;CACnG,MAAM,UAAU,WAAW;CAC3B,MAAM,QAAQ,KAAK,MAAM,OAAO;CAgChC,MAAM,UAAwB,CAAC;CAC/B,MAAM,kBAAwC,CAAC;CAC/C,MAAM,qBAA8C,CAAC;CAGrD,IAAI,oBAAoD;CAExD,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,KAAK;EAG1B,IAAI,CAAC,WAAY,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,WAAW,MAAM,GACpE;EAIF,IACE,YAAY,aACZ,QAAQ,WAAW,iBAAiB,KACpC,QAAQ,WAAW,6BAA6B,GAEhD;EAIF,MAAM,aAAa,SAAS,SAAS,aAAa;EAClD,IAAI,YAAY;GACd,MAAM,OAAO,WAAW,IAAI,MAAM;GAClC,MAAM,UAAU,WAAW,IAAI,UAAU;GACzC,MAAM,OAAO,WAAW,IAAI,MAAM;GAElC,IAAI,SAAS,WAAW,WAAW,MAAM;IACvC,MAAM,MAAM,WAAW,IAAI,KAAK;IAChC,gBAAgB,KAAK;KACnB;KACA;KACA,UAAU,WAAW,IAAI,UAAU;KACnC,KAAK,MAAM,WAAW,KAAK,OAAO,IAAI,KAAA;KACtC,SAAS,WAAW,QAAQ,SAAS;KACrC,YAAY,WAAW,QAAQ,YAAY;KAI3C,UAAU,WAAW,OAAO,UAAU;IACxC,CAAC;GACH;GAEA,IAAI,SAAS,eAAe,WAAW,MAAM;IAC3C,MAAM,MAAM,WAAW,IAAI,KAAK;IAEhC,IAAI,KACF,mBAAmB,KAAK;KACtB;KACA;KACA,UAAU,WAAW,IAAI,UAAU;KACnC,KAAK,WAAW,KAAK,OAAO;KAC5B,SAAS,WAAW,QAAQ,SAAS;KACrC,YAAY,WAAW,QAAQ,YAAY;KAC3C,QAAQ,WAAW,QAAQ,QAAQ;IACrC,CAAC;GAEL;GACA;EACF;EAGA,MAAM,iBAAiB,SAAS,SAAS,kBAAkB;EAC3D,IAAI,gBAAgB;GAClB,oBAAoB;IAClB,WAAW,eAAe,OAAO,aAAa,CAAC;IAC/C,YAAY,eAAe,cAAc,YAAY;IACrD,QAAQ,eAAe,IAAI,QAAQ;IACnC,WAAW,eAAe,aAAa,YAAY;IACnD,cAAc,eAAe,IAAI,OAAO;GAC1C;GACA;EACF;EAGA,IAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,mBAAmB;GACjD,QAAQ,KAAK;IACX,GAAG;IACH,KAAK,WAAW,SAAS,OAAO;GAClC,CAAC;GACD,oBAAoB;EACtB;CACF;CAIA,MAAM,eAA+B,CAAC;CACtC,MAAM,mBAAmC,CAAC;CAE1C,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,CAAC,OAAO,QAAQ;GAElB,aAAa,KAAK,MAAM;GACxB;EACF;EAEA,MAAM,eAAe,YAAY,OAAO,MAAM;EAI9C,IAHmB,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC,WAGzB,GACjB,IAAI,aAAa,SAAS,CAAC,aAAa,OAEtC,iBAAiB,KAAK,MAAM;OAG5B,aAAa,KAAK,MAAM;OAI1B,aAAa,KAAK,MAAM;CAE5B;CASA,MAAM,mCAAmB,IAAI,IAAyC;CACtE,KAAK,MAAM,UAAU,cAAc;EACjC,MAAM,WAAW,iBAAiB,IAAI,OAAO,GAAG;EAChD,IAAI,UAAU;GACZ,IAAI,OAAO,gBAAgB,CAAC,SAAS,eAAe,SAAS,OAAO,YAAY,GAC9E,SAAS,gBAAgB,CAAC,GAAI,SAAS,iBAAiB,CAAC,GAAI,OAAO,YAAY;GAKlF,IAAI,OAAO,YAAY,SAAS,WAC9B,SAAS,YAAY,OAAO;GAE9B;EACF;EAEA,MAAM,SAAS,OAAO,SAAS,YAAY,OAAO,MAAM,IAAI,KAAA;EAE5D,MAAM,QAAqC;GACzC,MAAM;GACN,IAAI,WAAW;GACf,KAAK,OAAO;GACZ,WAAW,OAAO;GAElB,UAAU;GACV,QAAQ,CAAC;EACX;EAEA,IAAI,OAAO,YAAY,UAAU,KAAA,GAC/B,MAAM,QAAQ,OAAO,WAAW;EAElC,IAAI,OAAO,YAAY,WAAW,KAAA,GAChC,MAAM,SAAS,OAAO,WAAW;EAEnC,IAAI,QAAQ,OACV,MAAM,SAAS,CAAC,OAAO,KAAK;EAE9B,IAAI,OAAO,WACT,MAAM,YAAY,OAAO;EAE3B,IAAI,OAAO,cACT,MAAM,gBAAgB,CAAC,OAAO,YAAY;EAG5C,iBAAiB,IAAI,OAAO,KAAK,KAAK;CACxC;CACA,MAAM,cAA6C,CAAC,GAAG,iBAAiB,OAAO,CAAC;CAGhF,MAAM,kBAAiD,iBAAiB,KAAK,WAAW;EACtF,MAAM,SAAS,OAAO,SAAS,YAAY,OAAO,MAAM,IAAI,KAAA;EAe5D,OAAO;GAZL,MAAM;GACN,IAAI,WAAW;GACf,KAAK,OAAO;GACZ,WAAW,OAAO;GAClB,UAAU;GACV,QAAQ,QAAQ,QAAQ,CAAC,OAAO,KAAK,IAAI,CAAC;GAC1C,SAAS,OAAO,gBAAgB;GAChC,MAAM;GACN,YAAY;GACZ,UAAU;EAGD;CACb,CAAC;CA+CD,MAAM,cAAc,CAAC,GA3CuC,gBAAgB,KAAK,cAAc;EAC7F,IAAI;EACJ,KAAK,MAAM,UAAU,SACnB,IAAI,OAAO,iBAAiB,UAAU,WAAW,OAAO,QAAQ;GAC9D,MAAM,SAAS,YAAY,OAAO,MAAM;GACxC,IAAI,OAAO,OAAO;IAChB,cAAc,CAAC,OAAO,KAAK;IAC3B;GACF;EACF;EAGF,MAAM,QAAqC;GACzC,MAAM;GACN,IAAI,WAAW;GACf,KAAK,UAAU,OAAO;GACtB,SAAS,UAAU;GACnB,MAAM,UAAU;GAEhB,UAAU;GACV,WAAW;GACX,YAAY;GACZ,UAAU,UAAU,YAAY;GAChC,QAAQ,CAAC;EACX;EAEA,IAAI,UAAU,UACZ,MAAM,WAAW,UAAU;EAE7B,IAAI,aACF,MAAM,SAAS;EAEjB,IAAI,UAAU,SACZ,MAAM,UAAU,UAAU;EAE5B,IAAI,UAAU,YACZ,MAAM,aAAa,UAAU;EAG/B,OAAO;CACT,CAG2C,GAAG,GAAG,eAAe;CAGhE,MAAM,aAA2C,mBAAmB,KAAK,cAAc;EACrF,MAAM,QAAoC;GACxC,MAAM;GACN,IAAI,WAAW;GACf,KAAK,UAAU;GACf,SAAS,UAAU;GACnB,OAAO,UAAU;GACjB,MAAM;GAEN,UAAU;GACV,WAAW;EACb;EAEA,IAAI,UAAU,UACZ,MAAM,WAAW,UAAU;EAG7B,IAAI,UAAU,WAAW,UAAU,YACjC,MAAM,UAAU;EAElB,IAAI,UAAU,YACZ,MAAM,aAAa,UAAU;EAE/B,IAAI,UAAU,QACZ,MAAM,SAAS,UAAU;EAG3B,OAAO;CACT,CAAC;CAGD,MAAM,gBAAgC,CAAC;CAEvC,IAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,oBAAuC;GAC3C,IAAI,WAAW;GACf,MAAM;GACN,QAAQ;EACV;EAEA,MAAM,oBAAuC;GAC3C,IAAI,WAAW;GACf,MAAM;GACN,eAAe,CAAC,iBAAiB;EACnC;EAEA,cAAc,KAAK,iBAAiB;CACtC;CAEA,IAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,oBAAuC;GAC3C,IAAI,WAAW;GACf,MAAM;GACN,QAAQ;EACV;EAEA,MAAM,oBAAuC;GAC3C,IAAI,WAAW;GACf,MAAM;GACN,eAAe,CAAC,iBAAiB;EACnC;EAEA,cAAc,KAAK,iBAAiB;CACtC;CAEA,IAAI,WAAW,SAAS,GAAG;EACzB,MAAM,mBAAqC;GACzC,IAAI,WAAW;GACf,MAAM;GACN,QAAQ;EACV;EAEA,MAAM,mBAAqC;GACzC,IAAI,WAAW;GACf,MAAM;GACN,eAAe,CAAC,gBAAgB;EAClC;EAEA,cAAc,KAAK,gBAAgB;CACrC;CAGA,OAAO;EACL,IAAI,WAAW;EACf,KAAK,WAAW;EAChB,WAAW;EAEX;CACF;AACF"}
@@ -1 +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"}
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;CAC/D,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC;AAC/B"}
@@ -0,0 +1,51 @@
1
+ import { AudioTrack, FrameRate, MaybeResolvedPresentation, VideoTrack } from "../types/index.js";
2
+ //#region src/media/media-tracks/media-tracks.d.ts
3
+ /** Properties that identify a distinct video rendition (multi-CDN copies share them). */
4
+ interface VideoDedupeKey {
5
+ width?: VideoTrack['width'];
6
+ height?: VideoTrack['height'];
7
+ bandwidth?: VideoTrack['bandwidth'];
8
+ }
9
+ /** Properties that identify a distinct audio track (multi-CDN copies share them). */
10
+ interface AudioDedupeKey {
11
+ language?: AudioTrack['language'];
12
+ name?: AudioTrack['name'];
13
+ }
14
+ /**
15
+ * The distinct video tracks of a presentation, deduped by {@link VideoDedupeKey} (first occurrence wins).
16
+ *
17
+ * Returns `[]` when the presentation is unresolved or has no video tracks.
18
+ */
19
+ declare function dedupedVideoTracks(presentation: MaybeResolvedPresentation | undefined): VideoTrack[];
20
+ /**
21
+ * The distinct audio tracks of a presentation, deduped by `language` + `name`
22
+ * (first occurrence wins). Returns `[]` when the presentation is unresolved or has no audio tracks.
23
+ */
24
+ declare function dedupedAudioTracks(presentation: MaybeResolvedPresentation | undefined): AudioTrack[];
25
+ /**
26
+ * Find a video track by id, searching the same candidate set the engine resolves
27
+ * against ({@link dedupedVideoTracks}'s pre-dedupe source). Returns `undefined`
28
+ * when absent. Maps the engine's resolved `selectedVideoTrackId` back to its
29
+ * properties for `active` reflection — the resolved id may be a per-CDN copy that
30
+ * isn't the representative {@link dedupedVideoTracks} kept.
31
+ */
32
+ declare function findVideoTrackById(presentation: MaybeResolvedPresentation | undefined, id: string | undefined): VideoTrack | undefined;
33
+ /** Audio counterpart of {@link findVideoTrackById}, for `enabled` reflection. */
34
+ declare function findAudioTrackById(presentation: MaybeResolvedPresentation | undefined, id: string | undefined): AudioTrack | undefined;
35
+ /**
36
+ * Build a partial video track that can be used as `userVideoTrackSelection`.
37
+ */
38
+ declare function toUserVideoTrackSelection<T extends VideoDedupeKey>(rendition?: T): Partial<VideoTrack> | undefined;
39
+ /**
40
+ * Build a partial audio track that can be used as a `userAudioTrackSelection`.
41
+ */
42
+ declare function toUserAudioTrackSelection<T extends AudioDedupeKey>(track?: T): Partial<AudioTrack> | undefined;
43
+ /** Whether two video tracks are the same by dedupe key */
44
+ declare function isSameVideoTrack(a: VideoDedupeKey, b: VideoDedupeKey | undefined): boolean;
45
+ /** Whether two audio tracks are the same by dedupe key */
46
+ declare function isSameAudioTrack(a: AudioDedupeKey, b: AudioDedupeKey | undefined): boolean;
47
+ /** Collapse a rational frame rate (numerator/denominator) to frames per second. */
48
+ declare const frameRateToNumber: (frameRate: FrameRate) => number;
49
+ //#endregion
50
+ export { AudioDedupeKey, type AudioTrack, VideoDedupeKey, type VideoTrack, dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection };
51
+ //# sourceMappingURL=media-tracks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-tracks.d.ts","names":[],"sources":["../../../../src/media/media-tracks/media-tracks.ts"],"mappings":";;;UA0BiB;EACf,QAAQ;EACR,SAAS;EACT,YAAY;;;UAIG;EACf,WAAW;EACX,OAAO;;;;;;;iBAQO,mBAAmB,cAAc,wCAAwC;;;;;iBAazE,mBAAmB,cAAc,wCAAwC;;;;;;;;iBAgBzE,mBACd,cAAc,uCACd,yBACC;;iBAOa,mBACd,cAAc,uCACd,yBACC;;;;iBA2Ca,0BAA0B,UAAU,gBAAgB,YAAY,IAAI,QAAQ;;;;iBAO5E,0BAA0B,UAAU,gBAAgB,QAAQ,IAAI,QAAQ;;iBAKxE,iBAAiB,GAAG,gBAAgB,GAAG;;iBAKvC,iBAAiB,GAAG,gBAAgB,GAAG;;cAK1C,oBAAiB,WAAe"}
@@ -0,0 +1,101 @@
1
+ import { findTrackById, getTracksByType } from "../utils/tracks.js";
2
+ //#region src/media/media-tracks/media-tracks.ts
3
+ /**
4
+ * The distinct video tracks of a presentation, deduped by {@link VideoDedupeKey} (first occurrence wins).
5
+ *
6
+ * Returns `[]` when the presentation is unresolved or has no video tracks.
7
+ */
8
+ function dedupedVideoTracks(presentation) {
9
+ if (!presentation) return [];
10
+ return dedupe({
11
+ tracks: getTracksByType(presentation, "video"),
12
+ keyFn: toUserVideoTrackSelection
13
+ });
14
+ }
15
+ /**
16
+ * The distinct audio tracks of a presentation, deduped by `language` + `name`
17
+ * (first occurrence wins). Returns `[]` when the presentation is unresolved or has no audio tracks.
18
+ */
19
+ function dedupedAudioTracks(presentation) {
20
+ if (!presentation) return [];
21
+ return dedupe({
22
+ tracks: getTracksByType(presentation, "audio"),
23
+ keyFn: toUserAudioTrackSelection
24
+ });
25
+ }
26
+ /**
27
+ * Find a video track by id, searching the same candidate set the engine resolves
28
+ * against ({@link dedupedVideoTracks}'s pre-dedupe source). Returns `undefined`
29
+ * when absent. Maps the engine's resolved `selectedVideoTrackId` back to its
30
+ * properties for `active` reflection — the resolved id may be a per-CDN copy that
31
+ * isn't the representative {@link dedupedVideoTracks} kept.
32
+ */
33
+ function findVideoTrackById(presentation, id) {
34
+ if (!presentation || !id) return void 0;
35
+ const track = findTrackById(presentation, id);
36
+ return track?.type === "video" ? track : void 0;
37
+ }
38
+ /** Audio counterpart of {@link findVideoTrackById}, for `enabled` reflection. */
39
+ function findAudioTrackById(presentation, id) {
40
+ if (!presentation || !id) return void 0;
41
+ const track = findTrackById(presentation, id);
42
+ return track?.type === "audio" ? track : void 0;
43
+ }
44
+ /**
45
+ * Shallow-equal two key objects by their own properties. Both come from the same
46
+ * key builder, so they carry the same keys — a one-directional scan suffices.
47
+ */
48
+ function sameKey(a, b) {
49
+ for (const attr in a) if (a[attr] !== b[attr]) return false;
50
+ return true;
51
+ }
52
+ /**
53
+ * Dedupe tracks by a key function, keeping the first occurrence of each key.
54
+ * Keys are compared field-by-field ({@link sameKey}).
55
+ */
56
+ function dedupe({ tracks, keyFn }) {
57
+ const seen = [];
58
+ const kept = [];
59
+ for (const track of tracks) {
60
+ const key = keyFn(track);
61
+ if (!key || seen.some((other) => sameKey(other, key))) continue;
62
+ seen.push(key);
63
+ kept.push(track);
64
+ }
65
+ return kept;
66
+ }
67
+ /**
68
+ * Build a partial video track that can be used as `userVideoTrackSelection`.
69
+ */
70
+ function toUserVideoTrackSelection(rendition) {
71
+ return rendition ? {
72
+ width: rendition.width,
73
+ height: rendition.height,
74
+ bandwidth: rendition.bandwidth
75
+ } : void 0;
76
+ }
77
+ /**
78
+ * Build a partial audio track that can be used as a `userAudioTrackSelection`.
79
+ */
80
+ function toUserAudioTrackSelection(track) {
81
+ return track ? {
82
+ language: track.language,
83
+ name: track.name
84
+ } : void 0;
85
+ }
86
+ /** Whether two video tracks are the same by dedupe key */
87
+ function isSameVideoTrack(a, b) {
88
+ return !!b && a.width === b.width && a.height === b.height && a.bandwidth === b.bandwidth;
89
+ }
90
+ /** Whether two audio tracks are the same by dedupe key */
91
+ function isSameAudioTrack(a, b) {
92
+ return !!b && (a.language ?? "") === (b.language ?? "") && a.name === b.name;
93
+ }
94
+ /** Collapse a rational frame rate (numerator/denominator) to frames per second. */
95
+ const frameRateToNumber = (frameRate) => {
96
+ return frameRate.frameRateNumerator / (frameRate.frameRateDenominator ?? 1);
97
+ };
98
+ //#endregion
99
+ export { dedupedAudioTracks, dedupedVideoTracks, findAudioTrackById, findVideoTrackById, frameRateToNumber, isSameAudioTrack, isSameVideoTrack, toUserAudioTrackSelection, toUserVideoTrackSelection };
100
+
101
+ //# sourceMappingURL=media-tracks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-tracks.js","names":[],"sources":["../../../../src/media/media-tracks/media-tracks.ts"],"sourcesContent":["/**\n * Media-track translation utilities.\n *\n * Pure, DOM-free transforms from SPF's CMAF-HAM track model onto the deduped\n * lists a media-element adapter exposes (video renditions, audio tracks), plus\n * the selection-criteria builders that turn a chosen rendition/track back into a\n * `user*TrackSelection` partial the engine's track-switching reads.\n *\n * These return SPF *model vocabulary* (`bandwidth`, `codecs: string[]`,\n * `frameRate` as a rational `FrameRate`); the consuming adapter owns the\n * mapping (e.g. for DOM `bandwidth` -> `bitrate`, `codecs.join(',')` -> `codec`,\n * `name` -> `label`).\n *\n * Deduplication is by *properties*, never URL, so a multi-CDN source that lists\n * the same rendition on several hosts collapses to one entry: video renditions\n * by `width` + `height` + `bandwidth`, audio tracks by `language` + `name`.\n * The selection builders emit those same properties as the match criteria, so\n * selecting a collapsed entry re-selects, for example, every underlying per-CDN track.\n */\n\nimport type { AudioTrack, FrameRate, MaybeResolvedPresentation, VideoTrack } from '../types';\nimport { findTrackById, getTracksByType } from '../utils/tracks';\n\nexport type { AudioTrack, VideoTrack };\n\n/** Properties that identify a distinct video rendition (multi-CDN copies share them). */\nexport interface VideoDedupeKey {\n width?: VideoTrack['width'];\n height?: VideoTrack['height'];\n bandwidth?: VideoTrack['bandwidth'];\n}\n\n/** Properties that identify a distinct audio track (multi-CDN copies share them). */\nexport interface AudioDedupeKey {\n language?: AudioTrack['language'];\n name?: AudioTrack['name'];\n}\n\n/**\n * The distinct video tracks of a presentation, deduped by {@link VideoDedupeKey} (first occurrence wins).\n *\n * Returns `[]` when the presentation is unresolved or has no video tracks.\n */\nexport function dedupedVideoTracks(presentation: MaybeResolvedPresentation | undefined): VideoTrack[] {\n if (!presentation) return [];\n\n return dedupe({\n tracks: getTracksByType(presentation, 'video') as readonly VideoTrack[],\n keyFn: toUserVideoTrackSelection,\n });\n}\n\n/**\n * The distinct audio tracks of a presentation, deduped by `language` + `name`\n * (first occurrence wins). Returns `[]` when the presentation is unresolved or has no audio tracks.\n */\nexport function dedupedAudioTracks(presentation: MaybeResolvedPresentation | undefined): AudioTrack[] {\n if (!presentation) return [];\n\n return dedupe({\n tracks: getTracksByType(presentation, 'audio') as readonly AudioTrack[],\n keyFn: toUserAudioTrackSelection,\n });\n}\n\n/**\n * Find a video track by id, searching the same candidate set the engine resolves\n * against ({@link dedupedVideoTracks}'s pre-dedupe source). Returns `undefined`\n * when absent. Maps the engine's resolved `selectedVideoTrackId` back to its\n * properties for `active` reflection — the resolved id may be a per-CDN copy that\n * isn't the representative {@link dedupedVideoTracks} kept.\n */\nexport function findVideoTrackById(\n presentation: MaybeResolvedPresentation | undefined,\n id: string | undefined\n): VideoTrack | undefined {\n if (!presentation || !id) return undefined;\n const track = findTrackById(presentation, id);\n return track?.type === 'video' ? (track as VideoTrack) : undefined;\n}\n\n/** Audio counterpart of {@link findVideoTrackById}, for `enabled` reflection. */\nexport function findAudioTrackById(\n presentation: MaybeResolvedPresentation | undefined,\n id: string | undefined\n): AudioTrack | undefined {\n if (!presentation || !id) return undefined;\n const track = findTrackById(presentation, id);\n return track?.type === 'audio' ? (track as AudioTrack) : undefined;\n}\n\n/**\n * Shallow-equal two key objects by their own properties. Both come from the same\n * key builder, so they carry the same keys — a one-directional scan suffices.\n */\nfunction sameKey<K extends object>(a: K, b: K): boolean {\n for (const attr in a) {\n if (a[attr] !== b[attr]) return false;\n }\n return true;\n}\n\n/**\n * Dedupe tracks by a key function, keeping the first occurrence of each key.\n * Keys are compared field-by-field ({@link sameKey}).\n */\nfunction dedupe<T, K extends object>({\n tracks,\n keyFn,\n}: {\n tracks: readonly T[];\n keyFn: (track: T) => K | undefined;\n}): T[] {\n const seen: K[] = [];\n const kept: T[] = [];\n for (const track of tracks) {\n const key = keyFn(track);\n if (!key || seen.some((other) => sameKey(other, key))) continue;\n seen.push(key);\n kept.push(track);\n }\n\n return kept;\n}\n\n/**\n * Build a partial video track that can be used as `userVideoTrackSelection`.\n */\nexport function toUserVideoTrackSelection<T extends VideoDedupeKey>(rendition?: T): Partial<VideoTrack> | undefined {\n return rendition ? { width: rendition.width, height: rendition.height, bandwidth: rendition.bandwidth } : undefined;\n}\n\n/**\n * Build a partial audio track that can be used as a `userAudioTrackSelection`.\n */\nexport function toUserAudioTrackSelection<T extends AudioDedupeKey>(track?: T): Partial<AudioTrack> | undefined {\n return track ? { language: track.language, name: track.name } : undefined;\n}\n\n/** Whether two video tracks are the same by dedupe key */\nexport function isSameVideoTrack(a: VideoDedupeKey, b: VideoDedupeKey | undefined): boolean {\n return !!b && a.width === b.width && a.height === b.height && a.bandwidth === b.bandwidth;\n}\n\n/** Whether two audio tracks are the same by dedupe key */\nexport function isSameAudioTrack(a: AudioDedupeKey, b: AudioDedupeKey | undefined): boolean {\n return !!b && (a.language ?? '') === (b.language ?? '') && a.name === b.name;\n}\n\n/** Collapse a rational frame rate (numerator/denominator) to frames per second. */\nexport const frameRateToNumber = (frameRate: FrameRate) => {\n return frameRate.frameRateNumerator / (frameRate.frameRateDenominator ?? 1);\n};\n"],"mappings":";;;;;;;AA2CA,SAAgB,mBAAmB,cAAmE;CACpG,IAAI,CAAC,cAAc,OAAO,CAAC;CAE3B,OAAO,OAAO;EACZ,QAAQ,gBAAgB,cAAc,OAAO;EAC7C,OAAO;CACT,CAAC;AACH;;;;;AAMA,SAAgB,mBAAmB,cAAmE;CACpG,IAAI,CAAC,cAAc,OAAO,CAAC;CAE3B,OAAO,OAAO;EACZ,QAAQ,gBAAgB,cAAc,OAAO;EAC7C,OAAO;CACT,CAAC;AACH;;;;;;;;AASA,SAAgB,mBACd,cACA,IACwB;CACxB,IAAI,CAAC,gBAAgB,CAAC,IAAI,OAAO,KAAA;CACjC,MAAM,QAAQ,cAAc,cAAc,EAAE;CAC5C,OAAO,OAAO,SAAS,UAAW,QAAuB,KAAA;AAC3D;;AAGA,SAAgB,mBACd,cACA,IACwB;CACxB,IAAI,CAAC,gBAAgB,CAAC,IAAI,OAAO,KAAA;CACjC,MAAM,QAAQ,cAAc,cAAc,EAAE;CAC5C,OAAO,OAAO,SAAS,UAAW,QAAuB,KAAA;AAC3D;;;;;AAMA,SAAS,QAA0B,GAAM,GAAe;CACtD,KAAK,MAAM,QAAQ,GACjB,IAAI,EAAE,UAAU,EAAE,OAAO,OAAO;CAElC,OAAO;AACT;;;;;AAMA,SAAS,OAA4B,EACnC,QACA,SAIM;CACN,MAAM,OAAY,CAAC;CACnB,MAAM,OAAY,CAAC;CACnB,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,MAAM,KAAK;EACvB,IAAI,CAAC,OAAO,KAAK,MAAM,UAAU,QAAQ,OAAO,GAAG,CAAC,GAAG;EACvD,KAAK,KAAK,GAAG;EACb,KAAK,KAAK,KAAK;CACjB;CAEA,OAAO;AACT;;;;AAKA,SAAgB,0BAAoD,WAAgD;CAClH,OAAO,YAAY;EAAE,OAAO,UAAU;EAAO,QAAQ,UAAU;EAAQ,WAAW,UAAU;CAAU,IAAI,KAAA;AAC5G;;;;AAKA,SAAgB,0BAAoD,OAA4C;CAC9G,OAAO,QAAQ;EAAE,UAAU,MAAM;EAAU,MAAM,MAAM;CAAK,IAAI,KAAA;AAClE;;AAGA,SAAgB,iBAAiB,GAAmB,GAAwC;CAC1F,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE;AAClF;;AAGA,SAAgB,iBAAiB,GAAmB,GAAwC;CAC1F,OAAO,CAAC,CAAC,MAAM,EAAE,YAAY,SAAS,EAAE,YAAY,OAAO,EAAE,SAAS,EAAE;AAC1E;;AAGA,MAAa,qBAAqB,cAAyB;CACzD,OAAO,UAAU,sBAAsB,UAAU,wBAAwB;AAC3E"}
@@ -0,0 +1,58 @@
1
+ //#region src/media/mp4/box.ts
2
+ function toDataView(data) {
3
+ return data instanceof Uint8Array ? new DataView(data.buffer, data.byteOffset, data.byteLength) : new DataView(data);
4
+ }
5
+ /** Read a 4-character box type / FourCC at `offset`. */
6
+ function readFourCC(view, offset) {
7
+ return String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
8
+ }
9
+ /** Iterate the boxes directly contained in `[start, end)`. */
10
+ function* iterateBoxes(view, start = 0, end = view.byteLength) {
11
+ let offset = start;
12
+ while (offset + 8 <= end) {
13
+ let size = view.getUint32(offset);
14
+ const type = readFourCC(view, offset + 4);
15
+ let dataStart = offset + 8;
16
+ if (size === 1) {
17
+ size = Number(view.getBigUint64(offset + 8));
18
+ dataStart = offset + 16;
19
+ } else if (size === 0) size = end - offset;
20
+ if (size < dataStart - offset) return;
21
+ yield {
22
+ type,
23
+ start: offset,
24
+ dataStart,
25
+ end: offset + size
26
+ };
27
+ offset += size;
28
+ }
29
+ }
30
+ /** Iterate the direct child boxes of a given `type` within `[start, end)`. */
31
+ function* iterateBoxesOfType(view, type, start = 0, end = view.byteLength) {
32
+ for (const box of iterateBoxes(view, start, end)) if (box.type === type) yield box;
33
+ }
34
+ /**
35
+ * Depth-first descent to the first box matching a nested path, e.g.
36
+ * `['moov', 'trak', 'mdia', 'mdhd']`. Returns `undefined` if any level is
37
+ * absent.
38
+ */
39
+ function findBox(view, path, start = 0, end = view.byteLength) {
40
+ const [head, ...rest] = path;
41
+ for (const box of iterateBoxes(view, start, end)) {
42
+ if (box.type !== head) continue;
43
+ if (rest.length === 0) return box;
44
+ const found = findBox(view, rest, box.dataStart, box.end);
45
+ if (found) return found;
46
+ }
47
+ }
48
+ /**
49
+ * Read the version byte of a FullBox — the `version(1) + flags(3)` header at the
50
+ * start of the payload of `mdhd` / `tkhd` / `tfdt` / `hdlr` / `elst` / etc.
51
+ */
52
+ function readFullBoxVersion(view, dataStart) {
53
+ return view.getUint8(dataStart);
54
+ }
55
+ //#endregion
56
+ export { findBox, iterateBoxes, iterateBoxesOfType, readFourCC, readFullBoxVersion, toDataView };
57
+
58
+ //# sourceMappingURL=box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box.js","names":[],"sources":["../../../../src/media/mp4/box.ts"],"sourcesContent":["/**\n * Minimal ISO-BMFF (MP4/CMAF) box walker.\n *\n * Just enough to locate boxes by nested path — the framework needs a couple of\n * leaf fields (`mdhd` timescale, `tfdt` baseMediaDecodeTime) to derive a\n * segment's decode-time origin, not a full demuxer. DOM-free: operates on an\n * `ArrayBuffer` / `Uint8Array` via `DataView`.\n *\n * Box layout: `[u32 size][u32 type][payload]`. `size === 1` means a `u64\n * largesize` follows the type (payload after it); `size === 0` means the box\n * runs to the end of its container.\n */\n\n/** A located box: its 4-char type and byte offsets within the buffer. */\nexport interface Box {\n type: string;\n /** Offset of the box's first byte (its size field). */\n start: number;\n /** Offset of the box's payload — after `size` + `type` (+ `largesize`). */\n dataStart: number;\n /** Offset one past the box's last byte. */\n end: number;\n}\n\nexport function toDataView(data: ArrayBuffer | Uint8Array): DataView {\n return data instanceof Uint8Array ? new DataView(data.buffer, data.byteOffset, data.byteLength) : new DataView(data);\n}\n\n/** Read a 4-character box type / FourCC at `offset`. */\nexport function readFourCC(view: DataView, offset: number): string {\n return String.fromCharCode(\n view.getUint8(offset),\n view.getUint8(offset + 1),\n view.getUint8(offset + 2),\n view.getUint8(offset + 3)\n );\n}\n\n/** Iterate the boxes directly contained in `[start, end)`. */\nexport function* iterateBoxes(view: DataView, start = 0, end = view.byteLength): Generator<Box> {\n let offset = start;\n while (offset + 8 <= end) {\n let size = view.getUint32(offset);\n const type = readFourCC(view, offset + 4);\n let dataStart = offset + 8;\n if (size === 1) {\n size = Number(view.getBigUint64(offset + 8));\n dataStart = offset + 16;\n } else if (size === 0) {\n size = end - offset;\n }\n // A size smaller than its own header is malformed — stop rather than loop.\n if (size < dataStart - offset) return;\n yield { type, start: offset, dataStart, end: offset + size };\n offset += size;\n }\n}\n\n/** Iterate the direct child boxes of a given `type` within `[start, end)`. */\nexport function* iterateBoxesOfType(view: DataView, type: string, start = 0, end = view.byteLength): Generator<Box> {\n for (const box of iterateBoxes(view, start, end)) {\n if (box.type === type) yield box;\n }\n}\n\n/**\n * Depth-first descent to the first box matching a nested path, e.g.\n * `['moov', 'trak', 'mdia', 'mdhd']`. Returns `undefined` if any level is\n * absent.\n */\nexport function findBox(view: DataView, path: readonly string[], start = 0, end = view.byteLength): Box | undefined {\n const [head, ...rest] = path;\n for (const box of iterateBoxes(view, start, end)) {\n if (box.type !== head) continue;\n if (rest.length === 0) return box;\n const found = findBox(view, rest, box.dataStart, box.end);\n if (found) return found;\n }\n return undefined;\n}\n\n/**\n * Read the version byte of a FullBox — the `version(1) + flags(3)` header at the\n * start of the payload of `mdhd` / `tkhd` / `tfdt` / `hdlr` / `elst` / etc.\n */\nexport function readFullBoxVersion(view: DataView, dataStart: number): number {\n return view.getUint8(dataStart);\n}\n"],"mappings":";AAwBA,SAAgB,WAAW,MAA0C;CACnE,OAAO,gBAAgB,aAAa,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,IAAI,IAAI,SAAS,IAAI;AACrH;;AAGA,SAAgB,WAAW,MAAgB,QAAwB;CACjE,OAAO,OAAO,aACZ,KAAK,SAAS,MAAM,GACpB,KAAK,SAAS,SAAS,CAAC,GACxB,KAAK,SAAS,SAAS,CAAC,GACxB,KAAK,SAAS,SAAS,CAAC,CAC1B;AACF;;AAGA,UAAiB,aAAa,MAAgB,QAAQ,GAAG,MAAM,KAAK,YAA4B;CAC9F,IAAI,SAAS;CACb,OAAO,SAAS,KAAK,KAAK;EACxB,IAAI,OAAO,KAAK,UAAU,MAAM;EAChC,MAAM,OAAO,WAAW,MAAM,SAAS,CAAC;EACxC,IAAI,YAAY,SAAS;EACzB,IAAI,SAAS,GAAG;GACd,OAAO,OAAO,KAAK,aAAa,SAAS,CAAC,CAAC;GAC3C,YAAY,SAAS;EACvB,OAAO,IAAI,SAAS,GAClB,OAAO,MAAM;EAGf,IAAI,OAAO,YAAY,QAAQ;EAC/B,MAAM;GAAE;GAAM,OAAO;GAAQ;GAAW,KAAK,SAAS;EAAK;EAC3D,UAAU;CACZ;AACF;;AAGA,UAAiB,mBAAmB,MAAgB,MAAc,QAAQ,GAAG,MAAM,KAAK,YAA4B;CAClH,KAAK,MAAM,OAAO,aAAa,MAAM,OAAO,GAAG,GAC7C,IAAI,IAAI,SAAS,MAAM,MAAM;AAEjC;;;;;;AAOA,SAAgB,QAAQ,MAAgB,MAAyB,QAAQ,GAAG,MAAM,KAAK,YAA6B;CAClH,MAAM,CAAC,MAAM,GAAG,QAAQ;CACxB,KAAK,MAAM,OAAO,aAAa,MAAM,OAAO,GAAG,GAAG;EAChD,IAAI,IAAI,SAAS,MAAM;EACvB,IAAI,KAAK,WAAW,GAAG,OAAO;EAC9B,MAAM,QAAQ,QAAQ,MAAM,MAAM,IAAI,WAAW,IAAI,GAAG;EACxD,IAAI,OAAO,OAAO;CACpB;AAEF;;;;;AAMA,SAAgB,mBAAmB,MAAgB,WAA2B;CAC5E,OAAO,KAAK,SAAS,SAAS;AAChC"}
@@ -0,0 +1,107 @@
1
+ import { findBox, iterateBoxesOfType, readFourCC, readFullBoxVersion, toDataView } from "./box.js";
2
+ //#region src/media/mp4/timestamp-origin.ts
3
+ /**
4
+ * Decode-time origin extraction from fMP4/CMAF segments.
5
+ *
6
+ * Non-zero-PTS sources encode media at a non-zero start time (an instant clip
7
+ * starting at asset second 60, an Apple bipbop asset starting at 10s, …). To
8
+ * relocate such a source onto a 0-based presentation timeline via
9
+ * `SourceBuffer.timestampOffset`, the engine needs that start time — the
10
+ * **decode-time origin** — read straight from the container:
11
+ *
12
+ * - `tfdt.baseMediaDecodeTime` (from a media segment's `moof`) — the decode
13
+ * time of the segment's first sample, in the track's media timescale. This is
14
+ * a DTS: relocating by `−(baseMediaDecodeTime / timescale)` lands the earliest
15
+ * DTS at exactly 0, so a negative-DTS append failure on Chromium is impossible
16
+ * by construction.
17
+ * - `mdhd.timescale` (from the init segment's `moov`) — ticks per second for
18
+ * that track, needed to convert the raw tick count to seconds.
19
+ *
20
+ * ## Presumptive vs. track-selected reads
21
+ *
22
+ * The two variants share only the leaf field-readers (`mdhd` timescale, `tfdt`
23
+ * baseMediaDecodeTime) and the box walker. Everything else differs, and the
24
+ * split is deliberate so the presumptive pair is *proportionally* smaller under
25
+ * tree-shaking:
26
+ *
27
+ * - **Presumptive** — {@link readFirstMediaTimescale} / {@link readFirstBaseMediaDecodeTime}
28
+ * read the first `mdhd` timescale and first `tfdt` baseMediaDecodeTime. There's
29
+ * no `track_id` (nothing to match against) and no `trak`/`traf` iteration — a
30
+ * direct `findBox` to the first leaf. Correct when the init/segment holds a
31
+ * single media track (the common CMAF case). A caption-free platform imports
32
+ * only this pair and tree-shakes away all the matching machinery below.
33
+ * - **Track-selected** — {@link findMediaTrack} / {@link readBaseMediaDecodeTime}.
34
+ * `findMediaTrack` returns `{ trackId, timescale }` for the `trak` whose `hdlr`
35
+ * handler matches; `readBaseMediaDecodeTime` takes that `trackId` and reads the
36
+ * `traf` whose `tfhd.track_id` matches. The `track_id` is the join that ties one
37
+ * track's timescale to the *same* track's baseMediaDecodeTime — required when a
38
+ * source muxes CEA-608/708 captions (a `clcp` track shares the same `moov` and
39
+ * `moof`, each track with its own timescale + baseMediaDecodeTime, so a
40
+ * presumptive read there risks `300000 / 6000 = 50s` instead of
41
+ * `60000 / 6000 = 10s`). This pair adds `trak`/`traf` iteration plus the handler
42
+ * and `track_id` reads. We only ever ask for buffered media handlers
43
+ * (`vide` / `soun`); the caption track is never selected — we read the origin,
44
+ * not the captions (caption *rendering* is out of scope).
45
+ *
46
+ * Both raw values are returned un-divided to leave room for an edit-list (`elst`)
47
+ * presentation-time correction term if a source ever carries one — the validated
48
+ * streams do not.
49
+ */
50
+ /**
51
+ * The `track_ID` + timescale of the `trak` whose `mdhd` handler matches
52
+ * `handlerType`, skipping a muxed `clcp` caption track. `undefined` if no
53
+ * matching track exists.
54
+ */
55
+ function findMediaTrack(initSegment, handlerType) {
56
+ const view = toDataView(initSegment);
57
+ const moov = findBox(view, ["moov"]);
58
+ if (!moov) return void 0;
59
+ for (const trak of iterateBoxesOfType(view, "trak", moov.dataStart, moov.end)) {
60
+ if (readTrakHandler(view, trak) !== handlerType) continue;
61
+ const tkhd = findBox(view, ["tkhd"], trak.dataStart, trak.end);
62
+ const mdhd = findBox(view, ["mdia", "mdhd"], trak.dataStart, trak.end);
63
+ if (!tkhd || !mdhd) return void 0;
64
+ return {
65
+ trackId: view.getUint32(tkhd.dataStart + 4 + (readFullBoxVersion(view, tkhd.dataStart) === 1 ? 16 : 8)),
66
+ timescale: readMdhdTimescale(view, mdhd)
67
+ };
68
+ }
69
+ }
70
+ /**
71
+ * `baseMediaDecodeTime` (in the track's media timescale) from the `traf` matching
72
+ * `trackId` (via `tfhd.track_id`) — required for muxed multi-`traf` segments.
73
+ * `undefined` if no matching `tfdt` exists.
74
+ */
75
+ function readBaseMediaDecodeTime(mediaSegment, trackId) {
76
+ const view = toDataView(mediaSegment);
77
+ const moof = findBox(view, ["moof"]);
78
+ if (!moof) return void 0;
79
+ for (const traf of iterateBoxesOfType(view, "traf", moof.dataStart, moof.end)) {
80
+ if (readTrafTrackId(view, traf) !== trackId) continue;
81
+ const tfdt = findBox(view, ["tfdt"], traf.dataStart, traf.end);
82
+ return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : void 0;
83
+ }
84
+ }
85
+ /** `mdhd.timescale`: FullBox version(1)+flags(3) + dates (v0: 4+4, v1: 8+8) + timescale. */
86
+ function readMdhdTimescale(view, mdhd) {
87
+ return view.getUint32(mdhd.dataStart + 4 + (readFullBoxVersion(view, mdhd.dataStart) === 1 ? 16 : 8));
88
+ }
89
+ /** `tfdt.baseMediaDecodeTime`: FullBox, then the value — v0: (4), v1: (8). */
90
+ function readTfdtBaseMediaDecodeTime(view, tfdt) {
91
+ const at = tfdt.dataStart + 4;
92
+ return readFullBoxVersion(view, tfdt.dataStart) === 1 ? Number(view.getBigUint64(at)) : view.getUint32(at);
93
+ }
94
+ /** `hdlr.handler_type` for a `trak`: FullBox version(1)+flags(3) + pre_defined(4) + handler_type(4). */
95
+ function readTrakHandler(view, trak) {
96
+ const hdlr = findBox(view, ["mdia", "hdlr"], trak.dataStart, trak.end);
97
+ return hdlr ? readFourCC(view, hdlr.dataStart + 8) : void 0;
98
+ }
99
+ /** `tfhd.track_id` for a `traf`: FullBox version(1)+flags(3) + track_id(4). */
100
+ function readTrafTrackId(view, traf) {
101
+ const tfhd = findBox(view, ["tfhd"], traf.dataStart, traf.end);
102
+ return tfhd ? view.getUint32(tfhd.dataStart + 4) : void 0;
103
+ }
104
+ //#endregion
105
+ export { findMediaTrack, readBaseMediaDecodeTime };
106
+
107
+ //# sourceMappingURL=timestamp-origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp-origin.js","names":[],"sources":["../../../../src/media/mp4/timestamp-origin.ts"],"sourcesContent":["/**\n * Decode-time origin extraction from fMP4/CMAF segments.\n *\n * Non-zero-PTS sources encode media at a non-zero start time (an instant clip\n * starting at asset second 60, an Apple bipbop asset starting at 10s, …). To\n * relocate such a source onto a 0-based presentation timeline via\n * `SourceBuffer.timestampOffset`, the engine needs that start time — the\n * **decode-time origin** — read straight from the container:\n *\n * - `tfdt.baseMediaDecodeTime` (from a media segment's `moof`) — the decode\n * time of the segment's first sample, in the track's media timescale. This is\n * a DTS: relocating by `−(baseMediaDecodeTime / timescale)` lands the earliest\n * DTS at exactly 0, so a negative-DTS append failure on Chromium is impossible\n * by construction.\n * - `mdhd.timescale` (from the init segment's `moov`) — ticks per second for\n * that track, needed to convert the raw tick count to seconds.\n *\n * ## Presumptive vs. track-selected reads\n *\n * The two variants share only the leaf field-readers (`mdhd` timescale, `tfdt`\n * baseMediaDecodeTime) and the box walker. Everything else differs, and the\n * split is deliberate so the presumptive pair is *proportionally* smaller under\n * tree-shaking:\n *\n * - **Presumptive** — {@link readFirstMediaTimescale} / {@link readFirstBaseMediaDecodeTime}\n * read the first `mdhd` timescale and first `tfdt` baseMediaDecodeTime. There's\n * no `track_id` (nothing to match against) and no `trak`/`traf` iteration — a\n * direct `findBox` to the first leaf. Correct when the init/segment holds a\n * single media track (the common CMAF case). A caption-free platform imports\n * only this pair and tree-shakes away all the matching machinery below.\n * - **Track-selected** — {@link findMediaTrack} / {@link readBaseMediaDecodeTime}.\n * `findMediaTrack` returns `{ trackId, timescale }` for the `trak` whose `hdlr`\n * handler matches; `readBaseMediaDecodeTime` takes that `trackId` and reads the\n * `traf` whose `tfhd.track_id` matches. The `track_id` is the join that ties one\n * track's timescale to the *same* track's baseMediaDecodeTime — required when a\n * source muxes CEA-608/708 captions (a `clcp` track shares the same `moov` and\n * `moof`, each track with its own timescale + baseMediaDecodeTime, so a\n * presumptive read there risks `300000 / 6000 = 50s` instead of\n * `60000 / 6000 = 10s`). This pair adds `trak`/`traf` iteration plus the handler\n * and `track_id` reads. We only ever ask for buffered media handlers\n * (`vide` / `soun`); the caption track is never selected — we read the origin,\n * not the captions (caption *rendering* is out of scope).\n *\n * Both raw values are returned un-divided to leave room for an edit-list (`elst`)\n * presentation-time correction term if a source ever carries one — the validated\n * streams do not.\n */\nimport { type Box, findBox, iterateBoxesOfType, readFourCC, readFullBoxVersion, toDataView } from './box';\n\n/** MSE-buffered media handler types (`mdhd`/`hdlr`). Captions/subtitles excluded. */\nexport type MediaHandlerType = 'vide' | 'soun';\n\nexport interface MediaTrackInfo {\n /** `tkhd.track_id` — used to match the corresponding `traf` in media segments. */\n trackId: number;\n /** `mdhd.timescale` — ticks per second for this track. */\n timescale: number;\n}\n\n// --- presumptive: first leaf, no track_id, no iteration -----------------------\n\n/**\n * Presumptive: the `timescale` of the **first** `mdhd` in an init segment.\n * Correct only for single-media-track inits — for muxed captions use\n * {@link findMediaTrack}. `undefined` if no `mdhd` exists.\n */\nexport function readFirstMediaTimescale(initSegment: ArrayBuffer | Uint8Array): number | undefined {\n const view = toDataView(initSegment);\n const mdhd = findBox(view, ['moov', 'trak', 'mdia', 'mdhd']);\n return mdhd ? readMdhdTimescale(view, mdhd) : undefined;\n}\n\n/**\n * Presumptive: `baseMediaDecodeTime` from the **first** `tfdt` of a media\n * segment. Correct only for single-`traf` segments — for muxed captions use\n * {@link readBaseMediaDecodeTime}. `undefined` if no `tfdt` exists.\n */\nexport function readFirstBaseMediaDecodeTime(mediaSegment: ArrayBuffer | Uint8Array): number | undefined {\n const view = toDataView(mediaSegment);\n const tfdt = findBox(view, ['moof', 'traf', 'tfdt']);\n return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : undefined;\n}\n\n// --- track-selected: iterate + match by handler / track_id --------------------\n\n/**\n * The `track_ID` + timescale of the `trak` whose `mdhd` handler matches\n * `handlerType`, skipping a muxed `clcp` caption track. `undefined` if no\n * matching track exists.\n */\nexport function findMediaTrack(\n initSegment: ArrayBuffer | Uint8Array,\n handlerType: MediaHandlerType\n): MediaTrackInfo | undefined {\n const view = toDataView(initSegment);\n const moov = findBox(view, ['moov']);\n if (!moov) return undefined;\n\n for (const trak of iterateBoxesOfType(view, 'trak', moov.dataStart, moov.end)) {\n if (readTrakHandler(view, trak) !== handlerType) continue;\n const tkhd = findBox(view, ['tkhd'], trak.dataStart, trak.end);\n const mdhd = findBox(view, ['mdia', 'mdhd'], trak.dataStart, trak.end);\n if (!tkhd || !mdhd) return undefined;\n // tkhd FullBox: version(1)+flags(3), creation/modification dates (v0: 4+4, v1:\n // 8+8), then track_id.\n const trackId = view.getUint32(tkhd.dataStart + 4 + (readFullBoxVersion(view, tkhd.dataStart) === 1 ? 16 : 8));\n return { trackId, timescale: readMdhdTimescale(view, mdhd) };\n }\n return undefined;\n}\n\n/**\n * `baseMediaDecodeTime` (in the track's media timescale) from the `traf` matching\n * `trackId` (via `tfhd.track_id`) — required for muxed multi-`traf` segments.\n * `undefined` if no matching `tfdt` exists.\n */\nexport function readBaseMediaDecodeTime(mediaSegment: ArrayBuffer | Uint8Array, trackId: number): number | undefined {\n const view = toDataView(mediaSegment);\n const moof = findBox(view, ['moof']);\n if (!moof) return undefined;\n\n for (const traf of iterateBoxesOfType(view, 'traf', moof.dataStart, moof.end)) {\n if (readTrafTrackId(view, traf) !== trackId) continue;\n const tfdt = findBox(view, ['tfdt'], traf.dataStart, traf.end);\n return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : undefined;\n }\n return undefined;\n}\n\n// --- shared leaf field-readers ------------------------------------------------\n\n/** `mdhd.timescale`: FullBox version(1)+flags(3) + dates (v0: 4+4, v1: 8+8) + timescale. */\nfunction readMdhdTimescale(view: DataView, mdhd: Box): number {\n return view.getUint32(mdhd.dataStart + 4 + (readFullBoxVersion(view, mdhd.dataStart) === 1 ? 16 : 8));\n}\n\n/** `tfdt.baseMediaDecodeTime`: FullBox, then the value — v0: (4), v1: (8). */\nfunction readTfdtBaseMediaDecodeTime(view: DataView, tfdt: Box): number {\n const at = tfdt.dataStart + 4;\n return readFullBoxVersion(view, tfdt.dataStart) === 1 ? Number(view.getBigUint64(at)) : view.getUint32(at);\n}\n\n// --- track-selection readers (referenced only by the track-selected variants) -\n\n/** `hdlr.handler_type` for a `trak`: FullBox version(1)+flags(3) + pre_defined(4) + handler_type(4). */\nfunction readTrakHandler(view: DataView, trak: Box): string | undefined {\n const hdlr = findBox(view, ['mdia', 'hdlr'], trak.dataStart, trak.end);\n return hdlr ? readFourCC(view, hdlr.dataStart + 8) : undefined;\n}\n\n/** `tfhd.track_id` for a `traf`: FullBox version(1)+flags(3) + track_id(4). */\nfunction readTrafTrackId(view: DataView, traf: Box): number | undefined {\n const tfhd = findBox(view, ['tfhd'], traf.dataStart, traf.end);\n return tfhd ? view.getUint32(tfhd.dataStart + 4) : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,SAAgB,eACd,aACA,aAC4B;CAC5B,MAAM,OAAO,WAAW,WAAW;CACnC,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,CAAC;CACnC,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,KAAK,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,KAAK,WAAW,KAAK,GAAG,GAAG;EAC7E,IAAI,gBAAgB,MAAM,IAAI,MAAM,aAAa;EACjD,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;EAC7D,MAAM,OAAO,QAAQ,MAAM,CAAC,QAAQ,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;EACrE,IAAI,CAAC,QAAQ,CAAC,MAAM,OAAO,KAAA;EAI3B,OAAO;GAAE,SADO,KAAK,UAAU,KAAK,YAAY,KAAK,mBAAmB,MAAM,KAAK,SAAS,MAAM,IAAI,KAAK,EAC5F;GAAG,WAAW,kBAAkB,MAAM,IAAI;EAAE;CAC7D;AAEF;;;;;;AAOA,SAAgB,wBAAwB,cAAwC,SAAqC;CACnH,MAAM,OAAO,WAAW,YAAY;CACpC,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,CAAC;CACnC,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,KAAK,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,KAAK,WAAW,KAAK,GAAG,GAAG;EAC7E,IAAI,gBAAgB,MAAM,IAAI,MAAM,SAAS;EAC7C,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;EAC7D,OAAO,OAAO,4BAA4B,MAAM,IAAI,IAAI,KAAA;CAC1D;AAEF;;AAKA,SAAS,kBAAkB,MAAgB,MAAmB;CAC5D,OAAO,KAAK,UAAU,KAAK,YAAY,KAAK,mBAAmB,MAAM,KAAK,SAAS,MAAM,IAAI,KAAK,EAAE;AACtG;;AAGA,SAAS,4BAA4B,MAAgB,MAAmB;CACtE,MAAM,KAAK,KAAK,YAAY;CAC5B,OAAO,mBAAmB,MAAM,KAAK,SAAS,MAAM,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC,IAAI,KAAK,UAAU,EAAE;AAC3G;;AAKA,SAAS,gBAAgB,MAAgB,MAA+B;CACtE,MAAM,OAAO,QAAQ,MAAM,CAAC,QAAQ,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;CACrE,OAAO,OAAO,WAAW,MAAM,KAAK,YAAY,CAAC,IAAI,KAAA;AACvD;;AAGA,SAAS,gBAAgB,MAAgB,MAA+B;CACtE,MAAM,OAAO,QAAQ,MAAM,CAAC,MAAM,GAAG,KAAK,WAAW,KAAK,GAAG;CAC7D,OAAO,OAAO,KAAK,UAAU,KAAK,YAAY,CAAC,IAAI,KAAA;AACrD"}
@@ -0,0 +1,35 @@
1
+ import { MaybeResolvedPresentation } from "../types/index.js";
2
+ //#region src/media/primitives/select-tracks.d.ts
3
+ /**
4
+ * Configuration for video track selection.
5
+ */
6
+ interface VideoSelectionConfig {
7
+ /**
8
+ * Initial bandwidth estimate for cold start (bits per second).
9
+ * Used to select video quality before we have real measurements.
10
+ * Default: 1 Mbps (conservative).
11
+ */
12
+ initialBandwidth?: number;
13
+ /**
14
+ * Safety margin for quality selection (0-1).
15
+ * Default: 0.85 (15% headroom).
16
+ */
17
+ safetyMargin?: number;
18
+ }
19
+ /**
20
+ * Contract for a track picker — a pure function that consults a
21
+ * presentation (and optional config) and returns the id of the track to
22
+ * select, or `undefined` to leave the slot unset.
23
+ *
24
+ * Behaviors that own a track-selection slot (`selectAudioTrack`,
25
+ * `selectVideoTrack`, `switchVideoTrack`) accept a
26
+ * `TrackPicker` via config. The behavior passes its own config straight
27
+ * through as the picker's second argument — pickers that need richer
28
+ * options (language preferences, default-track filtering, bandwidth-aware
29
+ * selection) read from `config`; pickers that don't (e.g., first-track)
30
+ * ignore it.
31
+ */
32
+ type TrackPicker<Config = unknown> = (presentation: MaybeResolvedPresentation, config?: Config) => string | undefined;
33
+ //#endregion
34
+ export { TrackPicker, VideoSelectionConfig };
35
+ //# sourceMappingURL=select-tracks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-tracks.d.ts","names":[],"sources":["../../../../src/media/primitives/select-tracks.ts"],"mappings":";;;;;UA0CiB;;;;;;EAMf;;;;;EAMA;;;;;;;;;;;;;;;KA8DU,YAAY,qBACtB,cAAc,2BACd,SAAS"}
@@ -1,5 +1,22 @@
1
1
  //#region src/media/primitives/select-tracks.ts
2
2
  /**
3
+ * Test whether a track matches a partial-track description: every present,
4
+ * defined field of `filter` equals the track's. Absent or `undefined` filter
5
+ * fields don't constrain. Used to narrow candidates by a user selection
6
+ * (`{ id }`, `{ language }`, `{ height }`, …).
7
+ *
8
+ * @param track - The track to test
9
+ * @param filter - Partial-track description; only present, defined fields constrain
10
+ * @returns `true` when the track matches every constraining field
11
+ */
12
+ function matchesPartialTrack(track, filter) {
13
+ for (const key in filter) {
14
+ const filterValue = filter[key];
15
+ if (filterValue !== void 0 && track[key] !== filterValue) return false;
16
+ }
17
+ return true;
18
+ }
19
+ /**
3
20
  * Pick the first track of the given type from a presentation.
4
21
  *
5
22
  * Returns the first track in the first switching set of the matching
@@ -12,21 +29,89 @@ function pickFirstTrackId(presentation, type) {
12
29
  return presentation.selectionSets?.find((set) => set.type === type)?.switchingSets[0]?.tracks[0]?.id;
13
30
  }
14
31
  /**
15
- * Pick text track to activate. Conforms to the `TrackPicker` contract so it
16
- * can be used directly as a default picker for `selectTextTrack` without an
17
- * adapter wrapper.
32
+ * Translates a "max resolution" into a total total pixel area
33
+ * for comparisons with video track resolutions with an assumed
34
+ * 16:9 ratio.
35
+ *
36
+ * Example: "720p" translates to a 921600 pixel area.
37
+ *
38
+ * Because 720 * 1280 = 720 * (720 * (16/9) ) = 921_600
39
+ *
40
+ * Accepts:
41
+ * - string with the format '{height}p'. ('720p')
42
+ * - bare number, interpreted as pixel area. (921_600)
43
+ * - anything else will translate to `+Infinity`, meaning no cap specified
44
+ */
45
+ function maxResolutionToPixelArea(value) {
46
+ if (value === void 0 || value === null) return Number.POSITIVE_INFINITY;
47
+ if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : Number.POSITIVE_INFINITY;
48
+ const match = value.trim().match(/^(\d+)p?$/i);
49
+ if (!match) return Number.POSITIVE_INFINITY;
50
+ const height = Number(match[1]);
51
+ if (!(Number.isFinite(height) && height > 0)) return Number.POSITIVE_INFINITY;
52
+ return height * height * 16 / 9;
53
+ }
54
+ /**
55
+ * Pick the track with the highest pixel area at or below `maxPixelArea`.
56
+ * Falls back to the lowest track when nothing satisfies the cap (the
57
+ * lowest of the above-cap set is the closest to the cap from above).
58
+ * Tiebreak on bandwidth. Missing dimensions are treated as area `0`.
59
+ */
60
+ function pickTrackUnderPixelArea(tracks, maxPixelArea = Number.POSITIVE_INFINITY) {
61
+ if (tracks.length === 0) return void 0;
62
+ const sorted = [...tracks].sort((a, b) => (b.width ?? 0) * (b.height ?? 0) - (a.width ?? 0) * (a.height ?? 0) || (b.bandwidth ?? 0) - (a.bandwidth ?? 0));
63
+ return sorted.find((t) => (t.width ?? 0) * (t.height ?? 0) <= maxPixelArea) ?? sorted[sorted.length - 1];
64
+ }
65
+ /**
66
+ * Pick the video track with the highest pixel area.
67
+ *
68
+ * Pair with `selectVideoTrack`; compose `switchVideoQuality` instead
69
+ * for runtime-adapted quality.
70
+ */
71
+ function pickHighestResolutionVideoTrack(presentation) {
72
+ const tracks = (presentation.selectionSets?.find((set) => set.type === "video"))?.switchingSets[0]?.tracks;
73
+ if (!tracks?.length) return void 0;
74
+ return pickTrackUnderPixelArea(tracks)?.id;
75
+ }
76
+ /**
77
+ * Pick audio track.
18
78
  *
19
- * Selection priority (if enabled):
20
- * 1. User preference (preferredSubtitleLanguage)
21
- * 2. DEFAULT track (if enableDefaultTrack is true and track has DEFAULT=YES + AUTOSELECT=YES)
22
- * 3. No auto-selection (user opt-in)
79
+ * Selection priority:
80
+ * 1. First track matching preferred language (if specified)
81
+ * 2. First default track
82
+ * 3. First audio track
83
+ *
84
+ * @param presentation - Presentation with audio tracks
85
+ * @param config - Selection configuration (preferred language)
86
+ * @returns Selected audio track ID, or undefined if no audio tracks
87
+ */
88
+ function pickAudioTrack(presentation, config) {
89
+ const audioSet = presentation.selectionSets?.find((set) => set.type === "audio");
90
+ if (!audioSet || audioSet.switchingSets.length === 0) return;
91
+ const switchingSet = audioSet.switchingSets[0];
92
+ if (!switchingSet || switchingSet.tracks.length === 0) return;
93
+ const tracks = switchingSet.tracks;
94
+ if (config?.preferredAudioLanguage) {
95
+ const languageMatch = tracks.find((track) => track.language === config.preferredAudioLanguage);
96
+ if (languageMatch) return languageMatch.id;
97
+ }
98
+ const defaultTrack = tracks.find((track) => track.default === true);
99
+ if (defaultTrack) return defaultTrack.id;
100
+ return tracks[0]?.id;
101
+ }
102
+ /**
103
+ * Default text-track policy over an explicit candidate list (rather than a whole
104
+ * presentation): the opt-in three-tier pick `pickTextTrack` delegates to, factored
105
+ * out so a caller that has already narrowed the candidates — a constrained,
106
+ * CDN-scoped track-switching chain — applies the same policy without re-deriving
107
+ * from the presentation.
23
108
  *
24
- * By default, FORCED tracks are excluded per Apple's HLS spec.
109
+ * Priority: `preferredSubtitleLanguage` match `DEFAULT=YES + AUTOSELECT=YES`
110
+ * (only when `enableDefaultTrack`) → `undefined` (opt-in). FORCED tracks are
111
+ * excluded unless `includeForcedTracks` (Apple-spec: a regular track must carry
112
+ * forced content when both exist, so a forced-only track is redundant).
25
113
  */
26
- function pickTextTrack(presentation, config) {
27
- const textSet = presentation.selectionSets?.find((set) => set.type === "text");
28
- if (!textSet?.switchingSets?.[0]?.tracks.length) return void 0;
29
- const tracks = textSet.switchingSets[0].tracks;
114
+ function pickTextTrackFromTracks(tracks, config) {
30
115
  const availableTracks = config?.includeForcedTracks ? tracks : tracks.filter((track) => !track.forced);
31
116
  if (availableTracks.length === 0) return void 0;
32
117
  const { preferredSubtitleLanguage, enableDefaultTrack = false } = config ?? {};
@@ -40,6 +125,6 @@ function pickTextTrack(presentation, config) {
40
125
  }
41
126
  }
42
127
  //#endregion
43
- export { pickFirstTrackId, pickTextTrack };
128
+ export { matchesPartialTrack, maxResolutionToPixelArea, pickAudioTrack, pickFirstTrackId, pickHighestResolutionVideoTrack, pickTextTrackFromTracks, pickTrackUnderPixelArea };
44
129
 
45
130
  //# sourceMappingURL=select-tracks.js.map