@videojs/html 10.2.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/dist/default/define/audio/compat-skin.css +161 -41
  2. package/dist/default/define/audio/compat-skin2.js +1 -1
  3. package/dist/default/define/audio/neutral-skin.css +73 -34
  4. package/dist/default/define/audio/neutral-skin2.js +1 -1
  5. package/dist/default/define/audio/scaffold-skin.css +166 -42
  6. package/dist/default/define/audio/scaffold-skin2.js +1 -1
  7. package/dist/default/define/audio/skin.css +72 -34
  8. package/dist/default/define/audio/skin2.js +1 -1
  9. package/dist/default/define/extensions/google-analytics.js +6 -0
  10. package/dist/default/define/live-audio/compat-skin.css +143 -41
  11. package/dist/default/define/live-audio/compat-skin2.js +1 -1
  12. package/dist/default/define/live-audio/neutral-skin.css +56 -31
  13. package/dist/default/define/live-audio/neutral-skin2.js +1 -1
  14. package/dist/default/define/live-audio/scaffold-skin.css +148 -42
  15. package/dist/default/define/live-audio/scaffold-skin2.js +1 -1
  16. package/dist/default/define/live-audio/skin.css +55 -31
  17. package/dist/default/define/live-audio/skin2.js +1 -1
  18. package/dist/default/define/live-video/compat-skin.css +234 -51
  19. package/dist/default/define/live-video/compat-skin2.js +1 -1
  20. package/dist/default/define/live-video/neutral-skin.css +300 -46
  21. package/dist/default/define/live-video/neutral-skin2.js +1 -1
  22. package/dist/default/define/live-video/scaffold-skin.css +231 -53
  23. package/dist/default/define/live-video/scaffold-skin2.js +1 -1
  24. package/dist/default/define/live-video/skin.css +314 -49
  25. package/dist/default/define/live-video/skin2.js +1 -1
  26. package/dist/default/define/media/dailymotion-video.js +11 -0
  27. package/dist/default/define/media/mixcloud-audio.js +11 -0
  28. package/dist/default/define/media/soundcloud-audio.js +11 -0
  29. package/dist/default/define/ui/time-slider-markers.js +5 -0
  30. package/dist/default/define/video/compat-skin.css +242 -51
  31. package/dist/default/define/video/compat-skin2.js +1 -1
  32. package/dist/default/define/video/neutral-skin.css +382 -57
  33. package/dist/default/define/video/neutral-skin2.js +1 -1
  34. package/dist/default/define/video/scaffold-skin.css +240 -53
  35. package/dist/default/define/video/scaffold-skin2.js +1 -1
  36. package/dist/default/define/video/skin.css +415 -55
  37. package/dist/default/define/video/skin2.js +1 -1
  38. package/dist/default/extensions/google-analytics/extension.js +75 -0
  39. package/dist/default/extensions/mux-data/extension.js +7 -2
  40. package/dist/default/index.js +3 -1
  41. package/dist/default/internal/skins/compat-audio/skin.css +161 -41
  42. package/dist/default/internal/skins/compat-audio/template.js +1 -1
  43. package/dist/default/internal/skins/compat-live-audio/skin.css +143 -41
  44. package/dist/default/internal/skins/compat-live-video/skin.css +234 -51
  45. package/dist/default/internal/skins/compat-live-video/template.js +2 -0
  46. package/dist/default/internal/skins/compat-video/skin.css +242 -51
  47. package/dist/default/internal/skins/compat-video/template.js +2 -0
  48. package/dist/default/internal/skins/default-audio/skin.css +72 -34
  49. package/dist/default/internal/skins/default-live-audio/skin.css +55 -31
  50. package/dist/default/internal/skins/default-live-video/skin.css +314 -49
  51. package/dist/default/internal/skins/default-live-video/template.js +14 -0
  52. package/dist/default/internal/skins/default-video/register.js +3 -0
  53. package/dist/default/internal/skins/default-video/skin.css +415 -55
  54. package/dist/default/internal/skins/default-video/template.js +33 -5
  55. package/dist/default/internal/skins/neutral-audio/skin.css +73 -34
  56. package/dist/default/internal/skins/neutral-live-audio/skin.css +56 -31
  57. package/dist/default/internal/skins/neutral-live-video/skin.css +300 -46
  58. package/dist/default/internal/skins/neutral-live-video/template.js +14 -0
  59. package/dist/default/internal/skins/neutral-video/skin.css +382 -57
  60. package/dist/default/internal/skins/neutral-video/template.js +25 -11
  61. package/dist/default/internal/skins/scaffold-audio/skin.css +166 -42
  62. package/dist/default/internal/skins/scaffold-audio/template.js +1 -1
  63. package/dist/default/internal/skins/scaffold-live-audio/skin.css +148 -42
  64. package/dist/default/internal/skins/scaffold-live-video/skin.css +231 -53
  65. package/dist/default/internal/skins/scaffold-live-video/template.js +2 -0
  66. package/dist/default/internal/skins/scaffold-video/skin.css +240 -53
  67. package/dist/default/internal/skins/scaffold-video/template.js +3 -1
  68. package/dist/default/media/background-video/adapter.js +13 -35
  69. package/dist/default/media/background-video/base.js +95 -0
  70. package/dist/default/media/background-video/template.js +13 -20
  71. package/dist/default/media/dailymotion-video/adapter.js +31 -0
  72. package/dist/default/media/hls-background-video/adapter.js +23 -35
  73. package/dist/default/media/mixcloud-audio/adapter.js +16 -0
  74. package/dist/default/media/soundcloud-audio/adapter.js +16 -0
  75. package/dist/default/player/element.js +1 -5
  76. package/dist/default/presets/background/skin.js +2 -2
  77. package/dist/default/presets/skin.js +4 -3
  78. package/dist/default/ui/captions-radio-group/element.js +14 -1
  79. package/dist/default/ui/controls/element.js +6 -3
  80. package/dist/default/ui/dialog/element.js +13 -1
  81. package/dist/default/ui/gesture/element.js +3 -0
  82. package/dist/default/ui/media-button-element.js +10 -1
  83. package/dist/default/ui/playback-rate-button/element.js +7 -2
  84. package/dist/default/ui/playback-rate-radio-group/element.js +10 -3
  85. package/dist/default/ui/radio-options/controller.js +18 -5
  86. package/dist/default/ui/time-slider/markers.js +160 -0
  87. package/dist/default/version.js +1 -1
  88. package/dist/dev/define/audio/compat-skin.css +161 -41
  89. package/dist/dev/define/audio/compat-skin2.js +1 -1
  90. package/dist/dev/define/audio/compat-skin2.js.map +1 -1
  91. package/dist/dev/define/audio/neutral-skin.css +73 -34
  92. package/dist/dev/define/audio/neutral-skin2.js +1 -1
  93. package/dist/dev/define/audio/neutral-skin2.js.map +1 -1
  94. package/dist/dev/define/audio/scaffold-skin.css +166 -42
  95. package/dist/dev/define/audio/scaffold-skin2.js +1 -1
  96. package/dist/dev/define/audio/scaffold-skin2.js.map +1 -1
  97. package/dist/dev/define/audio/skin.css +72 -34
  98. package/dist/dev/define/audio/skin2.js +1 -1
  99. package/dist/dev/define/audio/skin2.js.map +1 -1
  100. package/dist/dev/define/extensions/google-analytics.d.ts +11 -0
  101. package/dist/dev/define/extensions/google-analytics.js +8 -0
  102. package/dist/dev/define/extensions/google-analytics.js.map +1 -0
  103. package/dist/dev/define/live-audio/compat-skin.css +143 -41
  104. package/dist/dev/define/live-audio/compat-skin2.js +1 -1
  105. package/dist/dev/define/live-audio/compat-skin2.js.map +1 -1
  106. package/dist/dev/define/live-audio/neutral-skin.css +56 -31
  107. package/dist/dev/define/live-audio/neutral-skin2.js +1 -1
  108. package/dist/dev/define/live-audio/neutral-skin2.js.map +1 -1
  109. package/dist/dev/define/live-audio/scaffold-skin.css +148 -42
  110. package/dist/dev/define/live-audio/scaffold-skin2.js +1 -1
  111. package/dist/dev/define/live-audio/scaffold-skin2.js.map +1 -1
  112. package/dist/dev/define/live-audio/skin.css +55 -31
  113. package/dist/dev/define/live-audio/skin2.js +1 -1
  114. package/dist/dev/define/live-audio/skin2.js.map +1 -1
  115. package/dist/dev/define/live-video/compat-skin.css +234 -51
  116. package/dist/dev/define/live-video/compat-skin2.js +1 -1
  117. package/dist/dev/define/live-video/compat-skin2.js.map +1 -1
  118. package/dist/dev/define/live-video/neutral-skin.css +300 -46
  119. package/dist/dev/define/live-video/neutral-skin2.js +1 -1
  120. package/dist/dev/define/live-video/neutral-skin2.js.map +1 -1
  121. package/dist/dev/define/live-video/scaffold-skin.css +231 -53
  122. package/dist/dev/define/live-video/scaffold-skin2.js +1 -1
  123. package/dist/dev/define/live-video/scaffold-skin2.js.map +1 -1
  124. package/dist/dev/define/live-video/skin.css +314 -49
  125. package/dist/dev/define/live-video/skin2.js +1 -1
  126. package/dist/dev/define/live-video/skin2.js.map +1 -1
  127. package/dist/dev/define/media/dailymotion-video.d.ts +15 -0
  128. package/dist/dev/define/media/dailymotion-video.js +13 -0
  129. package/dist/dev/define/media/dailymotion-video.js.map +1 -0
  130. package/dist/dev/define/media/mixcloud-audio.d.ts +15 -0
  131. package/dist/dev/define/media/mixcloud-audio.js +13 -0
  132. package/dist/dev/define/media/mixcloud-audio.js.map +1 -0
  133. package/dist/dev/define/media/soundcloud-audio.d.ts +15 -0
  134. package/dist/dev/define/media/soundcloud-audio.js +13 -0
  135. package/dist/dev/define/media/soundcloud-audio.js.map +1 -0
  136. package/dist/dev/define/ui/time-slider-markers.d.ts +8 -0
  137. package/dist/dev/define/ui/time-slider-markers.js +7 -0
  138. package/dist/dev/define/ui/time-slider-markers.js.map +1 -0
  139. package/dist/dev/define/video/compat-skin.css +242 -51
  140. package/dist/dev/define/video/compat-skin2.js +1 -1
  141. package/dist/dev/define/video/compat-skin2.js.map +1 -1
  142. package/dist/dev/define/video/neutral-skin.css +382 -57
  143. package/dist/dev/define/video/neutral-skin2.js +1 -1
  144. package/dist/dev/define/video/neutral-skin2.js.map +1 -1
  145. package/dist/dev/define/video/scaffold-skin.css +240 -53
  146. package/dist/dev/define/video/scaffold-skin2.js +1 -1
  147. package/dist/dev/define/video/scaffold-skin2.js.map +1 -1
  148. package/dist/dev/define/video/skin.css +415 -55
  149. package/dist/dev/define/video/skin2.js +1 -1
  150. package/dist/dev/define/video/skin2.js.map +1 -1
  151. package/dist/dev/extensions/google-analytics/extension.d.ts +52 -0
  152. package/dist/dev/extensions/google-analytics/extension.js +77 -0
  153. package/dist/dev/extensions/google-analytics/extension.js.map +1 -0
  154. package/dist/dev/extensions/google-analytics/index.d.ts +2 -0
  155. package/dist/dev/extensions/mux-data/extension.d.ts +4 -1
  156. package/dist/dev/extensions/mux-data/extension.js +7 -2
  157. package/dist/dev/extensions/mux-data/extension.js.map +1 -1
  158. package/dist/dev/index.d.ts +5 -4
  159. package/dist/dev/index.js +3 -1
  160. package/dist/dev/internal/skins/compat-audio/skin.css +161 -41
  161. package/dist/dev/internal/skins/compat-audio/template.js +1 -1
  162. package/dist/dev/internal/skins/compat-audio/template.js.map +1 -1
  163. package/dist/dev/internal/skins/compat-live-audio/skin.css +143 -41
  164. package/dist/dev/internal/skins/compat-live-video/skin.css +234 -51
  165. package/dist/dev/internal/skins/compat-live-video/template.js +2 -0
  166. package/dist/dev/internal/skins/compat-live-video/template.js.map +1 -1
  167. package/dist/dev/internal/skins/compat-video/skin.css +242 -51
  168. package/dist/dev/internal/skins/compat-video/template.js +2 -0
  169. package/dist/dev/internal/skins/compat-video/template.js.map +1 -1
  170. package/dist/dev/internal/skins/default-audio/skin.css +72 -34
  171. package/dist/dev/internal/skins/default-live-audio/skin.css +55 -31
  172. package/dist/dev/internal/skins/default-live-video/register.js.map +1 -1
  173. package/dist/dev/internal/skins/default-live-video/skin.css +314 -49
  174. package/dist/dev/internal/skins/default-live-video/template.js +14 -0
  175. package/dist/dev/internal/skins/default-live-video/template.js.map +1 -1
  176. package/dist/dev/internal/skins/default-video/register.d.ts +1 -0
  177. package/dist/dev/internal/skins/default-video/register.js +3 -0
  178. package/dist/dev/internal/skins/default-video/register.js.map +1 -1
  179. package/dist/dev/internal/skins/default-video/skin.css +415 -55
  180. package/dist/dev/internal/skins/default-video/template.js +33 -5
  181. package/dist/dev/internal/skins/default-video/template.js.map +1 -1
  182. package/dist/dev/internal/skins/neutral-audio/skin.css +73 -34
  183. package/dist/dev/internal/skins/neutral-live-audio/skin.css +56 -31
  184. package/dist/dev/internal/skins/neutral-live-video/register.js.map +1 -1
  185. package/dist/dev/internal/skins/neutral-live-video/skin.css +300 -46
  186. package/dist/dev/internal/skins/neutral-live-video/template.js +14 -0
  187. package/dist/dev/internal/skins/neutral-live-video/template.js.map +1 -1
  188. package/dist/dev/internal/skins/neutral-video/register.js.map +1 -1
  189. package/dist/dev/internal/skins/neutral-video/skin.css +382 -57
  190. package/dist/dev/internal/skins/neutral-video/template.js +25 -11
  191. package/dist/dev/internal/skins/neutral-video/template.js.map +1 -1
  192. package/dist/dev/internal/skins/scaffold-audio/skin.css +166 -42
  193. package/dist/dev/internal/skins/scaffold-audio/template.js +1 -1
  194. package/dist/dev/internal/skins/scaffold-audio/template.js.map +1 -1
  195. package/dist/dev/internal/skins/scaffold-live-audio/skin.css +148 -42
  196. package/dist/dev/internal/skins/scaffold-live-video/skin.css +231 -53
  197. package/dist/dev/internal/skins/scaffold-live-video/template.js +2 -0
  198. package/dist/dev/internal/skins/scaffold-live-video/template.js.map +1 -1
  199. package/dist/dev/internal/skins/scaffold-video/skin.css +240 -53
  200. package/dist/dev/internal/skins/scaffold-video/template.js +3 -1
  201. package/dist/dev/internal/skins/scaffold-video/template.js.map +1 -1
  202. package/dist/dev/media/background-video/adapter.d.ts +7 -13
  203. package/dist/dev/media/background-video/adapter.js +13 -35
  204. package/dist/dev/media/background-video/adapter.js.map +1 -1
  205. package/dist/dev/media/background-video/base.d.ts +44 -0
  206. package/dist/dev/media/background-video/base.js +97 -0
  207. package/dist/dev/media/background-video/base.js.map +1 -0
  208. package/dist/dev/media/background-video/template.d.ts +3 -0
  209. package/dist/dev/media/background-video/template.js +13 -20
  210. package/dist/dev/media/background-video/template.js.map +1 -1
  211. package/dist/dev/media/dailymotion-video/adapter.d.ts +17 -0
  212. package/dist/dev/media/dailymotion-video/adapter.js +47 -0
  213. package/dist/dev/media/dailymotion-video/adapter.js.map +1 -0
  214. package/dist/dev/media/dailymotion-video/index.d.ts +2 -0
  215. package/dist/dev/media/hls-background-video/adapter.d.ts +14 -19
  216. package/dist/dev/media/hls-background-video/adapter.js +23 -35
  217. package/dist/dev/media/hls-background-video/adapter.js.map +1 -1
  218. package/dist/dev/media/mixcloud-audio/adapter.d.ts +11 -0
  219. package/dist/dev/media/mixcloud-audio/adapter.js +52 -0
  220. package/dist/dev/media/mixcloud-audio/adapter.js.map +1 -0
  221. package/dist/dev/media/mixcloud-audio/index.d.ts +2 -0
  222. package/dist/dev/media/soundcloud-audio/adapter.d.ts +11 -0
  223. package/dist/dev/media/soundcloud-audio/adapter.js +53 -0
  224. package/dist/dev/media/soundcloud-audio/adapter.js.map +1 -0
  225. package/dist/dev/media/soundcloud-audio/index.d.ts +2 -0
  226. package/dist/dev/player/element.js +1 -5
  227. package/dist/dev/player/element.js.map +1 -1
  228. package/dist/dev/presets/background/skin.d.ts +2 -1
  229. package/dist/dev/presets/background/skin.js +2 -2
  230. package/dist/dev/presets/background/skin.js.map +1 -1
  231. package/dist/dev/presets/skin.d.ts +4 -3
  232. package/dist/dev/presets/skin.js +4 -3
  233. package/dist/dev/presets/skin.js.map +1 -1
  234. package/dist/dev/ui/captions-button/element.d.ts +2 -0
  235. package/dist/dev/ui/captions-radio-group/element.d.ts +2 -1
  236. package/dist/dev/ui/captions-radio-group/element.js +14 -1
  237. package/dist/dev/ui/captions-radio-group/element.js.map +1 -1
  238. package/dist/dev/ui/controls/element.d.ts +2 -2
  239. package/dist/dev/ui/controls/element.js +6 -3
  240. package/dist/dev/ui/controls/element.js.map +1 -1
  241. package/dist/dev/ui/dialog/element.js +13 -1
  242. package/dist/dev/ui/dialog/element.js.map +1 -1
  243. package/dist/dev/ui/gesture/element.js +3 -0
  244. package/dist/dev/ui/gesture/element.js.map +1 -1
  245. package/dist/dev/ui/media-button-element.d.ts +9 -0
  246. package/dist/dev/ui/media-button-element.js +10 -1
  247. package/dist/dev/ui/media-button-element.js.map +1 -1
  248. package/dist/dev/ui/play-button/element.d.ts +2 -0
  249. package/dist/dev/ui/playback-rate-button/element.d.ts +9 -1
  250. package/dist/dev/ui/playback-rate-button/element.js +7 -2
  251. package/dist/dev/ui/playback-rate-button/element.js.map +1 -1
  252. package/dist/dev/ui/playback-rate-radio-group/element.d.ts +4 -0
  253. package/dist/dev/ui/playback-rate-radio-group/element.js +10 -3
  254. package/dist/dev/ui/playback-rate-radio-group/element.js.map +1 -1
  255. package/dist/dev/ui/radio-options/controller.js +18 -5
  256. package/dist/dev/ui/radio-options/controller.js.map +1 -1
  257. package/dist/dev/ui/time-slider/markers.d.ts +48 -0
  258. package/dist/dev/ui/time-slider/markers.js +165 -0
  259. package/dist/dev/ui/time-slider/markers.js.map +1 -0
  260. package/dist/dev/version.js +1 -1
  261. package/docs/README.md +1 -1
  262. package/docs/guides/autoplay.md +2 -2
  263. package/docs/guides/background-video.md +2 -4
  264. package/docs/guides/browser-support.md +23 -15
  265. package/docs/guides/build-your-own-skin.md +3 -1
  266. package/docs/guides/cdn.md +10 -9
  267. package/docs/guides/content-security-policy.md +3 -0
  268. package/docs/guides/customize-skins.md +7 -2
  269. package/docs/guides/features.md +1 -1
  270. package/docs/guides/google-analytics.md +199 -0
  271. package/docs/guides/installation-cdn.md +4 -4
  272. package/docs/guides/installation-shadcn.md +3 -3
  273. package/docs/guides/installation-svelte.md +4 -4
  274. package/docs/guides/installation-vue.md +4 -4
  275. package/docs/guides/installation.md +4 -4
  276. package/docs/guides/internationalization.md +5 -5
  277. package/docs/guides/keyboard-shortcuts.md +11 -1
  278. package/docs/guides/llms.txt +3 -1
  279. package/docs/guides/media-sources.md +3 -0
  280. package/docs/guides/migrate-from-media-chrome.md +4 -4
  281. package/docs/guides/migrate-from-mux-player.md +9 -9
  282. package/docs/guides/migrate-from-plyr.md +8 -8
  283. package/docs/guides/migrate-from-video-js-8.md +8 -9
  284. package/docs/guides/migrate-from-vidstack.md +10 -10
  285. package/docs/guides/mux-data.md +22 -0
  286. package/docs/guides/presets.md +5 -5
  287. package/docs/guides/self-hosting.md +5 -5
  288. package/docs/guides/skins.md +21 -5
  289. package/docs/guides/timed-metadata.md +318 -0
  290. package/docs/guides/ui-components.md +1 -1
  291. package/docs/llms.txt +25 -6
  292. package/docs/reference/api/chapters-track.md +71 -0
  293. package/docs/reference/api/define-text-track.md +113 -0
  294. package/docs/reference/api/feature-playback-rate.md +54 -3
  295. package/docs/reference/api/feature-playback.md +5 -1
  296. package/docs/reference/api/feature-text-cues.md +133 -0
  297. package/docs/reference/api/feature-text-tracks.md +11 -5
  298. package/docs/reference/api/get-active-text-cues.md +69 -0
  299. package/docs/reference/api/get-text-cues.md +69 -0
  300. package/docs/reference/api/get-text-tracks.md +41 -0
  301. package/docs/reference/api/hls-metadata-track.md +48 -0
  302. package/docs/reference/api/html-create-player.md +2 -2
  303. package/docs/reference/api/llms.txt +13 -1
  304. package/docs/reference/api/media-capabilities.md +4 -4
  305. package/docs/reference/api/player-store.md +15 -7
  306. package/docs/reference/api/preset-audio.md +15 -5
  307. package/docs/reference/api/preset-background.md +2 -2
  308. package/docs/reference/api/preset-live-audio.md +15 -5
  309. package/docs/reference/api/preset-live-video.md +14 -5
  310. package/docs/reference/api/preset-video.md +14 -5
  311. package/docs/reference/api/resolve-adapter-type.md +4 -1
  312. package/docs/reference/api/ui-element.md +2 -2
  313. package/docs/reference/components/alert-dialog.md +21 -16
  314. package/docs/reference/components/audio-compat-skin.md +8 -2
  315. package/docs/reference/components/audio-neutral-skin.md +2 -2
  316. package/docs/reference/components/audio-scaffold-skin.md +2 -2
  317. package/docs/reference/components/audio-skin.md +2 -2
  318. package/docs/reference/components/audio-track-radio-group.md +2 -2
  319. package/docs/reference/components/audio.md +19 -1
  320. package/docs/reference/components/background-video-skin.md +1 -1
  321. package/docs/reference/components/background-video.md +26 -11
  322. package/docs/reference/components/buffering-indicator.md +2 -2
  323. package/docs/reference/components/captions-radio-group.md +3 -3
  324. package/docs/reference/components/cloudflare-video.md +1 -1
  325. package/docs/reference/components/controls.md +16 -3
  326. package/docs/reference/components/dailymotion-video.md +192 -0
  327. package/docs/reference/components/dash-video.md +20 -2
  328. package/docs/reference/components/dialog.md +49 -22
  329. package/docs/reference/components/error-dialog.md +13 -10
  330. package/docs/reference/components/gesture.md +2 -2
  331. package/docs/reference/components/google-analytics.md +51 -0
  332. package/docs/reference/components/google-cast.md +1 -1
  333. package/docs/reference/components/hls-audio.md +56 -2
  334. package/docs/reference/components/hls-background-video.md +25 -5
  335. package/docs/reference/components/hls-video.md +99 -7
  336. package/docs/reference/components/hlsjs-video.md +53 -12
  337. package/docs/reference/components/hotkey.md +2 -2
  338. package/docs/reference/components/live-audio-compat-skin.md +8 -2
  339. package/docs/reference/components/live-audio-neutral-skin.md +2 -2
  340. package/docs/reference/components/live-audio-scaffold-skin.md +2 -2
  341. package/docs/reference/components/live-audio-skin.md +2 -2
  342. package/docs/reference/components/live-video-compat-skin.md +12 -2
  343. package/docs/reference/components/live-video-neutral-skin.md +6 -2
  344. package/docs/reference/components/live-video-scaffold-skin.md +6 -2
  345. package/docs/reference/components/live-video-skin.md +6 -2
  346. package/docs/reference/components/llms.txt +10 -5
  347. package/docs/reference/components/mixcloud-audio.md +203 -0
  348. package/docs/reference/components/mux-audio.md +35 -2
  349. package/docs/reference/components/mux-background-video.md +1 -1
  350. package/docs/reference/components/mux-data.md +20 -2
  351. package/docs/reference/components/mux-video.md +18 -2
  352. package/docs/reference/components/native-hls-video.md +32 -2
  353. package/docs/reference/components/play-button.md +7 -5
  354. package/docs/reference/components/playback-rate-button.md +26 -7
  355. package/docs/reference/components/playback-rate-radio-group.md +7 -4
  356. package/docs/reference/components/player.md +3 -3
  357. package/docs/reference/components/playerjs-video.md +2 -2
  358. package/docs/reference/components/quality-radio-group.md +2 -2
  359. package/docs/reference/components/shaka-video.md +2 -2
  360. package/docs/reference/components/soundcloud-audio.md +227 -0
  361. package/docs/reference/components/spotify-audio.md +3 -3
  362. package/docs/reference/components/tiktok-video.md +3 -3
  363. package/docs/reference/components/time-slider-markers.md +194 -0
  364. package/docs/reference/components/time-slider.md +14 -0
  365. package/docs/reference/components/twitch-video.md +2 -3
  366. package/docs/reference/components/video-compat-skin.md +12 -2
  367. package/docs/reference/components/video-neutral-skin.md +6 -2
  368. package/docs/reference/components/video-scaffold-skin.md +6 -2
  369. package/docs/reference/components/video-skin.md +6 -2
  370. package/docs/reference/components/video.md +19 -1
  371. package/docs/reference/components/vimeo-video.md +1 -1
  372. package/docs/reference/components/wistia-video.md +1 -1
  373. package/docs/reference/components/youtube-video.md +1 -1
  374. package/package.json +60 -40
