@videojs/html 10.2.0 → 10.4.0

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 (374) hide show
  1. package/dist/default/define/audio/compat-skin.css +161 -41
  2. package/dist/default/define/audio/compat-skin2.js +1 -1
  3. package/dist/default/define/audio/neutral-skin.css +73 -34
  4. package/dist/default/define/audio/neutral-skin2.js +1 -1
  5. package/dist/default/define/audio/scaffold-skin.css +166 -42
  6. package/dist/default/define/audio/scaffold-skin2.js +1 -1
  7. package/dist/default/define/audio/skin.css +72 -34
  8. package/dist/default/define/audio/skin2.js +1 -1
  9. package/dist/default/define/extensions/google-analytics.js +6 -0
  10. package/dist/default/define/live-audio/compat-skin.css +143 -41
  11. package/dist/default/define/live-audio/compat-skin2.js +1 -1
  12. package/dist/default/define/live-audio/neutral-skin.css +56 -31
  13. package/dist/default/define/live-audio/neutral-skin2.js +1 -1
  14. package/dist/default/define/live-audio/scaffold-skin.css +148 -42
  15. package/dist/default/define/live-audio/scaffold-skin2.js +1 -1
  16. package/dist/default/define/live-audio/skin.css +55 -31
  17. package/dist/default/define/live-audio/skin2.js +1 -1
  18. package/dist/default/define/live-video/compat-skin.css +234 -51
  19. package/dist/default/define/live-video/compat-skin2.js +1 -1
  20. package/dist/default/define/live-video/neutral-skin.css +300 -46
  21. package/dist/default/define/live-video/neutral-skin2.js +1 -1
  22. package/dist/default/define/live-video/scaffold-skin.css +231 -53
  23. package/dist/default/define/live-video/scaffold-skin2.js +1 -1
  24. package/dist/default/define/live-video/skin.css +314 -49
  25. package/dist/default/define/live-video/skin2.js +1 -1
  26. package/dist/default/define/media/dailymotion-video.js +11 -0
  27. package/dist/default/define/media/mixcloud-audio.js +11 -0
  28. package/dist/default/define/media/soundcloud-audio.js +11 -0
  29. package/dist/default/define/ui/time-slider-markers.js +5 -0
  30. package/dist/default/define/video/compat-skin.css +242 -51
  31. package/dist/default/define/video/compat-skin2.js +1 -1
  32. package/dist/default/define/video/neutral-skin.css +382 -57
  33. package/dist/default/define/video/neutral-skin2.js +1 -1
  34. package/dist/default/define/video/scaffold-skin.css +240 -53
  35. package/dist/default/define/video/scaffold-skin2.js +1 -1
  36. package/dist/default/define/video/skin.css +415 -55
  37. package/dist/default/define/video/skin2.js +1 -1
  38. package/dist/default/extensions/google-analytics/extension.js +75 -0
  39. package/dist/default/extensions/mux-data/extension.js +7 -2
  40. package/dist/default/index.js +3 -1
  41. package/dist/default/internal/skins/compat-audio/skin.css +161 -41
  42. package/dist/default/internal/skins/compat-audio/template.js +1 -1
  43. package/dist/default/internal/skins/compat-live-audio/skin.css +143 -41
  44. package/dist/default/internal/skins/compat-live-video/skin.css +234 -51
  45. package/dist/default/internal/skins/compat-live-video/template.js +2 -0
  46. package/dist/default/internal/skins/compat-video/skin.css +242 -51
  47. package/dist/default/internal/skins/compat-video/template.js +2 -0
  48. package/dist/default/internal/skins/default-audio/skin.css +72 -34
  49. package/dist/default/internal/skins/default-live-audio/skin.css +55 -31
  50. package/dist/default/internal/skins/default-live-video/skin.css +314 -49
  51. package/dist/default/internal/skins/default-live-video/template.js +14 -0
  52. package/dist/default/internal/skins/default-video/register.js +3 -0
  53. package/dist/default/internal/skins/default-video/skin.css +415 -55
  54. package/dist/default/internal/skins/default-video/template.js +33 -5
  55. package/dist/default/internal/skins/neutral-audio/skin.css +73 -34
  56. package/dist/default/internal/skins/neutral-live-audio/skin.css +56 -31
  57. package/dist/default/internal/skins/neutral-live-video/skin.css +300 -46
  58. package/dist/default/internal/skins/neutral-live-video/template.js +14 -0
  59. package/dist/default/internal/skins/neutral-video/skin.css +382 -57
  60. package/dist/default/internal/skins/neutral-video/template.js +25 -11
  61. package/dist/default/internal/skins/scaffold-audio/skin.css +166 -42
  62. package/dist/default/internal/skins/scaffold-audio/template.js +1 -1
  63. package/dist/default/internal/skins/scaffold-live-audio/skin.css +148 -42
  64. package/dist/default/internal/skins/scaffold-live-video/skin.css +231 -53
  65. package/dist/default/internal/skins/scaffold-live-video/template.js +2 -0
  66. package/dist/default/internal/skins/scaffold-video/skin.css +240 -53
  67. package/dist/default/internal/skins/scaffold-video/template.js +3 -1
  68. package/dist/default/media/background-video/adapter.js +13 -35
  69. package/dist/default/media/background-video/base.js +95 -0
  70. package/dist/default/media/background-video/template.js +13 -20
  71. package/dist/default/media/dailymotion-video/adapter.js +31 -0
  72. package/dist/default/media/hls-background-video/adapter.js +23 -35
  73. package/dist/default/media/mixcloud-audio/adapter.js +16 -0
  74. package/dist/default/media/soundcloud-audio/adapter.js +16 -0
  75. package/dist/default/player/element.js +1 -5
  76. package/dist/default/presets/background/skin.js +2 -2
  77. package/dist/default/presets/skin.js +4 -3
  78. package/dist/default/ui/captions-radio-group/element.js +14 -1
  79. package/dist/default/ui/controls/element.js +6 -3
  80. package/dist/default/ui/dialog/element.js +13 -1
  81. package/dist/default/ui/gesture/element.js +3 -0
  82. package/dist/default/ui/media-button-element.js +10 -1
  83. package/dist/default/ui/playback-rate-button/element.js +7 -2
  84. package/dist/default/ui/playback-rate-radio-group/element.js +10 -3
  85. package/dist/default/ui/radio-options/controller.js +18 -5
  86. package/dist/default/ui/time-slider/markers.js +160 -0
  87. package/dist/default/version.js +1 -1
  88. package/dist/dev/define/audio/compat-skin.css +161 -41
  89. package/dist/dev/define/audio/compat-skin2.js +1 -1
  90. package/dist/dev/define/audio/compat-skin2.js.map +1 -1
  91. package/dist/dev/define/audio/neutral-skin.css +73 -34
  92. package/dist/dev/define/audio/neutral-skin2.js +1 -1
  93. package/dist/dev/define/audio/neutral-skin2.js.map +1 -1
  94. package/dist/dev/define/audio/scaffold-skin.css +166 -42
  95. package/dist/dev/define/audio/scaffold-skin2.js +1 -1
  96. package/dist/dev/define/audio/scaffold-skin2.js.map +1 -1
  97. package/dist/dev/define/audio/skin.css +72 -34
  98. package/dist/dev/define/audio/skin2.js +1 -1
  99. package/dist/dev/define/audio/skin2.js.map +1 -1
  100. package/dist/dev/define/extensions/google-analytics.d.ts +11 -0
  101. package/dist/dev/define/extensions/google-analytics.js +8 -0
  102. package/dist/dev/define/extensions/google-analytics.js.map +1 -0
  103. package/dist/dev/define/live-audio/compat-skin.css +143 -41
  104. package/dist/dev/define/live-audio/compat-skin2.js +1 -1
  105. package/dist/dev/define/live-audio/compat-skin2.js.map +1 -1
  106. package/dist/dev/define/live-audio/neutral-skin.css +56 -31
  107. package/dist/dev/define/live-audio/neutral-skin2.js +1 -1
  108. package/dist/dev/define/live-audio/neutral-skin2.js.map +1 -1
  109. package/dist/dev/define/live-audio/scaffold-skin.css +148 -42
  110. package/dist/dev/define/live-audio/scaffold-skin2.js +1 -1
  111. package/dist/dev/define/live-audio/scaffold-skin2.js.map +1 -1
  112. package/dist/dev/define/live-audio/skin.css +55 -31
  113. package/dist/dev/define/live-audio/skin2.js +1 -1
  114. package/dist/dev/define/live-audio/skin2.js.map +1 -1
  115. package/dist/dev/define/live-video/compat-skin.css +234 -51
  116. package/dist/dev/define/live-video/compat-skin2.js +1 -1
  117. package/dist/dev/define/live-video/compat-skin2.js.map +1 -1
  118. package/dist/dev/define/live-video/neutral-skin.css +300 -46
  119. package/dist/dev/define/live-video/neutral-skin2.js +1 -1
  120. package/dist/dev/define/live-video/neutral-skin2.js.map +1 -1
  121. package/dist/dev/define/live-video/scaffold-skin.css +231 -53
  122. package/dist/dev/define/live-video/scaffold-skin2.js +1 -1
  123. package/dist/dev/define/live-video/scaffold-skin2.js.map +1 -1
  124. package/dist/dev/define/live-video/skin.css +314 -49
  125. package/dist/dev/define/live-video/skin2.js +1 -1
  126. package/dist/dev/define/live-video/skin2.js.map +1 -1
  127. package/dist/dev/define/media/dailymotion-video.d.ts +15 -0
  128. package/dist/dev/define/media/dailymotion-video.js +13 -0
  129. package/dist/dev/define/media/dailymotion-video.js.map +1 -0
  130. package/dist/dev/define/media/mixcloud-audio.d.ts +15 -0
  131. package/dist/dev/define/media/mixcloud-audio.js +13 -0
  132. package/dist/dev/define/media/mixcloud-audio.js.map +1 -0
  133. package/dist/dev/define/media/soundcloud-audio.d.ts +15 -0
  134. package/dist/dev/define/media/soundcloud-audio.js +13 -0
  135. package/dist/dev/define/media/soundcloud-audio.js.map +1 -0
  136. package/dist/dev/define/ui/time-slider-markers.d.ts +8 -0
  137. package/dist/dev/define/ui/time-slider-markers.js +7 -0
  138. package/dist/dev/define/ui/time-slider-markers.js.map +1 -0
  139. package/dist/dev/define/video/compat-skin.css +242 -51
  140. package/dist/dev/define/video/compat-skin2.js +1 -1
  141. package/dist/dev/define/video/compat-skin2.js.map +1 -1
  142. package/dist/dev/define/video/neutral-skin.css +382 -57
  143. package/dist/dev/define/video/neutral-skin2.js +1 -1
  144. package/dist/dev/define/video/neutral-skin2.js.map +1 -1
  145. package/dist/dev/define/video/scaffold-skin.css +240 -53
  146. package/dist/dev/define/video/scaffold-skin2.js +1 -1
  147. package/dist/dev/define/video/scaffold-skin2.js.map +1 -1
  148. package/dist/dev/define/video/skin.css +415 -55
  149. package/dist/dev/define/video/skin2.js +1 -1
  150. package/dist/dev/define/video/skin2.js.map +1 -1
  151. package/dist/dev/extensions/google-analytics/extension.d.ts +52 -0
  152. package/dist/dev/extensions/google-analytics/extension.js +77 -0
  153. package/dist/dev/extensions/google-analytics/extension.js.map +1 -0
  154. package/dist/dev/extensions/google-analytics/index.d.ts +2 -0
  155. package/dist/dev/extensions/mux-data/extension.d.ts +4 -1
  156. package/dist/dev/extensions/mux-data/extension.js +7 -2
  157. package/dist/dev/extensions/mux-data/extension.js.map +1 -1
  158. package/dist/dev/index.d.ts +5 -4
  159. package/dist/dev/index.js +3 -1
  160. package/dist/dev/internal/skins/compat-audio/skin.css +161 -41
  161. package/dist/dev/internal/skins/compat-audio/template.js +1 -1
  162. package/dist/dev/internal/skins/compat-audio/template.js.map +1 -1
  163. package/dist/dev/internal/skins/compat-live-audio/skin.css +143 -41
  164. package/dist/dev/internal/skins/compat-live-video/skin.css +234 -51
  165. package/dist/dev/internal/skins/compat-live-video/template.js +2 -0
  166. package/dist/dev/internal/skins/compat-live-video/template.js.map +1 -1
  167. package/dist/dev/internal/skins/compat-video/skin.css +242 -51
  168. package/dist/dev/internal/skins/compat-video/template.js +2 -0
  169. package/dist/dev/internal/skins/compat-video/template.js.map +1 -1
  170. package/dist/dev/internal/skins/default-audio/skin.css +72 -34
  171. package/dist/dev/internal/skins/default-live-audio/skin.css +55 -31
  172. package/dist/dev/internal/skins/default-live-video/register.js.map +1 -1
  173. package/dist/dev/internal/skins/default-live-video/skin.css +314 -49
  174. package/dist/dev/internal/skins/default-live-video/template.js +14 -0
  175. package/dist/dev/internal/skins/default-live-video/template.js.map +1 -1
  176. package/dist/dev/internal/skins/default-video/register.d.ts +1 -0
  177. package/dist/dev/internal/skins/default-video/register.js +3 -0
  178. package/dist/dev/internal/skins/default-video/register.js.map +1 -1
  179. package/dist/dev/internal/skins/default-video/skin.css +415 -55
  180. package/dist/dev/internal/skins/default-video/template.js +33 -5
  181. package/dist/dev/internal/skins/default-video/template.js.map +1 -1
  182. package/dist/dev/internal/skins/neutral-audio/skin.css +73 -34
  183. package/dist/dev/internal/skins/neutral-live-audio/skin.css +56 -31
  184. package/dist/dev/internal/skins/neutral-live-video/register.js.map +1 -1
  185. package/dist/dev/internal/skins/neutral-live-video/skin.css +300 -46
  186. package/dist/dev/internal/skins/neutral-live-video/template.js +14 -0
  187. package/dist/dev/internal/skins/neutral-live-video/template.js.map +1 -1
  188. package/dist/dev/internal/skins/neutral-video/register.js.map +1 -1
  189. package/dist/dev/internal/skins/neutral-video/skin.css +382 -57
  190. package/dist/dev/internal/skins/neutral-video/template.js +25 -11
  191. package/dist/dev/internal/skins/neutral-video/template.js.map +1 -1
  192. package/dist/dev/internal/skins/scaffold-audio/skin.css +166 -42
  193. package/dist/dev/internal/skins/scaffold-audio/template.js +1 -1
  194. package/dist/dev/internal/skins/scaffold-audio/template.js.map +1 -1
  195. package/dist/dev/internal/skins/scaffold-live-audio/skin.css +148 -42
  196. package/dist/dev/internal/skins/scaffold-live-video/skin.css +231 -53
  197. package/dist/dev/internal/skins/scaffold-live-video/template.js +2 -0
  198. package/dist/dev/internal/skins/scaffold-live-video/template.js.map +1 -1
  199. package/dist/dev/internal/skins/scaffold-video/skin.css +240 -53
  200. package/dist/dev/internal/skins/scaffold-video/template.js +3 -1
  201. package/dist/dev/internal/skins/scaffold-video/template.js.map +1 -1
  202. package/dist/dev/media/background-video/adapter.d.ts +7 -13
  203. package/dist/dev/media/background-video/adapter.js +13 -35
  204. package/dist/dev/media/background-video/adapter.js.map +1 -1
  205. package/dist/dev/media/background-video/base.d.ts +44 -0
  206. package/dist/dev/media/background-video/base.js +97 -0
  207. package/dist/dev/media/background-video/base.js.map +1 -0
  208. package/dist/dev/media/background-video/template.d.ts +3 -0
  209. package/dist/dev/media/background-video/template.js +13 -20
  210. package/dist/dev/media/background-video/template.js.map +1 -1
  211. package/dist/dev/media/dailymotion-video/adapter.d.ts +17 -0
  212. package/dist/dev/media/dailymotion-video/adapter.js +47 -0
  213. package/dist/dev/media/dailymotion-video/adapter.js.map +1 -0
  214. package/dist/dev/media/dailymotion-video/index.d.ts +2 -0
  215. package/dist/dev/media/hls-background-video/adapter.d.ts +14 -19
  216. package/dist/dev/media/hls-background-video/adapter.js +23 -35
  217. package/dist/dev/media/hls-background-video/adapter.js.map +1 -1
  218. package/dist/dev/media/mixcloud-audio/adapter.d.ts +11 -0
  219. package/dist/dev/media/mixcloud-audio/adapter.js +52 -0
  220. package/dist/dev/media/mixcloud-audio/adapter.js.map +1 -0
  221. package/dist/dev/media/mixcloud-audio/index.d.ts +2 -0
  222. package/dist/dev/media/soundcloud-audio/adapter.d.ts +11 -0
  223. package/dist/dev/media/soundcloud-audio/adapter.js +53 -0
  224. package/dist/dev/media/soundcloud-audio/adapter.js.map +1 -0
  225. package/dist/dev/media/soundcloud-audio/index.d.ts +2 -0
  226. package/dist/dev/player/element.js +1 -5
  227. package/dist/dev/player/element.js.map +1 -1
  228. package/dist/dev/presets/background/skin.d.ts +2 -1
  229. package/dist/dev/presets/background/skin.js +2 -2
  230. package/dist/dev/presets/background/skin.js.map +1 -1
  231. package/dist/dev/presets/skin.d.ts +4 -3
  232. package/dist/dev/presets/skin.js +4 -3
  233. package/dist/dev/presets/skin.js.map +1 -1
  234. package/dist/dev/ui/captions-button/element.d.ts +2 -0
  235. package/dist/dev/ui/captions-radio-group/element.d.ts +2 -1
  236. package/dist/dev/ui/captions-radio-group/element.js +14 -1
  237. package/dist/dev/ui/captions-radio-group/element.js.map +1 -1
  238. package/dist/dev/ui/controls/element.d.ts +2 -2
  239. package/dist/dev/ui/controls/element.js +6 -3
  240. package/dist/dev/ui/controls/element.js.map +1 -1
  241. package/dist/dev/ui/dialog/element.js +13 -1
  242. package/dist/dev/ui/dialog/element.js.map +1 -1
  243. package/dist/dev/ui/gesture/element.js +3 -0
  244. package/dist/dev/ui/gesture/element.js.map +1 -1
  245. package/dist/dev/ui/media-button-element.d.ts +9 -0
  246. package/dist/dev/ui/media-button-element.js +10 -1
  247. package/dist/dev/ui/media-button-element.js.map +1 -1
  248. package/dist/dev/ui/play-button/element.d.ts +2 -0
  249. package/dist/dev/ui/playback-rate-button/element.d.ts +9 -1
  250. package/dist/dev/ui/playback-rate-button/element.js +7 -2
  251. package/dist/dev/ui/playback-rate-button/element.js.map +1 -1
  252. package/dist/dev/ui/playback-rate-radio-group/element.d.ts +4 -0
  253. package/dist/dev/ui/playback-rate-radio-group/element.js +10 -3
  254. package/dist/dev/ui/playback-rate-radio-group/element.js.map +1 -1
  255. package/dist/dev/ui/radio-options/controller.js +18 -5
  256. package/dist/dev/ui/radio-options/controller.js.map +1 -1
  257. package/dist/dev/ui/time-slider/markers.d.ts +48 -0
  258. package/dist/dev/ui/time-slider/markers.js +165 -0
  259. package/dist/dev/ui/time-slider/markers.js.map +1 -0
  260. package/dist/dev/version.js +1 -1
  261. package/docs/README.md +1 -1
  262. package/docs/guides/autoplay.md +2 -2
  263. package/docs/guides/background-video.md +2 -4
  264. package/docs/guides/browser-support.md +23 -15
  265. package/docs/guides/build-your-own-skin.md +3 -1
  266. package/docs/guides/cdn.md +10 -9
  267. package/docs/guides/content-security-policy.md +3 -0
  268. package/docs/guides/customize-skins.md +7 -2
  269. package/docs/guides/features.md +1 -1
  270. package/docs/guides/google-analytics.md +199 -0
  271. package/docs/guides/installation-cdn.md +4 -4
  272. package/docs/guides/installation-shadcn.md +3 -3
  273. package/docs/guides/installation-svelte.md +4 -4
  274. package/docs/guides/installation-vue.md +4 -4
  275. package/docs/guides/installation.md +4 -4
  276. package/docs/guides/internationalization.md +5 -5
  277. package/docs/guides/keyboard-shortcuts.md +11 -1
  278. package/docs/guides/llms.txt +3 -1
  279. package/docs/guides/media-sources.md +3 -0
  280. package/docs/guides/migrate-from-media-chrome.md +4 -4
  281. package/docs/guides/migrate-from-mux-player.md +9 -9
  282. package/docs/guides/migrate-from-plyr.md +8 -8
  283. package/docs/guides/migrate-from-video-js-8.md +8 -9
  284. package/docs/guides/migrate-from-vidstack.md +10 -10
  285. package/docs/guides/mux-data.md +22 -0
  286. package/docs/guides/presets.md +5 -5
  287. package/docs/guides/self-hosting.md +5 -5
  288. package/docs/guides/skins.md +21 -5
  289. package/docs/guides/timed-metadata.md +318 -0
  290. package/docs/guides/ui-components.md +1 -1
  291. package/docs/llms.txt +25 -6
  292. package/docs/reference/api/chapters-track.md +71 -0
  293. package/docs/reference/api/define-text-track.md +113 -0
  294. package/docs/reference/api/feature-playback-rate.md +54 -3
  295. package/docs/reference/api/feature-playback.md +5 -1
  296. package/docs/reference/api/feature-text-cues.md +133 -0
  297. package/docs/reference/api/feature-text-tracks.md +11 -5
  298. package/docs/reference/api/get-active-text-cues.md +69 -0
  299. package/docs/reference/api/get-text-cues.md +69 -0
  300. package/docs/reference/api/get-text-tracks.md +41 -0
  301. package/docs/reference/api/hls-metadata-track.md +48 -0
  302. package/docs/reference/api/html-create-player.md +2 -2
  303. package/docs/reference/api/llms.txt +13 -1
  304. package/docs/reference/api/media-capabilities.md +4 -4
  305. package/docs/reference/api/player-store.md +15 -7
  306. package/docs/reference/api/preset-audio.md +15 -5
  307. package/docs/reference/api/preset-background.md +2 -2
  308. package/docs/reference/api/preset-live-audio.md +15 -5
  309. package/docs/reference/api/preset-live-video.md +14 -5
  310. package/docs/reference/api/preset-video.md +14 -5
  311. package/docs/reference/api/resolve-adapter-type.md +4 -1
  312. package/docs/reference/api/ui-element.md +2 -2
  313. package/docs/reference/components/alert-dialog.md +21 -16
  314. package/docs/reference/components/audio-compat-skin.md +8 -2
  315. package/docs/reference/components/audio-neutral-skin.md +2 -2
  316. package/docs/reference/components/audio-scaffold-skin.md +2 -2
  317. package/docs/reference/components/audio-skin.md +2 -2
  318. package/docs/reference/components/audio-track-radio-group.md +2 -2
  319. package/docs/reference/components/audio.md +19 -1
  320. package/docs/reference/components/background-video-skin.md +1 -1
  321. package/docs/reference/components/background-video.md +26 -11
  322. package/docs/reference/components/buffering-indicator.md +2 -2
  323. package/docs/reference/components/captions-radio-group.md +3 -3
  324. package/docs/reference/components/cloudflare-video.md +1 -1
  325. package/docs/reference/components/controls.md +16 -3
  326. package/docs/reference/components/dailymotion-video.md +192 -0
  327. package/docs/reference/components/dash-video.md +20 -2
  328. package/docs/reference/components/dialog.md +49 -22
  329. package/docs/reference/components/error-dialog.md +13 -10
  330. package/docs/reference/components/gesture.md +2 -2
  331. package/docs/reference/components/google-analytics.md +51 -0
  332. package/docs/reference/components/google-cast.md +1 -1
  333. package/docs/reference/components/hls-audio.md +56 -2
  334. package/docs/reference/components/hls-background-video.md +25 -5
  335. package/docs/reference/components/hls-video.md +99 -7
  336. package/docs/reference/components/hlsjs-video.md +53 -12
  337. package/docs/reference/components/hotkey.md +2 -2
  338. package/docs/reference/components/live-audio-compat-skin.md +8 -2
  339. package/docs/reference/components/live-audio-neutral-skin.md +2 -2
  340. package/docs/reference/components/live-audio-scaffold-skin.md +2 -2
  341. package/docs/reference/components/live-audio-skin.md +2 -2
  342. package/docs/reference/components/live-video-compat-skin.md +12 -2
  343. package/docs/reference/components/live-video-neutral-skin.md +6 -2
  344. package/docs/reference/components/live-video-scaffold-skin.md +6 -2
  345. package/docs/reference/components/live-video-skin.md +6 -2
  346. package/docs/reference/components/llms.txt +10 -5
  347. package/docs/reference/components/mixcloud-audio.md +203 -0
  348. package/docs/reference/components/mux-audio.md +35 -2
  349. package/docs/reference/components/mux-background-video.md +1 -1
  350. package/docs/reference/components/mux-data.md +20 -2
  351. package/docs/reference/components/mux-video.md +18 -2
  352. package/docs/reference/components/native-hls-video.md +32 -2
  353. package/docs/reference/components/play-button.md +7 -5
  354. package/docs/reference/components/playback-rate-button.md +26 -7
  355. package/docs/reference/components/playback-rate-radio-group.md +7 -4
  356. package/docs/reference/components/player.md +3 -3
  357. package/docs/reference/components/playerjs-video.md +2 -2
  358. package/docs/reference/components/quality-radio-group.md +2 -2
  359. package/docs/reference/components/shaka-video.md +2 -2
  360. package/docs/reference/components/soundcloud-audio.md +227 -0
  361. package/docs/reference/components/spotify-audio.md +3 -3
  362. package/docs/reference/components/tiktok-video.md +3 -3
  363. package/docs/reference/components/time-slider-markers.md +194 -0
  364. package/docs/reference/components/time-slider.md +14 -0
  365. package/docs/reference/components/twitch-video.md +2 -3
  366. package/docs/reference/components/video-compat-skin.md +12 -2
  367. package/docs/reference/components/video-neutral-skin.md +6 -2
  368. package/docs/reference/components/video-scaffold-skin.md +6 -2
  369. package/docs/reference/components/video-skin.md +6 -2
  370. package/docs/reference/components/video.md +19 -1
  371. package/docs/reference/components/vimeo-video.md +1 -1
  372. package/docs/reference/components/wistia-video.md +1 -1
  373. package/docs/reference/components/youtube-video.md +1 -1
  374. package/package.json +60 -40
