@videojs/html 10.2.0 → 10.3.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 (297) hide show
  1. package/dist/default/define/audio/compat-skin.css +72 -5
  2. package/dist/default/define/audio/compat-skin2.js +1 -1
  3. package/dist/default/define/audio/neutral-skin.css +23 -5
  4. package/dist/default/define/audio/neutral-skin2.js +1 -1
  5. package/dist/default/define/audio/scaffold-skin.css +63 -4
  6. package/dist/default/define/audio/scaffold-skin2.js +1 -1
  7. package/dist/default/define/audio/skin.css +21 -5
  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 +54 -5
  11. package/dist/default/define/live-audio/compat-skin2.js +1 -1
  12. package/dist/default/define/live-audio/neutral-skin.css +8 -4
  13. package/dist/default/define/live-audio/neutral-skin2.js +1 -1
  14. package/dist/default/define/live-audio/scaffold-skin.css +45 -4
  15. package/dist/default/define/live-audio/scaffold-skin2.js +1 -1
  16. package/dist/default/define/live-audio/skin.css +6 -4
  17. package/dist/default/define/live-audio/skin2.js +1 -1
  18. package/dist/default/define/live-video/compat-skin.css +115 -13
  19. package/dist/default/define/live-video/compat-skin2.js +1 -1
  20. package/dist/default/define/live-video/neutral-skin.css +82 -14
  21. package/dist/default/define/live-video/neutral-skin2.js +1 -1
  22. package/dist/default/define/live-video/scaffold-skin.css +95 -13
  23. package/dist/default/define/live-video/scaffold-skin2.js +1 -1
  24. package/dist/default/define/live-video/skin.css +96 -16
  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/video/compat-skin.css +115 -13
  30. package/dist/default/define/video/compat-skin2.js +1 -1
  31. package/dist/default/define/video/neutral-skin.css +83 -14
  32. package/dist/default/define/video/neutral-skin2.js +1 -1
  33. package/dist/default/define/video/scaffold-skin.css +95 -13
  34. package/dist/default/define/video/scaffold-skin2.js +1 -1
  35. package/dist/default/define/video/skin.css +94 -16
  36. package/dist/default/define/video/skin2.js +1 -1
  37. package/dist/default/extensions/google-analytics/extension.js +75 -0
  38. package/dist/default/extensions/mux-data/extension.js +7 -2
  39. package/dist/default/internal/skins/compat-audio/skin.css +72 -5
  40. package/dist/default/internal/skins/compat-audio/template.js +1 -1
  41. package/dist/default/internal/skins/compat-live-audio/skin.css +54 -5
  42. package/dist/default/internal/skins/compat-live-video/skin.css +115 -13
  43. package/dist/default/internal/skins/compat-video/skin.css +115 -13
  44. package/dist/default/internal/skins/default-audio/skin.css +21 -5
  45. package/dist/default/internal/skins/default-live-audio/skin.css +6 -4
  46. package/dist/default/internal/skins/default-live-video/skin.css +96 -16
  47. package/dist/default/internal/skins/default-video/skin.css +94 -16
  48. package/dist/default/internal/skins/neutral-audio/skin.css +23 -5
  49. package/dist/default/internal/skins/neutral-live-audio/skin.css +8 -4
  50. package/dist/default/internal/skins/neutral-live-video/skin.css +82 -14
  51. package/dist/default/internal/skins/neutral-video/skin.css +83 -14
  52. package/dist/default/internal/skins/scaffold-audio/skin.css +63 -4
  53. package/dist/default/internal/skins/scaffold-audio/template.js +1 -1
  54. package/dist/default/internal/skins/scaffold-live-audio/skin.css +45 -4
  55. package/dist/default/internal/skins/scaffold-live-video/skin.css +95 -13
  56. package/dist/default/internal/skins/scaffold-video/skin.css +95 -13
  57. package/dist/default/media/dailymotion-video/adapter.js +31 -0
  58. package/dist/default/media/hls-background-video/adapter.js +7 -0
  59. package/dist/default/media/mixcloud-audio/adapter.js +16 -0
  60. package/dist/default/media/soundcloud-audio/adapter.js +16 -0
  61. package/dist/default/player/element.js +1 -5
  62. package/dist/default/presets/background/skin.js +2 -2
  63. package/dist/default/presets/skin.js +4 -3
  64. package/dist/default/ui/dialog/element.js +13 -1
  65. package/dist/default/ui/gesture/element.js +3 -0
  66. package/dist/default/ui/media-button-element.js +10 -1
  67. package/dist/default/ui/playback-rate-button/element.js +7 -2
  68. package/dist/default/ui/playback-rate-radio-group/element.js +10 -3
  69. package/dist/default/ui/radio-options/controller.js +18 -5
  70. package/dist/default/version.js +1 -1
  71. package/dist/dev/define/audio/compat-skin.css +72 -5
  72. package/dist/dev/define/audio/compat-skin2.js +1 -1
  73. package/dist/dev/define/audio/compat-skin2.js.map +1 -1
  74. package/dist/dev/define/audio/neutral-skin.css +23 -5
  75. package/dist/dev/define/audio/neutral-skin2.js +1 -1
  76. package/dist/dev/define/audio/neutral-skin2.js.map +1 -1
  77. package/dist/dev/define/audio/scaffold-skin.css +63 -4
  78. package/dist/dev/define/audio/scaffold-skin2.js +1 -1
  79. package/dist/dev/define/audio/scaffold-skin2.js.map +1 -1
  80. package/dist/dev/define/audio/skin.css +21 -5
  81. package/dist/dev/define/audio/skin2.js +1 -1
  82. package/dist/dev/define/audio/skin2.js.map +1 -1
  83. package/dist/dev/define/extensions/google-analytics.d.ts +11 -0
  84. package/dist/dev/define/extensions/google-analytics.js +8 -0
  85. package/dist/dev/define/extensions/google-analytics.js.map +1 -0
  86. package/dist/dev/define/live-audio/compat-skin.css +54 -5
  87. package/dist/dev/define/live-audio/compat-skin2.js +1 -1
  88. package/dist/dev/define/live-audio/compat-skin2.js.map +1 -1
  89. package/dist/dev/define/live-audio/neutral-skin.css +8 -4
  90. package/dist/dev/define/live-audio/neutral-skin2.js +1 -1
  91. package/dist/dev/define/live-audio/neutral-skin2.js.map +1 -1
  92. package/dist/dev/define/live-audio/scaffold-skin.css +45 -4
  93. package/dist/dev/define/live-audio/scaffold-skin2.js +1 -1
  94. package/dist/dev/define/live-audio/scaffold-skin2.js.map +1 -1
  95. package/dist/dev/define/live-audio/skin.css +6 -4
  96. package/dist/dev/define/live-audio/skin2.js +1 -1
  97. package/dist/dev/define/live-audio/skin2.js.map +1 -1
  98. package/dist/dev/define/live-video/compat-skin.css +115 -13
  99. package/dist/dev/define/live-video/compat-skin2.js +1 -1
  100. package/dist/dev/define/live-video/compat-skin2.js.map +1 -1
  101. package/dist/dev/define/live-video/neutral-skin.css +82 -14
  102. package/dist/dev/define/live-video/neutral-skin2.js +1 -1
  103. package/dist/dev/define/live-video/neutral-skin2.js.map +1 -1
  104. package/dist/dev/define/live-video/scaffold-skin.css +95 -13
  105. package/dist/dev/define/live-video/scaffold-skin2.js +1 -1
  106. package/dist/dev/define/live-video/scaffold-skin2.js.map +1 -1
  107. package/dist/dev/define/live-video/skin.css +96 -16
  108. package/dist/dev/define/live-video/skin2.js +1 -1
  109. package/dist/dev/define/live-video/skin2.js.map +1 -1
  110. package/dist/dev/define/media/dailymotion-video.d.ts +15 -0
  111. package/dist/dev/define/media/dailymotion-video.js +13 -0
  112. package/dist/dev/define/media/dailymotion-video.js.map +1 -0
  113. package/dist/dev/define/media/mixcloud-audio.d.ts +15 -0
  114. package/dist/dev/define/media/mixcloud-audio.js +13 -0
  115. package/dist/dev/define/media/mixcloud-audio.js.map +1 -0
  116. package/dist/dev/define/media/soundcloud-audio.d.ts +15 -0
  117. package/dist/dev/define/media/soundcloud-audio.js +13 -0
  118. package/dist/dev/define/media/soundcloud-audio.js.map +1 -0
  119. package/dist/dev/define/video/compat-skin.css +115 -13
  120. package/dist/dev/define/video/compat-skin2.js +1 -1
  121. package/dist/dev/define/video/compat-skin2.js.map +1 -1
  122. package/dist/dev/define/video/neutral-skin.css +83 -14
  123. package/dist/dev/define/video/neutral-skin2.js +1 -1
  124. package/dist/dev/define/video/neutral-skin2.js.map +1 -1
  125. package/dist/dev/define/video/scaffold-skin.css +95 -13
  126. package/dist/dev/define/video/scaffold-skin2.js +1 -1
  127. package/dist/dev/define/video/scaffold-skin2.js.map +1 -1
  128. package/dist/dev/define/video/skin.css +94 -16
  129. package/dist/dev/define/video/skin2.js +1 -1
  130. package/dist/dev/define/video/skin2.js.map +1 -1
  131. package/dist/dev/extensions/google-analytics/extension.d.ts +52 -0
  132. package/dist/dev/extensions/google-analytics/extension.js +77 -0
  133. package/dist/dev/extensions/google-analytics/extension.js.map +1 -0
  134. package/dist/dev/extensions/google-analytics/index.d.ts +2 -0
  135. package/dist/dev/extensions/mux-data/extension.d.ts +4 -1
  136. package/dist/dev/extensions/mux-data/extension.js +7 -2
  137. package/dist/dev/extensions/mux-data/extension.js.map +1 -1
  138. package/dist/dev/index.d.ts +3 -3
  139. package/dist/dev/internal/skins/compat-audio/skin.css +72 -5
  140. package/dist/dev/internal/skins/compat-audio/template.js +1 -1
  141. package/dist/dev/internal/skins/compat-audio/template.js.map +1 -1
  142. package/dist/dev/internal/skins/compat-live-audio/skin.css +54 -5
  143. package/dist/dev/internal/skins/compat-live-video/skin.css +115 -13
  144. package/dist/dev/internal/skins/compat-video/skin.css +115 -13
  145. package/dist/dev/internal/skins/default-audio/skin.css +21 -5
  146. package/dist/dev/internal/skins/default-live-audio/skin.css +6 -4
  147. package/dist/dev/internal/skins/default-live-video/skin.css +96 -16
  148. package/dist/dev/internal/skins/default-video/skin.css +94 -16
  149. package/dist/dev/internal/skins/neutral-audio/skin.css +23 -5
  150. package/dist/dev/internal/skins/neutral-live-audio/skin.css +8 -4
  151. package/dist/dev/internal/skins/neutral-live-video/skin.css +82 -14
  152. package/dist/dev/internal/skins/neutral-video/skin.css +83 -14
  153. package/dist/dev/internal/skins/scaffold-audio/skin.css +63 -4
  154. package/dist/dev/internal/skins/scaffold-audio/template.js +1 -1
  155. package/dist/dev/internal/skins/scaffold-audio/template.js.map +1 -1
  156. package/dist/dev/internal/skins/scaffold-live-audio/skin.css +45 -4
  157. package/dist/dev/internal/skins/scaffold-live-video/skin.css +95 -13
  158. package/dist/dev/internal/skins/scaffold-video/skin.css +95 -13
  159. package/dist/dev/media/dailymotion-video/adapter.d.ts +17 -0
  160. package/dist/dev/media/dailymotion-video/adapter.js +47 -0
  161. package/dist/dev/media/dailymotion-video/adapter.js.map +1 -0
  162. package/dist/dev/media/dailymotion-video/index.d.ts +2 -0
  163. package/dist/dev/media/hls-background-video/adapter.d.ts +6 -1
  164. package/dist/dev/media/hls-background-video/adapter.js +7 -0
  165. package/dist/dev/media/hls-background-video/adapter.js.map +1 -1
  166. package/dist/dev/media/mixcloud-audio/adapter.d.ts +11 -0
  167. package/dist/dev/media/mixcloud-audio/adapter.js +52 -0
  168. package/dist/dev/media/mixcloud-audio/adapter.js.map +1 -0
  169. package/dist/dev/media/mixcloud-audio/index.d.ts +2 -0
  170. package/dist/dev/media/soundcloud-audio/adapter.d.ts +11 -0
  171. package/dist/dev/media/soundcloud-audio/adapter.js +53 -0
  172. package/dist/dev/media/soundcloud-audio/adapter.js.map +1 -0
  173. package/dist/dev/media/soundcloud-audio/index.d.ts +2 -0
  174. package/dist/dev/player/element.js +1 -5
  175. package/dist/dev/player/element.js.map +1 -1
  176. package/dist/dev/presets/background/skin.d.ts +2 -1
  177. package/dist/dev/presets/background/skin.js +2 -2
  178. package/dist/dev/presets/background/skin.js.map +1 -1
  179. package/dist/dev/presets/skin.d.ts +4 -3
  180. package/dist/dev/presets/skin.js +4 -3
  181. package/dist/dev/presets/skin.js.map +1 -1
  182. package/dist/dev/ui/dialog/element.js +13 -1
  183. package/dist/dev/ui/dialog/element.js.map +1 -1
  184. package/dist/dev/ui/gesture/element.js +3 -0
  185. package/dist/dev/ui/gesture/element.js.map +1 -1
  186. package/dist/dev/ui/media-button-element.d.ts +9 -0
  187. package/dist/dev/ui/media-button-element.js +10 -1
  188. package/dist/dev/ui/media-button-element.js.map +1 -1
  189. package/dist/dev/ui/play-button/element.d.ts +2 -0
  190. package/dist/dev/ui/playback-rate-button/element.d.ts +9 -1
  191. package/dist/dev/ui/playback-rate-button/element.js +7 -2
  192. package/dist/dev/ui/playback-rate-button/element.js.map +1 -1
  193. package/dist/dev/ui/playback-rate-radio-group/element.d.ts +4 -0
  194. package/dist/dev/ui/playback-rate-radio-group/element.js +10 -3
  195. package/dist/dev/ui/playback-rate-radio-group/element.js.map +1 -1
  196. package/dist/dev/ui/radio-options/controller.js +18 -5
  197. package/dist/dev/ui/radio-options/controller.js.map +1 -1
  198. package/dist/dev/version.js +1 -1
  199. package/docs/README.md +1 -1
  200. package/docs/guides/autoplay.md +1 -1
  201. package/docs/guides/browser-support.md +2 -0
  202. package/docs/guides/build-your-own-skin.md +3 -1
  203. package/docs/guides/cdn.md +10 -9
  204. package/docs/guides/content-security-policy.md +3 -0
  205. package/docs/guides/customize-skins.md +4 -2
  206. package/docs/guides/features.md +1 -1
  207. package/docs/guides/google-analytics.md +199 -0
  208. package/docs/guides/installation-cdn.md +4 -4
  209. package/docs/guides/installation-shadcn.md +3 -3
  210. package/docs/guides/installation-svelte.md +4 -4
  211. package/docs/guides/installation-vue.md +4 -4
  212. package/docs/guides/installation.md +4 -4
  213. package/docs/guides/internationalization.md +5 -5
  214. package/docs/guides/keyboard-shortcuts.md +1 -1
  215. package/docs/guides/llms.txt +2 -1
  216. package/docs/guides/media-sources.md +3 -0
  217. package/docs/guides/migrate-from-media-chrome.md +4 -4
  218. package/docs/guides/migrate-from-mux-player.md +9 -9
  219. package/docs/guides/migrate-from-plyr.md +8 -8
  220. package/docs/guides/migrate-from-video-js-8.md +8 -9
  221. package/docs/guides/migrate-from-vidstack.md +8 -8
  222. package/docs/guides/mux-data.md +22 -0
  223. package/docs/guides/presets.md +1 -1
  224. package/docs/guides/self-hosting.md +5 -5
  225. package/docs/guides/skins.md +16 -1
  226. package/docs/guides/ui-components.md +1 -1
  227. package/docs/llms.txt +10 -5
  228. package/docs/reference/api/feature-playback-rate.md +54 -3
  229. package/docs/reference/api/feature-playback.md +5 -1
  230. package/docs/reference/api/html-create-player.md +2 -2
  231. package/docs/reference/api/llms.txt +1 -1
  232. package/docs/reference/api/media-capabilities.md +1 -1
  233. package/docs/reference/api/player-store.md +6 -3
  234. package/docs/reference/api/preset-audio.md +13 -5
  235. package/docs/reference/api/preset-background.md +2 -2
  236. package/docs/reference/api/preset-live-audio.md +13 -5
  237. package/docs/reference/api/preset-live-video.md +13 -5
  238. package/docs/reference/api/preset-video.md +13 -5
  239. package/docs/reference/api/resolve-adapter-type.md +4 -1
  240. package/docs/reference/api/ui-element.md +2 -2
  241. package/docs/reference/components/alert-dialog.md +21 -16
  242. package/docs/reference/components/audio-compat-skin.md +8 -2
  243. package/docs/reference/components/audio-neutral-skin.md +2 -2
  244. package/docs/reference/components/audio-scaffold-skin.md +2 -2
  245. package/docs/reference/components/audio-skin.md +2 -2
  246. package/docs/reference/components/audio-track-radio-group.md +2 -2
  247. package/docs/reference/components/background-video-skin.md +1 -1
  248. package/docs/reference/components/background-video.md +1 -1
  249. package/docs/reference/components/buffering-indicator.md +2 -2
  250. package/docs/reference/components/captions-radio-group.md +2 -2
  251. package/docs/reference/components/cloudflare-video.md +1 -1
  252. package/docs/reference/components/dailymotion-video.md +192 -0
  253. package/docs/reference/components/dash-video.md +2 -1
  254. package/docs/reference/components/dialog.md +49 -22
  255. package/docs/reference/components/error-dialog.md +13 -10
  256. package/docs/reference/components/gesture.md +2 -2
  257. package/docs/reference/components/google-analytics.md +51 -0
  258. package/docs/reference/components/google-cast.md +1 -1
  259. package/docs/reference/components/hls-audio.md +1 -1
  260. package/docs/reference/components/hls-background-video.md +4 -1
  261. package/docs/reference/components/hls-video.md +1 -1
  262. package/docs/reference/components/hlsjs-video.md +3 -1
  263. package/docs/reference/components/hotkey.md +2 -2
  264. package/docs/reference/components/live-audio-compat-skin.md +8 -2
  265. package/docs/reference/components/live-audio-neutral-skin.md +2 -2
  266. package/docs/reference/components/live-audio-scaffold-skin.md +2 -2
  267. package/docs/reference/components/live-audio-skin.md +2 -2
  268. package/docs/reference/components/live-video-compat-skin.md +8 -2
  269. package/docs/reference/components/live-video-neutral-skin.md +2 -2
  270. package/docs/reference/components/live-video-scaffold-skin.md +2 -2
  271. package/docs/reference/components/live-video-skin.md +2 -2
  272. package/docs/reference/components/llms.txt +9 -5
  273. package/docs/reference/components/mixcloud-audio.md +203 -0
  274. package/docs/reference/components/mux-audio.md +3 -1
  275. package/docs/reference/components/mux-background-video.md +1 -1
  276. package/docs/reference/components/mux-data.md +20 -2
  277. package/docs/reference/components/mux-video.md +3 -1
  278. package/docs/reference/components/native-hls-video.md +2 -1
  279. package/docs/reference/components/play-button.md +7 -5
  280. package/docs/reference/components/playback-rate-button.md +26 -7
  281. package/docs/reference/components/playback-rate-radio-group.md +7 -4
  282. package/docs/reference/components/player.md +3 -3
  283. package/docs/reference/components/playerjs-video.md +1 -1
  284. package/docs/reference/components/quality-radio-group.md +2 -2
  285. package/docs/reference/components/shaka-video.md +2 -1
  286. package/docs/reference/components/soundcloud-audio.md +227 -0
  287. package/docs/reference/components/spotify-audio.md +2 -2
  288. package/docs/reference/components/tiktok-video.md +2 -2
  289. package/docs/reference/components/twitch-video.md +2 -3
  290. package/docs/reference/components/video-compat-skin.md +8 -2
  291. package/docs/reference/components/video-neutral-skin.md +2 -2
  292. package/docs/reference/components/video-scaffold-skin.md +2 -2
  293. package/docs/reference/components/video-skin.md +2 -2
  294. package/docs/reference/components/vimeo-video.md +2 -1
  295. package/docs/reference/components/wistia-video.md +1 -1
  296. package/docs/reference/components/youtube-video.md +2 -1
  297. package/package.json +60 -40