@@ -0,0 +1,95 @@
1
+ import { MediaAttachMixin } from "../../store/media-attach-mixin.js";
2
+ import { getTemplateHTML, getTemplateVideo } from "./template.js";
3
+ import { namedNodeMapToObject } from "@videojs/utils/dom";
4
+ import { pick } from "@videojs/utils/object";
5
+ //#region src/media/background-video/base.ts
6
+ const HTMLElementBase = globalThis.HTMLElement ?? class {};
7
+ const MediaAttachBase = MediaAttachMixin(HTMLElementBase);
8
+ const PLAYBACK_DEFAULTS = [
9
+ "muted",
10
+ "loop",
11
+ "autoplay"
12
+ ];
13
+ /**
14
+ * The shadow setup, `src` wiring, and `target` that `<background-video>` and `<hls-background-video>` share. Each
15
+ * subclass supplies its own `src` and declares which playback defaults authors may opt out of.
16
+ *
17
+ * @internal
18
+ */
19
+ var BackgroundVideoBase = class extends MediaAttachBase {
20
+ static {
21
+ this.shadowRootOptions = { mode: "open" };
22
+ }
23
+ static {
24
+ this.getTemplateHTML = getTemplateHTML;
25
+ }
26
+ static {
27
+ this.optOuts = [
28
+ "muted",
29
+ "loop",
30
+ "autoplay"
31
+ ];
32
+ }
33
+ static {
34
+ this.passthroughAttributes = [
35
+ "controls",
36
+ "controlslist",
37
+ "crossorigin",
38
+ "preload"
39
+ ];
40
+ }
41
+ static get observedAttributes() {
42
+ return [
43
+ "src",
44
+ ...this.optOuts,
45
+ ...this.optOuts.map((name) => `no${name}`),
46
+ ...this.passthroughAttributes
47
+ ];
48
+ }
49
+ constructor() {
50
+ super();
51
+ const ctor = this.constructor;
52
+ if (!this.shadowRoot) {
53
+ const attrs = {
54
+ ...pick(namedNodeMapToObject(this.attributes), ctor.passthroughAttributes),
55
+ playsinline: "",
56
+ disableremoteplayback: "",
57
+ disablepictureinpicture: ""
58
+ };
59
+ for (const name of PLAYBACK_DEFAULTS) if (this.#isDefaultOn(name)) attrs[name] = "";
60
+ this.attachShadow(ctor.shadowRootOptions);
61
+ this.shadowRoot.innerHTML = ctor.getTemplateHTML(attrs);
62
+ }
63
+ this.syncPlaybackDefaults();
64
+ }
65
+ /**
66
+ * Writes muted, loop, and autoplay onto the inner video as properties. The template's attributes only cover markup
67
+ * parsed with its attributes in place; an element created by script gets its `no*` attributes afterwards, and neither
68
+ * Chrome nor Firefox honor a `muted` attribute set after `document.createElement`.
69
+ */
70
+ syncPlaybackDefaults() {
71
+ const target = this.target;
72
+ if (!target) return;
73
+ for (const name of PLAYBACK_DEFAULTS) target[name] = this.#isDefaultOn(name);
74
+ }
75
+ /** On unless its `no*` attribute is set and allowed; an explicit attribute such as `muted` wins over its opt-out. */
76
+ #isDefaultOn(name) {
77
+ const { optOuts } = this.constructor;
78
+ return this.hasAttribute(name) || !optOuts.includes(name) || !this.hasAttribute(`no${name}`);
79
+ }
80
+ attributeChangedCallback(name, oldValue, newValue) {
81
+ if (oldValue === newValue) return;
82
+ const { passthroughAttributes } = this.constructor;
83
+ if (name === "src") this.src = newValue ?? "";
84
+ else if (passthroughAttributes.includes(name)) {
85
+ if (newValue === null) this.target?.removeAttribute(name);
86
+ else this.target?.setAttribute(name, newValue);
87
+ } else this.syncPlaybackDefaults();
88
+ }
89
+ /** The inner `<video>` in the shadow root: what plays, and what `play()` / `pause()` belong on. */
90
+ get target() {
91
+ return getTemplateVideo(this.shadowRoot);
92
+ }
93
+ };
94
+ //#endregion
95
+ export { BackgroundVideoBase };
@@ -1,32 +1,25 @@
1
1
  import { serializeAttributes } from "@videojs/utils/dom";