@@ -0,0 +1,48 @@
1
+ import { UIElement } from "../ui-element.js";
2
+ import { TextTrackDefinition } from "@videojs/core";
3
+ import { PropertyValues } from "@videojs/element";
4
+ import { TextTrackFilter, TextTrackKind } from "@videojs/media";
5
+ //#region src/ui/time-slider/markers.d.ts
6
+ type TextTrackQuery = TextTrackFilter | TextTrackDefinition;
7
+ /**
8
+ * Draws a marker on the time slider for each cue on the text tracks a filter matches, such as ad breaks. Pick tracks
9
+ * with the `kind`, `label`, and `language` attributes, or with the `filter` property; with neither, nothing is drawn.
10
+ * The store reads the matching tracks while the element is connected, so tracks it doesn't read by default work too.
11
+ *
12
+ * Each marker clones the light-DOM template, which must contain exactly one HTML root element, or is a `<div>` without
13
+ * one. Markers are decorative: the slider's own value is what assistive technology reads.
14
+ */
15
+ export declare class TimeSliderMarkersElement extends UIElement {
16
+ #private;
17
+ static readonly tagName = "media-time-slider-markers";
18
+ static properties: {
19
+ kind: {
20
+ type: StringConstructor;
21
+ };
22
+ label: {
23
+ type: StringConstructor;
24
+ };
25
+ language: {
26
+ type: StringConstructor;
27
+ };
28
+ };
29
+ /** Draw cues from tracks of this kind. */
30
+ kind: TextTrackKind | null | undefined;
31
+ /** Draw cues from tracks with this label. */
32
+ label: string | null | undefined;
33
+ /** Draw cues from tracks in this language. */
34
+ language: string | null | undefined;
35
+ /**
36
+ * Picks the tracks whose cues are drawn: a filter object, a function, or a track definition. Takes priority over the
37
+ * `kind`, `label`, and `language` attributes. Set as a JavaScript property; it has no attribute.
38
+ */
39
+ get filter(): TextTrackQuery | undefined;
40
+ set filter(value: TextTrackQuery | undefined);
41
+ connectedCallback(): void;
42
+ disconnectedCallback(): void;
43
+ destroyCallback(): void;
44
+ protected willUpdate(changed: PropertyValues): void;
45
+ protected update(changed: PropertyValues): void;
46
+ }
47
+ //#endregion
48
+ //# sourceMappingURL=markers.d.ts.map
@@ -0,0 +1,165 @@
1
+ import { playerContext } from "../../player/context.js";
2
+ import { PlayerController } from "../../player/controller.js";
3
+ import { UIElement } from "../ui-element.js";
4
+ import { sliderContext } from "../slider/context.js";
5
+ import { TimeSliderMarkerCSSVars, TimeSliderMarkerDataAttrs, TimeSliderMarkersCore, getActiveTextCues, getTextCues } from "@videojs/core";
6
+ import { applyStateDataAttrs, selectBuffer, selectTextCues, selectTime } from "@videojs/core/dom";
7
+ import { getTimeRangeEnd } from "@videojs/media";
8
+ import { ContextConsumer } from "@videojs/element/context";
9
+ import { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from "@videojs/utils/dom";
10
+ import { isNil } from "@videojs/utils/predicate";
11
+ //#region src/ui/time-slider/markers.ts
12
+ /**
13
+ * Draws a marker on the time slider for each cue on the text tracks a filter matches, such as ad breaks. Pick tracks
14
+ * with the `kind`, `label`, and `language` attributes, or with the `filter` property; with neither, nothing is drawn.
15
+ * The store reads the matching tracks while the element is connected, so tracks it doesn't read by default work too.
16
+ *
17
+ * Each marker clones the light-DOM template, which must contain exactly one HTML root element, or is a `<div>` without
18
+ * one. Markers are decorative: the slider's own value is what assistive technology reads.
19
+ */
20
+ var TimeSliderMarkersElement = class extends UIElement {
21
+ static {
22
+ this.tagName = "media-time-slider-markers";
23
+ }
24
+ static {
25
+ this.properties = {
26
+ kind: { type: String },
27
+ label: { type: String },
28
+ language: { type: String }
29
+ };
30
+ }
31
+ #core = new TimeSliderMarkersCore();
32
+ #slider = new ContextConsumer(this, {
33
+ context: sliderContext,
34
+ subscribe: true
35
+ });
36
+ #textCues = new PlayerController(this, playerContext, selectTextCues);
37
+ #time = new PlayerController(this, playerContext, selectTime);
38
+ #buffer = new PlayerController(this, playerContext, selectBuffer);
39
+ #rendered = /* @__PURE__ */ new Map();
40
+ #filter;
41
+ #attributeFilter;
42
+ #watching = null;
43
+ #templateRoot = null;
44
+ #templateChecked = false;
45
+ /**
46
+ * Picks the tracks whose cues are drawn: a filter object, a function, or a track definition. Takes priority over the
47
+ * `kind`, `label`, and `language` attributes. Set as a JavaScript property; it has no attribute.
48
+ */
49
+ get filter() {
50
+ return this.#filter;
51
+ }
52
+ set filter(value) {
53
+ this.#filter = value;
54
+ this.requestUpdate();
55
+ }
56
+ connectedCallback() {
57
+ super.connectedCallback();
58
+ this.setAttribute("aria-hidden", "true");
59
+ }
60
+ disconnectedCallback() {
61
+ this.#unwatch();
62
+ super.disconnectedCallback();
63
+ }
64
+ destroyCallback() {
65
+ this.#unwatch();
66
+ super.destroyCallback();
67
+ }
68
+ willUpdate(changed) {
69
+ super.willUpdate(changed);
70
+ if (changed.has("kind") || changed.has("label") || changed.has("language")) this.#attributeFilter = this.#getAttributeFilter();
71
+ this.#watch(this.#getFilter());
72
+ }
73
+ update(changed) {
74
+ super.update(changed);
75
+ const slider = this.#slider.value;
76
+ const templateRoot = this.#getTemplateRoot();
77
+ if (!slider) return;
78
+ applyStateDataAttrs(this, slider.state, slider.stateAttrMap);
79
+ const filter = this.#getFilter();
80
+ const textCues = this.#textCues.value;
81
+ const time = this.#time.value;
82
+ const max = getTimeRangeEnd({
83
+ duration: time?.duration ?? 0,
84
+ seekable: this.#buffer.value?.seekable ?? []
85
+ });
86
+ const markers = filter && textCues ? this.#core.getMarkers({
87
+ cues: getTextCues(textCues, filter),
88
+ activeCues: getActiveTextCues(textCues, filter),
89
+ min: 0,
90
+ max,
91
+ orientation: slider.state.orientation
92
+ }) : [];
93
+ const next = /* @__PURE__ */ new Map();
94
+ for (const marker of markers) {
95
+ const root = this.#rendered.get(marker.key) ?? this.#createMarker(templateRoot);
96
+ root.style.setProperty(TimeSliderMarkerCSSVars.start, marker.startPercent);
97
+ root.style.setProperty(TimeSliderMarkerCSSVars.end, marker.endPercent);
98
+ root.style.setProperty(TimeSliderMarkerCSSVars.width, marker.width ?? marker.height ?? "0%");
99
+ applyStateDataAttrs(root, slider.state, slider.stateAttrMap);
100
+ applyStateDataAttrs(root, marker, TimeSliderMarkerDataAttrs);
101
+ next.set(marker.key, root);
102
+ }
103
+ for (const [key, root] of this.#rendered) if (!next.has(key)) root.remove();
104
+ let before = null;
105
+ for (const root of [...next.values()].reverse()) {
106
+ if (root.parentNode !== this || root.nextSibling !== before) this.insertBefore(root, before);
107
+ before = root;
108
+ }
109
+ this.#rendered.clear();
110
+ for (const [key, rendered] of next) this.#rendered.set(key, rendered);
111
+ }
112
+ #getFilter() {
113
+ return this.#filter ?? this.#attributeFilter;
114
+ }
115
+ /** A filter from the attributes that are present, or none when no attribute is. */
116
+ #getAttributeFilter() {
117
+ const { kind, label, language } = this;
118
+ if (isNil(kind) && isNil(label) && isNil(language)) return void 0;
119
+ return {
120
+ kind: kind ?? void 0,
121
+ label: label ?? void 0,
122
+ language: language ?? void 0
123
+ };
124
+ }
125
+ /** Ask the store to read the tracks the filter matches while the element is connected. */
126
+ #watch(filter) {
127
+ const watch = this.isConnected ? this.#textCues.value?.watchTextCues : void 0;
128
+ if (this.#watching?.watch === watch && this.#watching?.filter === filter) return;
129
+ this.#unwatch();
130
+ if (watch && filter) this.#watching = {
131
+ watch,
132
+ filter,
133
+ stop: watch(filter)
134
+ };
135
+ }
136
+ #unwatch() {
137
+ this.#watching?.stop();
138
+ this.#watching = null;
139
+ }
140
+ #createMarker(templateRoot) {
141
+ return templateRoot ? cloneTemplateRoot(templateRoot, this.ownerDocument) : this.ownerDocument.createElement("div");
142
+ }
143
+ /** The template's root, found once; until there is a template, other content is cleared and markers are `<div>`s. */
144
+ #getTemplateRoot() {
145
+ if (this.#templateChecked) return this.#templateRoot;
146
+ const template = getTemplateElement(this);
147
+ const rendered = new Set(this.#rendered.values());
148
+ for (const node of [...this.childNodes]) if (node !== template && !rendered.has(node)) node.remove();
149
+ if (!template) return null;
150
+ this.#templateChecked = true;
151
+ for (const node of rendered) node.remove();
152
+ this.#rendered.clear();
153
+ const root = getTemplateRoot(template);
154
+ if (root?.namespaceURI !== "http://www.w3.org/1999/xhtml") {
155
+ console.warn(`[${this.localName}] template must contain exactly one HTML root element.`);
156
+ return null;
157
+ }
158
+ this.#templateRoot = root;
159
+ return this.#templateRoot;
160
+ }
161
+ };
162
+ //#endregion
163
+ export { TimeSliderMarkersElement };
164
+
165
+ //# sourceMappingURL=markers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markers.js","names":[],"sources":["../../../../src/ui/time-slider/markers.ts"],"sourcesContent":["import {\n getActiveTextCues,\n getTextCues,\n type TextTrackDefinition,\n TimeSliderMarkerCSSVars,\n TimeSliderMarkerDataAttrs,\n TimeSliderMarkersCore,\n} from '@videojs/core';\nimport { applyStateDataAttrs, selectBuffer, selectTextCues, selectTime } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextConsumer } from '@videojs/element/context';\nimport { getTimeRangeEnd, type TextTrackFilter, type TextTrackKind } from '@videojs/media';\nimport { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from '@videojs/utils/dom';\nimport { isNil } from '@videojs/utils/predicate';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { sliderContext } from '../slider/context';\nimport { UIElement } from '../ui-element';\n\ntype TextTrackQuery = TextTrackFilter | TextTrackDefinition;\n\ninterface TextCuesWatch {\n watch: (filter: TextTrackFilter) => () => void;\n filter: TextTrackQuery;\n stop: () => void;\n}\n\n/**\n * Draws a marker on the time slider for each cue on the text tracks a filter matches, such as ad breaks. Pick tracks\n * with the `kind`, `label`, and `language` attributes, or with the `filter` property; with neither, nothing is drawn.\n * The store reads the matching tracks while the element is connected, so tracks it doesn't read by default work too.\n *\n * Each marker clones the light-DOM template, which must contain exactly one HTML root element, or is a `<div>` without\n * one. Markers are decorative: the slider's own value is what assistive technology reads.\n */\nexport class TimeSliderMarkersElement extends UIElement {\n static readonly tagName = 'media-time-slider-markers';\n\n static override properties = {\n kind: { type: String },\n label: { type: String },\n language: { type: String },\n } satisfies PropertyDeclarationMap<'kind' | 'label' | 'language'>;\n\n /** Draw cues from tracks of this kind. */\n kind: TextTrackKind | null | undefined;\n /** Draw cues from tracks with this label. */\n label: string | null | undefined;\n /** Draw cues from tracks in this language. */\n language: string | null | undefined;\n\n readonly #core = new TimeSliderMarkersCore();\n readonly #slider = new ContextConsumer(this, { context: sliderContext, subscribe: true });\n readonly #textCues = new PlayerController(this, playerContext, selectTextCues);\n readonly #time = new PlayerController(this, playerContext, selectTime);\n readonly #buffer = new PlayerController(this, playerContext, selectBuffer);\n readonly #rendered = new Map<string, HTMLElement>();\n #filter: TextTrackQuery | undefined;\n #attributeFilter: TextTrackFilter | undefined;\n #watching: TextCuesWatch | null = null;\n #templateRoot: HTMLElement | null = null;\n #templateChecked = false;\n\n /**\n * Picks the tracks whose cues are drawn: a filter object, a function, or a track definition. Takes priority over the\n * `kind`, `label`, and `language` attributes. Set as a JavaScript property; it has no attribute.\n */\n get filter(): TextTrackQuery | undefined {\n return this.#filter;\n }\n\n set filter(value: TextTrackQuery | undefined) {\n this.#filter = value;\n this.requestUpdate();\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('aria-hidden', 'true');\n }\n\n override disconnectedCallback(): void {\n this.#unwatch();\n super.disconnectedCallback();\n }\n\n override destroyCallback(): void {\n this.#unwatch();\n super.destroyCallback();\n }\n\n protected override willUpdate(changed: PropertyValues): void {\n super.willUpdate(changed);\n\n if (changed.has('kind') || changed.has('label') || changed.has('language')) {\n this.#attributeFilter = this.#getAttributeFilter();\n }\n\n this.#watch(this.#getFilter());\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed);\n\n const slider = this.#slider.value;\n const templateRoot = this.#getTemplateRoot();\n\n if (!slider) return;\n\n applyStateDataAttrs(this, slider.state, slider.stateAttrMap);\n\n const filter = this.#getFilter();\n const textCues = this.#textCues.value;\n const time = this.#time.value;\n const max = getTimeRangeEnd({ duration: time?.duration ?? 0, seekable: this.#buffer.value?.seekable ?? [] });\n\n const markers =\n filter && textCues\n ? this.#core.getMarkers({\n cues: getTextCues(textCues, filter),\n activeCues: getActiveTextCues(textCues, filter),\n min: 0,\n max,\n orientation: slider.state.orientation,\n })\n : [];\n\n const next = new Map<string, HTMLElement>();\n\n for (const marker of markers) {\n const root = this.#rendered.get(marker.key) ?? this.#createMarker(templateRoot);\n\n root.style.setProperty(TimeSliderMarkerCSSVars.start, marker.startPercent);\n root.style.setProperty(TimeSliderMarkerCSSVars.end, marker.endPercent);\n root.style.setProperty(TimeSliderMarkerCSSVars.width, marker.width ?? marker.height ?? '0%');\n applyStateDataAttrs(root, slider.state, slider.stateAttrMap);\n applyStateDataAttrs(root, marker, TimeSliderMarkerDataAttrs);\n\n next.set(marker.key, root);\n }\n\n for (const [key, root] of this.#rendered) {\n if (!next.has(key)) root.remove();\n }\n\n let before: ChildNode | null = null;\n\n for (const root of [...next.values()].reverse()) {\n if (root.parentNode !== this || root.nextSibling !== before) this.insertBefore(root, before);\n\n before = root;\n }\n\n this.#rendered.clear();\n\n for (const [key, rendered] of next) this.#rendered.set(key, rendered);\n }\n\n #getFilter(): TextTrackQuery | undefined {\n return this.#filter ?? this.#attributeFilter;\n }\n\n /** A filter from the attributes that are present, or none when no attribute is. */\n #getAttributeFilter(): TextTrackFilter | undefined {\n const { kind, label, language } = this;\n if (isNil(kind) && isNil(label) && isNil(language)) return undefined;\n\n return { kind: kind ?? undefined, label: label ?? undefined, language: language ?? undefined };\n }\n\n /** Ask the store to read the tracks the filter matches while the element is connected. */\n #watch(filter: TextTrackQuery | undefined): void {\n const watch = this.isConnected ? this.#textCues.value?.watchTextCues : undefined;\n if (this.#watching?.watch === watch && this.#watching?.filter === filter) return;\n\n this.#unwatch();\n\n if (watch && filter) this.#watching = { watch, filter, stop: watch(filter) };\n }\n\n #unwatch(): void {\n this.#watching?.stop();\n this.#watching = null;\n }\n\n #createMarker(templateRoot: HTMLElement | null): HTMLElement {\n return templateRoot ? cloneTemplateRoot(templateRoot, this.ownerDocument) : this.ownerDocument.createElement('div');\n }\n\n /** The template's root, found once; until there is a template, other content is cleared and markers are `<div>`s. */\n #getTemplateRoot(): HTMLElement | null {\n if (this.#templateChecked) return this.#templateRoot;\n\n const template = getTemplateElement(this);\n const rendered = new Set<ChildNode>(this.#rendered.values());\n\n for (const node of [...this.childNodes]) {\n if (node !== template && !rendered.has(node)) node.remove();\n }\n\n if (!template) return null;\n\n this.#templateChecked = true;\n\n // Markers drawn before the template arrived are redrawn from it.\n for (const node of rendered) node.remove();\n\n this.#rendered.clear();\n\n const root = getTemplateRoot(template);\n\n if (root?.namespaceURI !== 'http://www.w3.org/1999/xhtml') {\n if (__DEV__) {\n console.warn(`[${this.localName}] template must contain exactly one HTML root element.`);\n }\n\n return null;\n }\n\n // SAFETY: the root was just checked to be in the XHTML namespace, so it is an HTML element.\n this.#templateRoot = root as HTMLElement;\n return this.#templateRoot;\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  //#region src/version.ts
2
2
  const readVersion = () => {
3
3
  try {
4
- return "10.2.0";
4
+ return "10.4.0";
5
5
  } catch {}
6
6
  return "UNKNOWN";
7
7
  };
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @videojs/html documentation
2
2
 