@@ -1 +1 @@
1
- {"version":3,"file":"controller.js","names":[],"sources":["../../../../src/ui/radio-options/controller.ts"],"sourcesContent":["import type { RadioOption, RadioOptionsState } from '@videojs/core';\nimport { applyElementProps } from '@videojs/core/dom';\nimport { type Translator, translateText } from '@videojs/core/i18n';\nimport type { ReactiveController, ReactiveControllerHost } from '@videojs/element';\nimport { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from '@videojs/utils/dom';\n\nimport { cacheKey } from '../../i18n/cache-key';\nimport { MenuItemIndicatorElement } from '../menu/item-indicator';\nimport { MenuRadioItemElement } from '../menu/radio-item';\n\nexport type RadioOptionsControllerHost = ReactiveControllerHost &\n HTMLElement & {\n value: string;\n };\n\nexport interface RadioOptionsControllerConfig<Option extends RadioOption> {\n renderItem?: ((item: MenuRadioItemElement, label: string, option: Option) => void) | undefined;\n setItemAttributes?: ((item: MenuRadioItemElement, option: Option) => void) | undefined;\n getOptionCacheKey?: ((option: Option) => string) | undefined;\n onValueChange: (value: string) => void;\n}\n\n/** Renders normalized options into menu radio items and manages their interaction lifecycle. */\nexport class RadioOptionsController<Option extends RadioOption> implements ReactiveController {\n readonly #host: RadioOptionsControllerHost;\n readonly #config: RadioOptionsControllerConfig<Option>;\n\n #contentKey = '';\n #translator: Translator | null = null;\n #disconnect: AbortController | null = null;\n\n constructor(host: RadioOptionsControllerHost, config: RadioOptionsControllerConfig<Option>) {\n this.#host = host;\n this.#config = config;\n host.addController(this);\n }\n\n hostConnected(): void {\n this.#disconnect = new AbortController();\n this.#host.addEventListener('value-change', this.#handleValueChange, { signal: this.#disconnect.signal });\n }\n\n hostDisconnected(): void {\n this.#disconnect?.abort();\n this.#disconnect = null;\n }\n\n hostDestroyed(): void {\n this.hostDisconnected();\n }\n\n sync(state: RadioOptionsState<Option>, translator: Translator, locale: string): void {\n this.#host.value = state.value;\n applyElementProps(this.#host, {\n 'aria-disabled': state.disabled ? 'true' : undefined,\n hidden: state.hidden ? '' : undefined,\n });\n\n const template = getTemplateElement(this.#host);\n const templateRoot = template ? getTemplateRoot(template) : null;\n const itemRoot = templateRoot?.localName === MenuRadioItemElement.tagName ? templateRoot : null;\n\n const contentKey = `${state.options\n .map(\n (option) =>\n `${option.value}:${cacheKey(option.label, option.labelParams)}:${this.#config.getOptionCacheKey?.(option) ?? ''}`\n )\n .join('|')}::${locale}::${template?.innerHTML ?? ''}`;\n\n if (contentKey !== this.#contentKey || translator !== this.#translator) {\n this.#contentKey = contentKey;\n this.#translator = translator;\n\n for (const child of [...this.#host.children]) {\n if (child === template) continue;\n\n child.remove();\n }\n\n const items = state.options.map((option) => {\n const item = itemRoot\n ? (cloneTemplateRoot(itemRoot, this.#host.ownerDocument) as MenuRadioItemElement)\n : (this.#host.ownerDocument.createElement(MenuRadioItemElement.tagName) as MenuRadioItemElement);\n\n item.value = option.value;\n this.#config.setItemAttributes?.(item, option);\n const label = translateText(option.label, translator, option.labelParams);\n\n if (this.#config.renderItem) this.#config.renderItem(item, label, option);\n else this.#setItemLabel(item, label);\n\n return item;\n });\n\n this.#host.append(...items);\n }\n\n const optionsByValue = new Map(state.options.map((option) => [option.value, option]));\n\n for (const item of this.#host.querySelectorAll<MenuRadioItemElement>(MenuRadioItemElement.tagName)) {\n const checked = item.value === state.value;\n const option = optionsByValue.get(item.value);\n\n item.disabled = state.disabled || option?.disabled === true;\n\n for (const indicator of item.querySelectorAll<MenuItemIndicatorElement>(MenuItemIndicatorElement.tagName)) {\n indicator.checked = checked;\n }\n }\n }\n\n #handleValueChange = (event: Event): void => {\n if (event.target !== this.#host) return;\n\n const { value } = (event as CustomEvent<{ value: string }>).detail;\n\n this.#config.onValueChange(value);\n };\n\n #setItemLabel(item: MenuRadioItemElement, label: string): void {\n const labelPart = item.querySelector<HTMLElement>('[data-part~=\"label\"]');\n\n if (labelPart) labelPart.textContent = label;\n else item.textContent = label;\n }\n}\n"],"mappings":";;;;;;;;AAuBA,IAAa,yBAAb,MAA8F;CAC5F;CACA;CAEA,cAAc;CACd,cAAiC;CACjC,cAAsC;CAEtC,YAAY,MAAkC,QAA8C;EAC1F,KAAK,QAAQ;EACb,KAAK,UAAU;EACf,KAAK,cAAc,IAAI;CACzB;CAEA,gBAAsB;EACpB,KAAK,cAAc,IAAI,gBAAgB;EACvC,KAAK,MAAM,iBAAiB,gBAAgB,KAAK,oBAAoB,EAAE,QAAQ,KAAK,YAAY,OAAO,CAAC;CAC1G;CAEA,mBAAyB;EACvB,KAAK,aAAa,MAAM;EACxB,KAAK,cAAc;CACrB;CAEA,gBAAsB;EACpB,KAAK,iBAAiB;CACxB;CAEA,KAAK,OAAkC,YAAwB,QAAsB;EACnF,KAAK,MAAM,QAAQ,MAAM;EACzB,kBAAkB,KAAK,OAAO;GAC5B,iBAAiB,MAAM,WAAW,SAAS,KAAA;GAC3C,QAAQ,MAAM,SAAS,KAAK,KAAA;EAC9B,CAAC;EAED,MAAM,WAAW,mBAAmB,KAAK,KAAK;EAC9C,MAAM,eAAe,WAAW,gBAAgB,QAAQ,IAAI;EAC5D,MAAM,WAAW,cAAc,cAAc,qBAAqB,UAAU,eAAe;EAE3F,MAAM,aAAa,GAAG,MAAM,QACzB,KACE,WACC,GAAG,OAAO,MAAM,GAAG,SAAS,OAAO,OAAO,OAAO,WAAW,EAAE,GAAG,KAAK,QAAQ,oBAAoB,MAAM,KAAK,IACjH,CAAC,CACA,KAAK,GAAG,EAAE,IAAI,OAAO,IAAI,UAAU,aAAa;EAEnD,IAAI,eAAe,KAAK,eAAe,eAAe,KAAK,aAAa;GACtE,KAAK,cAAc;GACnB,KAAK,cAAc;GAEnB,KAAK,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,QAAQ,GAAG;IAC5C,IAAI,UAAU,UAAU;IAExB,MAAM,OAAO;GACf;GAEA,MAAM,QAAQ,MAAM,QAAQ,KAAK,WAAW;IAC1C,MAAM,OAAO,WACR,kBAAkB,UAAU,KAAK,MAAM,aAAa,IACpD,KAAK,MAAM,cAAc,cAAc,qBAAqB,OAAO;IAExE,KAAK,QAAQ,OAAO;IACpB,KAAK,QAAQ,oBAAoB,MAAM,MAAM;IAC7C,MAAM,QAAQ,cAAc,OAAO,OAAO,YAAY,OAAO,WAAW;IAExE,IAAI,KAAK,QAAQ,YAAY,KAAK,QAAQ,WAAW,MAAM,OAAO,MAAM;SACnE,KAAK,cAAc,MAAM,KAAK;IAEnC,OAAO;GACT,CAAC;GAED,KAAK,MAAM,OAAO,GAAG,KAAK;EAC5B;EAEA,MAAM,iBAAiB,IAAI,IAAI,MAAM,QAAQ,KAAK,WAAW,CAAC,OAAO,OAAO,MAAM,CAAC,CAAC;EAEpF,KAAK,MAAM,QAAQ,KAAK,MAAM,iBAAuC,qBAAqB,OAAO,GAAG;GAClG,MAAM,UAAU,KAAK,UAAU,MAAM;GACrC,MAAM,SAAS,eAAe,IAAI,KAAK,KAAK;GAE5C,KAAK,WAAW,MAAM,YAAY,QAAQ,aAAa;GAEvD,KAAK,MAAM,aAAa,KAAK,iBAA2C,yBAAyB,OAAO,GACtG,UAAU,UAAU;EAExB;CACF;CAEA,sBAAsB,UAAuB;EAC3C,IAAI,MAAM,WAAW,KAAK,OAAO;EAEjC,MAAM,EAAE,UAAW,MAAyC;EAE5D,KAAK,QAAQ,cAAc,KAAK;CAClC;CAEA,cAAc,MAA4B,OAAqB;EAC7D,MAAM,YAAY,KAAK,cAA2B,wBAAsB;EAExE,IAAI,WAAW,UAAU,cAAc;OAClC,KAAK,cAAc;CAC1B;AACF"}
1
+ {"version":3,"file":"controller.js","names":[],"sources":["../../../../src/ui/radio-options/controller.ts"],"sourcesContent":["import type { RadioOption, RadioOptionsState } from '@videojs/core';\nimport { applyElementProps } from '@videojs/core/dom';\nimport { type Translator, translateText } from '@videojs/core/i18n';\nimport type { ReactiveController, ReactiveControllerHost } from '@videojs/element';\nimport { cloneTemplateRoot, getTemplateElement, getTemplateRoot } from '@videojs/utils/dom';\nimport { isUndefined } from '@videojs/utils/predicate';\n\nimport { cacheKey } from '../../i18n/cache-key';\nimport { MenuItemIndicatorElement } from '../menu/item-indicator';\nimport { MenuRadioItemElement } from '../menu/radio-item';\n\nexport type RadioOptionsControllerHost = ReactiveControllerHost &\n HTMLElement & {\n value: string;\n };\n\nexport interface RadioOptionsControllerConfig<Option extends RadioOption> {\n renderItem?: ((item: MenuRadioItemElement, label: string, option: Option) => void) | undefined;\n setItemAttributes?: ((item: MenuRadioItemElement, option: Option) => void) | undefined;\n getOptionCacheKey?: ((option: Option) => string) | undefined;\n onValueChange: (value: string) => void;\n}\n\n/** Renders normalized options into menu radio items and manages their interaction lifecycle. */\nexport class RadioOptionsController<Option extends RadioOption> implements ReactiveController {\n readonly #host: RadioOptionsControllerHost;\n readonly #config: RadioOptionsControllerConfig<Option>;\n\n #contentKey = '';\n #translator: Translator | null = null;\n #disconnect: AbortController | null = null;\n\n constructor(host: RadioOptionsControllerHost, config: RadioOptionsControllerConfig<Option>) {\n this.#host = host;\n this.#config = config;\n host.addController(this);\n }\n\n hostConnected(): void {\n this.#disconnect = new AbortController();\n this.#host.addEventListener('value-change', this.#handleValueChange, { signal: this.#disconnect.signal });\n }\n\n hostDisconnected(): void {\n this.#disconnect?.abort();\n this.#disconnect = null;\n }\n\n hostDestroyed(): void {\n this.hostDisconnected();\n }\n\n sync(state: RadioOptionsState<Option>, translator: Translator, locale: string): void {\n this.#host.value = state.value;\n applyElementProps(this.#host, {\n 'aria-disabled': state.disabled ? 'true' : undefined,\n hidden: state.hidden ? '' : undefined,\n });\n\n const template = getTemplateElement(this.#host);\n const templateRoot = template ? getTemplateRoot(template) : null;\n const itemRoot = templateRoot?.localName === MenuRadioItemElement.tagName ? templateRoot : null;\n\n const contentKey = `${state.options\n .map(\n (option) =>\n `${option.value}:${cacheKey(option.label, option.labelParams)}:${cacheKey(option.ariaLabel ?? '', option.ariaLabelParams)}:${this.#config.getOptionCacheKey?.(option) ?? ''}`\n )\n .join('|')}::${locale}::${template?.innerHTML ?? ''}`;\n\n if (contentKey !== this.#contentKey || translator !== this.#translator) {\n this.#contentKey = contentKey;\n this.#translator = translator;\n\n const staleChildren = [...this.#host.children].filter((child) => child !== template);\n const focusedValue = this.#getFocusedItem()?.value;\n\n const items = state.options.map((option) => {\n const item = itemRoot\n ? (cloneTemplateRoot(itemRoot, this.#host.ownerDocument) as MenuRadioItemElement)\n : (this.#host.ownerDocument.createElement(MenuRadioItemElement.tagName) as MenuRadioItemElement);\n\n item.value = option.value;\n this.#config.setItemAttributes?.(item, option);\n const label = translateText(option.label, translator, option.labelParams);\n\n if (this.#config.renderItem) this.#config.renderItem(item, label, option);\n else this.#setItemLabel(item, label);\n\n if (!isUndefined(option.ariaLabel)) {\n item.setAttribute('aria-label', translateText(option.ariaLabel, translator, option.ariaLabelParams));\n }\n\n return item;\n });\n\n this.#host.append(...items);\n\n const removeStale = () => {\n for (const child of staleChildren) child.remove();\n };\n\n // Removing the focused item fires a `focusout` with no destination, which closes an open menu. Its replacement\n // registers with the menu, and becomes focusable, on its first update, so the old items stay until focus can move.\n const replacement = isUndefined(focusedValue)\n ? undefined\n : (items.find((item) => item.value === focusedValue) ?? items.find((item) => item.value === state.value));\n\n if (replacement) {\n void replacement.updateComplete.then(() => {\n replacement.focus();\n removeStale();\n });\n } else {\n removeStale();\n }\n }\n\n const optionsByValue = new Map(state.options.map((option) => [option.value, option]));\n\n for (const item of this.#host.querySelectorAll<MenuRadioItemElement>(MenuRadioItemElement.tagName)) {\n const checked = item.value === state.value;\n const option = optionsByValue.get(item.value);\n\n item.disabled = state.disabled || option?.disabled === true;\n\n for (const indicator of item.querySelectorAll<MenuItemIndicatorElement>(MenuItemIndicatorElement.tagName)) {\n indicator.checked = checked;\n }\n }\n }\n\n #handleValueChange = (event: Event): void => {\n if (event.target !== this.#host) return;\n\n const { value } = (event as CustomEvent<{ value: string }>).detail;\n\n this.#config.onValueChange(value);\n };\n\n #getFocusedItem(): MenuRadioItemElement | null {\n const active = (this.#host.getRootNode() as Document | ShadowRoot).activeElement;\n\n return active instanceof MenuRadioItemElement && active.parentElement === this.#host ? active : null;\n }\n\n #setItemLabel(item: MenuRadioItemElement, label: string): void {\n const labelPart = item.querySelector<HTMLElement>('[data-part~=\"label\"]');\n\n if (labelPart) labelPart.textContent = label;\n else item.textContent = label;\n }\n}\n"],"mappings":";;;;;;;;;AAwBA,IAAa,yBAAb,MAA8F;CAC5F;CACA;CAEA,cAAc;CACd,cAAiC;CACjC,cAAsC;CAEtC,YAAY,MAAkC,QAA8C;EAC1F,KAAK,QAAQ;EACb,KAAK,UAAU;EACf,KAAK,cAAc,IAAI;CACzB;CAEA,gBAAsB;EACpB,KAAK,cAAc,IAAI,gBAAgB;EACvC,KAAK,MAAM,iBAAiB,gBAAgB,KAAK,oBAAoB,EAAE,QAAQ,KAAK,YAAY,OAAO,CAAC;CAC1G;CAEA,mBAAyB;EACvB,KAAK,aAAa,MAAM;EACxB,KAAK,cAAc;CACrB;CAEA,gBAAsB;EACpB,KAAK,iBAAiB;CACxB;CAEA,KAAK,OAAkC,YAAwB,QAAsB;EACnF,KAAK,MAAM,QAAQ,MAAM;EACzB,kBAAkB,KAAK,OAAO;GAC5B,iBAAiB,MAAM,WAAW,SAAS,KAAA;GAC3C,QAAQ,MAAM,SAAS,KAAK,KAAA;EAC9B,CAAC;EAED,MAAM,WAAW,mBAAmB,KAAK,KAAK;EAC9C,MAAM,eAAe,WAAW,gBAAgB,QAAQ,IAAI;EAC5D,MAAM,WAAW,cAAc,cAAc,qBAAqB,UAAU,eAAe;EAE3F,MAAM,aAAa,GAAG,MAAM,QACzB,KACE,WACC,GAAG,OAAO,MAAM,GAAG,SAAS,OAAO,OAAO,OAAO,WAAW,EAAE,GAAG,SAAS,OAAO,aAAa,IAAI,OAAO,eAAe,EAAE,GAAG,KAAK,QAAQ,oBAAoB,MAAM,KAAK,IAC7K,CAAC,CACA,KAAK,GAAG,EAAE,IAAI,OAAO,IAAI,UAAU,aAAa;EAEnD,IAAI,eAAe,KAAK,eAAe,eAAe,KAAK,aAAa;GACtE,KAAK,cAAc;GACnB,KAAK,cAAc;GAEnB,MAAM,gBAAgB,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC,QAAQ,UAAU,UAAU,QAAQ;GACnF,MAAM,eAAe,KAAK,gBAAgB,CAAC,EAAE;GAE7C,MAAM,QAAQ,MAAM,QAAQ,KAAK,WAAW;IAC1C,MAAM,OAAO,WACR,kBAAkB,UAAU,KAAK,MAAM,aAAa,IACpD,KAAK,MAAM,cAAc,cAAc,qBAAqB,OAAO;IAExE,KAAK,QAAQ,OAAO;IACpB,KAAK,QAAQ,oBAAoB,MAAM,MAAM;IAC7C,MAAM,QAAQ,cAAc,OAAO,OAAO,YAAY,OAAO,WAAW;IAExE,IAAI,KAAK,QAAQ,YAAY,KAAK,QAAQ,WAAW,MAAM,OAAO,MAAM;SACnE,KAAK,cAAc,MAAM,KAAK;IAEnC,IAAI,CAAC,YAAY,OAAO,SAAS,GAC/B,KAAK,aAAa,cAAc,cAAc,OAAO,WAAW,YAAY,OAAO,eAAe,CAAC;IAGrG,OAAO;GACT,CAAC;GAED,KAAK,MAAM,OAAO,GAAG,KAAK;GAE1B,MAAM,oBAAoB;IACxB,KAAK,MAAM,SAAS,eAAe,MAAM,OAAO;GAClD;GAIA,MAAM,cAAc,YAAY,YAAY,IACxC,KAAA,IACC,MAAM,MAAM,SAAS,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,SAAS,KAAK,UAAU,MAAM,KAAK;GAEzG,IAAI,aACF,YAAiB,eAAe,WAAW;IACzC,YAAY,MAAM;IAClB,YAAY;GACd,CAAC;QAED,YAAY;EAEhB;EAEA,MAAM,iBAAiB,IAAI,IAAI,MAAM,QAAQ,KAAK,WAAW,CAAC,OAAO,OAAO,MAAM,CAAC,CAAC;EAEpF,KAAK,MAAM,QAAQ,KAAK,MAAM,iBAAuC,qBAAqB,OAAO,GAAG;GAClG,MAAM,UAAU,KAAK,UAAU,MAAM;GACrC,MAAM,SAAS,eAAe,IAAI,KAAK,KAAK;GAE5C,KAAK,WAAW,MAAM,YAAY,QAAQ,aAAa;GAEvD,KAAK,MAAM,aAAa,KAAK,iBAA2C,yBAAyB,OAAO,GACtG,UAAU,UAAU;EAExB;CACF;CAEA,sBAAsB,UAAuB;EAC3C,IAAI,MAAM,WAAW,KAAK,OAAO;EAEjC,MAAM,EAAE,UAAW,MAAyC;EAE5D,KAAK,QAAQ,cAAc,KAAK;CAClC;CAEA,kBAA+C;EAC7C,MAAM,SAAU,KAAK,MAAM,YAAY,CAAC,CAA2B;EAEnE,OAAO,kBAAkB,wBAAwB,OAAO,kBAAkB,KAAK,QAAQ,SAAS;CAClG;CAEA,cAAc,MAA4B,OAAqB;EAC7D,MAAM,YAAY,KAAK,cAA2B,wBAAsB;EAExE,IAAI,WAAW,UAAU,cAAc;OAClC,KAAK,cAAc;CAC1B;AACF"}
@@ -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.3.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.3.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
 