2
- import { pick } from "@videojs/utils/object";
3
2
  //#region src/media/background-video/template.ts
4
3
  /**
5
- * Attributes forwarded to the inner `<video>`. `src` is deliberately not one of them: each element decides what to do
6
- * with it, whether that is assigning the inner video or handing it to a playback engine, and serializing it into the
7
- * template would start a native load either way.
8
- */
9
- const VideoAttributes = [
10
- "autoplay",
11
- "controls",
12
- "controlslist",
13
- "crossorigin",
14
- "disablepictureinpicture",
15
- "disableremoteplayback",
16
- "loop",
17
- "muted",
18
- "playsinline",
19
- "preload"
20
- ];
21
- /**
22
4
  * The shadow template both background-video elements render: a `<slot>` for a placeholder image, and a `<video>`
23
5
  * stretched over the host.
24
6
  *
7
+ * `attrs` go onto the `<video>` as given; each element decides which of its own attributes to forward. `src` is never
8
+ * among them, since serializing it would start a native load before an engine could take it.
9
+ *
25
10
  * Shared so the two can't drift in presentation. `object-fit` and `object-position` are the only styling hooks — a
26
11
  * background video has no controls to theme.
27
12
  */
28
13
  function getTemplateHTML(attrs) {
29
- return `<style> :host { position: relative; } video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: var(--media-object-fit, inherit); object-position: var(--media-object-position, 50% 50%); } </style><slot></slot><video${serializeAttributes(pick(attrs, [...VideoAttributes]))}></video>`;
14
+ return `<style> :host { position: relative; } video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: var(--media-object-fit, inherit); object-position: var(--media-object-position, 50% 50%); } </style><slot></slot><video${serializeAttributes(attrs)}></video>`;
15
+ }
16
+ /**
17
+ * The `<video>` from `getTemplateHTML`. Always the shadow one: the template has no slot that would render a light-DOM
18
+ * `<video>`, so anything found there is either the placeholder or not displayed at all.
19
+ */
20
+ function getTemplateVideo(shadowRoot) {
21
+ const video = shadowRoot?.querySelector("video");
22
+ return video instanceof HTMLVideoElement ? video : null;
30
23
  }
