@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,514 @@
1
+ import * as React from 'react';
2
+
3
+ import { createScope, effect, peek, signal, tick } from 'maverick.js';
4
+ import { useSignal } from 'maverick.js/react';
5
+ import { deferredPromise, isFunction, listenEvent, type DeferredPromise } from 'maverick.js/std';
6
+ import {
7
+ Internals,
8
+ type MediaVolumeContextValue,
9
+ type PlayableMediaTag,
10
+ type SetMediaVolumeContextValue,
11
+ type SetTimelineContextValue,
12
+ type TimelineContextValue,
13
+ } from 'remotion';
14
+ import { TimeRange, type MediaContext, type MediaProviderAdapter, type Src } from 'vidstack';
15
+
16
+ import { RemotionLayoutEngine } from './layout-engine';
17
+ import { RemotionPlaybackEngine } from './playback-engine';
18
+ import { isRemotionSrc } from './type-check';
19
+ import type { RemotionSrc } from './types';
20
+ import { REMOTION_PROVIDER_ID, RemotionContextProvider } from './ui/context';
21
+ import { ErrorBoundary } from './ui/error-boundary';
22
+ import { validatePlaybackRate, validateRemotionResource } from './validate';
23
+
24
+ export class RemotionProvider implements MediaProviderAdapter {
25
+ protected readonly $$PROVIDER_TYPE = 'REMOTION';
26
+
27
+ readonly scope = createScope();
28
+
29
+ #src = signal<RemotionSrc | null>(null);
30
+ #setup = false;
31
+ #loadStart = false;
32
+ #audio: any = null;
33
+ #waiting = signal(false);
34
+ #waitingPromise: DeferredPromise<void, string> | null = null;
35
+ #mediaTags = signal<PlayableMediaTag[]>([]);
36
+ #mediaElements = signal<HTMLMediaElement[]>([]);
37
+ #bufferingElements = new Set<HTMLMediaElement>();
38
+ #timeline: TimelineContextValue | null = null;
39
+ #frame = signal<Record<string, number>>({ [REMOTION_PROVIDER_ID]: 0 });
40
+
41
+ #layoutEngine = new RemotionLayoutEngine();
42
+ #playbackEngine: RemotionPlaybackEngine | null = null;
43
+
44
+ readonly #container: HTMLElement;
45
+ readonly #ctx: MediaContext;
46
+
47
+ #setTimeline: SetTimelineContextValue;
48
+
49
+ #setMediaVolume: SetMediaVolumeContextValue = {
50
+ setMediaMuted: this.setMuted.bind(this),
51
+ setMediaVolume: this.setVolume.bind(this),
52
+ };
53
+
54
+ get type() {
55
+ return 'remotion';
56
+ }
57
+
58
+ get currentSrc() {
59
+ return peek(this.#src);
60
+ }
61
+
62
+ get frame() {
63
+ return this.#frame();
64
+ }
65
+
66
+ constructor(container: HTMLElement, ctx: MediaContext) {
67
+ this.#container = container;
68
+ this.#ctx = ctx;
69
+ this.#setTimeline = {
70
+ setFrame: this.#setFrame.bind(this),
71
+ setPlaying: this.#setPlaying.bind(this),
72
+ };
73
+ this.#layoutEngine.setContainer(container);
74
+ }
75
+
76
+ setup() {
77
+ effect(this.#watchWaiting.bind(this));
78
+ effect(this.#watchMediaTags.bind(this));
79
+ effect(this.#watchMediaElements.bind(this));
80
+ }
81
+
82
+ #watchMediaTags() {
83
+ this.#mediaTags();
84
+ this.#discoverMediaElements();
85
+ }
86
+
87
+ #discoverMediaElements() {
88
+ const elements = [...this.#container.querySelectorAll<HTMLMediaElement>('audio,video')];
89
+ this.#mediaElements.set(elements);
90
+ }
91
+
92
+ #watchMediaElements() {
93
+ const elements = this.#mediaElements();
94
+
95
+ for (const tag of elements) {
96
+ const onWait = this.#onWaitFor.bind(this, tag),
97
+ onStopWaiting = this.#onStopWaitingFor.bind(this, tag);
98
+
99
+ if (tag.currentSrc && tag.readyState < 4) {
100
+ this.#onWaitFor(tag);
101
+ listenEvent(tag, 'canplay', onStopWaiting);
102
+ }
103
+
104
+ listenEvent(tag, 'waiting', onWait);
105
+ listenEvent(tag, 'playing', onStopWaiting);
106
+ }
107
+
108
+ // User might have seeked to a new position, old media elements are removed.
109
+ for (const el of this.#bufferingElements) {
110
+ if (!elements.includes(el)) this.#onStopWaitingFor(el);
111
+ }
112
+ }
113
+
114
+ #onFrameChange(frame: number) {
115
+ const { inFrame, fps } = this.#src()!,
116
+ { seeking } = this.#ctx.$state,
117
+ time = Math.max(0, frame - inFrame!) / fps!;
118
+
119
+ this.#frame.set((record) => ({
120
+ ...record,
121
+ [REMOTION_PROVIDER_ID]: frame,
122
+ }));
123
+
124
+ this.#ctx.notify('time-change', time);
125
+
126
+ if (seeking()) {
127
+ tick();
128
+ this.#ctx.notify('seeked', time);
129
+ }
130
+ }
131
+
132
+ #onFrameEnd() {
133
+ this.pause();
134
+ this.#ctx.notify('end');
135
+ }
136
+
137
+ async play() {
138
+ const { ended } = this.#ctx.$state;
139
+
140
+ if (peek(ended)) {
141
+ this.#setFrame({ [REMOTION_PROVIDER_ID]: 0 });
142
+ }
143
+
144
+ try {
145
+ const mediaElements = peek(this.#mediaElements);
146
+ if (mediaElements.length) {
147
+ await Promise.all(mediaElements.map((media) => media.play()));
148
+ }
149
+
150
+ this.#ctx.notify('play');
151
+ tick();
152
+
153
+ if (this.#waitingPromise) {
154
+ this.#ctx.notify('waiting');
155
+ return this.#waitingPromise.promise;
156
+ } else {
157
+ this.#playbackEngine?.play();
158
+ this.#ctx.notify('playing');
159
+ }
160
+ } catch (error) {
161
+ throw error;
162
+ }
163
+ }
164
+
165
+ async pause() {
166
+ const { paused } = this.#ctx.$state;
167
+ this.#playbackEngine?.stop();
168
+ this.#ctx.notify('pause');
169
+ }
170
+
171
+ setMuted(value: React.SetStateAction<boolean>) {
172
+ if (!this.#ctx) return;
173
+
174
+ const { muted, volume } = this.#ctx.$state;
175
+
176
+ if (isFunction(value)) {
177
+ this.setMuted(value(muted()));
178
+ return;
179
+ }
180
+
181
+ this.#ctx.notify('volume-change', {
182
+ volume: peek(volume),
183
+ muted: value,
184
+ });
185
+ }
186
+
187
+ setCurrentTime(time: number) {
188
+ const { fps } = this.#src()!,
189
+ frame = time * fps!;
190
+
191
+ this.#ctx.notify('seeking', time);
192
+ this.#setFrame({ [REMOTION_PROVIDER_ID]: frame });
193
+ }
194
+
195
+ setVolume(value: React.SetStateAction<number>) {
196
+ if (!this.#ctx) return;
197
+
198
+ const { volume, muted } = this.#ctx.$state;
199
+
200
+ if (isFunction(value)) {
201
+ this.setVolume(value(volume()));
202
+ return;
203
+ }
204
+
205
+ this.#ctx.notify('volume-change', {
206
+ volume: value,
207
+ muted: peek(muted),
208
+ });
209
+ }
210
+
211
+ setPlaybackRate(rate: React.SetStateAction<number>) {
212
+ if (isFunction(rate)) {
213
+ const { playbackRate } = this.#ctx.$state;
214
+ this.setPlaybackRate(rate(peek(playbackRate)));
215
+ return;
216
+ }
217
+
218
+ if (__DEV__) validatePlaybackRate(rate);
219
+ this.#playbackEngine?.setPlaybackRate(rate);
220
+ this.#ctx.notify('rate-change', rate);
221
+ }
222
+
223
+ async loadSource(src: Src) {
224
+ if (!isRemotionSrc(src)) return;
225
+
226
+ const onUserError = src.onError,
227
+ resolvedSrc: RemotionSrc = {
228
+ compositionWidth: 1920,
229
+ compositionHeight: 1080,
230
+ fps: 30,
231
+ initialFrame: 0,
232
+ inFrame: 0,
233
+ outFrame: src.durationInFrames,
234
+ numberOfSharedAudioTags: 5,
235
+ inputProps: {},
236
+ ...src,
237
+ onError: (error) => {
238
+ if (__DEV__) {
239
+ this.#ctx.logger
240
+ ?.errorGroup(`[vidstack] ${error.message}`)
241
+ .labelledLog('Source', peek(this.#src))
242
+ .labelledLog('Error', error)
243
+ .dispatch();
244
+ }
245
+
246
+ this.pause();
247
+ this.#ctx.notify('error', {
248
+ message: error.message,
249
+ code: 1,
250
+ });
251
+
252
+ onUserError?.(error);
253
+ },
254
+ };
255
+
256
+ this.#src.set(resolvedSrc);
257
+
258
+ // Copy initialized props over to main src object.
259
+ for (const prop of Object.keys(resolvedSrc)) {
260
+ src[prop] = resolvedSrc[prop];
261
+ }
262
+
263
+ this.changeSrc(resolvedSrc);
264
+ }
265
+
266
+ destroy() {
267
+ this.changeSrc(null);
268
+ }
269
+
270
+ changeSrc(src: RemotionSrc | null) {
271
+ this.#playbackEngine?.destroy();
272
+
273
+ this.#waiting.set(false);
274
+ this.#waitingPromise?.reject('src changed');
275
+ this.#waitingPromise = null;
276
+ this.#audio = null;
277
+ this.#timeline = null;
278
+ this.#playbackEngine = null;
279
+ this.#mediaTags.set([]);
280
+ this.#bufferingElements.clear();
281
+ this.#frame.set({ [REMOTION_PROVIDER_ID]: 0 });
282
+
283
+ this.#layoutEngine.setSrc(src);
284
+
285
+ if (src) {
286
+ this.#timeline = this.#createTimelineContextValue();
287
+ this.#playbackEngine = new RemotionPlaybackEngine(
288
+ src,
289
+ this.#onFrameChange.bind(this),
290
+ this.#onFrameEnd.bind(this),
291
+ );
292
+ }
293
+ }
294
+
295
+ render = (): React.ReactNode => {
296
+ const $src = useSignal(this.#src);
297
+
298
+ if (!$src) {
299
+ throw Error(
300
+ __DEV__
301
+ ? '[vidstack] attempting to render remotion provider without src'
302
+ : '[vidstack] no src',
303
+ );
304
+ }
305
+
306
+ React.useEffect(() => {
307
+ if (!isRemotionSrc($src)) return;
308
+ validateRemotionResource($src);
309
+
310
+ const rafId = requestAnimationFrame(() => {
311
+ if (!this.#setup) {
312
+ this.#ctx.notify('provider-setup', this);
313
+ this.#setup = true;
314
+ }
315
+
316
+ if (!this.#loadStart) {
317
+ this.#ctx.notify('load-start');
318
+ this.#loadStart = true;
319
+ }
320
+
321
+ this.#discoverMediaElements();
322
+ tick();
323
+ if (!this.#waiting()) this.#ready($src);
324
+ });
325
+
326
+ return () => {
327
+ cancelAnimationFrame(rafId);
328
+ this.#loadStart = false;
329
+ };
330
+ }, [$src]);
331
+
332
+ const Component = Internals.useLazyComponent({
333
+ component: $src.src,
334
+ }) as React.LazyExoticComponent<React.ComponentType<unknown>>;
335
+
336
+ const { $state } = this.#ctx,
337
+ $volume = useSignal($state.volume),
338
+ $isMuted = useSignal($state.muted);
339
+
340
+ const mediaVolume = React.useMemo((): MediaVolumeContextValue => {
341
+ const { muted, volume } = this.#ctx.$state;
342
+ return { mediaMuted: muted(), mediaVolume: volume() };
343
+ }, [$isMuted, $volume]);
344
+
345
+ return (
346
+ <RemotionContextProvider
347
+ src={$src}
348
+ component={Component}
349
+ timeline={this.#timeline!}
350
+ mediaVolume={mediaVolume}
351
+ setMediaVolume={this.#setMediaVolume}
352
+ >
353
+ <Internals.Timeline.SetTimelineContext.Provider value={this.#setTimeline}>
354
+ {React.createElement(this.renderVideo, { src: $src })}
355
+ </Internals.Timeline.SetTimelineContext.Provider>
356
+ </RemotionContextProvider>
357
+ );
358
+ };
359
+
360
+ renderVideo = ({ src }: { src: RemotionSrc }): React.ReactNode => {
361
+ const video = Internals.useVideo(),
362
+ Video = video ? video.component : null,
363
+ audioContext = React.useContext(Internals.SharedAudioContext);
364
+
365
+ const { $state } = this.#ctx;
366
+
367
+ useSignal(this.#frame);
368
+ useSignal($state.playing);
369
+ useSignal($state.playbackRate);
370
+
371
+ React.useEffect(() => {
372
+ this.#audio = audioContext;
373
+ return () => {
374
+ this.#audio = null;
375
+ };
376
+ }, [audioContext]);
377
+
378
+ const LoadingContent = React.useMemo(() => src.renderLoading?.(), [src]);
379
+
380
+ const Content = Video ? (
381
+ <ErrorBoundary fallback={src.errorFallback} onError={src.onError!}>
382
+ <Internals.ClipComposition>
383
+ <Video {...video?.props} {...src.inputProps} />
384
+ </Internals.ClipComposition>
385
+ </ErrorBoundary>
386
+ ) : null;
387
+
388
+ return <React.Suspense fallback={LoadingContent}>{Content}</React.Suspense>;
389
+ };
390
+
391
+ #ready(src: RemotionSrc | null) {
392
+ if (!src) return;
393
+
394
+ const { outFrame, inFrame, fps } = src,
395
+ duration = (outFrame! - inFrame!) / fps!;
396
+
397
+ this.#ctx.notify('loaded-metadata');
398
+ this.#ctx.notify('loaded-data');
399
+
400
+ this.#ctx.delegate.ready({
401
+ duration,
402
+ seekable: new TimeRange(0, duration),
403
+ buffered: new TimeRange(0, duration),
404
+ });
405
+
406
+ if (src.initialFrame) {
407
+ this.#setFrame({
408
+ [REMOTION_PROVIDER_ID]: src.initialFrame,
409
+ });
410
+ }
411
+ }
412
+
413
+ #onWaitFor(el: HTMLMediaElement) {
414
+ this.#bufferingElements.add(el);
415
+ this.#waiting.set(true);
416
+ if (!this.#waitingPromise) {
417
+ this.#waitingPromise = deferredPromise();
418
+ }
419
+ }
420
+
421
+ #onStopWaitingFor(el: HTMLMediaElement) {
422
+ this.#bufferingElements.delete(el);
423
+
424
+ // There's still elements we're waiting on.
425
+ if (this.#bufferingElements.size) return;
426
+
427
+ this.#waiting.set(false);
428
+ this.#waitingPromise?.resolve();
429
+ this.#waitingPromise = null;
430
+
431
+ const { canPlay } = this.#ctx.$state;
432
+ if (!peek(canPlay)) {
433
+ this.#ready(peek(this.#src));
434
+ }
435
+ }
436
+
437
+ #watchWaiting() {
438
+ this.#waiting(); // subscribe
439
+
440
+ const { paused } = this.#ctx.$state;
441
+ if (peek(paused)) return;
442
+
443
+ if (this.#waiting()) {
444
+ this.#playbackEngine?.stop();
445
+ this.#ctx.notify('waiting');
446
+ } else {
447
+ this.#playbackEngine?.play();
448
+ this.#ctx.notify('playing');
449
+ }
450
+ }
451
+
452
+ #setFrame(value: React.SetStateAction<Record<string, number>>) {
453
+ if (isFunction(value)) {
454
+ this.#setFrame(value(this.#frame()));
455
+ return;
456
+ }
457
+
458
+ this.#frame.set((record) => ({ ...record, ...value }));
459
+
460
+ const nextFrame = value[REMOTION_PROVIDER_ID];
461
+ if (this.#playbackEngine && this.#playbackEngine.frame !== nextFrame) {
462
+ this.#playbackEngine.frame = nextFrame;
463
+ }
464
+ }
465
+
466
+ #setPlaying(value: React.SetStateAction<boolean>) {
467
+ const { playing } = this.#ctx.$state;
468
+
469
+ if (isFunction(value)) {
470
+ this.#setPlaying(value(playing()));
471
+ return;
472
+ }
473
+
474
+ if (value) {
475
+ this.play();
476
+ } else if (!value) {
477
+ this.pause();
478
+ }
479
+ }
480
+
481
+ #createTimelineContextValue(): TimelineContextValue {
482
+ const { playing, playbackRate } = this.#ctx.$state,
483
+ frame = this.#frame,
484
+ mediaTags = this.#mediaTags,
485
+ setPlaybackRate = this.setPlaybackRate.bind(this);
486
+
487
+ return {
488
+ rootId: REMOTION_PROVIDER_ID,
489
+ get frame() {
490
+ return frame();
491
+ },
492
+ get playing() {
493
+ return playing();
494
+ },
495
+ get playbackRate() {
496
+ return playbackRate();
497
+ },
498
+ imperativePlaying: {
499
+ get current() {
500
+ return playing();
501
+ },
502
+ },
503
+ setPlaybackRate,
504
+ audioAndVideoTags: {
505
+ get current() {
506
+ return mediaTags();
507
+ },
508
+ set current(tags) {
509
+ mediaTags.set(tags);
510
+ },
511
+ },
512
+ };
513
+ }
514
+ }
@@ -0,0 +1,13 @@
1
+ import type { Src } from 'vidstack';
2
+
3
+ import type { RemotionProvider } from './provider';
4
+ import type { RemotionSrc } from './types';
5
+
6
+ /** @see {@link https://www.vidstack.io/docs/player/providers/remotion} */
7
+ export function isRemotionProvider(provider: any): provider is RemotionProvider {
8
+ return provider?.$$PROVIDER_TYPE === 'REMOTION';
9
+ }
10
+
11
+ export function isRemotionSrc(src?: Src | null): src is RemotionSrc {
12
+ return src?.type === 'video/remotion';
13
+ }
@@ -0,0 +1,94 @@
1
+ import type * as React from 'react';
2
+
3
+ export interface RemotionSrc<InputProps extends RemotionInputProps = RemotionInputProps> {
4
+ /** React component which is generally a Remotion video. */
5
+ src: React.ComponentType<unknown>;
6
+ /** Remotion source type. */
7
+ type: 'video/remotion';
8
+ /**
9
+ * Pass props to the component that you have specified using the component prop.
10
+ */
11
+ inputProps?: InputProps;
12
+ /**
13
+ * The width of the composition.
14
+ *
15
+ * @defaultValue 1920
16
+ */
17
+ compositionWidth?: number;
18
+ /**
19
+ * The height of the composition.
20
+ *
21
+ * @defaultValue 1080
22
+ */
23
+ compositionHeight?: number;
24
+ /**
25
+ * The frame rate of the video per second.
26
+ *
27
+ * @defaultValue 30
28
+ */
29
+ fps?: number;
30
+ /**
31
+ * The duration of the video in frames. Must be an integer and greater than 0.
32
+ */
33
+ durationInFrames: number;
34
+ /**
35
+ * Start the playback from a specific frame.
36
+ *
37
+ * @defaultValue 0
38
+ */
39
+ initialFrame?: number;
40
+ /**
41
+ * Limit playback to only play after a certain frame. The video will start from this frame and
42
+ * move to this position once it has ended. Must be an integer, not smaller than 0, not bigger
43
+ * than `outFrame` and not bigger than `durationInFrames - 1`.
44
+ *
45
+ * @defaultValue 0
46
+ */
47
+ inFrame?: number | null;
48
+ /**
49
+ * Limit playback to only play before a certain frame. The video will end at this frame
50
+ * and move to the beginning once it has ended. Must be an integer, not smaller than 1, not
51
+ * smaller than `inFrame` and not bigger than `durationInFrames`.
52
+ *
53
+ * @defaultValue `durationInFrames`
54
+ */
55
+ outFrame?: number;
56
+ /**
57
+ * If you use an `<Audio />` tag, it might not play in some browsers (specifically iOS Safari)
58
+ * due to browser autoplay policies. This is why the player pre-mounts a set of audio tags with
59
+ * silent audio that get played upon user interaction. These audio tags can then be used to play
60
+ * real audio later and will not be subject to the autoplay policy of the browser.
61
+ *
62
+ * This option controls how many audio tags are being rendered, the default is 5. If you mount
63
+ * more audio tags than shared audio tags are available, then an error will be thrown.
64
+ *
65
+ * If you'd like to opt out of this behavior, you can pass 0 to mount native audio tags
66
+ * simultaneously as you mount Remotion's <Audio /> tags.
67
+ *
68
+ * @defaultValue 5
69
+ */
70
+ numberOfSharedAudioTags?: number;
71
+ /**
72
+ * A callback function that allows you to return a custom UI that gets displayed while the
73
+ * provider is loading.
74
+ */
75
+ renderLoading?: RemotionLoadingRenderer;
76
+ /**
77
+ * A callback for rendering a custom error message.
78
+ */
79
+ errorFallback?: RemotionErrorRenderer;
80
+ /**
81
+ * Called when an error or uncaught exception has happened in the video.
82
+ */
83
+ onError?(error: Error): void;
84
+ }
85
+
86
+ export interface RemotionInputProps extends Record<string, unknown> {}
87
+
88
+ export interface RemotionLoadingRenderer {
89
+ (): React.ReactNode;
90
+ }
91
+
92
+ export interface RemotionErrorRenderer {
93
+ (error: Error): React.ReactNode;
94
+ }