@hanifhan1f/vidstack-react 1.12.13

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 (284) hide show
  1. package/.templates/sandbox/document.css +27 -0
  2. package/.templates/sandbox/favicon-32x32.png +0 -0
  3. package/.templates/sandbox/index.html +21 -0
  4. package/.templates/sandbox/main.tsx +12 -0
  5. package/.templates/sandbox/player.css +39 -0
  6. package/.templates/sandbox/player.tsx +121 -0
  7. package/.templates/sandbox/tracks.ts +23 -0
  8. package/LICENSE +21 -0
  9. package/README.md +23 -0
  10. package/analyze.config.ts +7 -0
  11. package/build/build-icons.js +62 -0
  12. package/npm/analyze.json.d.ts +7 -0
  13. package/package.json +162 -0
  14. package/rollup.config.ts +256 -0
  15. package/src/components/announcer.tsx +47 -0
  16. package/src/components/layouts/default/audio-layout.tsx +231 -0
  17. package/src/components/layouts/default/context.ts +28 -0
  18. package/src/components/layouts/default/hooks.ts +13 -0
  19. package/src/components/layouts/default/icons.tsx +225 -0
  20. package/src/components/layouts/default/index.ts +11 -0
  21. package/src/components/layouts/default/media-layout.tsx +259 -0
  22. package/src/components/layouts/default/slots.tsx +98 -0
  23. package/src/components/layouts/default/ui/announcer.tsx +22 -0
  24. package/src/components/layouts/default/ui/buttons.tsx +301 -0
  25. package/src/components/layouts/default/ui/captions.tsx +16 -0
  26. package/src/components/layouts/default/ui/controls.tsx +12 -0
  27. package/src/components/layouts/default/ui/keyboard-display.tsx +132 -0
  28. package/src/components/layouts/default/ui/menus/accessibility-menu.tsx +100 -0
  29. package/src/components/layouts/default/ui/menus/audio-menu.tsx +167 -0
  30. package/src/components/layouts/default/ui/menus/captions-menu.tsx +61 -0
  31. package/src/components/layouts/default/ui/menus/chapters-menu.tsx +132 -0
  32. package/src/components/layouts/default/ui/menus/font-menu.tsx +331 -0
  33. package/src/components/layouts/default/ui/menus/items/menu-checkbox.tsx +72 -0
  34. package/src/components/layouts/default/ui/menus/items/menu-items.tsx +135 -0
  35. package/src/components/layouts/default/ui/menus/items/menu-slider.tsx +92 -0
  36. package/src/components/layouts/default/ui/menus/playback-menu.tsx +232 -0
  37. package/src/components/layouts/default/ui/menus/settings-menu.tsx +114 -0
  38. package/src/components/layouts/default/ui/menus/utils.ts +12 -0
  39. package/src/components/layouts/default/ui/sliders.tsx +136 -0
  40. package/src/components/layouts/default/ui/time.tsx +73 -0
  41. package/src/components/layouts/default/ui/title.tsx +24 -0
  42. package/src/components/layouts/default/ui/tooltip.tsx +27 -0
  43. package/src/components/layouts/default/ui.ts +8 -0
  44. package/src/components/layouts/default/video-layout.tsx +344 -0
  45. package/src/components/layouts/plyr/context.ts +26 -0
  46. package/src/components/layouts/plyr/icons/plyr-airplay.js +1 -0
  47. package/src/components/layouts/plyr/icons/plyr-captions-off.js +1 -0
  48. package/src/components/layouts/plyr/icons/plyr-captions-on.js +1 -0
  49. package/src/components/layouts/plyr/icons/plyr-download.js +1 -0
  50. package/src/components/layouts/plyr/icons/plyr-enter-fullscreen.js +1 -0
  51. package/src/components/layouts/plyr/icons/plyr-exit-fullscreen.js +1 -0
  52. package/src/components/layouts/plyr/icons/plyr-fast-forward.js +1 -0
  53. package/src/components/layouts/plyr/icons/plyr-muted.js +1 -0
  54. package/src/components/layouts/plyr/icons/plyr-pause.js +1 -0
  55. package/src/components/layouts/plyr/icons/plyr-pip.js +1 -0
  56. package/src/components/layouts/plyr/icons/plyr-play.js +1 -0
  57. package/src/components/layouts/plyr/icons/plyr-restart.js +1 -0
  58. package/src/components/layouts/plyr/icons/plyr-rewind.js +1 -0
  59. package/src/components/layouts/plyr/icons/plyr-settings.js +1 -0
  60. package/src/components/layouts/plyr/icons/plyr-volume.js +1 -0
  61. package/src/components/layouts/plyr/icons.tsx +71 -0
  62. package/src/components/layouts/plyr/index.ts +11 -0
  63. package/src/components/layouts/plyr/layout.tsx +1024 -0
  64. package/src/components/layouts/plyr/props.ts +104 -0
  65. package/src/components/layouts/plyr/slots.tsx +52 -0
  66. package/src/components/layouts/remotion-ui.ts +13 -0
  67. package/src/components/layouts/utils.ts +17 -0
  68. package/src/components/player-callbacks.ts +67 -0
  69. package/src/components/player.tsx +67 -0
  70. package/src/components/primitives/instances.ts +92 -0
  71. package/src/components/primitives/nodes.tsx +58 -0
  72. package/src/components/primitives/slot.tsx +132 -0
  73. package/src/components/provider.tsx +187 -0
  74. package/src/components/text-track.tsx +106 -0
  75. package/src/components/ui/buttons/airplay-button.tsx +53 -0
  76. package/src/components/ui/buttons/caption-button.tsx +55 -0
  77. package/src/components/ui/buttons/fullscreen-button.tsx +55 -0
  78. package/src/components/ui/buttons/google-cast-button.tsx +53 -0
  79. package/src/components/ui/buttons/live-button.tsx +56 -0
  80. package/src/components/ui/buttons/mute-button.tsx +60 -0
  81. package/src/components/ui/buttons/pip-button.tsx +54 -0
  82. package/src/components/ui/buttons/play-button.tsx +53 -0
  83. package/src/components/ui/buttons/seek-button.tsx +55 -0
  84. package/src/components/ui/buttons/toggle-button.tsx +51 -0
  85. package/src/components/ui/caption.tsx +70 -0
  86. package/src/components/ui/captions.tsx +41 -0
  87. package/src/components/ui/chapter-title.tsx +40 -0
  88. package/src/components/ui/controls.tsx +90 -0
  89. package/src/components/ui/gesture.tsx +43 -0
  90. package/src/components/ui/menu.tsx +251 -0
  91. package/src/components/ui/poster.tsx +101 -0
  92. package/src/components/ui/radio-group.tsx +88 -0
  93. package/src/components/ui/sliders/audio-gain-slider.tsx +55 -0
  94. package/src/components/ui/sliders/quality-slider.tsx +54 -0
  95. package/src/components/ui/sliders/slider-callbacks.ts +14 -0
  96. package/src/components/ui/sliders/slider-value.tsx +13 -0
  97. package/src/components/ui/sliders/slider.tsx +254 -0
  98. package/src/components/ui/sliders/speed-slider.tsx +54 -0
  99. package/src/components/ui/sliders/time-slider.tsx +379 -0
  100. package/src/components/ui/sliders/volume-slider.tsx +55 -0
  101. package/src/components/ui/spinner.tsx +105 -0
  102. package/src/components/ui/thumbnail.tsx +82 -0
  103. package/src/components/ui/time.tsx +77 -0
  104. package/src/components/ui/title.tsx +32 -0
  105. package/src/components/ui/tooltip.tsx +135 -0
  106. package/src/globals.d.ts +3 -0
  107. package/src/hooks/create-text-track.ts +22 -0
  108. package/src/hooks/options/use-audio-gain-options.ts +75 -0
  109. package/src/hooks/options/use-audio-options.ts +71 -0
  110. package/src/hooks/options/use-caption-options.ts +95 -0
  111. package/src/hooks/options/use-chapter-options.ts +97 -0
  112. package/src/hooks/options/use-playback-rate-options.ts +75 -0
  113. package/src/hooks/options/use-video-quality-options.ts +123 -0
  114. package/src/hooks/use-active-text-cues.ts +28 -0
  115. package/src/hooks/use-active-text-track.ts +19 -0
  116. package/src/hooks/use-chapter-title.ts +12 -0
  117. package/src/hooks/use-dom.ts +121 -0
  118. package/src/hooks/use-media-context.ts +6 -0
  119. package/src/hooks/use-media-player.ts +19 -0
  120. package/src/hooks/use-media-provider.ts +31 -0
  121. package/src/hooks/use-media-remote.ts +37 -0
  122. package/src/hooks/use-media-state.ts +58 -0
  123. package/src/hooks/use-signals.ts +24 -0
  124. package/src/hooks/use-slider-preview.ts +126 -0
  125. package/src/hooks/use-slider-state.ts +63 -0
  126. package/src/hooks/use-state.ts +47 -0
  127. package/src/hooks/use-text-cues.ts +33 -0
  128. package/src/hooks/use-thumbnails.ts +69 -0
  129. package/src/icon.ts +37 -0
  130. package/src/icons.ts +754 -0
  131. package/src/index.ts +181 -0
  132. package/src/providers/remotion/index.ts +10 -0
  133. package/src/providers/remotion/layout-engine.ts +123 -0
  134. package/src/providers/remotion/loader.ts +35 -0
  135. package/src/providers/remotion/playback-engine.ts +142 -0
  136. package/src/providers/remotion/provider.tsx +514 -0
  137. package/src/providers/remotion/type-check.ts +13 -0
  138. package/src/providers/remotion/types.ts +94 -0
  139. package/src/providers/remotion/ui/context.tsx +120 -0
  140. package/src/providers/remotion/ui/error-boundary.tsx +57 -0
  141. package/src/providers/remotion/ui/poster.tsx +33 -0
  142. package/src/providers/remotion/ui/slider-thumbnail.tsx +41 -0
  143. package/src/providers/remotion/ui/thumbnail.tsx +166 -0
  144. package/src/providers/remotion/validate.ts +220 -0
  145. package/src/source.ts +5 -0
  146. package/src/utils.ts +27 -0
  147. package/tsconfig.build.json +10 -0
  148. package/tsconfig.json +11 -0
  149. package/types/react/src/components/announcer.d.ts +16 -0
  150. package/types/react/src/components/layouts/default/audio-layout.d.ts +27 -0
  151. package/types/react/src/components/layouts/default/context.d.ts +14 -0
  152. package/types/react/src/components/layouts/default/hooks.d.ts +2 -0
  153. package/types/react/src/components/layouts/default/icons.d.ts +95 -0
  154. package/types/react/src/components/layouts/default/index.d.ts +5 -0
  155. package/types/react/src/components/layouts/default/media-layout.d.ts +133 -0
  156. package/types/react/src/components/layouts/default/slots.d.ts +22 -0
  157. package/types/react/src/components/layouts/default/ui/announcer.d.ts +6 -0
  158. package/types/react/src/components/layouts/default/ui/buttons.d.ts +54 -0
  159. package/types/react/src/components/layouts/default/ui/captions.d.ts +6 -0
  160. package/types/react/src/components/layouts/default/ui/controls.d.ts +6 -0
  161. package/types/react/src/components/layouts/default/ui/keyboard-display.d.ts +8 -0
  162. package/types/react/src/components/layouts/default/ui/menus/accessibility-menu.d.ts +10 -0
  163. package/types/react/src/components/layouts/default/ui/menus/audio-menu.d.ts +10 -0
  164. package/types/react/src/components/layouts/default/ui/menus/captions-menu.d.ts +10 -0
  165. package/types/react/src/components/layouts/default/ui/menus/chapters-menu.d.ts +7 -0
  166. package/types/react/src/components/layouts/default/ui/menus/font-menu.d.ts +6 -0
  167. package/types/react/src/components/layouts/default/ui/menus/items/menu-checkbox.d.ts +13 -0
  168. package/types/react/src/components/layouts/default/ui/menus/items/menu-items.d.ts +49 -0
  169. package/types/react/src/components/layouts/default/ui/menus/items/menu-slider.d.ts +26 -0
  170. package/types/react/src/components/layouts/default/ui/menus/playback-menu.d.ts +10 -0
  171. package/types/react/src/components/layouts/default/ui/menus/settings-menu.d.ts +15 -0
  172. package/types/react/src/components/layouts/default/ui/menus/utils.d.ts +1 -0
  173. package/types/react/src/components/layouts/default/ui/sliders.d.ts +24 -0
  174. package/types/react/src/components/layouts/default/ui/time.d.ts +30 -0
  175. package/types/react/src/components/layouts/default/ui/title.d.ts +6 -0
  176. package/types/react/src/components/layouts/default/ui/tooltip.d.ts +12 -0
  177. package/types/react/src/components/layouts/default/ui.d.ts +8 -0
  178. package/types/react/src/components/layouts/default/video-layout.d.ts +47 -0
  179. package/types/react/src/components/layouts/plyr/context.d.ts +12 -0
  180. package/types/react/src/components/layouts/plyr/icons/plyr-airplay.d.ts +2 -0
  181. package/types/react/src/components/layouts/plyr/icons/plyr-captions-off.d.ts +2 -0
  182. package/types/react/src/components/layouts/plyr/icons/plyr-captions-on.d.ts +2 -0
  183. package/types/react/src/components/layouts/plyr/icons/plyr-download.d.ts +2 -0
  184. package/types/react/src/components/layouts/plyr/icons/plyr-enter-fullscreen.d.ts +2 -0
  185. package/types/react/src/components/layouts/plyr/icons/plyr-exit-fullscreen.d.ts +2 -0
  186. package/types/react/src/components/layouts/plyr/icons/plyr-fast-forward.d.ts +2 -0
  187. package/types/react/src/components/layouts/plyr/icons/plyr-muted.d.ts +2 -0
  188. package/types/react/src/components/layouts/plyr/icons/plyr-pause.d.ts +2 -0
  189. package/types/react/src/components/layouts/plyr/icons/plyr-pip.d.ts +2 -0
  190. package/types/react/src/components/layouts/plyr/icons/plyr-play.d.ts +2 -0
  191. package/types/react/src/components/layouts/plyr/icons/plyr-restart.d.ts +2 -0
  192. package/types/react/src/components/layouts/plyr/icons/plyr-rewind.d.ts +2 -0
  193. package/types/react/src/components/layouts/plyr/icons/plyr-settings.d.ts +2 -0
  194. package/types/react/src/components/layouts/plyr/icons/plyr-volume.d.ts +2 -0
  195. package/types/react/src/components/layouts/plyr/icons.d.ts +25 -0
  196. package/types/react/src/components/layouts/plyr/index.d.ts +6 -0
  197. package/types/react/src/components/layouts/plyr/layout.d.ts +17 -0
  198. package/types/react/src/components/layouts/plyr/props.d.ts +71 -0
  199. package/types/react/src/components/layouts/plyr/slots.d.ts +9 -0
  200. package/types/react/src/components/layouts/remotion-ui.d.ts +3 -0
  201. package/types/react/src/components/layouts/utils.d.ts +1 -0
  202. package/types/react/src/components/player-callbacks.d.ts +6 -0
  203. package/types/react/src/components/player.d.ts +32 -0
  204. package/types/react/src/components/primitives/instances.d.ts +83 -0
  205. package/types/react/src/components/primitives/nodes.d.ts +15 -0
  206. package/types/react/src/components/primitives/slot.d.ts +11 -0
  207. package/types/react/src/components/provider.d.ts +26 -0
  208. package/types/react/src/components/text-track.d.ts +100 -0
  209. package/types/react/src/components/ui/buttons/airplay-button.d.ts +22 -0
  210. package/types/react/src/components/ui/buttons/caption-button.d.ts +24 -0
  211. package/types/react/src/components/ui/buttons/fullscreen-button.d.ts +24 -0
  212. package/types/react/src/components/ui/buttons/google-cast-button.d.ts +22 -0
  213. package/types/react/src/components/ui/buttons/live-button.d.ts +26 -0
  214. package/types/react/src/components/ui/buttons/mute-button.d.ts +30 -0
  215. package/types/react/src/components/ui/buttons/pip-button.d.ts +24 -0
  216. package/types/react/src/components/ui/buttons/play-button.d.ts +23 -0
  217. package/types/react/src/components/ui/buttons/seek-button.d.ts +25 -0
  218. package/types/react/src/components/ui/buttons/toggle-button.d.ts +22 -0
  219. package/types/react/src/components/ui/caption.d.ts +11 -0
  220. package/types/react/src/components/ui/captions.d.ts +20 -0
  221. package/types/react/src/components/ui/chapter-title.d.ts +20 -0
  222. package/types/react/src/components/ui/controls.d.ts +40 -0
  223. package/types/react/src/components/ui/gesture.d.ts +20 -0
  224. package/types/react/src/components/ui/menu.d.ts +102 -0
  225. package/types/react/src/components/ui/poster.d.ts +25 -0
  226. package/types/react/src/components/ui/radio-group.d.ts +39 -0
  227. package/types/react/src/components/ui/sliders/audio-gain-slider.d.ts +29 -0
  228. package/types/react/src/components/ui/sliders/quality-slider.d.ts +28 -0
  229. package/types/react/src/components/ui/sliders/slider-callbacks.d.ts +6 -0
  230. package/types/react/src/components/ui/sliders/slider-value.d.ts +9 -0
  231. package/types/react/src/components/ui/sliders/slider.d.ts +134 -0
  232. package/types/react/src/components/ui/sliders/speed-slider.d.ts +28 -0
  233. package/types/react/src/components/ui/sliders/time-slider.d.ts +124 -0
  234. package/types/react/src/components/ui/sliders/volume-slider.d.ts +29 -0
  235. package/types/react/src/components/ui/spinner.d.ts +31 -0
  236. package/types/react/src/components/ui/thumbnail.d.ts +26 -0
  237. package/types/react/src/components/ui/time.d.ts +20 -0
  238. package/types/react/src/components/ui/title.d.ts +15 -0
  239. package/types/react/src/components/ui/tooltip.d.ts +63 -0
  240. package/types/react/src/hooks/create-text-track.d.ts +7 -0
  241. package/types/react/src/hooks/options/use-audio-gain-options.d.ts +22 -0
  242. package/types/react/src/hooks/options/use-audio-options.d.ts +17 -0
  243. package/types/react/src/hooks/options/use-caption-options.d.ts +24 -0
  244. package/types/react/src/hooks/options/use-chapter-options.d.ts +18 -0
  245. package/types/react/src/hooks/options/use-playback-rate-options.d.ts +22 -0
  246. package/types/react/src/hooks/options/use-video-quality-options.d.ts +35 -0
  247. package/types/react/src/hooks/use-active-text-cues.d.ts +6 -0
  248. package/types/react/src/hooks/use-active-text-track.d.ts +5 -0
  249. package/types/react/src/hooks/use-chapter-title.d.ts +4 -0
  250. package/types/react/src/hooks/use-dom.d.ts +9 -0
  251. package/types/react/src/hooks/use-media-context.d.ts +1 -0
  252. package/types/react/src/hooks/use-media-player.d.ts +7 -0
  253. package/types/react/src/hooks/use-media-provider.d.ts +7 -0
  254. package/types/react/src/hooks/use-media-remote.d.ts +12 -0
  255. package/types/react/src/hooks/use-media-state.d.ts +15 -0
  256. package/types/react/src/hooks/use-signals.d.ts +5 -0
  257. package/types/react/src/hooks/use-slider-preview.d.ts +27 -0
  258. package/types/react/src/hooks/use-slider-state.d.ts +16 -0
  259. package/types/react/src/hooks/use-state.d.ts +18 -0
  260. package/types/react/src/hooks/use-text-cues.d.ts +6 -0
  261. package/types/react/src/hooks/use-thumbnails.d.ts +16 -0
  262. package/types/react/src/icon.d.ts +17 -0
  263. package/types/react/src/icons.d.ts +215 -0
  264. package/types/react/src/index.d.ts +78 -0
  265. package/types/react/src/providers/remotion/index.d.ts +7 -0
  266. package/types/react/src/providers/remotion/layout-engine.d.ts +8 -0
  267. package/types/react/src/providers/remotion/loader.d.ts +9 -0
  268. package/types/react/src/providers/remotion/playback-engine.d.ts +11 -0
  269. package/types/react/src/providers/remotion/provider.d.ts +26 -0
  270. package/types/react/src/providers/remotion/type-check.d.ts +6 -0
  271. package/types/react/src/providers/remotion/types.d.ts +91 -0
  272. package/types/react/src/providers/remotion/ui/context.d.ts +17 -0
  273. package/types/react/src/providers/remotion/ui/error-boundary.d.ts +21 -0
  274. package/types/react/src/providers/remotion/ui/poster.d.ts +18 -0
  275. package/types/react/src/providers/remotion/ui/slider-thumbnail.d.ts +17 -0
  276. package/types/react/src/providers/remotion/ui/thumbnail.d.ts +32 -0
  277. package/types/react/src/providers/remotion/validate.d.ts +12 -0
  278. package/types/react/src/source.d.ts +3 -0
  279. package/types/react/src/utils.d.ts +3 -0
  280. package/types/vidstack/src/core/api/src-types.d.ts +50 -0
  281. package/types/vidstack/src/utils/mime.d.ts +15 -0
  282. package/types/vidstack/src/utils/network.d.ts +17 -0
  283. package/types/vidstack/src/utils/support.d.ts +72 -0
  284. package/vite.config.ts +23 -0