3
- Bundled markdown documentation for `@videojs/html` v10.2.0.
3
+ Bundled markdown documentation for `@videojs/html` v10.4.0.
4
4
 
5
5
  Start at [`./llms.txt`](./llms.txt) — it's the structured index of every page in this directory.
6
6
 
@@ -87,7 +87,7 @@ import '@videojs/html/ui/play-button';
87
87
 
88
88
  The `autoplay`, `muted`, and `playsinline` attributes on `<video>` and similar media components pass through to the underlying media element. The media element applies the attributes it supports and requests playback as soon as it has enough data.
89
89
 
90
- The [playback feature](../reference/api/feature-playback.md) reflects the result in player state. When autoplay succeeds, `paused` becomes `false` and `started` becomes `true`. When the browser blocks autoplay, the player stays `paused: true` with `started: false`, and the [Play button](../reference/components/play-button.md) renders its play state. There is no separate autoplay failure event: a blocked autoplay looks like media that never started.
90
+ The [playback feature](../reference/api/feature-playback.md) reflects the result in player state. While the media is trying to autoplay, `autoplaying` is `true`: the [Play button](../reference/components/play-button.md) shows pause, so clicking it cancels the autoplay, and the [Buffering indicator](../reference/components/buffering-indicator.md) appears after its delay. When autoplay succeeds, `paused` becomes `false` and `started` becomes `true`. When the browser blocks autoplay, the player stays `paused: true` with `started: false`, and the [Play button](../reference/components/play-button.md) renders its play state. There is no separate autoplay failure event: a blocked autoplay looks like media that never started.
91
91
 
