@videojs/html 10.3.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 (272) hide show
  1. package/dist/default/define/audio/compat-skin.css +91 -38
  2. package/dist/default/define/audio/compat-skin2.js +1 -1
  3. package/dist/default/define/audio/neutral-skin.css +52 -31
  4. package/dist/default/define/audio/neutral-skin2.js +1 -1
  5. package/dist/default/define/audio/scaffold-skin.css +105 -40
  6. package/dist/default/define/audio/scaffold-skin2.js +1 -1
  7. package/dist/default/define/audio/skin.css +53 -31
  8. package/dist/default/define/audio/skin2.js +1 -1
  9. package/dist/default/define/live-audio/compat-skin.css +91 -38
  10. package/dist/default/define/live-audio/compat-skin2.js +1 -1
  11. package/dist/default/define/live-audio/neutral-skin.css +50 -29
  12. package/dist/default/define/live-audio/neutral-skin2.js +1 -1
  13. package/dist/default/define/live-audio/scaffold-skin.css +105 -40
  14. package/dist/default/define/live-audio/scaffold-skin2.js +1 -1
  15. package/dist/default/define/live-audio/skin.css +51 -29
  16. package/dist/default/define/live-audio/skin2.js +1 -1
  17. package/dist/default/define/live-video/compat-skin.css +142 -61
  18. package/dist/default/define/live-video/compat-skin2.js +1 -1
  19. package/dist/default/define/live-video/neutral-skin.css +241 -55
  20. package/dist/default/define/live-video/neutral-skin2.js +1 -1
  21. package/dist/default/define/live-video/scaffold-skin.css +159 -63
  22. package/dist/default/define/live-video/scaffold-skin2.js +1 -1
  23. package/dist/default/define/live-video/skin.css +245 -60
  24. package/dist/default/define/live-video/skin2.js +1 -1
  25. package/dist/default/define/ui/time-slider-markers.js +5 -0
  26. package/dist/default/define/video/compat-skin.css +150 -61
  27. package/dist/default/define/video/compat-skin2.js +1 -1
  28. package/dist/default/define/video/neutral-skin.css +322 -66
  29. package/dist/default/define/video/neutral-skin2.js +1 -1
  30. package/dist/default/define/video/scaffold-skin.css +168 -63
  31. package/dist/default/define/video/scaffold-skin2.js +1 -1
  32. package/dist/default/define/video/skin.css +348 -66
  33. package/dist/default/define/video/skin2.js +1 -1
  34. package/dist/default/index.js +3 -1
  35. package/dist/default/internal/skins/compat-audio/skin.css +91 -38
  36. package/dist/default/internal/skins/compat-live-audio/skin.css +91 -38
  37. package/dist/default/internal/skins/compat-live-video/skin.css +142 -61
  38. package/dist/default/internal/skins/compat-live-video/template.js +2 -0
  39. package/dist/default/internal/skins/compat-video/skin.css +150 -61
  40. package/dist/default/internal/skins/compat-video/template.js +2 -0
  41. package/dist/default/internal/skins/default-audio/skin.css +53 -31
  42. package/dist/default/internal/skins/default-live-audio/skin.css +51 -29
  43. package/dist/default/internal/skins/default-live-video/skin.css +245 -60
  44. package/dist/default/internal/skins/default-live-video/template.js +14 -0
  45. package/dist/default/internal/skins/default-video/register.js +3 -0
  46. package/dist/default/internal/skins/default-video/skin.css +348 -66
  47. package/dist/default/internal/skins/default-video/template.js +33 -5
  48. package/dist/default/internal/skins/neutral-audio/skin.css +52 -31
  49. package/dist/default/internal/skins/neutral-live-audio/skin.css +50 -29
  50. package/dist/default/internal/skins/neutral-live-video/skin.css +241 -55
  51. package/dist/default/internal/skins/neutral-live-video/template.js +14 -0
  52. package/dist/default/internal/skins/neutral-video/skin.css +322 -66
  53. package/dist/default/internal/skins/neutral-video/template.js +25 -11
  54. package/dist/default/internal/skins/scaffold-audio/skin.css +105 -40
  55. package/dist/default/internal/skins/scaffold-live-audio/skin.css +105 -40
  56. package/dist/default/internal/skins/scaffold-live-video/skin.css +159 -63
  57. package/dist/default/internal/skins/scaffold-live-video/template.js +2 -0
  58. package/dist/default/internal/skins/scaffold-video/skin.css +168 -63
  59. package/dist/default/internal/skins/scaffold-video/template.js +3 -1
  60. package/dist/default/media/background-video/adapter.js +13 -35
  61. package/dist/default/media/background-video/base.js +95 -0
  62. package/dist/default/media/background-video/template.js +13 -20
  63. package/dist/default/media/hls-background-video/adapter.js +16 -35
  64. package/dist/default/ui/captions-radio-group/element.js +14 -1
  65. package/dist/default/ui/controls/element.js +6 -3
  66. package/dist/default/ui/time-slider/markers.js +160 -0
  67. package/dist/default/version.js +1 -1
  68. package/dist/dev/define/audio/compat-skin.css +91 -38
  69. package/dist/dev/define/audio/compat-skin2.js +1 -1
  70. package/dist/dev/define/audio/compat-skin2.js.map +1 -1
  71. package/dist/dev/define/audio/neutral-skin.css +52 -31
  72. package/dist/dev/define/audio/neutral-skin2.js +1 -1
  73. package/dist/dev/define/audio/neutral-skin2.js.map +1 -1
  74. package/dist/dev/define/audio/scaffold-skin.css +105 -40
  75. package/dist/dev/define/audio/scaffold-skin2.js +1 -1
  76. package/dist/dev/define/audio/scaffold-skin2.js.map +1 -1
  77. package/dist/dev/define/audio/skin.css +53 -31
  78. package/dist/dev/define/audio/skin2.js +1 -1
  79. package/dist/dev/define/audio/skin2.js.map +1 -1
  80. package/dist/dev/define/live-audio/compat-skin.css +91 -38
  81. package/dist/dev/define/live-audio/compat-skin2.js +1 -1
  82. package/dist/dev/define/live-audio/compat-skin2.js.map +1 -1
  83. package/dist/dev/define/live-audio/neutral-skin.css +50 -29
  84. package/dist/dev/define/live-audio/neutral-skin2.js +1 -1
  85. package/dist/dev/define/live-audio/neutral-skin2.js.map +1 -1
  86. package/dist/dev/define/live-audio/scaffold-skin.css +105 -40
  87. package/dist/dev/define/live-audio/scaffold-skin2.js +1 -1
  88. package/dist/dev/define/live-audio/scaffold-skin2.js.map +1 -1
  89. package/dist/dev/define/live-audio/skin.css +51 -29
  90. package/dist/dev/define/live-audio/skin2.js +1 -1
  91. package/dist/dev/define/live-audio/skin2.js.map +1 -1
  92. package/dist/dev/define/live-video/compat-skin.css +142 -61
  93. package/dist/dev/define/live-video/compat-skin2.js +1 -1
  94. package/dist/dev/define/live-video/compat-skin2.js.map +1 -1
  95. package/dist/dev/define/live-video/neutral-skin.css +241 -55
  96. package/dist/dev/define/live-video/neutral-skin2.js +1 -1
  97. package/dist/dev/define/live-video/neutral-skin2.js.map +1 -1
  98. package/dist/dev/define/live-video/scaffold-skin.css +159 -63
  99. package/dist/dev/define/live-video/scaffold-skin2.js +1 -1
  100. package/dist/dev/define/live-video/scaffold-skin2.js.map +1 -1
  101. package/dist/dev/define/live-video/skin.css +245 -60
  102. package/dist/dev/define/live-video/skin2.js +1 -1
  103. package/dist/dev/define/live-video/skin2.js.map +1 -1
  104. package/dist/dev/define/ui/time-slider-markers.d.ts +8 -0
  105. package/dist/dev/define/ui/time-slider-markers.js +7 -0
  106. package/dist/dev/define/ui/time-slider-markers.js.map +1 -0
  107. package/dist/dev/define/video/compat-skin.css +150 -61
  108. package/dist/dev/define/video/compat-skin2.js +1 -1
  109. package/dist/dev/define/video/compat-skin2.js.map +1 -1
  110. package/dist/dev/define/video/neutral-skin.css +322 -66
  111. package/dist/dev/define/video/neutral-skin2.js +1 -1
  112. package/dist/dev/define/video/neutral-skin2.js.map +1 -1
  113. package/dist/dev/define/video/scaffold-skin.css +168 -63
  114. package/dist/dev/define/video/scaffold-skin2.js +1 -1
  115. package/dist/dev/define/video/scaffold-skin2.js.map +1 -1
  116. package/dist/dev/define/video/skin.css +348 -66
  117. package/dist/dev/define/video/skin2.js +1 -1
  118. package/dist/dev/define/video/skin2.js.map +1 -1
  119. package/dist/dev/index.d.ts +5 -4
  120. package/dist/dev/index.js +3 -1
  121. package/dist/dev/internal/skins/compat-audio/skin.css +91 -38
  122. package/dist/dev/internal/skins/compat-live-audio/skin.css +91 -38
  123. package/dist/dev/internal/skins/compat-live-video/skin.css +142 -61
  124. package/dist/dev/internal/skins/compat-live-video/template.js +2 -0
  125. package/dist/dev/internal/skins/compat-live-video/template.js.map +1 -1
  126. package/dist/dev/internal/skins/compat-video/skin.css +150 -61
  127. package/dist/dev/internal/skins/compat-video/template.js +2 -0
  128. package/dist/dev/internal/skins/compat-video/template.js.map +1 -1
  129. package/dist/dev/internal/skins/default-audio/skin.css +53 -31
  130. package/dist/dev/internal/skins/default-live-audio/skin.css +51 -29
  131. package/dist/dev/internal/skins/default-live-video/register.js.map +1 -1
  132. package/dist/dev/internal/skins/default-live-video/skin.css +245 -60
  133. package/dist/dev/internal/skins/default-live-video/template.js +14 -0
  134. package/dist/dev/internal/skins/default-live-video/template.js.map +1 -1
  135. package/dist/dev/internal/skins/default-video/register.d.ts +1 -0
  136. package/dist/dev/internal/skins/default-video/register.js +3 -0
  137. package/dist/dev/internal/skins/default-video/register.js.map +1 -1
  138. package/dist/dev/internal/skins/default-video/skin.css +348 -66
  139. package/dist/dev/internal/skins/default-video/template.js +33 -5
  140. package/dist/dev/internal/skins/default-video/template.js.map +1 -1
  141. package/dist/dev/internal/skins/neutral-audio/skin.css +52 -31
  142. package/dist/dev/internal/skins/neutral-live-audio/skin.css +50 -29
  143. package/dist/dev/internal/skins/neutral-live-video/register.js.map +1 -1
  144. package/dist/dev/internal/skins/neutral-live-video/skin.css +241 -55
  145. package/dist/dev/internal/skins/neutral-live-video/template.js +14 -0
  146. package/dist/dev/internal/skins/neutral-live-video/template.js.map +1 -1
  147. package/dist/dev/internal/skins/neutral-video/register.js.map +1 -1
  148. package/dist/dev/internal/skins/neutral-video/skin.css +322 -66
  149. package/dist/dev/internal/skins/neutral-video/template.js +25 -11
  150. package/dist/dev/internal/skins/neutral-video/template.js.map +1 -1
  151. package/dist/dev/internal/skins/scaffold-audio/skin.css +105 -40
  152. package/dist/dev/internal/skins/scaffold-live-audio/skin.css +105 -40
  153. package/dist/dev/internal/skins/scaffold-live-video/skin.css +159 -63
  154. package/dist/dev/internal/skins/scaffold-live-video/template.js +2 -0
  155. package/dist/dev/internal/skins/scaffold-live-video/template.js.map +1 -1
  156. package/dist/dev/internal/skins/scaffold-video/skin.css +168 -63
  157. package/dist/dev/internal/skins/scaffold-video/template.js +3 -1
  158. package/dist/dev/internal/skins/scaffold-video/template.js.map +1 -1
  159. package/dist/dev/media/background-video/adapter.d.ts +7 -13
  160. package/dist/dev/media/background-video/adapter.js +13 -35
  161. package/dist/dev/media/background-video/adapter.js.map +1 -1
  162. package/dist/dev/media/background-video/base.d.ts +44 -0
  163. package/dist/dev/media/background-video/base.js +97 -0
  164. package/dist/dev/media/background-video/base.js.map +1 -0
  165. package/dist/dev/media/background-video/template.d.ts +3 -0
  166. package/dist/dev/media/background-video/template.js +13 -20
  167. package/dist/dev/media/background-video/template.js.map +1 -1
  168. package/dist/dev/media/hls-background-video/adapter.d.ts +8 -18
  169. package/dist/dev/media/hls-background-video/adapter.js +16 -35
  170. package/dist/dev/media/hls-background-video/adapter.js.map +1 -1
  171. package/dist/dev/ui/captions-button/element.d.ts +2 -0
  172. package/dist/dev/ui/captions-radio-group/element.d.ts +2 -1
  173. package/dist/dev/ui/captions-radio-group/element.js +14 -1
  174. package/dist/dev/ui/captions-radio-group/element.js.map +1 -1
  175. package/dist/dev/ui/controls/element.d.ts +2 -2
  176. package/dist/dev/ui/controls/element.js +6 -3
  177. package/dist/dev/ui/controls/element.js.map +1 -1
  178. package/dist/dev/ui/time-slider/markers.d.ts +48 -0
  179. package/dist/dev/ui/time-slider/markers.js +165 -0
  180. package/dist/dev/ui/time-slider/markers.js.map +1 -0
  181. package/dist/dev/version.js +1 -1
  182. package/docs/README.md +1 -1
  183. package/docs/guides/autoplay.md +1 -1
  184. package/docs/guides/background-video.md +2 -4
  185. package/docs/guides/browser-support.md +22 -16
  186. package/docs/guides/cdn.md +9 -9
  187. package/docs/guides/customize-skins.md +3 -0
  188. package/docs/guides/installation-cdn.md +2 -2
  189. package/docs/guides/installation-shadcn.md +1 -1
  190. package/docs/guides/installation-svelte.md +2 -2
  191. package/docs/guides/installation-vue.md +2 -2
  192. package/docs/guides/installation.md +2 -2
  193. package/docs/guides/internationalization.md +5 -5
  194. package/docs/guides/keyboard-shortcuts.md +10 -0
  195. package/docs/guides/llms.txt +2 -1
  196. package/docs/guides/migrate-from-media-chrome.md +4 -4
  197. package/docs/guides/migrate-from-mux-player.md +6 -6
  198. package/docs/guides/migrate-from-plyr.md +7 -7
  199. package/docs/guides/migrate-from-video-js-8.md +6 -6
  200. package/docs/guides/migrate-from-vidstack.md +4 -4
  201. package/docs/guides/presets.md +4 -4
  202. package/docs/guides/self-hosting.md +5 -5
  203. package/docs/guides/skins.md +8 -7
  204. package/docs/guides/timed-metadata.md +318 -0
  205. package/docs/llms.txt +16 -2
  206. package/docs/reference/api/chapters-track.md +71 -0
  207. package/docs/reference/api/define-text-track.md +113 -0
  208. package/docs/reference/api/feature-text-cues.md +133 -0
  209. package/docs/reference/api/feature-text-tracks.md +11 -5
  210. package/docs/reference/api/get-active-text-cues.md +69 -0
  211. package/docs/reference/api/get-text-cues.md +69 -0
  212. package/docs/reference/api/get-text-tracks.md +41 -0
  213. package/docs/reference/api/hls-metadata-track.md +48 -0
  214. package/docs/reference/api/html-create-player.md +2 -2
  215. package/docs/reference/api/llms.txt +13 -1
  216. package/docs/reference/api/media-capabilities.md +4 -4
  217. package/docs/reference/api/player-store.md +9 -4
  218. package/docs/reference/api/preset-audio.md +3 -1
  219. package/docs/reference/api/preset-background.md +1 -1
  220. package/docs/reference/api/preset-live-audio.md +3 -1
  221. package/docs/reference/api/preset-live-video.md +2 -1
  222. package/docs/reference/api/preset-video.md +2 -1
  223. package/docs/reference/components/audio-compat-skin.md +1 -1
  224. package/docs/reference/components/audio-neutral-skin.md +1 -1
  225. package/docs/reference/components/audio-scaffold-skin.md +1 -1
  226. package/docs/reference/components/audio-skin.md +1 -1
  227. package/docs/reference/components/audio.md +19 -1
  228. package/docs/reference/components/background-video-skin.md +1 -1
  229. package/docs/reference/components/background-video.md +26 -11
  230. package/docs/reference/components/captions-radio-group.md +1 -1
  231. package/docs/reference/components/cloudflare-video.md +1 -1
  232. package/docs/reference/components/controls.md +16 -3
  233. package/docs/reference/components/dailymotion-video.md +2 -2
  234. package/docs/reference/components/dash-video.md +20 -3
  235. package/docs/reference/components/google-analytics.md +1 -1
  236. package/docs/reference/components/google-cast.md +1 -1
  237. package/docs/reference/components/hls-audio.md +56 -2
  238. package/docs/reference/components/hls-background-video.md +22 -5
  239. package/docs/reference/components/hls-video.md +99 -7
  240. package/docs/reference/components/hlsjs-video.md +52 -13
  241. package/docs/reference/components/live-audio-compat-skin.md +1 -1
  242. package/docs/reference/components/live-audio-neutral-skin.md +1 -1
  243. package/docs/reference/components/live-audio-scaffold-skin.md +1 -1
  244. package/docs/reference/components/live-audio-skin.md +1 -1
  245. package/docs/reference/components/live-video-compat-skin.md +5 -1
  246. package/docs/reference/components/live-video-neutral-skin.md +5 -1
  247. package/docs/reference/components/live-video-scaffold-skin.md +5 -1
  248. package/docs/reference/components/live-video-skin.md +5 -1
  249. package/docs/reference/components/llms.txt +2 -1
  250. package/docs/reference/components/mixcloud-audio.md +2 -2
  251. package/docs/reference/components/mux-audio.md +34 -3
  252. package/docs/reference/components/mux-background-video.md +1 -1
  253. package/docs/reference/components/mux-data.md +1 -1
  254. package/docs/reference/components/mux-video.md +17 -3
  255. package/docs/reference/components/native-hls-video.md +32 -3
  256. package/docs/reference/components/playerjs-video.md +2 -2
  257. package/docs/reference/components/shaka-video.md +2 -3
  258. package/docs/reference/components/soundcloud-audio.md +2 -2
  259. package/docs/reference/components/spotify-audio.md +2 -2
  260. package/docs/reference/components/tiktok-video.md +2 -2
  261. package/docs/reference/components/time-slider-markers.md +194 -0
  262. package/docs/reference/components/time-slider.md +14 -0
  263. package/docs/reference/components/twitch-video.md +1 -1
  264. package/docs/reference/components/video-compat-skin.md +5 -1
  265. package/docs/reference/components/video-neutral-skin.md +5 -1
  266. package/docs/reference/components/video-scaffold-skin.md +5 -1
  267. package/docs/reference/components/video-skin.md +5 -1
  268. package/docs/reference/components/video.md +19 -1
  269. package/docs/reference/components/vimeo-video.md +1 -2
  270. package/docs/reference/components/wistia-video.md +1 -1
  271. package/docs/reference/components/youtube-video.md +1 -2
  272. package/package.json +48 -48