@@ -16,6 +16,8 @@ These versions account for 92.5% of global web usage according to [caniuse-lite]
16
16
 
17
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.
18
18
 
19
+ These versions apply to every skin. The [Compat and Scaffold skins](./skins.md#choose-a-skin) avoid newer visual effects such as backdrop blur, anchor-positioned menu highlights, and squircle corners, so they look the same in browsers that skip them. They still need the CSS features below.
20
+
19
21
  ## CSS requirements
20
22
 
21
23
  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).
@@ -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.3.0/video-player.js"></script>
41
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.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.3.0/video.js"></script>
52
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.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.3.0/video.js"></script>
63
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/media/mux-video.js"></script>
64
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.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.3.0/locales/es.js"></script>
75
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.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
  | --- | --- |
@@ -36,7 +38,7 @@ Packaged skins expose [CSS custom properties](https://developer.mozilla.org/en-U
36
38
 
37
39
  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
40
 
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.
41
+ 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.
40
42
 
41
43
  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
44
 
@@ -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
  | --- | --- |
@@ -0,0 +1,199 @@
1
+ # Track video telemetry with Google Analytics 4
2
+
3
+ Track video engagement, progress milestones, and playback interactions by adding the Google Analytics extension to a player
4
+
5
+ Google Analytics 4 (GA4) Enhanced Measurement tracks video engagement and viewer interaction. Add the Google Analytics [extension](./architecture.md) to a player and it monitors whichever media that player is playing, automatically dispatching standard GA4 Enhanced Measurement video events and playback interaction events to `window.dataLayer` and `window.gtag`.
6
+
7
+ Install the extension alongside the framework façade:
8
+
9
+ ```bash
10
+ pnpm add @videojs/html @videojs/google-analytics
11
+ ```
12
+
13
+ **index.html**
14
+
15
+ ```html
16
+ <video-player>
17
+ <media-container>
18
+ <video src="https://example.com/video.mp4"></video>
19
+ <google-analytics></google-analytics>
20
+ </media-container>
21
+ </video-player>
22
+ <script type="module">
23
+ import '@videojs/html/video/player';
24
+ import '@videojs/html/ui/container';
25
+ import '@videojs/html/extensions/google-analytics';
26
+
27
+ document.querySelector('google-analytics').metadata = { video_title: 'My Video' };
28
+ </script>
29
+ ```
30
+
31
+ ### Load from a CDN
32
+
33
+ When loading Video.js from a CDN, include the `extensions/google-analytics.js` script tag:
34
+
35
+ **index.html**
36
+
37
+ ```html
38
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn/video.js"></script>
39
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn/extensions/google-analytics.js"></script>
40
+
41
+ <video-player>
42
+ <media-container>
43
+ <video src="https://example.com/video.mp4"></video>
44
+ <google-analytics></google-analytics>
45
+ </media-container>
46
+ </video-player>
47
+ <script type="module">
48
+ document.querySelector('google-analytics').metadata = { video_title: 'My Video' };
49
+ </script>
50
+ ```
51
+
52
+ That’s the entire setup for standard video tracking. The extension renders nothing; place it anywhere inside the player.
53
+
54
+ > **Note**
55
+ >
56
+ > Google Analytics tracking is opt-in. No media component sends telemetry on its own until you add the extension to the player.
57
+
58
+ ## How events are reported
59
+
60
+ When Google Tag (`gtag.js`) or Google Tag Manager (GTM) is configured on your site, `window.dataLayer` is available globally. The extension pushes structured event objects into `window.dataLayer`:
61
+
62
+ ```js
63
+ window.dataLayer.push({
64
+ event: 'video_progress',
65
+ video_current_time: 30,
66
+ video_duration: 120,
67
+ video_percent: 25,
68
+ video_provider: 'video.js',
69
+ video_stream_type: 'on-demand',
70
+ video_title: 'My Video',
71
+ video_url: 'https://example.com/video.mp4',
72
+ visible: true,
73
+ });
74
+ ```
75
+
76
+ If `window.gtag` is defined, the extension also calls `window.gtag('event', event, params)` with the same parameters. If you use a custom data layer array on `window`, specify its property name with `dataLayerName` (or `data-layer-name` in HTML).
77
+
78
+ ### GA4 Enhanced Measurement events
79
+
80
+ The extension dispatches standard GA4 Enhanced Measurement video events:
81
+
82
+ - `video_start`: Dispatched when playback begins for the first time or starts at `currentTime > 0`.
83
+ - `video_progress`: Dispatched when playback crosses 10%, 25%, 50%, and 75% milestones.
84
+ - `video_complete`: Dispatched when the video reaches the end (`ended` event) or loops back to the start.
85
+
86
+ ### Video.js interaction events
87
+
88
+ In addition to GA4 Enhanced Measurement events, the extension emits player interaction events to measure how viewers engage with controls:
89
+
90
+ - `play`: Dispatched on first play.
91
+ - `pause`: Dispatched when playback is paused (not triggered during seeking or when ended).
92
+ - `resume`: Dispatched when playback resumes after a pause.
93
+ - `end`: Dispatched when the video finishes.
94
+ - `seek`: Dispatched when scrubbing or seeking completes (bypassed on loop restarts).
95
+ - `mute`: Dispatched when audio is muted or volume drops to 0.
96
+ - `unmute`: Dispatched when audio is unmuted with non-zero volume.
97
+ - `fullscreenEnter`: Dispatched when entering fullscreen.
98
+ - `fullscreenExit`: Dispatched when exiting fullscreen.
99
+
100
+ ## Set stream type
101
+
102
+ By default, the stream type is inferred from the media (`media.streamType === 'live' ? 'live' : 'on-demand'`). For on-demand videos, duration is known and the extension calculates playback percentage to fire progress milestones at 10%, 25%, 50%, and 75%.
103
+
104
+ To override or explicitly specify a live stream, pass `video_stream_type: 'live'` in `metadata`:
105
+
106
+ ```html
107
+ <google-analytics></google-analytics>
108
+ <script type="module">
109
+ const ga = document.querySelector('google-analytics');
110
+ ga.metadata = { video_title: 'Live Stream', video_stream_type: 'live' };
111
+ </script>
112
+ ```
113
+
114
+ When the effective stream type is `'live'`, milestone progress tracking is bypassed since live streams have unbounded or dynamic duration.
115
+
116
+ ## Disable progress tracking
117
+
118
+ By default, the extension tracks progress milestones at 10%, 25%, 50%, and 75% for on-demand playback. To turn off milestone progress tracking, set `disableProgress` (or `disable-progress` in HTML):
119
+
120
+ ```html
121
+ <google-analytics disable-progress></google-analytics>
122
+ ```
123
+
124
+ ## Set video title
125
+
126
+ Set `metadata.video_title` on the extension so GA4 gets an explicit title for every media type, including iframe embeds that do not expose a useful DOM `title`.
127
+
128
+ ```html
129
+ <google-analytics></google-analytics>
130
+ <script type="module">
131
+ const ga = document.querySelector('google-analytics');
132
+ ga.metadata = { video_title: 'Keynote Presentation' };
133
+ </script>
134
+ ```
135
+
136
+ When `metadata.video_title` is omitted, the extension derives a title in this order: the title the media reports in its content data (Mux, Vimeo, and SoundCloud media report one, for example), then the resolved `video_url`, then `'Untitled Video'`. Do not rely on a `<video title="...">` attribute as your analytics title; set `metadata.video_title` instead.
137
+
138
+ ## Metadata and custom dimensions
139
+
140
+ Pass `metadata` to set official GA4 video parameters (`video_title`, `video_provider`, `video_url`) and custom dimensions (such as Mux Data compatible names `video_id`, `video_series`, `video_producer`, `video_content_type`, or custom dimensions like `custom_1`):
141
+
142
+ ```html
143
+ <google-analytics></google-analytics>
144
+ <script type="module">
145
+ const ga = document.querySelector('google-analytics');
146
+ ga.metadata = {
147
+ video_title: 'Keynote Presentation',
148
+ video_series: 'Annual Conference',
149
+ video_producer: 'Video.js Team',
150
+ video_id: 'conf_2026_01',
151
+ custom_1: 'attendee_pro',
152
+ };
153
+ </script>
154
+ ```
155
+
156
+ ### GA4 collection limits
157
+
158
+ Google Analytics 4 enforces collection limits on event parameters:
159
+
160
+ - **25 parameters per event**: The extension sends 8 standard parameters (`video_current_time`, `video_duration`, `video_percent`, `video_provider`, `video_stream_type`, `video_title`, `video_url`, and `visible`), leaving room for up to 17 custom dimensions in `metadata`.
161
+ - **Parameter names**: Up to 40 characters in length.
162
+ - **String values**: Up to 100 characters in length.
163
+
164
+ ## Debugging
165
+
166
+ Enable console logging by setting the `debug` option:
167
+
168
+ ```html
169
+ <google-analytics debug></google-analytics>
170
+ ```
171
+
172
+ When enabled, the extension logs every dispatched event and its parameters:
173
+
174
+ ```plaintext
175
+ [GoogleAnalyticsExtension] Event: "video_start" {video_current_time: 0, video_duration: 120, ...}
176
+ ```
177
+
178
+ You can also provide an `onEvent` callback to intercept dispatched events programmatically:
179
+
180
+ ```html
181
+ <google-analytics></google-analytics>
182
+ <script type="module">
183
+ const ga = document.querySelector('google-analytics');
184
+ ga.onEvent = (event, params) => {
185
+ console.log('Telemetry event:', event, params);
186
+ };
187
+ </script>
188
+ ```
189
+
190
+ ## Related pages
191
+
192
+ ### Components
193
+
194
+ - [google-analytics](../reference/components/google-analytics.md): Options for the Google Analytics 4 extension that tracks video engagement and playback events
195
+ - [mux-data](../reference/components/mux-data.md): Options for the Mux Data extension that monitors playback quality
196
+
197
+ ### Guides
198
+
199
+ - [Monitor playback with Mux Data](./mux-data.md): Measure startup time, rebuffering, failures, and watch time by adding the Mux Data extension to a player
@@ -30,7 +30,7 @@ npx @videojs/cli agents init
30
30
 
31
31
  - `project`: Whether to adapt the current project or scaffold a new one. New projects need a named app setup. Values: `new`, `existing`. Default: existing.
32
32
  - `preset`: The player configuration and control set. Values: `video`, `audio`, `live-video`, `live-audio`, `background-video`. Default: video.
33
- - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
33
+ - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Compat has plain, unbranded surfaces. Scaffold has minimal styling meant to be restyled. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
34
34
  - `media`: The media source or playback adapter. See the preset compatibility map below. Values: `background-video`, `hls-background-video`, `cloudflare`, `dash`, `hls`, `html5-audio`, `html5-video`, `mux-audio`, `mux-background-video`, `mux-video`, `spotify`, `tiktok`, `twitch`, `vimeo`, `youtube`. Default: the selected preset's first compatible media source.
35
35
  - `extensions`: A comma-separated list of optional player extensions compatible with the selected player. Pass `none` when no extension is needed. Values: `none`, `google-cast`, `mux-data`. Default: mux-data for Mux media; none otherwise (reported as defaulted).
36
36
  - `source-url`: The http:// or https:// media URL placed in the generated player example. Pass `demo` to choose the Video.js demo source for the selected media explicitly. Default: the Video.js demo source for the selected media (reported as defaulted).
@@ -44,7 +44,7 @@ Run the command above without flags to see the corresponding CLI flags for every
44
44
  1. **Inspect the project.** Read package.json, framework config, and lockfiles to infer the framework, app setup, and package manager. React installs @videojs/react; HTML, Vue, and Svelte install @videojs/html.
45
45
  2. **Choose the starting point.** Use existing when adapting a compatible project. Use new only when the user wants a new app or the intended workspace has no app. Confirm the choice when the workspace and request do not make it clear.
46
46
  3. **Choose the player.** Use video unless the request signals another experience: audio, music, or podcasts use audio; a live stream uses live-video or live-audio; a muted, looping decorative video uses background-video. Ask only when those signals conflict.
47
- 4. **Choose the skin.** Use default unless the request asks for a minimal, cleaner, or more subtle look, which uses neutral; both contain the same controls. Use none only when the project builds its own controls. Ask only when those signals conflict.
47
+ 4. **Choose the skin.** Use default unless the request signals another look. Use neutral to apply a brand to a finished design, or for a minimal, cleaner, or more subtle look (it was formerly named minimal); it has the same controls as default. Use compat for plain, unbranded controls that blend into content pages, a step above native controls. Use scaffold when the user wants an unbranded foundation to make their own, such as shadcn-style components they restyle; it has the same controls as compat with minimal styling, meant to be restyled rather than used as is. All four support the same browsers. Use none only when the project builds its own controls. Ask only when those signals conflict.
48
48
  5. **Choose the media.** Infer the adapter from the source when possible. Mux wins for Mux playback URLs: stream.mux.com/\<playback-id>.m3u8 or a bare playback ID uses mux-video, mux-audio, or mux-background-video rather than hls. Mux static renditions such as .mp4 or .m4a files use html5-video or html5-audio. Other .m3u8 URLs use hls.
49
49
  6. **Choose extensions.** Mux Data is included by default for Mux video and audio sources. Add Google Cast when a standard or live video player with a ready-made skin should cast a compatible source. Use none when no extension is needed.
50
50
  7. **Choose how to install.** This guide uses CDN scripts for plain HTML. Use an existing page when one is available, and scaffold a minimal Vite app only when no app exists.
@@ -89,7 +89,7 @@ Defaulted options: project, preset, skin, source-url, media, extensions, templat
89
89
  ## Reproduce or change these instructions
90
90
 
91
91
  ```sh
92
- npx @videojs/cli@10.2.0 agents init --method cdn --framework html --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --template none
92
+ npx @videojs/cli@10.3.0 agents init --method cdn --framework html --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --template none
93
93
  ```
94
94
 
95
95
  ## Load Video.js
@@ -101,7 +101,7 @@ Add these module scripts to the page head. Module scripts are deferred, so they
101
101
  _Merge this into the page `<head>`._
102
102
 
103
103
  ```html
104
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.2.0/video.js"></script>
104
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/video.js"></script>
105
105
  ```
106
106
 
107
107
  ## Add your player
@@ -31,7 +31,7 @@ npx @videojs/cli agents init
31
31
  - `framework`: The application framework that will host the player. React installs @videojs/react; HTML, Vue, and Svelte install @videojs/html. Values: `react`, `html`. Default: react.
32
32
  - `project`: Whether to adapt the current project or scaffold a new one. New projects need a named app setup. Values: `new`, `existing`. Default: existing.
33
33
  - `preset`: The player configuration and control set. Values: `video`, `audio`, `live-video`, `live-audio`. Default: video.
34
- - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Values: `default`, `neutral`, `compat`, `scaffold`. Default: default. Applies when preset is not background-video.
34
+ - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Compat has plain, unbranded surfaces. Scaffold has minimal styling meant to be restyled. Values: `default`, `neutral`, `compat`, `scaffold`. Default: default. Applies when preset is not background-video.
35
35
  - `media`: The media source or playback adapter. See the preset compatibility map below. Values: `background-video`, `hls-background-video`, `cloudflare`, `dash`, `hls`, `html5-audio`, `html5-video`, `mux-audio`, `mux-background-video`, `mux-video`, `spotify`, `tiktok`, `twitch`, `vimeo`, `youtube`. Default: the selected preset's first compatible media source.
36
36
  - `extensions`: A comma-separated list of optional player extensions compatible with the selected player. Pass `none` when no extension is needed. Values: `none`, `google-cast`, `mux-data`. Default: mux-data for Mux media; none otherwise (reported as defaulted).
37
37
  - `source-url`: The http:// or https:// media URL placed in the generated player example. Pass `demo` to choose the Video.js demo source for the selected media explicitly. Default: the Video.js demo source for the selected media (reported as defaulted).
@@ -46,7 +46,7 @@ Run the command above without flags to see the corresponding CLI flags for every
46
46
  1. **Inspect the project.** Read package.json, framework config, and lockfiles to infer the framework, app setup, and package manager. React installs @videojs/react; HTML, Vue, and Svelte install @videojs/html.
47
47
  2. **Choose the starting point.** Use existing when adapting a compatible project. Use new only when the user wants a new app or the intended workspace has no app. Confirm the choice when the workspace and request do not make it clear.
48
48
  3. **Choose the player.** Use video unless the request signals another experience: audio, music, or podcasts use audio; a live stream uses live-video or live-audio; a muted, looping decorative video uses background-video. Ask only when those signals conflict.
49
- 4. **Choose the skin.** Use default unless the request asks for a minimal, cleaner, or more subtle look, which uses neutral; both contain the same controls. Ask only when those signals conflict.
49
+ 4. **Choose the skin.** Use default unless the request signals another look. Use neutral to apply a brand to a finished design, or for a minimal, cleaner, or more subtle look (it was formerly named minimal); it has the same controls as default. Use compat for plain, unbranded controls that blend into content pages, a step above native controls. Use scaffold when the user wants an unbranded foundation to make their own, such as shadcn-style components they restyle; it has the same controls as compat with minimal styling, meant to be restyled rather than used as is. All four support the same browsers. Ask only when those signals conflict.
50
50
  5. **Choose the media.** Infer the adapter from the source when possible. Mux wins for Mux playback URLs: stream.mux.com/\<playback-id>.m3u8 or a bare playback ID uses mux-video, mux-audio, or mux-background-video rather than hls. Mux static renditions such as .mp4 or .m4a files use html5-video or html5-audio. Other .m3u8 URLs use hls.
51
51
  6. **Choose extensions.** Mux Data is included by default for Mux video and audio sources. Add Google Cast when a standard or live video player with a ready-made skin should cast a compatible source. Use none when no extension is needed.
52
52
  7. **Choose how to install.** This guide uses Shadcn to copy editable skin source. React uses the React source catalog; plain HTML uses the HTML source catalog. Vue and Svelte use packaged modules.
@@ -94,7 +94,7 @@ Defaulted options: project, preset, skin, source-url, media, extensions, templat
94
94
  ## Reproduce or change these instructions
95
95
 
96
96
  ```sh
97
- npx @videojs/cli@10.2.0 agents init --method shadcn --framework html --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite --styling css
97
+ npx @videojs/cli@10.3.0 agents init --method shadcn --framework html --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite --styling css
98
98
  ```
99
99
 
100
100
  ## Configure app aliases
@@ -30,7 +30,7 @@ npx @videojs/cli agents init
30
30
 
31
31
  - `project`: Whether to adapt the current project or scaffold a new one. New projects need a named app setup. Values: `new`, `existing`. Default: existing.
32
32
  - `preset`: The player configuration and control set. Values: `video`, `audio`, `live-video`, `live-audio`, `background-video`. Default: video.
33
- - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
33
+ - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Compat has plain, unbranded surfaces. Scaffold has minimal styling meant to be restyled. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
34
34
  - `media`: The media source or playback adapter. See the preset compatibility map below. Values: `background-video`, `hls-background-video`, `cloudflare`, `dash`, `hls`, `html5-audio`, `html5-video`, `mux-audio`, `mux-background-video`, `mux-video`, `spotify`, `tiktok`, `twitch`, `vimeo`, `youtube`. Default: the selected preset's first compatible media source.
35
35
  - `extensions`: A comma-separated list of optional player extensions compatible with the selected player. Pass `none` when no extension is needed. Values: `none`, `google-cast`, `mux-data`. Default: mux-data for Mux media; none otherwise (reported as defaulted).
36
36
  - `source-url`: The http:// or https:// media URL placed in the generated player example. Pass `demo` to choose the Video.js demo source for the selected media explicitly. Default: the Video.js demo source for the selected media (reported as defaulted).
@@ -44,7 +44,7 @@ Run the command above without flags to see the corresponding CLI flags for every
44
44
  1. **Inspect the project.** Read package.json, framework config, and lockfiles to infer the framework, app setup, and package manager. React installs @videojs/react; HTML, Vue, and Svelte install @videojs/html.
45
45
  2. **Choose the starting point.** Use existing when adapting a compatible project. Use new only when the user wants a new app or the intended workspace has no app. Confirm the choice when the workspace and request do not make it clear.
46
46
  3. **Choose the player.** Use video unless the request signals another experience: audio, music, or podcasts use audio; a live stream uses live-video or live-audio; a muted, looping decorative video uses background-video. Ask only when those signals conflict.
47
- 4. **Choose the skin.** Use default unless the request asks for a minimal, cleaner, or more subtle look, which uses neutral; both contain the same controls. Use none only when the project builds its own controls. Ask only when those signals conflict.
47
+ 4. **Choose the skin.** Use default unless the request signals another look. Use neutral to apply a brand to a finished design, or for a minimal, cleaner, or more subtle look (it was formerly named minimal); it has the same controls as default. Use compat for plain, unbranded controls that blend into content pages, a step above native controls. Use scaffold when the user wants an unbranded foundation to make their own, such as shadcn-style components they restyle; it has the same controls as compat with minimal styling, meant to be restyled rather than used as is. All four support the same browsers. Use none only when the project builds its own controls. Ask only when those signals conflict.
48
48
  5. **Choose the media.** Infer the adapter from the source when possible. Mux wins for Mux playback URLs: stream.mux.com/\<playback-id>.m3u8 or a bare playback ID uses mux-video, mux-audio, or mux-background-video rather than hls. Mux static renditions such as .mp4 or .m4a files use html5-video or html5-audio. Other .m3u8 URLs use hls.
49
49
  6. **Choose extensions.** Mux Data is included by default for Mux video and audio sources. Add Google Cast when a standard or live video player with a ready-made skin should cast a compatible source. Use none when no extension is needed.
50
50
  7. **Choose how to install.** This guide uses packaged modules, which need a bundler. Match the package manager to the project lockfile. For an existing HTML site without a build step, use CDN scripts instead.
@@ -90,13 +90,13 @@ Defaulted options: project, preset, skin, source-url, media, extensions, templat
90
90
  ## Reproduce or change these instructions
91
91
 
92
92
  ```sh
93
- npx @videojs/cli@10.2.0 agents init --method packaged --framework svelte --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite
93
+ npx @videojs/cli@10.3.0 agents init --method packaged --framework svelte --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite
94
94
  ```
95
95
 
96
96
  ## Install the packages
97
97
 
98
98
  ```bash
99
- pnpm add @videojs/html@10.2.0
99
+ pnpm add @videojs/html@10.3.0
100
100
  ```
101
101
 
102
102
  ## Add your player
@@ -30,7 +30,7 @@ npx @videojs/cli agents init
30
30
 
31
31
  - `project`: Whether to adapt the current project or scaffold a new one. New projects need a named app setup. Values: `new`, `existing`. Default: existing.
32
32
  - `preset`: The player configuration and control set. Values: `video`, `audio`, `live-video`, `live-audio`, `background-video`. Default: video.
33
- - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
33
+ - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Compat has plain, unbranded surfaces. Scaffold has minimal styling meant to be restyled. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
34
34
  - `media`: The media source or playback adapter. See the preset compatibility map below. Values: `background-video`, `hls-background-video`, `cloudflare`, `dash`, `hls`, `html5-audio`, `html5-video`, `mux-audio`, `mux-background-video`, `mux-video`, `spotify`, `tiktok`, `twitch`, `vimeo`, `youtube`. Default: the selected preset's first compatible media source.
35
35
  - `extensions`: A comma-separated list of optional player extensions compatible with the selected player. Pass `none` when no extension is needed. Values: `none`, `google-cast`, `mux-data`. Default: mux-data for Mux media; none otherwise (reported as defaulted).
36
36
  - `source-url`: The http:// or https:// media URL placed in the generated player example. Pass `demo` to choose the Video.js demo source for the selected media explicitly. Default: the Video.js demo source for the selected media (reported as defaulted).
@@ -44,7 +44,7 @@ Run the command above without flags to see the corresponding CLI flags for every
44
44
  1. **Inspect the project.** Read package.json, framework config, and lockfiles to infer the framework, app setup, and package manager. React installs @videojs/react; HTML, Vue, and Svelte install @videojs/html.
45
45
  2. **Choose the starting point.** Use existing when adapting a compatible project. Use new only when the user wants a new app or the intended workspace has no app. Confirm the choice when the workspace and request do not make it clear.
46
46
  3. **Choose the player.** Use video unless the request signals another experience: audio, music, or podcasts use audio; a live stream uses live-video or live-audio; a muted, looping decorative video uses background-video. Ask only when those signals conflict.
47
- 4. **Choose the skin.** Use default unless the request asks for a minimal, cleaner, or more subtle look, which uses neutral; both contain the same controls. Use none only when the project builds its own controls. Ask only when those signals conflict.
47
+ 4. **Choose the skin.** Use default unless the request signals another look. Use neutral to apply a brand to a finished design, or for a minimal, cleaner, or more subtle look (it was formerly named minimal); it has the same controls as default. Use compat for plain, unbranded controls that blend into content pages, a step above native controls. Use scaffold when the user wants an unbranded foundation to make their own, such as shadcn-style components they restyle; it has the same controls as compat with minimal styling, meant to be restyled rather than used as is. All four support the same browsers. Use none only when the project builds its own controls. Ask only when those signals conflict.
48
48
  5. **Choose the media.** Infer the adapter from the source when possible. Mux wins for Mux playback URLs: stream.mux.com/\<playback-id>.m3u8 or a bare playback ID uses mux-video, mux-audio, or mux-background-video rather than hls. Mux static renditions such as .mp4 or .m4a files use html5-video or html5-audio. Other .m3u8 URLs use hls.
49
49
  6. **Choose extensions.** Mux Data is included by default for Mux video and audio sources. Add Google Cast when a standard or live video player with a ready-made skin should cast a compatible source. Use none when no extension is needed.
50
50
  7. **Choose how to install.** This guide uses packaged modules, which need a bundler. Match the package manager to the project lockfile. For an existing HTML site without a build step, use CDN scripts instead.
@@ -90,13 +90,13 @@ Defaulted options: project, preset, skin, source-url, media, extensions, templat
90
90
  ## Reproduce or change these instructions
91
91
 
92
92
  ```sh
93
- npx @videojs/cli@10.2.0 agents init --method packaged --framework vue --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite
93
+ npx @videojs/cli@10.3.0 agents init --method packaged --framework vue --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite
94
94
  ```
95
95
 
96
96
  ## Install the packages
97
97
 
98
98
  ```bash
99
- pnpm add @videojs/html@10.2.0
99
+ pnpm add @videojs/html@10.3.0
100
100
  ```
101
101
 
102
102
  ## Register custom elements
@@ -30,7 +30,7 @@ npx @videojs/cli agents init
30
30
 
31
31
  - `project`: Whether to adapt the current project or scaffold a new one. New projects need a named app setup. Values: `new`, `existing`. Default: existing.
32
32
  - `preset`: The player configuration and control set. Values: `video`, `audio`, `live-video`, `live-audio`, `background-video`. Default: video.
33
- - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
33
+ - `skin`: The visual skin. Neutral has cleaner surfaces and the same controls as Default. Compat has plain, unbranded surfaces. Scaffold has minimal styling meant to be restyled. Values: `default`, `neutral`, `compat`, `scaffold`, `none`. Default: default. Applies when preset is not background-video.
34
34
  - `media`: The media source or playback adapter. See the preset compatibility map below. Values: `background-video`, `hls-background-video`, `cloudflare`, `dash`, `hls`, `html5-audio`, `html5-video`, `mux-audio`, `mux-background-video`, `mux-video`, `spotify`, `tiktok`, `twitch`, `vimeo`, `youtube`. Default: the selected preset's first compatible media source.
35
35
  - `extensions`: A comma-separated list of optional player extensions compatible with the selected player. Pass `none` when no extension is needed. Values: `none`, `google-cast`, `mux-data`. Default: mux-data for Mux media; none otherwise (reported as defaulted).
36
36
  - `source-url`: The http:// or https:// media URL placed in the generated player example. Pass `demo` to choose the Video.js demo source for the selected media explicitly. Default: the Video.js demo source for the selected media (reported as defaulted).
@@ -44,7 +44,7 @@ Run the command above without flags to see the corresponding CLI flags for every
44
44
  1. **Inspect the project.** Read package.json, framework config, and lockfiles to infer the framework, app setup, and package manager. React installs @videojs/react; HTML, Vue, and Svelte install @videojs/html.
45
45
  2. **Choose the starting point.** Use existing when adapting a compatible project. Use new only when the user wants a new app or the intended workspace has no app. Confirm the choice when the workspace and request do not make it clear.
46
46
  3. **Choose the player.** Use video unless the request signals another experience: audio, music, or podcasts use audio; a live stream uses live-video or live-audio; a muted, looping decorative video uses background-video. Ask only when those signals conflict.
47
- 4. **Choose the skin.** Use default unless the request asks for a minimal, cleaner, or more subtle look, which uses neutral; both contain the same controls. Use none only when the project builds its own controls. Ask only when those signals conflict.
47
+ 4. **Choose the skin.** Use default unless the request signals another look. Use neutral to apply a brand to a finished design, or for a minimal, cleaner, or more subtle look (it was formerly named minimal); it has the same controls as default. Use compat for plain, unbranded controls that blend into content pages, a step above native controls. Use scaffold when the user wants an unbranded foundation to make their own, such as shadcn-style components they restyle; it has the same controls as compat with minimal styling, meant to be restyled rather than used as is. All four support the same browsers. Use none only when the project builds its own controls. Ask only when those signals conflict.
48
48
  5. **Choose the media.** Infer the adapter from the source when possible. Mux wins for Mux playback URLs: stream.mux.com/\<playback-id>.m3u8 or a bare playback ID uses mux-video, mux-audio, or mux-background-video rather than hls. Mux static renditions such as .mp4 or .m4a files use html5-video or html5-audio. Other .m3u8 URLs use hls.
49
49
  6. **Choose extensions.** Mux Data is included by default for Mux video and audio sources. Add Google Cast when a standard or live video player with a ready-made skin should cast a compatible source. Use none when no extension is needed.
50
50
  7. **Choose how to install.** This guide uses packaged modules, which need a bundler. Match the package manager to the project lockfile. For an existing HTML site without a build step, use CDN scripts instead.
@@ -92,13 +92,13 @@ Defaulted options: project, preset, skin, source-url, media, extensions, templat
92
92
  ## Reproduce or change these instructions
93
93
 
94
94
  ```sh
95
- npx @videojs/cli@10.2.0 agents init --method packaged --framework html --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite
95
+ npx @videojs/cli@10.3.0 agents init --method packaged --framework html --project existing --preset video --skin default --media html5-video --extensions none --source-url https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4 --package-manager pnpm --template vite
96
96
  ```
97
97
 
98
98
  ## Install the packages
99
99
 
100
100
  ```bash
101
- pnpm add @videojs/html@10.2.0
101
+ pnpm add @videojs/html@10.3.0
102
102
  ```
103
103
 
104
104
  ## Add your player