92
92
  Calling `play()` from player state returns the native promise from the media element. When the browser blocks playback, that promise rejects with a `NotAllowedError`.
93
93
 
@@ -106,7 +106,7 @@ Calling `play()` from player state returns the native promise from the media ele
106
106
 
107
107
  Use `<background-video>` for an MP4 or WebM file, `<hls-background-video>` for an HLS stream, or `<mux-background-video>` for a complete Mux HLS URL.
108
108
 
109
- These components default to muted, looping playback without player controls. Provide a separate motion control and respect reduced-motion preferences. For setup and motion controls, follow [Add a background video](./background-video.md). That guide also covers streaming backgrounds, which always autoplay and need conditional mounting.
109
+ These components default to muted, looping playback without player controls. Provide a separate motion control and respect reduced-motion preferences. For setup and motion controls, follow [Add a background video](./background-video.md). That guide also covers streaming backgrounds, which use the same pause and play pattern.
110
110
 
111
111
  ### Autoplay with sound
112
112
 
@@ -190,7 +190,7 @@ Set size, position, and transforms on the decorative wrapper so the poster and v
190
190
  - Use a source the browser can play directly, such as MP4 or WebM.
191
191
  - Safari can play HLS URLs directly. Other browsers cannot do so reliably, so use an HLS variation below when the same `.m3u8` URL must work across browsers.