31
24
  //#endregion
32
- export { VideoAttributes, getTemplateHTML };
25
+ export { getTemplateHTML, getTemplateVideo };
@@ -0,0 +1,31 @@
1
+ import { MediaAttachMixin } from "../../store/media-attach-mixin.js";
2
+ import { CustomMediaElement } from "@videojs/media/dom";
3
+ import { DailymotionAdapter } from "@videojs/dailymotion-video";
4
+ //#region src/media/dailymotion-video/adapter.ts
5
+ var DailymotionCustomMediaElement = class extends CustomMediaElement("iframe", DailymotionAdapter) {
6
+ static {
7
+ this.getTemplateHTML = () => `<style> :host { display: inline-block; min-width: 300px; min-height: 150px; position: relative; } ::slotted([slot=media]) { position: absolute; inset: 0; } </style><slot name="media"></slot>`;
8
+ }
9
+ };
10
+ var DailymotionVideo = class extends MediaAttachMixin(DailymotionCustomMediaElement) {
11
+ constructor() {
12
+ super();
13
+ this.adapter.addEventListener("volumechange", () => {
14
+ const { muted } = this.adapter;
15
+ if (this.hasAttribute("muted") !== muted) this.toggleAttribute("muted", muted);
16
+ });
17
+ }
18
+ connectedCallback() {
19
+ super.connectedCallback?.();
20
+ const existing = this.querySelector(":scope > [slot=media]");
21
+ if (existing) {
22
+ if (this.adapter.src && !existing.hasChildNodes() && !this.adapter.error) this.adapter.load();
23
+ return;
24
+ }
25
+ const container = document.createElement("div");
26
+ container.slot = "media";
27
+ this.append(container);
28
+ }
29
+ };
30
+ //#endregion
31
+ export { DailymotionVideo };
@@ -1,10 +1,6 @@
1
- import { MediaAttachMixin } from "../../store/media-attach-mixin.js";
2
- import { getTemplateHTML } from "../background-video/template.js";
3
- import { namedNodeMapToObject } from "@videojs/utils/dom";
1
+ import { BackgroundVideoBase } from "../background-video/base.js";
4
2
  import { HlsBackgroundVideoAdapter } from "@videojs/spf/adapters/hls-background-video";