@@ -3,13 +3,13 @@ import { PlayerController } from "../../player/controller.js";
3
3
  import { UIElement } from "../ui-element.js";
4
4
  import { controlsContext } from "./context.js";
5
5
  import { ControlsCore, ControlsDataAttrs, POPUP_HOST_SELECTOR } from "@videojs/core";
6
- import { applyStateDataAttrs, logMissingFeature, selectControls } from "@videojs/core/dom";
6
+ import { applyStateDataAttrs, logMissingFeature, selectControls, selectPlayback } from "@videojs/core/dom";
7
7
  import { ContextProvider } from "@videojs/element/context";
8
8
  import { isFunction } from "@videojs/utils/predicate";
9
9
  //#region src/ui/controls/element.ts
10
10
  /**
11
- * Tracks controls visibility, reflects it as data attributes, and shares it with its descendant controls parts. Hiding
12
- * the controls closes any popup open inside it.
11
+ * Tracks controls visibility and whether playback has started, reflects them as data attributes, and shares them with
12
+ * its descendant controls parts. Hiding the controls closes any popup open inside it.
13
13
  */
14
14
  var ControlsElement = class extends UIElement {
15
15
  constructor(..._args) {
@@ -17,6 +17,7 @@ var ControlsElement = class extends UIElement {
17
17
  this.visibility = ControlsCore.defaultProps.visibility;
18
18
  this.#core = new ControlsCore();
19
19
  this.#mediaState = new PlayerController(this, playerContext, selectControls);
20
+ this.#playback = new PlayerController(this, playerContext, selectPlayback);
20
21
  this.#provider = new ContextProvider(this, { context: controlsContext });
21
22
  this.#visible = true;
22
23
  }
@@ -28,6 +29,7 @@ var ControlsElement = class extends UIElement {
28
29
  }
29
30
  #core;
30
31
  #mediaState;
32
+ #playback;
31
33
  #provider;
32
34
  #visible;
33
35
  connectedCallback() {
@@ -38,6 +40,7 @@ var ControlsElement = class extends UIElement {
38
40
  super.update(_changed);
39
41
  this.#core.setProps({ visibility: this.visibility });
40
42
  this.#core.setMedia(this.#mediaState.value ?? null);
43
+ this.#core.setPlayback(this.#playback.value ?? null);
41
44
  const state = this.#core.getState();
42
45
  if (!state) return;
43
46
  applyStateDataAttrs(this, state, ControlsDataAttrs);
@@ -1 +1 @@
1
- {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/controls/element.ts"],"sourcesContent":["import { ControlsCore, ControlsDataAttrs, type ControlsVisibility, POPUP_HOST_SELECTOR } from '@videojs/core';\nimport { applyStateDataAttrs, logMissingFeature, selectControls } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextProvider } from '@videojs/element/context';\nimport { isFunction } from '@videojs/utils/predicate';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { UIElement } from '../ui-element';\nimport { controlsContext } from './context';\n\n/**\n * Tracks controls visibility, reflects it as data attributes, and shares it with its descendant controls parts. Hiding\n * the controls closes any popup open inside it.\n */\nexport class ControlsElement extends UIElement {\n static readonly tagName = 'media-controls';\n\n static override properties = {\n visibility: { type: String },\n } satisfies PropertyDeclarationMap<'visibility'>;\n\n visibility: ControlsVisibility = ControlsCore.defaultProps.visibility;\n\n readonly #core = new ControlsCore();\n readonly #mediaState = new PlayerController(this, playerContext, selectControls);\n readonly #provider = new ContextProvider(this, { context: controlsContext });\n #visible = true;\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (__DEV__ && this.visibility === 'auto' && !this.#mediaState.value && this.#mediaState.displayName) {\n logMissingFeature(this.localName, this.#mediaState.displayName);\n }\n }\n\n protected override update(_changed: PropertyValues): void {\n super.update(_changed);\n\n this.#core.setProps({ visibility: this.visibility });\n this.#core.setMedia(this.#mediaState.value ?? null);\n\n const state = this.#core.getState();\n if (!state) return;\n\n applyStateDataAttrs(this, state, ControlsDataAttrs);\n\n this.#provider.setValue({\n state,\n stateAttrMap: ControlsDataAttrs,\n });\n\n const wasVisible = this.#visible;\n\n this.#visible = state.visible;\n\n if (wasVisible && !state.visible) {\n this.#closeOwnedOverlays();\n }\n }\n\n #closeOwnedOverlays(): void {\n for (const element of this.querySelectorAll(POPUP_HOST_SELECTOR)) {\n const host = element as Element & { close?: unknown };\n if (!isFunction(host.close)) continue;\n\n host.close('imperative-action');\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,kBAAb,cAAqC,UAAU;;;EAOZ,KAAA,aAAA,aAAa,aAAa;EAE1C,KAAA,QAAA,IAAI,aAAa;EACX,KAAA,cAAA,IAAI,iBAAiB,MAAM,eAAe,cAAc;EAC1D,KAAA,YAAA,IAAI,gBAAgB,MAAM,EAAE,SAAS,gBAAgB,CAAC;EAChE,KAAA,WAAA;;;EAXe,KAAA,UAAA;;;EAEG,KAAA,aAAA,EAC3B,YAAY,EAAE,MAAM,OAAO,EAC7B;;CAIA;CACA;CACA;CACA;CAEA,oBAAmC;EACjC,MAAM,kBAAkB;EAExB,IAAe,KAAK,eAAe,UAAU,CAAC,KAAK,YAAY,SAAS,KAAK,YAAY,aACvF,kBAAkB,KAAK,WAAW,KAAK,YAAY,WAAW;CAElE;CAEA,OAA0B,UAAgC;EACxD,MAAM,OAAO,QAAQ;EAErB,KAAK,MAAM,SAAS,EAAE,YAAY,KAAK,WAAW,CAAC;EACnD,KAAK,MAAM,SAAS,KAAK,YAAY,SAAS,IAAI;EAElD,MAAM,QAAQ,KAAK,MAAM,SAAS;EAClC,IAAI,CAAC,OAAO;EAEZ,oBAAoB,MAAM,OAAO,iBAAiB;EAElD,KAAK,UAAU,SAAS;GACtB;GACA,cAAc;EAChB,CAAC;EAED,MAAM,aAAa,KAAK;EAExB,KAAK,WAAW,MAAM;EAEtB,IAAI,cAAc,CAAC,MAAM,SACvB,KAAK,oBAAoB;CAE7B;CAEA,sBAA4B;EAC1B,KAAK,MAAM,WAAW,KAAK,iBAAiB,mBAAmB,GAAG;GAChE,MAAM,OAAO;GACb,IAAI,CAAC,WAAW,KAAK,KAAK,GAAG;GAE7B,KAAK,MAAM,mBAAmB;EAChC;CACF;AACF"}
1
+ {"version":3,"file":"element.js","names":[],"sources":["../../../../src/ui/controls/element.ts"],"sourcesContent":["import { ControlsCore, ControlsDataAttrs, type ControlsVisibility, POPUP_HOST_SELECTOR } from '@videojs/core';\nimport { applyStateDataAttrs, logMissingFeature, selectControls, selectPlayback } from '@videojs/core/dom';\nimport type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';\nimport { ContextProvider } from '@videojs/element/context';\nimport { isFunction } from '@videojs/utils/predicate';\n\nimport { playerContext } from '../../player/context';\nimport { PlayerController } from '../../player/controller';\nimport { UIElement } from '../ui-element';\nimport { controlsContext } from './context';\n\n/**\n * Tracks controls visibility and whether playback has started, reflects them as data attributes, and shares them with\n * its descendant controls parts. Hiding the controls closes any popup open inside it.\n */\nexport class ControlsElement extends UIElement {\n static readonly tagName = 'media-controls';\n\n static override properties = {\n visibility: { type: String },\n } satisfies PropertyDeclarationMap<'visibility'>;\n\n visibility: ControlsVisibility = ControlsCore.defaultProps.visibility;\n\n readonly #core = new ControlsCore();\n readonly #mediaState = new PlayerController(this, playerContext, selectControls);\n readonly #playback = new PlayerController(this, playerContext, selectPlayback);\n readonly #provider = new ContextProvider(this, { context: controlsContext });\n #visible = true;\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n if (__DEV__ && this.visibility === 'auto' && !this.#mediaState.value && this.#mediaState.displayName) {\n logMissingFeature(this.localName, this.#mediaState.displayName);\n }\n }\n\n protected override update(_changed: PropertyValues): void {\n super.update(_changed);\n\n this.#core.setProps({ visibility: this.visibility });\n this.#core.setMedia(this.#mediaState.value ?? null);\n this.#core.setPlayback(this.#playback.value ?? null);\n\n const state = this.#core.getState();\n if (!state) return;\n\n applyStateDataAttrs(this, state, ControlsDataAttrs);\n\n this.#provider.setValue({\n state,\n stateAttrMap: ControlsDataAttrs,\n });\n\n const wasVisible = this.#visible;\n\n this.#visible = state.visible;\n\n if (wasVisible && !state.visible) {\n this.#closeOwnedOverlays();\n }\n }\n\n #closeOwnedOverlays(): void {\n for (const element of this.querySelectorAll(POPUP_HOST_SELECTOR)) {\n const host = element as Element & { close?: unknown };\n if (!isFunction(host.close)) continue;\n\n host.close('imperative-action');\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,IAAa,kBAAb,cAAqC,UAAU;;;EAOZ,KAAA,aAAA,aAAa,aAAa;EAE1C,KAAA,QAAA,IAAI,aAAa;EACX,KAAA,cAAA,IAAI,iBAAiB,MAAM,eAAe,cAAc;EAC1D,KAAA,YAAA,IAAI,iBAAiB,MAAM,eAAe,cAAc;EACxD,KAAA,YAAA,IAAI,gBAAgB,MAAM,EAAE,SAAS,gBAAgB,CAAC;EAChE,KAAA,WAAA;;;EAZe,KAAA,UAAA;;;EAEG,KAAA,aAAA,EAC3B,YAAY,EAAE,MAAM,OAAO,EAC7B;;CAIA;CACA;CACA;CACA;CACA;CAEA,oBAAmC;EACjC,MAAM,kBAAkB;EAExB,IAAe,KAAK,eAAe,UAAU,CAAC,KAAK,YAAY,SAAS,KAAK,YAAY,aACvF,kBAAkB,KAAK,WAAW,KAAK,YAAY,WAAW;CAElE;CAEA,OAA0B,UAAgC;EACxD,MAAM,OAAO,QAAQ;EAErB,KAAK,MAAM,SAAS,EAAE,YAAY,KAAK,WAAW,CAAC;EACnD,KAAK,MAAM,SAAS,KAAK,YAAY,SAAS,IAAI;EAClD,KAAK,MAAM,YAAY,KAAK,UAAU,SAAS,IAAI;EAEnD,MAAM,QAAQ,KAAK,MAAM,SAAS;EAClC,IAAI,CAAC,OAAO;EAEZ,oBAAoB,MAAM,OAAO,iBAAiB;EAElD,KAAK,UAAU,SAAS;GACtB;GACA,cAAc;EAChB,CAAC;EAED,MAAM,aAAa,KAAK;EAExB,KAAK,WAAW,MAAM;EAEtB,IAAI,cAAc,CAAC,MAAM,SACvB,KAAK,oBAAoB;CAE7B;CAEA,sBAA4B;EAC1B,KAAK,MAAM,WAAW,KAAK,iBAAiB,mBAAmB,GAAG;GAChE,MAAM,OAAO;GACb,IAAI,CAAC,WAAW,KAAK,KAAK,GAAG;GAE7B,KAAK,MAAM,mBAAmB;EAChC;CACF;AACF"}
@@ -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.3.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.3.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
 
@@ -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,25 +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
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.
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.
20
26
 
21
27
  ## CSS requirements
22
28
 
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).
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).
24
30
 
25
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.
26
32
 
@@ -40,16 +46,16 @@ The table reads the first fully supporting version of each feature from [caniuse
40
46
  | --- | --- | --- | --- | --- |
41
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% |
42
48
  | [`@layer`](https://caniuse.com/css-cascade-layers) Required. No component styling. | 99 | 97 | 15.4 | 96.0% |
43
- | [`:has()`](https://caniuse.com/css-has) Required. Menu and slider focus states are lost. | 105 | 121 | 15.4 | 94.8% |
44
- | [`@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% |
45
51
  | [`Media query range syntax`](https://caniuse.com/css-media-range-syntax) Required. Large-screen sizing is lost. | 104 | 63 | 16.4 | 94.8% |
46
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% |
47
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% |
48
54
  | [`scrollbar-color and scrollbar-width`](https://caniuse.com/css-scrollbar) Degrades. Menus show default scrollbars. | 121 | 64 | 26.2 | 87.0% |
49
- | [`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% |
50
56
  | [`Anchor positioning`](https://caniuse.com/css-anchor-positioning) Has a fallback. Popups are positioned by JavaScript. | Not supported | Not supported | 27 | 0.0% |
51
57
 
52
- 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.
53
59
 
54
60
  ## Build the skin CSS yourself
55
61
 
@@ -37,8 +37,8 @@ A skin bundle registers every UI element its skin is built from. Any element the
37
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.
38
38
 
39
39
  ```html
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>
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>
42
42
  ```
43
43
 
44
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.
@@ -48,8 +48,8 @@ Each path mirrors its npm subpath: `ui/play-button.js` registers the element tha
48
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:
49
49
 
50
50
  ```html
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>
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>
53
53
  ```
54
54
 
55
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.
@@ -59,9 +59,9 @@ Each path mirrors its npm subpath. Elements with more than one playback engine k
59
59
  Each [extension](./architecture.md) is its own bundle under `extensions/`, loaded beside the player and media bundles:
60
60
 
61
61
  ```html
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>
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>
65
65
  ```
66
66
 
67
67
  As with media, each path mirrors its npm subpath: `extensions/mux-data.js` registers the element that `@videojs/html/extensions/mux-data` does.
@@ -71,8 +71,8 @@ As with media, each path mirrors its npm subpath: `extensions/mux-data.js` regis
71
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:
72
72
 
73
73
  ```html
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>
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>
76
76
  ```
77
77
 
78
78
  One bundle exists per locale pack, plus bare-language aliases such as `pt`. For custom strings, see [Internationalize the player](./internationalization.md).
@@ -31,6 +31,7 @@ Packaged skins expose [CSS custom properties](https://developer.mozilla.org/en-U
31
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` |
32
32
  | `--media-border-color` | The hairline around the player | [`<color>`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) | `transparent` |
33
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` |
34
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` |
35
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` |
36
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` |
@@ -40,6 +41,8 @@ If you omit `--media-accent-text-color`, the skins choose a contrasting text col
40
41
 
41
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.
42
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.
45
+
43
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).
44
47
 
45
48
  ### Set the color scheme
@@ -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.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
92
+ npx @videojs/cli@10.4.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.3.0/video.js"></script>
104
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video.js"></script>
105
105
  ```
106
106
 
107
107
  ## Add your player
@@ -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.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
97
+ npx @videojs/cli@10.4.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
@@ -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.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
93
+ npx @videojs/cli@10.4.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.3.0
99
+ pnpm add @videojs/html@10.4.0
100
100
  ```
101
101
 
102
102
  ## Add your player
@@ -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.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
93
+ npx @videojs/cli@10.4.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.3.0
99
+ pnpm add @videojs/html@10.4.0
100
100
  ```
101
101
 
102
102
  ## Register custom elements
@@ -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.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
95
+ npx @videojs/cli@10.4.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.3.0
101
+ pnpm add @videojs/html@10.4.0
102
102
  ```
103
103
 
104
104
  ## Add your player
@@ -199,8 +199,8 @@ import '@videojs/html/i18n/locales/ja/register';
199
199
  CDN locale modules call `registerI18n` on import. Pin every URL to the same version:
200
200
 
201
201
  ```html
202
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/locales/es.js"></script>
203
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/video.js"></script>
202
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/locales/es.js"></script>
203
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video.js"></script>
204
204
  ```
205
205
 
206
206
  Use the same pinned version for your locale pack, `i18n.js`, and player script. Copies loaded in the same window share a registry; matching versions keeps their translation keys and APIs compatible. Import the full locale pack before partial overrides, and register before the player loads to translate the first render:
@@ -208,15 +208,15 @@ Use the same pinned version for your locale pack, `i18n.js`, and player script.
208
208
  **my-locale.js**
209
209
 
210
210
  ```js
211
- import 'https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/locales/es.js';
212
- import { registerI18n } from 'https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/i18n.js';
211
+ import 'https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/locales/es.js';
212
+ import { registerI18n } from 'https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/i18n.js';
213
213
 
214
214
  registerI18n('es', { buttons: { play: 'Reproducir', pause: 'Pausa' } });
215
215
  ```
216
216
 
217
217
  ```html
218
218
  <script type="module" src="/my-locale.js"></script>
219
- <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.3.0/video.js"></script>
219
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/cdn@10.4.0/video.js"></script>
220
220
  ```
221
221
 
222
222
  ### Switch locale at runtime
@@ -73,6 +73,16 @@ How the container splits into regions depends on which regions you bind for the
73
73
 
74
74
  For a packaged skin, [add the skin source to your project](./customize-skins.md#style-skin-source) and edit its `<media-hotkey>` declarations. Adding another binding alongside the packaged skin does not replace its bindings. For a custom UI, edit your own `<media-hotkey>` set directly.
75
75
 
76
+ ### Tap to play on touch
77
+
78
+ The packaged skins toggle controls on a touch tap, and the center play button starts playback. To play and pause with a tap anywhere on the video instead, bind `togglePaused` in place of `toggleControls` for touch:
79
+
80
+ ```html
81
+ <media-gesture type="tap" action="togglePaused" pointer="touch"></media-gesture>
82
+ ```
83
+
84
+ A tap then pauses playback as well, and taps no longer show or hide the controls.
85
+
76
86
  ### Seek step size
77
87
 
78
88
  Set the `seekStep` distance with `value`. Match it to your content: 5 seconds for short clips, 15 or 30 seconds for long-form video.