192
192
  - `<background-video>` does not create a poster or report analytics. Keep the poster in your page and add analytics only when you need them.
193
- - Provide a way to pause or hide persistent background motion. The example includes that control and follows `prefers-reduced-motion`. For the streaming variations below, stop and restart motion by adding and removing the element.
193
+ - Provide a way to pause or hide persistent background motion. The example includes that control and follows `prefers-reduced-motion`. The streaming variations below pause and play through the same `target` property.
194
194
  - If the video communicates information, remove `aria-hidden`, give it an accessible name and controls, and use a regular video player.
195
195
 
196
196
  ## Common variations
@@ -202,9 +202,7 @@ Choose a variation based on the source you already have:
202
202
  | Cross-browser HLS with a small background-only player | [`<hls-background-video>`](../reference/components/hls-background-video.md) |
203
203
  | The same background-only player for a Mux HLS URL | [`<mux-background-video>`](../reference/components/mux-background-video.md) |
204
204
 
205
- These streaming background components always autoplay. They ignore `autoPlay={false}` and `noautoplay`.
206
-
207
- Keep the poster and motion button from the primary example. Change `setMotion` to create and append a streaming element only when motion is enabled, and remove it when motion is disabled. Do not set `keep-alive`: removal must destroy playback. Create a new element each time motion is enabled; do not reuse the removed one. These elements expose the inner video as `video`, rather than the native background element’s `target`.
205
+ The primary example works unchanged with the streaming elements: replace `<background-video>` with `<hls-background-video>` or `<mux-background-video>`, keep `noautoplay`, and update the selector in the script. Like `<background-video>`, they expose the native video as `target` for `play()` and `pause()`. Pausing keeps the stream and its position, so motion resumes where it stopped. To stop network activity as well, remove the element instead. Without `keep-alive`, removal releases the engine.
208
206
 