5
3
  //#region src/media/hls-background-video/adapter.ts
6
- const HTMLElementBase = globalThis.HTMLElement ?? class {};
7
- const HlsBackgroundVideoBase = MediaAttachMixin(HTMLElementBase);
8
4
  /**
9
5
  * A muted, looping, chrome-less video over the SPF background-video engine.
10
6
  *
@@ -16,8 +12,9 @@ const HlsBackgroundVideoBase = MediaAttachMixin(HTMLElementBase);
16
12
  * package, whose `audio`, `debug`, `preload`, and `max-resolution` attributes are all deliberately absent. Capping
17
13
  * which rendition is fetched is a delivery param on the URL — `?max_resolution=720p` on a Mux stream, for one — which
18
14
  * keeps the renditions it excludes out of the manifest rather than merely unpicked; `preload` would have nothing to
19
- * say, since the engine loads from the moment it has a source. And unlike `<background-video>` there are no `nomuted` /
20
- * `noloop` / `noautoplay` opt-outs: those three are what this element is for, so the adapter fixes them on at attach.
15
+ * say, since the engine loads from the moment it has a source. Muted is fixed on: the engine drops audio, so there is
16
+ * nothing to unmute. `noloop` and `noautoplay` opt out as on `<background-video>`, so a page can play the video once,
17
+ * or hold it until it has checked the motion preference.
21
18
  *
22
19
  * Takes no structured `source` — `src` is an HLS URL, as the package it replaces required. Mux playback-ID identity,
23
20
  * poster, and storyboard belong to `<mux-video>`; none of them mean anything without controls to hang them on.
@@ -39,40 +36,36 @@ const HlsBackgroundVideoBase = MediaAttachMixin(HTMLElementBase);
39
36
  * `<mux-background-video>` is this element under the name the package it replaces used. Same class, so the tag is a
40
37
  * naming choice and nothing more.
41
38
  */