@@ -0,0 +1,1024 @@
1
+ import * as React from 'react';
2
+
3
+ import { signal } from 'maverick.js';
4
+ import { composeRefs, useSignal } from 'maverick.js/react';
5
+ import { isNumber, isString, listenEvent } from 'maverick.js/std';
6
+ import type { VTTCue } from 'media-captions';
7
+ import {
8
+ isKeyboardClick,
9
+ isKeyboardEvent,
10
+ usePlyrLayoutClasses,
11
+ type PlyrControl,
12
+ type PlyrLayoutWord,
13
+ type PlyrMarker,
14
+ } from 'vidstack';
15
+
16
+ import { getDownloadFile } from '../../../../../vidstack/src/utils/network';
17
+ import { useAudioOptions } from '../../../hooks/options/use-audio-options';
18
+ import { useCaptionOptions } from '../../../hooks/options/use-caption-options';
19
+ import { usePlaybackRateOptions } from '../../../hooks/options/use-playback-rate-options';
20
+ import { useVideoQualityOptions } from '../../../hooks/options/use-video-quality-options';
21
+ import { useClassName } from '../../../hooks/use-dom';
22
+ import { useMediaContext } from '../../../hooks/use-media-context';
23
+ import { useMediaRemote } from '../../../hooks/use-media-remote';
24
+ import { useMediaState } from '../../../hooks/use-media-state';
25
+ import { isRemotionSource } from '../../../providers/remotion';
26
+ import { appendParamsToURL } from '../../../utils';
27
+ import { Primitive, type PrimitivePropsWithRef } from '../../primitives/nodes';
28
+ import { AirPlayButton } from '../../ui/buttons/airplay-button';
29
+ import { CaptionButton } from '../../ui/buttons/caption-button';
30
+ import { FullscreenButton } from '../../ui/buttons/fullscreen-button';
31
+ import { LiveButton } from '../../ui/buttons/live-button';
32
+ import { MuteButton } from '../../ui/buttons/mute-button';
33
+ import { PIPButton } from '../../ui/buttons/pip-button';
34
+ import { PlayButton } from '../../ui/buttons/play-button';
35
+ import { SeekButton } from '../../ui/buttons/seek-button';
36
+ import { Gesture } from '../../ui/gesture';
37
+ import * as Menu from '../../ui/menu';
38
+ import * as TimeSlider from '../../ui/sliders/time-slider';
39
+ import * as VolumeSlider from '../../ui/sliders/volume-slider';
40
+ import * as Thumbnail from '../../ui/thumbnail';
41
+ import { Time } from '../../ui/time';
42
+ import { RemotionPoster, RemotionSliderThumbnail, RemotionThumbnail } from '../remotion-ui';
43
+ import { useLayoutName } from '../utils';
44
+ import { i18n, PlyrLayoutContext, usePlyrLayoutContext, usePlyrLayoutWord } from './context';
45
+ import { defaultPlyrLayoutProps, type PlyrLayoutProps } from './props';
46
+ import { slot } from './slots';
47
+
48
+ /* -------------------------------------------------------------------------------------------------
49
+ * PlyrLayout
50
+ * -----------------------------------------------------------------------------------------------*/
51
+
52
+ export interface PlyrLayoutElementProps extends PlyrLayoutProps, PrimitivePropsWithRef<'div'> {}
53
+
54
+ const PlyrLayout = React.forwardRef<HTMLElement, PlyrLayoutElementProps>(
55
+ (userProps, forwardRef) => {
56
+ const {
57
+ clickToPlay,
58
+ clickToFullscreen,
59
+ controls,
60
+ displayDuration,
61
+ download,
62
+ markers,
63
+ invertTime,
64
+ thumbnails,
65
+ toggleTime,
66
+ translations,
67
+ seekTime,
68
+ speed,
69
+ icons,
70
+ slots,
71
+ posterFrame,
72
+ className,
73
+ ...elProps
74
+ } = { ...defaultPlyrLayoutProps, ...userProps },
75
+ [el, setEl] = React.useState<HTMLElement | null>(null),
76
+ media = useMediaContext(),
77
+ previewTime = React.useMemo(() => signal(0), []),
78
+ $viewType = useMediaState('viewType');
79
+
80
+ useLayoutName('plyr');
81
+ useClassName(el, className);
82
+
83
+ React.useEffect(() => {
84
+ if (!el || !media) return;
85
+ return usePlyrLayoutClasses(el, media);
86
+ }, [el, media]);
87
+
88
+ return (
89
+ <PlyrLayoutContext.Provider
90
+ value={{
91
+ clickToPlay,
92
+ clickToFullscreen,
93
+ controls,
94
+ displayDuration,
95
+ download,
96
+ markers,
97
+ invertTime,
98
+ thumbnails,
99
+ toggleTime,
100
+ translations,
101
+ seekTime,
102
+ speed,
103
+ previewTime,
104
+ icons,
105
+ slots,
106
+ posterFrame,
107
+ }}
108
+ >
109
+ <Primitive.div
110
+ {...elProps}
111
+ className={
112
+ __SERVER__ ? `plyr plyr--full-ui plyr--${$viewType} ${className || ''}` : undefined
113
+ }
114
+ ref={composeRefs(setEl, forwardRef) as any}
115
+ >
116
+ {$viewType === 'audio' ? (
117
+ <PlyrAudioLayout />
118
+ ) : $viewType === 'video' ? (
119
+ <PlyrVideoLayout />
120
+ ) : null}
121
+ </Primitive.div>
122
+ </PlyrLayoutContext.Provider>
123
+ );
124
+ },
125
+ );
126
+
127
+ PlyrLayout.displayName = 'PlyrLayout';
128
+ export { PlyrLayout };
129
+
130
+ /* -------------------------------------------------------------------------------------------------
131
+ * PlyrAudioLayout
132
+ * -----------------------------------------------------------------------------------------------*/
133
+
134
+ function PlyrAudioLayout() {
135
+ return PlyrAudioControls();
136
+ }
137
+
138
+ PlyrAudioLayout.displayName = 'PlyrAudioLayout';
139
+ export { PlyrAudioLayout };
140
+
141
+ /* -------------------------------------------------------------------------------------------------
142
+ * PlyrVideoLayout
143
+ * -----------------------------------------------------------------------------------------------*/
144
+
145
+ function PlyrVideoLayout() {
146
+ const media = useMediaContext(),
147
+ { controls } = usePlyrLayoutContext(),
148
+ { load } = media.$props,
149
+ { canLoad } = media.$state,
150
+ $load = useSignal(load),
151
+ $canLoad = useSignal(canLoad);
152
+
153
+ if ($load === 'play' && !$canLoad) {
154
+ return (
155
+ <>
156
+ <PlyrPlayLargeButton />
157
+ <PlyrPoster />
158
+ </>
159
+ );
160
+ }
161
+
162
+ return (
163
+ <>
164
+ {controls!.includes('play-large') ? <PlyrPlayLargeButton /> : null}
165
+ <PlyrPreviewScrubbing />
166
+ <PlyrPoster />
167
+ <PlyrVideoControls />
168
+ <PlyrGestures />
169
+ <PlyrCaptions />
170
+ </>
171
+ );
172
+ }
173
+
174
+ PlyrVideoLayout.displayName = 'PlyrVideoLayout';
175
+ export { PlyrVideoLayout };
176
+
177
+ /* -------------------------------------------------------------------------------------------------
178
+ * PlyrPlayLargeButton
179
+ * -----------------------------------------------------------------------------------------------*/
180
+
181
+ function PlyrPlayLargeButton() {
182
+ const { translations, icons: Icons } = usePlyrLayoutContext(),
183
+ $title = useMediaState('title'),
184
+ label = `${i18n(translations, 'Play')} ${$title}`;
185
+ return slot(
186
+ 'playLargeButton',
187
+ <PlayButton
188
+ className="plyr__control plyr__control--overlaid"
189
+ aria-label={label}
190
+ data-plyr="play"
191
+ >
192
+ <Icons.Play />
193
+ </PlayButton>,
194
+ );
195
+ }
196
+
197
+ PlyrPlayLargeButton.displayName = 'PlyrPlayLargeButton';
198
+
199
+ /* -------------------------------------------------------------------------------------------------
200
+ * PlyrPreviewScrubbing
201
+ * -----------------------------------------------------------------------------------------------*/
202
+
203
+ function PlyrPreviewScrubbing() {
204
+ const $src = useMediaState('source'),
205
+ { thumbnails, previewTime } = usePlyrLayoutContext(),
206
+ $previewTime = useSignal(previewTime),
207
+ $RemotionThumbnail = useSignal(RemotionThumbnail),
208
+ $hasRemotionThumbnail = $RemotionThumbnail && isRemotionSource($src);
209
+
210
+ return $hasRemotionThumbnail ? (
211
+ <$RemotionThumbnail className="plyr__preview-scrubbing" frame={$previewTime * $src.fps!} />
212
+ ) : (
213
+ <Thumbnail.Root src={thumbnails} className="plyr__preview-scrubbing" time={$previewTime}>
214
+ <Thumbnail.Img />
215
+ </Thumbnail.Root>
216
+ );
217
+ }
218
+
219
+ PlyrPreviewScrubbing.displayName = 'PlyrPreviewScrubbing';
220
+
221
+ /* -------------------------------------------------------------------------------------------------
222
+ * PlyrPoster
223
+ * -----------------------------------------------------------------------------------------------*/
224
+
225
+ function PlyrPoster() {
226
+ const $src = useMediaState('source'),
227
+ $poster = useMediaState('poster'),
228
+ { posterFrame } = usePlyrLayoutContext(),
229
+ $RemotionPoster = useSignal(RemotionPoster),
230
+ $hasRemotionPoster = $RemotionPoster && isRemotionSource($src) && isNumber(posterFrame);
231
+
232
+ return slot(
233
+ 'poster',
234
+ $hasRemotionPoster ? (
235
+ <$RemotionPoster frame={posterFrame} className="plyr__poster" />
236
+ ) : (
237
+ <div className="plyr__poster" style={{ backgroundImage: `url("${$poster}")` }} />
238
+ ),
239
+ );
240
+ }
241
+
242
+ PlyrPoster.displayName = 'PlyrPoster';
243
+
244
+ /* -------------------------------------------------------------------------------------------------
245
+ * PlyrAudioControls
246
+ * -----------------------------------------------------------------------------------------------*/
247
+
248
+ const noAudioControl = new Set<PlyrControl>(['captions', 'pip', 'airplay', 'fullscreen']);
249
+
250
+ function PlyrAudioControls() {
251
+ const { controls } = usePlyrLayoutContext();
252
+ return (
253
+ <div className="plyr__controls">
254
+ {controls!
255
+ .filter((type) => !noAudioControl.has(type))
256
+ .map((type, i) => {
257
+ const Control = getPlyrControl(type);
258
+ return Control ? React.createElement(Control, { key: i }) : null;
259
+ })}
260
+ </div>
261
+ );
262
+ }
263
+
264
+ PlyrAudioControls.displayName = 'PlyrAudioControls';
265
+
266
+ /* -------------------------------------------------------------------------------------------------
267
+ * PlyrVideoControls
268
+ * -----------------------------------------------------------------------------------------------*/
269
+
270
+ function PlyrVideoControls() {
271
+ const { controls } = usePlyrLayoutContext();
272
+ return (
273
+ <div className="plyr__controls">
274
+ {controls!.map((type, i) => {
275
+ const Control = getPlyrControl(type);
276
+ return Control ? React.createElement(Control, { key: i }) : null;
277
+ })}
278
+ </div>
279
+ );
280
+ }
281
+
282
+ PlyrVideoControls.displayName = 'PlyrVideoControls';
283
+
284
+ /* -------------------------------------------------------------------------------------------------
285
+ * Control
286
+ * -----------------------------------------------------------------------------------------------*/
287
+
288
+ function getPlyrControl(type: PlyrControl) {
289
+ switch (type) {
290
+ case 'airplay':
291
+ return PlyrAirPlayButton;
292
+ case 'captions':
293
+ return PlyrCaptionsButton;
294
+ case 'current-time':
295
+ return PlyrCurrentTime;
296
+ case 'download':
297
+ return PlyrDownloadButton;
298
+ case 'duration':
299
+ return PlyrDuration;
300
+ case 'fast-forward':
301
+ return PlyrFastForwardButton;
302
+ case 'fullscreen':
303
+ return PlyrFullscreenButton;
304
+ case 'mute':
305
+ case 'volume':
306
+ case 'mute+volume':
307
+ return () => PlyrVolume({ type });
308
+ case 'pip':
309
+ return PlyrPIPButton;
310
+ case 'play':
311
+ return PlyrPlayButton;
312
+ case 'progress':
313
+ return PlyrTimeSlider;
314
+ case 'restart':
315
+ return PlyrRestartButton;
316
+ case 'rewind':
317
+ return PlyrRewindButton;
318
+ case 'settings':
319
+ return PlyrSettings;
320
+ default:
321
+ return null;
322
+ }
323
+ }
324
+
325
+ /* -------------------------------------------------------------------------------------------------
326
+ * PlyrAirPlayButton
327
+ * -----------------------------------------------------------------------------------------------*/
328
+
329
+ function PlyrAirPlayButton() {
330
+ const { icons: Icons } = usePlyrLayoutContext(),
331
+ airPlayText = usePlyrLayoutWord('AirPlay');
332
+ return slot(
333
+ 'airPlayButton',
334
+ <AirPlayButton className="plyr__controls__item plyr__control" data-plyr="airplay">
335
+ <Icons.AirPlay />
336
+ <span className="plyr__tooltip">{airPlayText}</span>
337
+ </AirPlayButton>,
338
+ );
339
+ }
340
+
341
+ PlyrAirPlayButton.displayName = 'PlyrAirPlayButton';
342
+
343
+ /* -------------------------------------------------------------------------------------------------
344
+ * PlyrCaptionsButton
345
+ * -----------------------------------------------------------------------------------------------*/
346
+
347
+ function PlyrCaptionsButton() {
348
+ const { icons: Icons } = usePlyrLayoutContext(),
349
+ enableText = usePlyrLayoutWord('Enable captions'),
350
+ disableText = usePlyrLayoutWord('Disable captions');
351
+ return slot(
352
+ 'captionsButton',
353
+ <CaptionButton
354
+ className="plyr__controls__item plyr__control"
355
+ data-no-label
356
+ data-plyr="captions"
357
+ >
358
+ <Icons.CaptionsOn className="icon--pressed" />
359
+ <Icons.CaptionsOff className="icon--not-pressed" />
360
+ <span className="label--pressed plyr__tooltip">{disableText}</span>
361
+ <span className="label--not-pressed plyr__tooltip">{enableText}</span>
362
+ </CaptionButton>,
363
+ );
364
+ }
365
+
366
+ PlyrCaptionsButton.displayName = 'PlyrCaptionsButton';
367
+
368
+ /* -------------------------------------------------------------------------------------------------
369
+ * PlyrFullscreenButton
370
+ * -----------------------------------------------------------------------------------------------*/
371
+
372
+ function PlyrFullscreenButton() {
373
+ const { icons: Icons } = usePlyrLayoutContext(),
374
+ enterText = usePlyrLayoutWord('Enter Fullscreen'),
375
+ exitText = usePlyrLayoutWord('Exit Fullscreen');
376
+ return slot(
377
+ 'fullscreenButton',
378
+ <FullscreenButton
379
+ className="plyr__controls__item plyr__control"
380
+ data-no-label
381
+ data-plyr="fullscreen"
382
+ >
383
+ <Icons.EnterFullscreen className="icon--pressed" />
384
+ <Icons.ExitFullscreen className="icon--not-pressed" />
385
+ <span className="label--pressed plyr__tooltip">{exitText}</span>
386
+ <span className="label--not-pressed plyr__tooltip">{enterText}</span>
387
+ </FullscreenButton>,
388
+ );
389
+ }
390
+
391
+ PlyrFullscreenButton.displayName = 'PlyrFullscreenButton';
392
+
393
+ /* -------------------------------------------------------------------------------------------------
394
+ * PlyrPIPButton
395
+ * -----------------------------------------------------------------------------------------------*/
396
+
397
+ function PlyrPIPButton() {
398
+ const { icons: Icons } = usePlyrLayoutContext(),
399
+ enterText = usePlyrLayoutWord('Enter PiP'),
400
+ exitText = usePlyrLayoutWord('Exit PiP');
401
+ return slot(
402
+ 'pipButton',
403
+ <PIPButton className="plyr__controls__item plyr__control" data-no-label data-plyr="pip">
404
+ <Icons.EnterPiP className="icon--pressed" />
405
+ <Icons.ExitPiP className="icon--not-pressed" />
406
+ <span className="label--pressed plyr__tooltip">{exitText}</span>
407
+ <span className="label--not-pressed plyr__tooltip">{enterText}</span>
408
+ </PIPButton>,
409
+ );
410
+ }
411
+
412
+ PlyrPIPButton.displayName = 'PlyrPIPButton';
413
+
414
+ /* -------------------------------------------------------------------------------------------------
415
+ * PlyrMuteButton
416
+ * -----------------------------------------------------------------------------------------------*/
417
+
418
+ function PlyrMuteButton() {
419
+ const { icons: Icons } = usePlyrLayoutContext(),
420
+ muteText = usePlyrLayoutWord('Mute'),
421
+ unmuteText = usePlyrLayoutWord('Unmute');
422
+ return slot(
423
+ 'muteButton',
424
+ <MuteButton className="plyr__control" data-no-label data-plyr="mute">
425
+ <Icons.Muted className="icon--pressed" />
426
+ <Icons.Volume className="icon--not-pressed" />
427
+ <span className="label--pressed plyr__tooltip">{unmuteText}</span>
428
+ <span className="label--not-pressed plyr__tooltip">{muteText}</span>
429
+ </MuteButton>,
430
+ );
431
+ }
432
+
433
+ PlyrMuteButton.displayName = 'PlyrMuteButton';
434
+
435
+ /* -------------------------------------------------------------------------------------------------
436
+ * PlyrPlayButton
437
+ * -----------------------------------------------------------------------------------------------*/
438
+
439
+ function PlyrPlayButton() {
440
+ const { icons: Icons } = usePlyrLayoutContext(),
441
+ playText = usePlyrLayoutWord('Play'),
442
+ pauseText = usePlyrLayoutWord('Pause');
443
+ return slot(
444
+ 'playButton',
445
+ <PlayButton className="plyr__controls__item plyr__control" data-no-label data-plyr="play">
446
+ <Icons.Pause className="icon--pressed" />
447
+ <Icons.Play className="icon--not-pressed" />
448
+ <span className="label--pressed plyr__tooltip">{pauseText}</span>
449
+ <span className="label--not-pressed plyr__tooltip">{playText}</span>
450
+ </PlayButton>,
451
+ );
452
+ }
453
+
454
+ PlyrPlayButton.displayName = 'PlyrPlayButton';
455
+
456
+ /* -------------------------------------------------------------------------------------------------
457
+ * PlyrRestartButton
458
+ * -----------------------------------------------------------------------------------------------*/
459
+
460
+ function PlyrRestartButton() {
461
+ const { icons: Icons } = usePlyrLayoutContext(),
462
+ restartText = usePlyrLayoutWord('Restart'),
463
+ remote = useMediaRemote();
464
+
465
+ function onPress({ nativeEvent: event }: React.SyntheticEvent) {
466
+ if (isKeyboardEvent(event) && !isKeyboardClick(event)) return;
467
+ remote.seek(0, event);
468
+ }
469
+
470
+ return slot(
471
+ 'restartButton',
472
+ <button
473
+ type="button"
474
+ className="plyr__control"
475
+ data-plyr="restart"
476
+ onPointerUp={onPress}
477
+ onKeyDown={onPress}
478
+ >
479
+ <slot name="restart-icon" data-class=""></slot>
480
+ <Icons.Restart />
481
+ <span className="plyr__tooltip">{restartText}</span>
482
+ </button>,
483
+ );
484
+ }
485
+
486
+ PlyrRestartButton.displayName = 'PlyrRestartButton';
487
+
488
+ /* -------------------------------------------------------------------------------------------------
489
+ * PlyrFastForwardButton
490
+ * -----------------------------------------------------------------------------------------------*/
491
+
492
+ function PlyrFastForwardButton() {
493
+ const { icons: Icons, seekTime } = usePlyrLayoutContext(),
494
+ forwardText = usePlyrLayoutWord('Forward'),
495
+ label = `${forwardText} ${seekTime}s`;
496
+ return slot(
497
+ 'fastForwardButton',
498
+ <SeekButton
499
+ className="plyr__controls__item plyr__control"
500
+ seconds={seekTime}
501
+ data-no-label
502
+ data-plyr="fast-forward"
503
+ >
504
+ <Icons.FastForward />
505
+ <span className="plyr__tooltip">{label}</span>
506
+ </SeekButton>,
507
+ );
508
+ }
509
+
510
+ PlyrFastForwardButton.displayName = 'PlyrFastForwardButton';
511
+
512
+ /* -------------------------------------------------------------------------------------------------
513
+ * PlyrRewindButton
514
+ * -----------------------------------------------------------------------------------------------*/
515
+
516
+ function PlyrRewindButton() {
517
+ const { icons: Icons, seekTime } = usePlyrLayoutContext(),
518
+ rewindText = usePlyrLayoutWord('Rewind'),
519
+ label = `${rewindText} ${seekTime}s`;
520
+ return slot(
521
+ 'rewindButton',
522
+ <SeekButton
523
+ className="plyr__controls__item plyr__control"
524
+ seconds={-1 * seekTime!}
525
+ data-no-label
526
+ data-plyr="rewind"
527
+ >
528
+ <Icons.Rewind />
529
+ <span className="plyr__tooltip">{label}</span>
530
+ </SeekButton>,
531
+ );
532
+ }
533
+
534
+ PlyrRewindButton.displayName = 'PlyrRewindButton';
535
+
536
+ /* -------------------------------------------------------------------------------------------------
537
+ * PlyrTimeSlider
538
+ * -----------------------------------------------------------------------------------------------*/
539
+
540
+ function PlyrTimeSlider() {
541
+ const { markers, thumbnails, seekTime, previewTime } = usePlyrLayoutContext(),
542
+ $src = useMediaState('source'),
543
+ $duration = useMediaState('duration'),
544
+ seekText = usePlyrLayoutWord('Seek'),
545
+ [activeMarker, setActiveMarker] = React.useState<PlyrMarker | null>(null),
546
+ $RemotionSliderThumbnail = useSignal(RemotionSliderThumbnail),
547
+ $hasRemotionSliderThumbnail = $RemotionSliderThumbnail && isRemotionSource($src);
548
+
549
+ function onSeekingRequest(time: number) {
550
+ previewTime.set(time);
551
+ }
552
+
553
+ function onMarkerEnter(this: PlyrMarker) {
554
+ setActiveMarker(this);
555
+ }
556
+
557
+ function onMarkerLeave() {
558
+ setActiveMarker(null);
559
+ }
560
+
561
+ const markerLabel = activeMarker ? (
562
+ <span
563
+ className="plyr__progress__marker-label"
564
+ dangerouslySetInnerHTML={{ __html: activeMarker.label + '<br />' }}
565
+ ></span>
566
+ ) : null;
567
+
568
+ return slot(
569
+ 'timeSlider',
570
+ <div className="plyr__controls__item plyr__progress__container">
571
+ <div className="plyr__progress">
572
+ <TimeSlider.Root
573
+ className="plyr__slider"
574
+ pauseWhileDragging
575
+ keyStep={seekTime}
576
+ aria-label={seekText}
577
+ data-plyr="seek"
578
+ onMediaSeekingRequest={onSeekingRequest}
579
+ >
580
+ <div className="plyr__slider__track"></div>
581
+ <div className="plyr__slider__thumb"></div>
582
+ <div className="plyr__slider__buffer"></div>
583
+
584
+ {!thumbnails && !$hasRemotionSliderThumbnail ? (
585
+ <span className="plyr__tooltip">
586
+ {markerLabel}
587
+ <TimeSlider.Value />
588
+ </span>
589
+ ) : $hasRemotionSliderThumbnail ? (
590
+ <TimeSlider.Preview className="plyr__slider__preview">
591
+ <div className="plyr__slider__preview__thumbnail">
592
+ <span className="plyr__slider__preview__time-container">
593
+ {markerLabel}
594
+ <TimeSlider.Value className="plyr__slider__preview__time" />
595
+ </span>
596
+ <$RemotionSliderThumbnail className="plyr__slider__preview__thumbnail" />
597
+ </div>
598
+ </TimeSlider.Preview>
599
+ ) : (
600
+ <TimeSlider.Preview className="plyr__slider__preview">
601
+ <TimeSlider.Thumbnail.Root
602
+ src={thumbnails}
603
+ className="plyr__slider__preview__thumbnail"
604
+ >
605
+ <span className="plyr__slider__preview__time-container">
606
+ {markerLabel}
607
+ <TimeSlider.Value className="plyr__slider__preview__time" />
608
+ </span>
609
+ <TimeSlider.Thumbnail.Img />
610
+ </TimeSlider.Thumbnail.Root>
611
+ </TimeSlider.Preview>
612
+ )}
613
+
614
+ {markers && Number.isFinite($duration)
615
+ ? markers.map((marker, i) => (
616
+ <span
617
+ className="plyr__progress__marker"
618
+ key={i}
619
+ onMouseEnter={onMarkerEnter.bind(marker)}
620
+ onMouseLeave={onMarkerLeave}
621
+ style={{ left: `${(marker.time / $duration) * 100}%` }}
622
+ ></span>
623
+ ))
624
+ : null}
625
+ </TimeSlider.Root>
626
+ </div>
627
+ </div>,
628
+ );
629
+ }
630
+
631
+ PlyrTimeSlider.displayName = 'PlyrTimeSlider';
632
+
633
+ /* -------------------------------------------------------------------------------------------------
634
+ * PlyrVolumeSlider
635
+ * -----------------------------------------------------------------------------------------------*/
636
+
637
+ function PlyrVolumeSlider() {
638
+ const volumeText = usePlyrLayoutWord('Volume');
639
+ return slot(
640
+ 'volumeSlider',
641
+ <VolumeSlider.Root className="plyr__slider" data-plyr="volume" aria-label={volumeText}>
642
+ <div className="plyr__slider__track"></div>
643
+ <div className="plyr__slider__thumb"></div>
644
+ </VolumeSlider.Root>,
645
+ );
646
+ }
647
+
648
+ PlyrVolumeSlider.displayName = 'PlyrVolumeSlider';
649
+
650
+ /* -------------------------------------------------------------------------------------------------
651
+ * PlyrVolume
652
+ * -----------------------------------------------------------------------------------------------*/
653
+
654
+ function PlyrVolume({ type }: { type: PlyrControl }) {
655
+ const hasMuteButton = type === 'mute' || type === 'mute+volume',
656
+ hasVolumeSlider = type === 'volume' || type === 'mute+volume';
657
+ return (
658
+ <div className="plyr__controls__item plyr__volume">
659
+ {hasMuteButton ? <PlyrMuteButton /> : null}
660
+ {hasVolumeSlider ? <PlyrVolumeSlider /> : null}
661
+ </div>
662
+ );
663
+ }
664
+
665
+ PlyrVolume.displayName = 'PlyrVolume';
666
+
667
+ /* -------------------------------------------------------------------------------------------------
668
+ * PlyrCurrentTime
669
+ * -----------------------------------------------------------------------------------------------*/
670
+
671
+ function PlyrCurrentTime() {
672
+ const { invertTime, toggleTime, displayDuration } = usePlyrLayoutContext(),
673
+ $streamType = useMediaState('streamType'),
674
+ currentTimeText = usePlyrLayoutWord('Current time'),
675
+ liveText = usePlyrLayoutWord('LIVE'),
676
+ [invert, setInvert] = React.useState(invertTime),
677
+ remainder = !displayDuration && invert;
678
+
679
+ function onPress({ nativeEvent: event }: React.SyntheticEvent) {
680
+ if (!toggleTime || displayDuration || (isKeyboardEvent(event) && !isKeyboardClick(event))) {
681
+ return;
682
+ }
683
+
684
+ setInvert((n) => !n);
685
+ }
686
+
687
+ return slot(
688
+ 'currentTime',
689
+ $streamType === 'live' || $streamType === 'll-live' ? (
690
+ <LiveButton className="plyr__controls__item plyr__control plyr__live-button" data-plyr="live">
691
+ <span className="plyr__live-button__text">{liveText}</span>
692
+ </LiveButton>
693
+ ) : (
694
+ <>
695
+ <Time
696
+ type="current"
697
+ className="plyr__controls__item plyr__time plyr__time--current"
698
+ role="timer"
699
+ aria-label={currentTimeText}
700
+ tabIndex={0}
701
+ remainder={remainder}
702
+ onPointerUp={onPress}
703
+ onKeyDown={onPress}
704
+ />
705
+ {displayDuration ? <PlyrDuration /> : null}
706
+ </>
707
+ ),
708
+ );
709
+ }
710
+
711
+ PlyrCurrentTime.displayName = 'PlyrCurrentTime';
712
+
713
+ /* -------------------------------------------------------------------------------------------------
714
+ * PlyrDuration
715
+ * -----------------------------------------------------------------------------------------------*/
716
+
717
+ function PlyrDuration() {
718
+ const durationText = usePlyrLayoutWord('Duration');
719
+ return slot(
720
+ 'duration',
721
+ <Time
722
+ className="plyr__controls__item plyr__time plyr__time--duration"
723
+ type="duration"
724
+ role="timer"
725
+ tabIndex={0}
726
+ aria-label={durationText}
727
+ />,
728
+ );
729
+ }
730
+
731
+ PlyrDuration.displayName = 'PlyrDuration';
732
+
733
+ /* -------------------------------------------------------------------------------------------------
734
+ * PlyrDownloadButton
735
+ * -----------------------------------------------------------------------------------------------*/
736
+
737
+ function PlyrDownloadButton() {
738
+ const { download } = usePlyrLayoutContext(),
739
+ $src = useMediaState('source'),
740
+ $title = useMediaState('title'),
741
+ file = getDownloadFile({
742
+ title: $title,
743
+ src: $src,
744
+ download,
745
+ }),
746
+ downloadText = usePlyrLayoutWord('Download');
747
+
748
+ return slot(
749
+ 'download',
750
+ isString(file?.url) ? (
751
+ <a
752
+ className="plyr__controls__item plyr__control"
753
+ href={appendParamsToURL(file.url, { download: file.name })}
754
+ download={file.name}
755
+ target="_blank"
756
+ >
757
+ <slot name="download-icon" />
758
+ <span className="plyr__tooltip">{downloadText}</span>
759
+ </a>
760
+ ) : null,
761
+ );
762
+ }
763
+
764
+ PlyrDownloadButton.displayName = 'PlyrDownloadButton';
765
+
766
+ /* -------------------------------------------------------------------------------------------------
767
+ * PlyrGestures
768
+ * -----------------------------------------------------------------------------------------------*/
769
+
770
+ function PlyrGestures() {
771
+ const { clickToPlay, clickToFullscreen } = usePlyrLayoutContext();
772
+ return (
773
+ <>
774
+ {clickToPlay ? (
775
+ <Gesture className="plyr__gesture" event="pointerup" action="toggle:paused" />
776
+ ) : null}
777
+ {clickToFullscreen ? (
778
+ <Gesture className="plyr__gesture" event="dblpointerup" action="toggle:fullscreen" />
779
+ ) : null}
780
+ </>
781
+ );
782
+ }
783
+
784
+ PlyrGestures.displayName = 'PlyrGestures';
785
+
786
+ /* -------------------------------------------------------------------------------------------------
787
+ * PlyrCaptions
788
+ * -----------------------------------------------------------------------------------------------*/
789
+
790
+ function PlyrCaptions() {
791
+ const $track = useMediaState('textTrack'),
792
+ [activeCue, setActiveCue] = React.useState<VTTCue | null>(null);
793
+
794
+ React.useEffect(() => {
795
+ if (!$track) return;
796
+
797
+ function onCueChange() {
798
+ setActiveCue($track ? $track.activeCues[0] : null);
799
+ }
800
+
801
+ onCueChange();
802
+ return listenEvent($track, 'cue-change', onCueChange);
803
+ }, [$track]);
804
+
805
+ return (
806
+ <div className="plyr__captions" dir="auto">
807
+ <span
808
+ className="plyr__caption"
809
+ dangerouslySetInnerHTML={{
810
+ __html: activeCue?.text || '',
811
+ }}
812
+ />
813
+ </div>
814
+ );
815
+ }
816
+
817
+ PlyrCaptions.displayName = 'PlyrCaptions';
818
+
819
+ /* -------------------------------------------------------------------------------------------------
820
+ * PlyrSettings
821
+ * -----------------------------------------------------------------------------------------------*/
822
+
823
+ function PlyrSettings() {
824
+ const { icons: Icons } = usePlyrLayoutContext(),
825
+ settingsText = usePlyrLayoutWord('Settings');
826
+ return slot(
827
+ 'settings',
828
+ <div className="plyr__controls__item plyr__menu">
829
+ <Menu.Root>
830
+ <Menu.Button className="plyr__control" data-plyr="settings">
831
+ {slot(
832
+ 'settingsButton',
833
+ <>
834
+ <Icons.Settings />
835
+ <span className="plyr__tooltip">{settingsText}</span>
836
+ </>,
837
+ )}
838
+ </Menu.Button>
839
+ <Menu.Items className="plyr__menu__container" placement="top end">
840
+ <div>
841
+ <div>
842
+ {slot(
843
+ 'settingsMenu',
844
+ <>
845
+ <PlyrAudioMenu />
846
+ <PlyrCaptionsMenu />
847
+ <PlyrQualityMenu />
848
+ <PlyrSpeedMenu />
849
+ </>,
850
+ )}
851
+ </div>
852
+ </div>
853
+ </Menu.Items>
854
+ </Menu.Root>
855
+ </div>,
856
+ );
857
+ }
858
+
859
+ PlyrSettings.displayName = 'PlyrSettings';
860
+
861
+ /* -------------------------------------------------------------------------------------------------
862
+ * PlyrMenuButton
863
+ * -----------------------------------------------------------------------------------------------*/
864
+
865
+ function PlyrMenuButton({
866
+ label,
867
+ hint,
868
+ open,
869
+ disabled,
870
+ }: {
871
+ label: PlyrLayoutWord;
872
+ hint?: string;
873
+ open: boolean;
874
+ disabled: boolean;
875
+ }) {
876
+ const buttonText = usePlyrLayoutWord(label),
877
+ goBackText = usePlyrLayoutWord('Go back to previous menu');
878
+ return (
879
+ <Menu.Button
880
+ className={`plyr__control plyr__control--${open ? 'back' : 'forward'}`}
881
+ data-plyr="settings"
882
+ disabled={disabled}
883
+ >
884
+ <span className="plyr__menu__label" aria-hidden={open ? 'true' : undefined}>
885
+ {buttonText}
886
+ </span>
887
+ {hint ? <span className="plyr__menu__value">{hint}</span> : null}
888
+ {open ? <span className="plyr__sr-only">{goBackText}</span> : null}
889
+ </Menu.Button>
890
+ );
891
+ }
892
+
893
+ PlyrMenuButton.displayName = 'PlyrMenuButton';
894
+
895
+ /* -------------------------------------------------------------------------------------------------
896
+ * PlyrMenu
897
+ * -----------------------------------------------------------------------------------------------*/
898
+
899
+ function PlyrMenu({
900
+ label,
901
+ hint,
902
+ children,
903
+ disabled,
904
+ }: {
905
+ label: PlyrLayoutWord;
906
+ hint?: string;
907
+ disabled;
908
+ children: React.ReactNode;
909
+ }) {
910
+ const [open, setOpen] = React.useState(false);
911
+
912
+ function onOpen() {
913
+ setOpen(true);
914
+ }
915
+
916
+ function onClose() {
917
+ setOpen(false);
918
+ }
919
+
920
+ return (
921
+ <Menu.Root onOpen={onOpen} onClose={onClose}>
922
+ <PlyrMenuButton label={label} open={open} hint={hint} disabled={disabled} />
923
+ <Menu.Items>{children}</Menu.Items>
924
+ </Menu.Root>
925
+ );
926
+ }
927
+
928
+ PlyrMenu.displayName = 'PlyrMenu';
929
+
930
+ /* -------------------------------------------------------------------------------------------------
931
+ * PlyrAudioMenu
932
+ * -----------------------------------------------------------------------------------------------*/
933
+
934
+ function PlyrAudioMenu() {
935
+ const defaultText = usePlyrLayoutWord('Default'),
936
+ $track = useMediaState('audioTrack'),
937
+ options = useAudioOptions();
938
+ return (
939
+ <PlyrMenu label="Audio" hint={$track?.label ?? defaultText} disabled={options.disabled}>
940
+ <Menu.RadioGroup value={options.selectedValue}>
941
+ {options.map(({ label, value, select }) => (
942
+ <Menu.Radio className="plyr__control" value={value} onSelect={select} key={value}>
943
+ <span>{label}</span>
944
+ </Menu.Radio>
945
+ ))}
946
+ </Menu.RadioGroup>
947
+ </PlyrMenu>
948
+ );
949
+ }
950
+
951
+ PlyrAudioMenu.displayName = 'PlyrAudioMenu';
952
+
953
+ /* -------------------------------------------------------------------------------------------------
954
+ * PlyrSpeedMenu
955
+ * -----------------------------------------------------------------------------------------------*/
956
+
957
+ function PlyrSpeedMenu() {
958
+ const normalLabel = usePlyrLayoutWord('Normal'),
959
+ options = usePlaybackRateOptions({ normalLabel }),
960
+ hint = options.selectedValue === '1' ? normalLabel : options.selectedValue + 'x';
961
+ return (
962
+ <PlyrMenu label="Speed" hint={hint} disabled={options.disabled}>
963
+ <Menu.RadioGroup value={options.selectedValue}>
964
+ {options.map(({ label, value, select }) => (
965
+ <Menu.Radio className="plyr__control" value={value} onSelect={select} key={value}>
966
+ <span>{label}</span>
967
+ </Menu.Radio>
968
+ ))}
969
+ </Menu.RadioGroup>
970
+ </PlyrMenu>
971
+ );
972
+ }
973
+
974
+ PlyrSpeedMenu.displayName = 'PlyrSpeedMenu';
975
+
976
+ /* -------------------------------------------------------------------------------------------------
977
+ * PlyrCaptionsMenu
978
+ * -----------------------------------------------------------------------------------------------*/
979
+
980
+ function PlyrCaptionsMenu() {
981
+ const offText = usePlyrLayoutWord('Disabled'),
982
+ options = useCaptionOptions({ off: offText }),
983
+ hint = options.selectedTrack?.label ?? offText;
984
+ return (
985
+ <PlyrMenu label="Captions" hint={hint} disabled={options.disabled}>
986
+ <Menu.RadioGroup value={options.selectedValue}>
987
+ {options.map(({ label, value, select }) => (
988
+ <Menu.Radio className="plyr__control" value={value} onSelect={select} key={value}>
989
+ <span>{label}</span>
990
+ </Menu.Radio>
991
+ ))}
992
+ </Menu.RadioGroup>
993
+ </PlyrMenu>
994
+ );
995
+ }
996
+
997
+ PlyrCaptionsMenu.displayName = 'PlyrCaptionsMenu';
998
+
999
+ /* -------------------------------------------------------------------------------------------------
1000
+ * PlyrQualityMenu
1001
+ * -----------------------------------------------------------------------------------------------*/
1002
+
1003
+ function PlyrQualityMenu() {
1004
+ const autoText = usePlyrLayoutWord('Auto'),
1005
+ options = useVideoQualityOptions({ auto: autoText, sort: 'descending' }),
1006
+ currentQuality = options.selectedQuality?.height,
1007
+ hint =
1008
+ options.selectedValue !== 'auto' && currentQuality
1009
+ ? `${currentQuality}p`
1010
+ : `${autoText}${currentQuality ? ` (${currentQuality}p)` : ''}`;
1011
+ return (
1012
+ <PlyrMenu label="Quality" hint={hint} disabled={options.disabled}>
1013
+ <Menu.RadioGroup value={options.selectedValue}>
1014
+ {options.map(({ label, value, select }) => (
1015
+ <Menu.Radio className="plyr__control" value={value} onSelect={select} key={value}>
1016
+ <span>{label}</span>
1017
+ </Menu.Radio>
1018
+ ))}
1019
+ </Menu.RadioGroup>
1020
+ </PlyrMenu>
1021
+ );
1022
+ }
1023
+
1024
+ PlyrQualityMenu.displayName = 'PlyrQualityMenu';