209
207
  ### Play an HLS video
210
208
 
@@ -2,23 +2,31 @@
2
2
 
3
3
  Browsers and rendering environments supported by Video.js 10, and how the packaged skins reach them
4
4
 
5
- *Supported browsers and the versions of each that Video.js supports*
5
+ *Supported browsers, the versions of each that Video.js supports, and the older versions the Compat skin also reaches*
6
6
 
7
- | Browser | Versions |
8
- | --- | --- |
9
- | Chrome | 111 and later |
10
- | Edge | 111 and later |
11
- | Firefox | 121 and later |
12
- | Safari | 16.4 and later |
13
- | Safari on iOS | 16.4 and later |
7
+ | Browser | Versions | Compat skin |
8
+ | --- | --- | --- |
9
+ | Chrome | 111 and later | 99 and later |
10
+ | Edge | 111 and later | 99 and later |
11
+ | Firefox | 121 and later | 97 and later |
12
+ | Safari | 16.4 and later | 15.4 and later |
13
+ | Safari on iOS | 16.4 and later | 15.4 and later |
14
14
 
15
- These versions account for 92.5% of global web usage according to [caniuse-lite](https://github.com/browserslist/caniuse-lite) 1.0.30001815, and this figure updates when we update that data.
15
+ These versions account for 92.5% of global web usage according to [caniuse-lite](https://github.com/browserslist/caniuse-lite) 1.0.30001815, and the Compat versions account for 94.4%. These figures update when we update that data.
16
16
 
17
- We test in and fix bugs for these versions. Each minimum comes from a feature with no fallback: `:has()` sets Firefox, [`color-mix()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) and [`oklch()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) set Chrome and Edge (Chrome 111, Firefox 113, Safari 16.2), and the JavaScript sets Safari, because it uses ES2022 syntax and [`adoptedStyleSheets`](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets). Newer JavaScript APIs are feature-detected.
17
+ We test in and fix bugs for the main versions. Each of those minimums comes from a feature with no fallback: `:has()` sets Firefox, [`color-mix()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) and [`oklch()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) set Chrome and Edge (Chrome 111, Firefox 113, Safari 16.2), and the JavaScript sets Safari, because it uses ES2022 syntax and [`adoptedStyleSheets`](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets). Newer JavaScript APIs are feature-detected.
18
+
19
+ The [Compat skin](./skins.md#choose-a-skin) also reaches the older versions in the last column. It leaves out backdrop blur, anchor-positioned menu highlights, and squircle corners, and its stylesheets fall back to plain colors where `color-mix()` and `oklch()` are missing. Below the main versions, it keeps its narrow layout and default size, because container queries and media query ranges are missing, and the time slider preview stays hidden on keyboard focus. Every build checks its stylesheets against these older versions, and our browser tests run at the main versions.
20
+
21
+ The Scaffold skin, a starting point for your own design, reaches the same older versions.
22
+
23
+ The HTML player’s JavaScript needs Firefox 101 and Safari 16.4, so with HTML the Compat skin reaches Chrome and Edge 99, Firefox 101, and Safari 16.4.
24
+
25
+ The older versions apply to the packaged skins. Tailwind skins follow Tailwind’s own browser support, and registry skins in plain CSS need `@scope` unless you [flatten it](#flatten-scope-in-registry-skins). If your build lowers CSS for its own targets, include these older versions in them, or it may rewrite the fallbacks.
18
26
 
19
27
  ## CSS requirements
20
28
 
21
- The packaged skins are generated CSS, and the build lowers them for the browsers above. It flattens CSS nesting, adds vendor prefixes such as `-webkit-backdrop-filter`, and writes each skin’s scope as a [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) selector, which adds no specificity, rather than [`@scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/@scope).
29
+ The packaged skins are generated CSS, and the build lowers them for the main versions above. It flattens CSS nesting, adds vendor prefixes such as `-webkit-backdrop-filter`, and writes each skin’s scope as a [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) selector, which adds no specificity, rather than [`@scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/@scope).
22
30
 
23
31
  Skins you add as source through the [Shadcn registry](./installation-shadcn.md) keep `@scope`, which reads more clearly in files you edit. Their plain CSS versions need Chrome and Edge 118, Firefox 146, and Safari and iOS 17.4 unless you [flatten `@scope` in your build](#flatten-scope-in-registry-skins); the Tailwind versions style elements with utility classes and follow the versions above.
24
32
 
@@ -38,16 +46,16 @@ The table reads the first fully supporting version of each feature from [caniuse
38
46
  | --- | --- | --- | --- | --- |
39
47
  | [`@scope`](https://caniuse.com/css-cascade-scope) Has a fallback. Packaged skins use :where() selectors instead; registry CSS skins need it. | 118 | 146 | 17.4 | 91.7% |
40
48
  | [`@layer`](https://caniuse.com/css-cascade-layers) Required. No component styling. | 99 | 97 | 15.4 | 96.0% |
41
- | [`:has()`](https://caniuse.com/css-has) Required. Menu and slider focus states are lost. | 105 | 121 | 15.4 | 94.8% |
42
- | [`@container`](https://caniuse.com/css-container-queries) Required. Controls do not adapt to player width. | 106 | 110 | 16 | 94.8% |
49
+ | [`:has()`](https://caniuse.com/css-has) Required. Menu and slider focus states are lost. Compat loses only the time slider preview on keyboard focus. | 105 | 121 | 15.4 | 94.8% |
50
+ | [`@container`](https://caniuse.com/css-container-queries) Required. Controls do not adapt to player width. Compat keeps its narrow layout, without the center controls or the duration. | 106 | 110 | 16 | 94.8% |
43
51
  | [`Media query range syntax`](https://caniuse.com/css-media-range-syntax) Required. Large-screen sizing is lost. | 104 | 63 | 16.4 | 94.8% |
44
52
  | [`:dir()`](https://caniuse.com/css-dir-pseudo) Has a fallback. Right-to-left layout follows the dir attribute instead. | 120 | 17 | 16.4 | 92.5% |
45
53
  | [`Relative color syntax`](https://caniuse.com/css-relative-colors) Degrades. Control scrims fall back to color-mix(), and subtle shadows are lost. | 131 | 133 | 18 | 87.0% |
46
54
  | [`scrollbar-color and scrollbar-width`](https://caniuse.com/css-scrollbar) Degrades. Menus show default scrollbars. | 121 | 64 | 26.2 | 87.0% |
47
- | [`backdrop-filter`](https://caniuse.com/css-backdrop-filter) Degrades. Surfaces lose their blur. | 76 / 17 | 103 | 9 | 96.4% |
55
+ | [`backdrop-filter`](https://caniuse.com/css-backdrop-filter) Degrades. Surfaces lose their blur. Compat does not use it. | 76 / 17 | 103 | 9 | 96.4% |
48
56
  | [`Anchor positioning`](https://caniuse.com/css-anchor-positioning) Has a fallback. Popups are positioned by JavaScript. | Not supported | Not supported | 27 | 0.0% |
49
57
 
50
- Older browsers than the baseline are not tested. Without `:has()`, `@container`, `@layer`, `color-mix()`, or `oklch()`, the skins lose focus states, layout, or colors.
58
+ Below the main versions, only the Compat and Scaffold skins are supported. Without `:has()`, `@container`, `@layer`, `color-mix()`, or `oklch()`, the Default and Neutral skins lose focus states, layout, or colors.
51
59
 
52
60
  ## Build the skin CSS yourself
53
61
 
@@ -8,7 +8,7 @@ If a packaged skin is already close to what you want, [Customize skins](./custom
8
8
 
9
9
  ## Create a skin project
10
10
 
11
- The quickest way to start is a standalone skin project:
11
+ The quickest way to start is a standalone skin project. You need [Node.js](https://nodejs.org/) to create the project, run the workbench that previews your skin, and deploy it. Run `node -v` to see which version you have. If you don’t have Node.js, or yours is a few years old, install the latest LTS release.
12
12
 
13
13
  **npm**
14
14
 
@@ -107,6 +107,8 @@ If you work with a coding agent, such as Claude Code, Codex, or Cursor, it can d
107
107
  npx @videojs/cli agents skills
108
108
  ```
109
109
 
110
+ The agent runs the same commands as you would, so it needs Node.js too. If Node.js isn’t installed, you can ask the agent to install it, but it may need your permission or your password to do so.
111
+
110
112
  Then ask your agent something like “Help me design my own Video.js skin for our podcast player.” It asks for the same choices as the command, creates the project, starts the workbench, and agrees a design with you before styling. It can also match an existing player’s look from a screenshot or a link, then checks its work against the reference and for accessibility before handing back. [Build with AI](./build-with-ai.md) covers installing the skills in each agent.
111
113
 
112
114
  ## Other starting points
@@ -25,6 +25,7 @@ Each installation [preset](./presets.md) ships its bundles at the package root:
25
25
  | `video.js` | `<video-player>`, `<video-skin>`, and the UI elements the skin composes |
26
26
  | `video-neutral.js` | `<video-player>`, `<video-neutral-skin>`, and the UI elements the neutral skin composes |
27
27
  | `video-compat.js` | `<video-player>`, `<video-compat-skin>`, and the UI elements the compat skin composes |
28
+ | `video-scaffold.js` | `<video-player>`, `<video-scaffold-skin>`, and the UI elements the scaffold skin composes |
28
29
  | `video-player.js` | `<video-player>` alone, for markup without a packaged skin |
29
30
 
30
31
  The other presets follow the same pattern. Background video is the exception: one bundle, `background.js`, registers the player, the skin, and the `<background-video>` media component.
@@ -36,8 +37,8 @@ A skin bundle registers every UI element its skin is built from. Any element the
36
37
  Each [UI element](./ui-components.md) is its own bundle under `ui/`, for markup that a skin bundle does not already cover: a player-only bundle, [skin source](./customize-skins.md#style-skin-source) that adds a component the packaged skin lacks, or a [dialog](../reference/components/dialog.md) your page opens beside a packaged skin.
37
38
 
38
39
  ```html
39
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/video-player.js"></script>
40
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/ui/play-button.js"></script>
40
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video-player.js"></script>
41
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/ui/play-button.js"></script>
41
42
  ```
42
43
 
43
44
  Each path mirrors its npm subpath: `ui/play-button.js` registers the element that `@videojs/html/ui/play-button` does. Compound components list one bundle per part in their reference page’s Import section, and all of the bundles share chunks with the player.
@@ -47,8 +48,8 @@ Each path mirrors its npm subpath: `ui/play-button.js` registers the element tha
47
48
  The default presets play through the browser’s own `<video>` and `<audio>`. Every other [media component](./media-sources.md) is its own bundle under `media/`, loaded beside the player bundle:
48
49
 
49
50
  ```html
50
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/video.js"></script>
51
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/media/hlsjs-video.js"></script>
51
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video.js"></script>
52
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/media/hlsjs-video.js"></script>
52
53
  ```
53
54
 
54
55
  Each path mirrors its npm subpath. Elements with more than one playback engine keep the extra flavors in a directory named for the element, such as `media/mux-video/spf.js`. Load one flavor per page: every flavor claims the same tag name, and the first registration wins.
@@ -58,9 +59,9 @@ Each path mirrors its npm subpath. Elements with more than one playback engine k
58
59
  Each [extension](./architecture.md) is its own bundle under `extensions/`, loaded beside the player and media bundles:
59
60
 
60
61
  ```html
61
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/video.js"></script>
62
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/media/mux-video.js"></script>
63
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/extensions/mux-data.js"></script>
62
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video.js"></script>
63
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/media/mux-video.js"></script>
64
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/extensions/mux-data.js"></script>
64
65
  ```
65
66
 
66
67
  As with media, each path mirrors its npm subpath: `extensions/mux-data.js` registers the element that `@videojs/html/extensions/mux-data` does.
@@ -70,8 +71,8 @@ As with media, each path mirrors its npm subpath: `extensions/mux-data.js` regis
70
71
  English ships inside the skin bundles. Each other locale is a bundle under `locales/` that registers its translations on import. Load it before the player script to avoid a flash of English:
71
72
 
72
73
  ```html
73
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/locales/es.js"></script>
74
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/video.js"></script>
74
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/locales/es.js"></script>
75
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video.js"></script>
75
76
  ```
76
77
 
77
78
  One bundle exists per locale pack, plus bare-language aliases such as `pt`. For custom strings, see [Internationalize the player](./internationalization.md).
@@ -40,9 +40,12 @@ Add only the origins for the providers you use. Extend `script-src` with their s
40
40
  | --- | --- | --- |
41
41
  | YouTube | `https://www.youtube.com` | `https://www.youtube.com` or `https://www.youtube-nocookie.com`, depending on your source URL |
42
42
  | Vimeo | — | `https://player.vimeo.com`; `https://vimeo.com` for live events |
43
+ | SoundCloud | — | `https://w.soundcloud.com` |
44
+ | Mixcloud | \- | `https://player-widget.mixcloud.com` |
43
45
  | Spotify | `https://open.spotify.com` | `https://open.spotify.com` |
44
46
  | TikTok | — | `https://www.tiktok.com` |
45
47
  | Twitch | — | `https://player.twitch.tv` |
48
+ | Dailymotion | `https://geo.dailymotion.com` | `https://geo.dailymotion.com` |
46
49
  | Cloudflare | `https://embed.videodelivery.net` | `https://iframe.videodelivery.net` or your `https://customer-<code>.cloudflarestream.com` origin |
47
50
  | Google Cast | `https://www.gstatic.com` | — |
48
51
  | Wistia | `https://fast.wistia.com` and `https://fast.wistia.net` | `https://fast.wistia.com` and `https://fast.wistia.net` if the SDK falls back to an iframe |
@@ -2,7 +2,9 @@
2
2
 
3
3
  Style a packaged Video.js skin or add its source to change controls, layout, styles, and interactions
4
4
 
5
- Start with the path that matches the change you want:
5
+ Pick the skin closest to the design you want first. To apply your brand to a finished design, the Neutral skin is the usual starting point. To build your own design, start from the Scaffold skin. See [Choose a skin](./skins.md#choose-a-skin).
6
+
7
+ Then start with the path that matches the change you want:
6
8
 
7
9
  | If you want to… | Use this path |
8
10
  | --- | --- |
@@ -29,6 +31,7 @@ Packaged skins expose [CSS custom properties](https://developer.mozilla.org/en-U
29
31
  | `--media-accent-text-color` | Text and icons shown on the accent color | [`<color>`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) | `black` |
30
32
  | `--media-border-color` | The hairline around the player | [`<color>`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) | `transparent` |
31
33
  | `--media-border-radius` | The player corner radius | A valid [border-radius value](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/border-radius#values) | `1rem` |
34
+ | `--media-controls-before-start` | Whether the video skins show their controls before playback starts | `hidden` (default) or `visible` | `visible` |
32
35
  | `--media-font-family` | The player interface font stack | A valid [font-family value](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-family) | `Roboto, sans-serif` |
33
36
  | `--media-object-fit` | How the media and poster fill the player | A valid [object-fit value](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/object-fit) | `cover` |
34
37
  | `--media-object-position` | Where the media and poster sit when they do not fill the player | A valid [object-position value](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/object-position) | `top` |
@@ -36,7 +39,9 @@ Packaged skins expose [CSS custom properties](https://developer.mozilla.org/en-U
36
39
 
37
40
  If you omit `--media-accent-text-color`, the skins choose a contrasting text color from `--media-accent-color` using [`contrast-color()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/contrast-color). Browsers before Chrome 147, Firefox 146, and Safari 26 lack `contrast-color()` and use the theme’s default text color instead, which suits only the default accent, so set both properties together if you support those browsers.
38
41
 
39
- The skins use [`Inter`](https://rsms.me/inter/) when your page loads it and fall back to the system UI font. Video.js does not load the webfont for you. Set `--media-font-family` to use another font.
42
+ The skins use [`Inter`](https://rsms.me/inter/) when your page loads it and fall back to the system UI font. Video.js does not load the webfont for you. Set `--media-font-family` to use another font. The Scaffold skin inherits your page’s font instead, and it has no hairline, so `--media-border-color` doesn’t change it.
43
+
44
+ Before playback starts, the video and live video skins show only the large center play button. The controls appear once playback starts or the media begins to autoplay. Set `--media-controls-before-start: visible` to show the controls from the start, for example so viewers can pick captions or quality before they press play.
40
45
 
41
46
  The media component and poster share `--media-object-fit` and `--media-object-position`, so a `cover` poster stays aligned with `cover` video. See [Add a poster and loading placeholder](./poster.md).
42
47
 
@@ -74,7 +74,7 @@ if (media?.paused) media.play();
74
74
 
75
75
  ## Feature availability
76
76
 
77
- Volume, fullscreen, and picture-in-picture expose availability properties because platform support varies. Volume has two independent properties: `volumeAvailability` for changing the volume level and `mutedAvailability` for toggling mute. For example, iOS Safari allows muting but doesn’t allow programmatic volume control.
77
+ Volume, playback rate, fullscreen, and picture-in-picture expose availability properties because platform and media support varies. Volume has two independent properties: `volumeAvailability` for changing the volume level and `mutedAvailability` for toggling mute. For example, iOS Safari allows muting but doesn’t allow programmatic volume control.
78
78
 
79
79
  | Value | Meaning |
80
80
  | --- | --- |