42
- var HlsBackgroundVideo = class extends HlsBackgroundVideoBase {
39
+ var HlsBackgroundVideo = class extends BackgroundVideoBase {
43
40
  static {
44
- this.shadowRootOptions = { mode: "open" };
41
+ this.optOuts = ["loop", "autoplay"];
45
42
  }
46
43
  static {
47
- this.getTemplateHTML = getTemplateHTML;
48
- }
49
- static get observedAttributes() {
50
- return ["src"];
44
+ this.passthroughAttributes = [
45
+ "controls",
46
+ "controlslist",
47
+ "crossorigin"
48
+ ];
51
49
  }
52
50
  #media = new HlsBackgroundVideoAdapter();
53
51
  constructor() {
54
52
  super();
55
- if (!this.shadowRoot) {
56
- this.attachShadow(this.constructor.shadowRootOptions);
57
- const attrs = {
58
- ...namedNodeMapToObject(this.attributes),
59
- muted: "",
60
- loop: "",
61
- autoplay: "",
62
- playsinline: "",
63
- disableremoteplayback: "",
64
- disablepictureinpicture: ""
65
- };
66
- this.shadowRoot.innerHTML = getTemplateHTML(attrs);
67
- }
68
- const video = this.video;
69
- if (video) this.#media.attach(video);
53
+ const target = this.target;
54
+ if (target) this.#media.attach(target);
55
+ this.syncPlaybackDefaults();
70
56
  this.#media.addEventListener("error", () => this.dispatchEvent(new Event("error")));
71
57
  }
72
58
  /** Register the Media (not the inner `<video>`) with the provider. */
73
59
  getMediaTarget() {
74
60
  return this.#media;
75
61
  }
62
+ /**
63
+ * The Media driving the inner `<video>`, matching `adapter` on `CustomMediaElement`. Call `adapter.destroy()` to
64
+ * release the engine after `keep-alive` kept it running past removal.
65
+ */
66
+ get adapter() {
67
+ return this.#media;
68
+ }
76
69
  disconnectedCallback() {
77
70
  super.disconnectedCallback?.();
78
71
  if (this.hasAttribute("keep-alive")) return;
@@ -80,14 +73,9 @@ var HlsBackgroundVideo = class extends HlsBackgroundVideoBase {
80
73
  if (!this.isConnected) this.#media.destroy();
81
74
  });
82
75
  }
83
- attributeChangedCallback(name, oldValue, newValue) {
84
- if (oldValue === newValue) return;
85
- if (name === "src") this.src = newValue ?? "";
86
- }
87
- /** The inner `<video>` the engine renders into. */
76
+ /** Same element as `target`. */
88
77
  get video() {
89
- const video = this.shadowRoot?.querySelector("video");
90
- return video instanceof HTMLVideoElement ? video : null;
78
+ return this.target;
91
79
  }
92
80
  /**
93
81
  * What made the current source unplayable, or `null`. An SVTA code rather than a `MediaError` one — 99001 where this
@@ -0,0 +1,16 @@
1
+ import { MediaAttachMixin } from "../../store/media-attach-mixin.js";
2
+ import { CustomMediaElement } from "@videojs/media/dom";
3
+ import { escapeHtml } from "@videojs/utils/string";
4
+ import { MixcloudAdapter, buildMixcloudIframeSrc } from "@videojs/mixcloud-audio";
5
+ //#region src/media/mixcloud-audio/adapter.ts
6
+ var MixcloudCustomMediaElement = class extends CustomMediaElement("iframe", MixcloudAdapter) {
7
+ static {
8
+ this.getTemplateHTML = (attrs) => {
9
+ const initialSrc = buildMixcloudIframeSrc(attrs.src ?? "");
10
+ return `<style> :host { display: block; position: relative; height: 120px; } iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; } /* * The widget is Mixcloud's own chrome, so without controls it is a * transport and nothing else. Hidden rather than merely inert, and * important so a consumer's own display rule cannot put it back on * screen. An iframe in a hidden subtree still loads and plays its src. */ :host(:not([controls])) { display: none !important; } </style><iframe part="iframe" title="Mixcloud audio player" ${initialSrc ? ` src="${escapeHtml(initialSrc)}"` : ""} allow="autoplay; encrypted-media" frameborder="0" width="100%" height="100%" referrerpolicy="${escapeHtml(attrs.referrerpolicy ?? "")}" ></iframe>`;
11
+ };
12
+ }
13
+ };
14
+ var MixcloudAudio = class extends MediaAttachMixin(MixcloudCustomMediaElement) {};
15
+ //#endregion
16
+ export { MixcloudAudio };
@@ -0,0 +1,16 @@
1
+ import { MediaAttachMixin } from "../../store/media-attach-mixin.js";
2
+ import { CustomMediaElement } from "@videojs/media/dom";
3
+ import { escapeHtml } from "@videojs/utils/string";
4
+ import { SoundCloudAdapter, buildSoundCloudIframeSrc } from "@videojs/soundcloud-audio";
5
+ //#region src/media/soundcloud-audio/adapter.ts
6
+ var SoundCloudCustomMediaElement = class extends CustomMediaElement("iframe", SoundCloudAdapter) {
7
+ static {
8
+ this.getTemplateHTML = (attrs) => {
9
+ const initialSrc = buildSoundCloudIframeSrc(attrs.src ?? "");
10
+ return `<style> :host { display: block; position: relative; height: 166px; } iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; } /* * The widget is SoundCloud's own chrome, so without controls it is a * transport and nothing else. Hidden rather than merely inert, and * important so a consumer's own display rule cannot put it back on * screen. An iframe in a hidden subtree still loads and plays its src. */ :host(:not([controls])) { display: none !important; } </style><iframe part="iframe" title="SoundCloud audio player" ${initialSrc ? ` src="${escapeHtml(initialSrc)}"` : ""} allow="autoplay; encrypted-media" scrolling="no" frameborder="0" width="100%" height="100%" referrerpolicy="${escapeHtml(attrs.referrerpolicy ?? "")}" ></iframe>`;
11
+ };
12
+ }
13
+ };
14
+ var SoundCloudAudio = class extends MediaAttachMixin(SoundCloudCustomMediaElement) {};
15
+ //#endregion
16
+ export { SoundCloudAudio };
@@ -1,6 +1,6 @@
1
1
  import { UIElement } from "../ui/ui-element.js";
2
2
  import { PlayerExtensionCoordinator, setPlayerConfigValue } from "@videojs/core/dom";
3
- import { ContextProvider, ContextRoot } from "@videojs/element/context";
3
+ import { ContextProvider } from "@videojs/element/context";
4
4
  import { camelCase, kebabCase } from "@videojs/utils/string";
5
5
  import { isNull } from "@videojs/utils/predicate";
6
6
  //#region src/player/element.ts
@@ -27,7 +27,6 @@ function createPlayerElement(options) {
27
27
  };
28
28
  }
29
29
  #store = options.factory();
30
- #contextRoot = new ContextRoot();
31
30
  #configuredStore = null;
32
31
  #detach = null;
33
32
  #connected = false;
@@ -94,7 +93,6 @@ function createPlayerElement(options) {
94
93
  }
95
94
  connectedCallback() {
96
95
  this.#connected = true;
97
- this.#contextRoot.attach(this);
98
96
  super.connectedCallback();
99
97
  this.#syncInitialConfig();
100
98
  this.#playerProvider.setValue(this.store);
@@ -111,13 +109,11 @@ function createPlayerElement(options) {
111
109
  }
112
110
  disconnectedCallback() {
113
111
  this.#connected = false;
114
- this.#contextRoot.detach(this);
115
112
  this.#observer.disconnect();
116
113
  this.#detachStore();
117
114
  super.disconnectedCallback();
118
115
  }
119
116
  destroyCallback() {
120
- this.#contextRoot.detach(this);
121
117
  this.#observer.disconnect();
122
118
  this.#detachStore();
123
119
  this.#extensions.destroy();
@@ -1,5 +1,5 @@
1
1
  import skin_default from "../../define/background/skin2.js";
2
- import { ReactiveElement } from "@videojs/element";
2
+ import { DestroyMixin, ReactiveElement } from "@videojs/element";
3
3
  import { ensureGlobalStyle } from "@videojs/utils/dom";
4
4
  //#region src/presets/background/skin.ts
5
5
  const STYLES_ID = "__media-background-styles";
@@ -12,7 +12,7 @@ function getTemplateHTML() {
12
12
  </media-container>
13
13
  `;
14
14
  }
15
- var BackgroundVideoSkinElement = class BackgroundVideoSkinElement extends ReactiveElement {
15
+ var BackgroundVideoSkinElement = class BackgroundVideoSkinElement extends DestroyMixin(ReactiveElement) {
16
16
  static {
17
17
  this.tagName = "background-video-skin";
18
18
  }
@@ -1,15 +1,16 @@
1
+ import { UIElement } from "../ui/ui-element.js";
1
2
  import global_default from "../define/global.js";
2
3
  import shadow_default from "../define/shadow.js";
3
- import { ReactiveElement } from "@videojs/element";
4
4
  import { applyShadowStyles, createShadowStyle, ensureGlobalStyle, renderTemplate } from "@videojs/utils/dom";
5
5
  //#region src/presets/skin.ts
6
6
  const STYLES_ID = "__media-styles";
7
7
  const shadowSheet = createShadowStyle(shadow_default);
8
8
  /**
9
9
  * Base element for skin definitions. Attaches a shadow root, clones `static template` into it, and applies shared +
10
- * per-skin styles via `adoptedStyleSheets` (or `<style>` fallback).
10
+ * per-skin styles via `adoptedStyleSheets` (or `<style>` fallback). Destroying it also destroys the controls in its
11
+ * shadow root.
11
12
  */
12
- var SkinElement = class extends ReactiveElement {
13
+ var SkinElement = class extends UIElement {
13
14
  static {
14
15
  this.shadowRootOptions = { mode: "open" };
15
16
  }
@@ -11,7 +11,8 @@ import { translateText } from "@videojs/core/i18n";
11
11
  /**
12
12
  * Menu radio group that generates an Off `<media-menu-radio-item>` plus one per captions and subtitles track, and
13
13
  * shares the selected label and availability with an enclosing menu. An optional `<template>` holding one
14
- * `<media-menu-radio-item>` customizes each generated item.
14
+ * `<media-menu-radio-item>` customizes each generated item; its `data-part` `label` and `badge` descendants receive the
15
+ * track's text.
15
16
  */
16
17
  var CaptionsRadioGroupElement = class extends MenuRadioGroupElement {
17
18
  constructor(..._args) {
@@ -23,6 +24,8 @@ var CaptionsRadioGroupElement = class extends MenuRadioGroupElement {
23
24
  this.#i18n = new I18nController(this, i18nContext);
24
25
  this.#mediaState = new PlayerController(this, playerContext, selectTextTrack);
25
26
  this.#options = new RadioOptionsController(this, {
27
+ renderItem: (item, label, option) => this.#setContent(item, label, option.badge),
28
+ getOptionCacheKey: (option) => option.badge ?? "",
26
29
  setItemAttributes: (item, option) => item.setAttribute("data-track", option.value),
27
30
  onValueChange: (value) => {
28
31
  const media = this.#mediaState.value;
@@ -66,6 +69,16 @@ var CaptionsRadioGroupElement = class extends MenuRadioGroupElement {
66
69
  super.update(changed);
67
70
  if (state) applyStateDataAttrs(this, state, CaptionsRadioGroupDataAttrs);
68
71
  }
72
+ #setContent(item, label, badge) {
73
+ const labelPart = item.querySelector("[data-part~=\"label\"]");
74
+ const badgePart = item.querySelector("[data-part~=\"badge\"]");
75
+ if (labelPart) labelPart.textContent = label;
76
+ if (badgePart) {
77
+ badgePart.textContent = badge ?? "";
78
+ badgePart.hidden = !badge;
79
+ }
80
+ if (!labelPart && !badgePart) item.textContent = [label, badge].filter(Boolean).join(" ");
81
+ }
69
82
  };
70
83
  //#endregion
71
84
  export { CaptionsRadioGroupElement };
@@ -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, selectControls } from "@videojs/core/dom";
6
+ import { applyStateDataAttrs, 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() {
@@ -37,6 +39,7 @@ var ControlsElement = class extends UIElement {
37
39
  super.update(_changed);
38
40
  this.#core.setProps({ visibility: this.visibility });
39
41
  this.#core.setMedia(this.#mediaState.value ?? null);
42
+ this.#core.setPlayback(this.#playback.value ?? null);
40
43
  const state = this.#core.getState();
41
44
  if (!state) return;
42
45
  applyStateDataAttrs(this, state, ControlsDataAttrs);
@@ -1,9 +1,10 @@
1
+ import { containerContext } from "../../player/context.js";
1
2
  import { UIElement } from "../ui-element.js";
2
3
  import { PositionController } from "../position-controller.js";
3
4
  import { dialogContext } from "./context.js";
4
5
  import { DialogCore, DialogDataAttrs } from "@videojs/core";
5
6
  import { applyElementProps, applyStateDataAttrs, createDialog, createTransition } from "@videojs/core/dom";
6
- import { ContextProvider } from "@videojs/element/context";
7
+ import { ContextConsumer, ContextProvider } from "@videojs/element/context";
7
8
  import { SnapshotController } from "@videojs/store/html";
8
9
  //#region src/ui/dialog/element.ts
9
10
  let idCounter = 0;
@@ -25,12 +26,14 @@ var DialogElementBase = class extends UIElement {
25
26
  #stateAttrMap;
26
27
  #provider;
27
28
  #position;
29
+ #container;
28
30
  #popupId;
29
31
  #titleId;
30
32
  #descriptionId;
31
33
  #bindTrigger;
32
34
  #dialog;
33
35
  #snapshot;
36
+ #modalitySnapshot;
34
37
  #triggerAbort;
35
38
  #triggerElement;
36
39
  constructor({ core = new DialogCore(), stateAttrMap = DialogDataAttrs, idPrefix = "dialog", bindTrigger = true } = {}) {
@@ -40,8 +43,13 @@ var DialogElementBase = class extends UIElement {
40
43
  this.closeOnEscape = DialogCore.defaultProps.closeOnEscape;
41
44
  this.#provider = new ContextProvider(this, { context: dialogContext });
42
45
  this.#position = new PositionController(this);
46
+ this.#container = new ContextConsumer(this, {
47
+ context: containerContext,
48
+ subscribe: true
49
+ });
43
50
  this.#dialog = null;
44
51
  this.#snapshot = null;
52
+ this.#modalitySnapshot = null;
45
53
  this.#triggerAbort = null;
46
54
  this.#triggerElement = null;
47
55
  this.#core = core;
@@ -69,6 +77,8 @@ var DialogElementBase = class extends UIElement {
69
77
  });
70
78
  if (this.#snapshot) this.#snapshot.track(this.#dialog.input);
71
79
  else this.#snapshot = new SnapshotController(this, this.#dialog.input);
80
+ if (this.#modalitySnapshot) this.#modalitySnapshot.track(this.#dialog.modality);
81
+ else this.#modalitySnapshot = new SnapshotController(this, this.#dialog.modality);
72
82
  }
73
83
  firstUpdated(changed) {
74
84
  super.firstUpdated(changed);
@@ -86,6 +96,7 @@ var DialogElementBase = class extends UIElement {
86
96
  willUpdate(changed) {
87
97
  super.willUpdate(changed);
88
98
  this.#core.setProps(this);
99
+ this.#dialog?.setInteractionRoot(this.#container.value?.container ?? null);
89
100
  if (this.#dialog && changed.has("open")) {
90
101
  const { active: inputOpen } = this.#dialog.input.current;
91
102
  if (this.open !== inputOpen) {
@@ -101,6 +112,7 @@ var DialogElementBase = class extends UIElement {
101
112
  this.#syncTrigger(triggerElement);
102
113
  const input = this.#dialog.input.current;
103
114
  this.#core.setInput(input);
115
+ this.#core.setDocumentModal(this.#dialog.modality.current.documentModal);
104
116
  const state = this.#core.getState();
105
117
  applyStateDataAttrs(this, state, this.#stateAttrMap);
106
118
  if (this.#triggerElement) applyElementProps(this.#triggerElement, this.#core.getTriggerAttrs(state, this.#popupId));
@@ -61,14 +61,17 @@ var GestureElement = class extends UIElement {
61
61
  if (!resolver) return;
62
62
  const { value, region } = this;
63
63
  const actionValue = getGestureActionValue(this.action, region, value);
64
+ let state;
64
65
  const onActivate = (event) => {
65
66
  resolver({
66
67
  store,
68
+ state,
67
69
  value: actionValue,
68
70
  event
69
71
  });
70
72
  };
71
73
  const options = {
74
+ onPointerDown: () => state = store.state,
72
75
  pointer: this.pointer,
73
76
  region,
74
77
  disabled: this.disabled,
@@ -71,6 +71,7 @@ var MediaButtonElement = class extends UIElement {
71
71
  if (!media) return void 0;
72
72
  this.core.setMedia(media);
73
73
  const state = this.core.getState();
74
+ if (this.core.getTooltipLabel) return translateText(this.core.getTooltipLabel(state), this.#i18n.value);
74
75
  return translateText(this.core.getLabel(state), this.#i18n.value, this.core.getLabelParams?.(state));
75
76
  }
76
77
  willUpdate(changed) {
@@ -89,9 +90,17 @@ var MediaButtonElement = class extends UIElement {
89
90
  applyElementProps(this, {
90
91
  ...attrs,
91
92
  "aria-keyshortcuts": this.#hotkeyRegistry?.aria,
92
- ...isHideable(state) && { hidden: state.hidden ? "" : void 0 }
93
+ ...isHideable(state) && this.reflectsHidden() && { hidden: state.hidden ? "" : void 0 }
93
94
  });
94
95
  applyStateDataAttrs(this, state, this.stateAttrMap);
96
+ for (const [name, value] of Object.entries(this.core.getCSSVars?.(state) ?? {})) this.style.setProperty(name, value);
97
+ }
98
+ /**
99
+ * Whether this button writes its core's `hidden` state to the `hidden` attribute. A subclass returns `false` while
100
+ * another owner, such as a linked menu, manages its visibility.
101
+ */
102
+ reflectsHidden() {
103
+ return true;
95
104
  }
96
105
  #syncHotkeyShortcut() {
97
106
  const shortcut = this.getShortcut();
@@ -8,6 +8,7 @@ var PlaybackRateButtonElement = class extends MediaButtonElement {
8
8
  constructor(..._args) {
9
9
  super(..._args);
10
10
  this.commandfor = void 0;
11
+ this.rates = void 0;
11
12
  this.core = new PlaybackRateButtonCore();
12
13
  this.stateAttrMap = PlaybackRateButtonDataAttrs;
13
14
  this.mediaState = new PlayerController(this, playerContext, selectPlaybackRate);
@@ -20,7 +21,8 @@ var PlaybackRateButtonElement = class extends MediaButtonElement {
20
21
  this.properties = {
21
22
  label: { type: String },
22
23
  disabled: { type: Boolean },
23
- commandfor: { type: String }
24
+ commandfor: { type: String },
25
+ rates: { type: String }
24
26
  };
25
27
  }
26
28
  activate(state, event) {
@@ -33,9 +35,12 @@ var PlaybackRateButtonElement = class extends MediaButtonElement {
33
35
  getIsButtonDisabled() {
34
36
  const media = this.mediaState.value;
35
37
  if (super.getIsButtonDisabled()) return true;
36
- if (this.commandfor && media && media.playbackRates.length === 0) return true;
38
+ if (this.commandfor && media && this.core.getRates(media).length === 0) return true;
37
39
  return false;
38
40
  }
41
+ reflectsHidden() {
42
+ return !this.commandfor;
43
+ }
39
44
  willUpdate(changed) {
40
45
  super.willUpdate(changed);
41
46
  if (changed.has("commandfor")) {
@@ -17,12 +17,17 @@ var PlaybackRateRadioGroupElement = class extends MenuRadioGroupElement {
17
17
  constructor(..._args) {
18
18
  super(..._args);
19
19
  this.disabled = false;
20
+ this.rates = void 0;
20
21
  this.formatRate = PlaybackRateRadioGroupCore.defaultProps.formatRate;
21
22
  this.#core = new PlaybackRateRadioGroupCore();
22
23
  this.#i18n = new I18nController(this, i18nContext);
23
24
  this.#mediaState = new PlayerController(this, playerContext, selectPlaybackRate);
24
25
  this.#options = new RadioOptionsController(this, {
25
- setItemAttributes: (item, option) => item.setAttribute("data-rate", option.value),
26
+ setItemAttributes: (item, option) => {
27
+ item.setAttribute("data-rate", option.value);
28
+ item.toggleAttribute("data-custom", option.custom);
29
+ },
30
+ getOptionCacheKey: (option) => option.custom ? "custom" : "",
26
31
  onValueChange: (value) => {
27
32
  const media = this.#mediaState.value;
28
33
  if (media) this.#core.selectValue(media, value);
@@ -35,7 +40,8 @@ var PlaybackRateRadioGroupElement = class extends MenuRadioGroupElement {
35
40
  static {
36
41
  this.properties = {
37
42
  ...MenuRadioGroupElement.properties,
38
- disabled: { type: Boolean }
43
+ disabled: { type: Boolean },
44
+ rates: { type: String }
39
45
  };
40
46
  }
41
47
  #core;
@@ -52,7 +58,8 @@ var PlaybackRateRadioGroupElement = class extends MenuRadioGroupElement {
52
58
  if (media) {
53
59
  this.#core.setProps({
54
60
  formatRate: this.formatRate,
55
- disabled: this.disabled
61
+ disabled: this.disabled,
62
+ rates: this.rates
56
63
  });
57
64
  this.#core.setMedia(media);
58
65
  state = this.#core.getState();