@remotion/player 4.0.0-webhook.26 → 4.1.0-alpha1

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 (144) hide show
  1. package/LICENSE.md +8 -8
  2. package/README.md +1 -1
  3. package/dist/{MediaVolumeSlider.d.ts → cjs/MediaVolumeSlider.d.ts} +0 -0
  4. package/dist/{MediaVolumeSlider.js → cjs/MediaVolumeSlider.js} +41 -17
  5. package/dist/cjs/PlaybackrateControl.d.ts +8 -0
  6. package/dist/cjs/PlaybackrateControl.js +163 -0
  7. package/dist/cjs/Player.d.ts +52 -0
  8. package/dist/{Player.js → cjs/Player.js} +41 -87
  9. package/dist/{PlayerControls.d.ts → cjs/PlayerControls.d.ts} +14 -5
  10. package/dist/cjs/PlayerControls.js +161 -0
  11. package/dist/{PlayerSeekBar.d.ts → cjs/PlayerSeekBar.d.ts} +0 -0
  12. package/dist/{PlayerSeekBar.js → cjs/PlayerSeekBar.js} +6 -6
  13. package/dist/{PlayerUI.d.ts → cjs/PlayerUI.d.ts} +13 -12
  14. package/dist/{PlayerUI.js → cjs/PlayerUI.js} +54 -72
  15. package/dist/cjs/SharedPlayerContext.d.ts +15 -0
  16. package/dist/cjs/SharedPlayerContext.js +75 -0
  17. package/dist/cjs/Thumbnail.d.ts +24 -0
  18. package/dist/cjs/Thumbnail.js +46 -0
  19. package/dist/cjs/ThumbnailUI.d.ts +11 -0
  20. package/dist/cjs/ThumbnailUI.js +107 -0
  21. package/dist/{calculate-next-frame.d.ts → cjs/calculate-next-frame.d.ts} +0 -0
  22. package/dist/{calculate-next-frame.js → cjs/calculate-next-frame.js} +0 -0
  23. package/dist/cjs/calculate-scale.d.ts +39 -0
  24. package/dist/cjs/calculate-scale.js +85 -0
  25. package/dist/cjs/emitter-context.d.ts +4 -0
  26. package/dist/{emitter-context.js → cjs/emitter-context.js} +2 -1
  27. package/dist/{error-boundary.d.ts → cjs/error-boundary.d.ts} +0 -0
  28. package/dist/{error-boundary.js → cjs/error-boundary.js} +0 -0
  29. package/dist/cjs/event-emitter.d.ts +81 -0
  30. package/dist/{event-emitter.js → cjs/event-emitter.js} +43 -3
  31. package/dist/{format-time.d.ts → cjs/format-time.d.ts} +0 -0
  32. package/dist/{format-time.js → cjs/format-time.js} +0 -0
  33. package/dist/{icons.d.ts → cjs/icons.d.ts} +1 -1
  34. package/dist/{icons.js → cjs/icons.js} +4 -4
  35. package/dist/{index.d.ts → cjs/index.d.ts} +13 -11
  36. package/dist/cjs/index.js +25 -0
  37. package/dist/cjs/is-backgrounded.d.ts +2 -0
  38. package/dist/cjs/is-backgrounded.js +24 -0
  39. package/dist/{player-css-classname.d.ts → cjs/player-css-classname.d.ts} +0 -0
  40. package/dist/{player-css-classname.js → cjs/player-css-classname.js} +0 -0
  41. package/dist/{player-methods.d.ts → cjs/player-methods.d.ts} +9 -6
  42. package/dist/{player-methods.js → cjs/player-methods.js} +0 -0
  43. package/dist/{test → cjs/test}/index.test.d.ts +0 -0
  44. package/dist/cjs/test/index.test.js +9 -0
  45. package/dist/{test → cjs/test}/test-utils.d.ts +0 -0
  46. package/dist/{test → cjs/test}/test-utils.js +0 -0
  47. package/dist/{test → cjs/test}/validate-in-out-frames.test.d.ts +0 -0
  48. package/dist/{test → cjs/test}/validate-in-out-frames.test.js +13 -12
  49. package/dist/{test → cjs/test}/validate-prop.test.d.ts +0 -0
  50. package/dist/cjs/test/validate-prop.test.js +131 -0
  51. package/dist/{use-hover-state.d.ts → cjs/use-hover-state.d.ts} +0 -0
  52. package/dist/{use-hover-state.js → cjs/use-hover-state.js} +0 -0
  53. package/dist/{use-playback.d.ts → cjs/use-playback.d.ts} +0 -0
  54. package/dist/{use-playback.js → cjs/use-playback.js} +46 -9
  55. package/dist/{use-player.d.ts → cjs/use-player.d.ts} +2 -2
  56. package/dist/{use-player.js → cjs/use-player.js} +2 -2
  57. package/dist/cjs/use-thumbnail.d.ts +6 -0
  58. package/dist/cjs/use-thumbnail.js +18 -0
  59. package/dist/{use-video-controls-resize.d.ts → cjs/use-video-controls-resize.d.ts} +8 -4
  60. package/dist/{use-video-controls-resize.js → cjs/use-video-controls-resize.js} +16 -13
  61. package/dist/{utils → cjs/utils}/calculate-player-size.d.ts +1 -1
  62. package/dist/{utils → cjs/utils}/calculate-player-size.js +9 -10
  63. package/dist/{utils → cjs/utils}/cancellable-promise.d.ts +1 -1
  64. package/dist/{utils → cjs/utils}/cancellable-promise.js +0 -0
  65. package/dist/{utils → cjs/utils}/delay.d.ts +0 -0
  66. package/dist/{utils → cjs/utils}/delay.js +0 -0
  67. package/dist/{utils → cjs/utils}/is-node.d.ts +0 -0
  68. package/dist/{utils → cjs/utils}/is-node.js +0 -0
  69. package/dist/{utils → cjs/utils}/preview-size.d.ts +2 -2
  70. package/dist/{utils → cjs/utils}/preview-size.js +0 -0
  71. package/dist/cjs/utils/props-if-has-props.d.ts +10 -0
  72. package/dist/cjs/utils/props-if-has-props.js +2 -0
  73. package/dist/{utils → cjs/utils}/use-cancellable-promises.d.ts +1 -1
  74. package/dist/{utils → cjs/utils}/use-cancellable-promises.js +0 -0
  75. package/dist/{utils → cjs/utils}/use-click-prevention-on-double-click.d.ts +0 -0
  76. package/dist/{utils → cjs/utils}/use-click-prevention-on-double-click.js +5 -5
  77. package/dist/cjs/utils/use-component-visible.d.ts +6 -0
  78. package/dist/cjs/utils/use-component-visible.js +21 -0
  79. package/dist/{utils → cjs/utils}/use-element-size.d.ts +2 -1
  80. package/dist/{utils → cjs/utils}/use-element-size.js +29 -12
  81. package/dist/{utils → cjs/utils}/validate-in-out-frame.d.ts +1 -1
  82. package/dist/{utils → cjs/utils}/validate-in-out-frame.js +13 -13
  83. package/dist/{utils → cjs/utils}/validate-initial-frame.d.ts +0 -0
  84. package/dist/{utils → cjs/utils}/validate-initial-frame.js +0 -0
  85. package/dist/{utils → cjs/utils}/validate-playbackrate.d.ts +0 -0
  86. package/dist/{utils → cjs/utils}/validate-playbackrate.js +0 -0
  87. package/dist/{volume-persistance.d.ts → cjs/volume-persistance.d.ts} +0 -0
  88. package/dist/{volume-persistance.js → cjs/volume-persistance.js} +0 -0
  89. package/dist/esm/MediaVolumeSlider.d.ts +5 -0
  90. package/dist/esm/PlaybackrateControl.d.ts +8 -0
  91. package/dist/esm/Player.d.ts +52 -0
  92. package/dist/esm/PlayerControls.d.ts +42 -0
  93. package/dist/esm/PlayerSeekBar.d.ts +8 -0
  94. package/dist/esm/PlayerUI.d.ts +40 -0
  95. package/dist/esm/SharedPlayerContext.d.ts +15 -0
  96. package/dist/esm/Thumbnail.d.ts +24 -0
  97. package/dist/esm/ThumbnailUI.d.ts +11 -0
  98. package/dist/esm/calculate-next-frame.d.ts +14 -0
  99. package/dist/esm/calculate-scale.d.ts +39 -0
  100. package/dist/esm/emitter-context.d.ts +4 -0
  101. package/dist/esm/error-boundary.d.ts +19 -0
  102. package/dist/esm/event-emitter.d.ts +81 -0
  103. package/dist/esm/format-time.d.ts +1 -0
  104. package/dist/esm/icons.d.ts +10 -0
  105. package/dist/esm/index.d.ts +61 -0
  106. package/dist/esm/index.mjs +2132 -0
  107. package/dist/esm/is-backgrounded.d.ts +2 -0
  108. package/dist/esm/player-css-classname.d.ts +1 -0
  109. package/dist/esm/player-methods.d.ts +24 -0
  110. package/dist/esm/test/index.test.d.ts +1 -0
  111. package/dist/esm/test/test-utils.d.ts +6 -0
  112. package/dist/esm/test/validate-in-out-frames.test.d.ts +1 -0
  113. package/dist/esm/test/validate-prop.test.d.ts +1 -0
  114. package/dist/esm/use-hover-state.d.ts +1 -0
  115. package/dist/esm/use-playback.d.ts +7 -0
  116. package/dist/esm/use-player.d.ts +19 -0
  117. package/dist/esm/use-thumbnail.d.ts +6 -0
  118. package/dist/esm/use-video-controls-resize.d.ts +11 -0
  119. package/dist/esm/utils/calculate-player-size.d.ts +9 -0
  120. package/dist/esm/utils/cancellable-promise.d.ts +5 -0
  121. package/dist/esm/utils/delay.d.ts +1 -0
  122. package/dist/esm/utils/is-node.d.ts +1 -0
  123. package/dist/esm/utils/preview-size.d.ts +8 -0
  124. package/dist/esm/utils/props-if-has-props.d.ts +10 -0
  125. package/dist/esm/utils/use-cancellable-promises.d.ts +7 -0
  126. package/dist/esm/utils/use-click-prevention-on-double-click.d.ts +3 -0
  127. package/dist/esm/utils/use-component-visible.d.ts +6 -0
  128. package/dist/esm/utils/use-element-size.d.ts +16 -0
  129. package/dist/esm/utils/validate-in-out-frame.d.ts +6 -0
  130. package/dist/esm/utils/validate-initial-frame.d.ts +4 -0
  131. package/dist/esm/utils/validate-playbackrate.d.ts +1 -0
  132. package/dist/esm/volume-persistance.d.ts +2 -0
  133. package/dist/tsconfig-esm.tsbuildinfo +1 -0
  134. package/dist/tsconfig.tsbuildinfo +1 -0
  135. package/package.json +80 -66
  136. package/dist/Player.d.ts +0 -49
  137. package/dist/PlayerControls.js +0 -137
  138. package/dist/calculate-scale.d.ts +0 -20
  139. package/dist/calculate-scale.js +0 -33
  140. package/dist/emitter-context.d.ts +0 -3
  141. package/dist/event-emitter.d.ts +0 -52
  142. package/dist/index.js +0 -23
  143. package/dist/test/index.test.js +0 -8
  144. package/dist/test/validate-prop.test.js +0 -130
@@ -0,0 +1,2132 @@
1
+ "use client";
2
+ import React, { useState, useEffect, useRef, useContext, useCallback, useMemo, forwardRef, useImperativeHandle, Suspense, useLayoutEffect } from 'react';
3
+ import { Internals, random, interpolate, Composition } from 'remotion';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ const calculatePlayerSize = ({ currentSize, width, height, compositionWidth, compositionHeight, }) => {
7
+ if (width !== undefined && height === undefined) {
8
+ return {
9
+ aspectRatio: [compositionWidth, compositionHeight].join('/'),
10
+ };
11
+ }
12
+ // Opposite: If has height specified, evaluate the height and specify a default width.
13
+ if (height !== undefined && width === undefined) {
14
+ return {
15
+ // Aspect ratio CSS prop will work
16
+ aspectRatio: [compositionWidth, compositionHeight].join('/'),
17
+ };
18
+ }
19
+ if (!currentSize) {
20
+ return {
21
+ width: compositionWidth,
22
+ height: compositionHeight,
23
+ };
24
+ }
25
+ return {
26
+ width: compositionWidth,
27
+ height: compositionHeight,
28
+ };
29
+ };
30
+
31
+ const calculateScale = ({ canvasSize, compositionHeight, compositionWidth, previewSize, }) => {
32
+ const heightRatio = canvasSize.height / compositionHeight;
33
+ const widthRatio = canvasSize.width / compositionWidth;
34
+ const ratio = Math.min(heightRatio, widthRatio);
35
+ return previewSize === 'auto' ? ratio : Number(previewSize);
36
+ };
37
+ const calculateCanvasTransformation = ({ previewSize, compositionWidth, compositionHeight, canvasSize, }) => {
38
+ const scale = calculateScale({
39
+ canvasSize,
40
+ compositionHeight,
41
+ compositionWidth,
42
+ previewSize,
43
+ });
44
+ const correction = 0 - (1 - scale) / 2;
45
+ const xCorrection = correction * compositionWidth;
46
+ const yCorrection = correction * compositionHeight;
47
+ const width = compositionWidth * scale;
48
+ const height = compositionHeight * scale;
49
+ const centerX = canvasSize.width / 2 - width / 2;
50
+ const centerY = canvasSize.height / 2 - height / 2;
51
+ return {
52
+ centerX,
53
+ centerY,
54
+ xCorrection,
55
+ yCorrection,
56
+ scale,
57
+ };
58
+ };
59
+ const calculateOuterStyle = ({ config, style, canvasSize, }) => {
60
+ if (!config) {
61
+ return {};
62
+ }
63
+ return {
64
+ position: 'relative',
65
+ overflow: 'hidden',
66
+ ...calculatePlayerSize({
67
+ compositionHeight: config.height,
68
+ compositionWidth: config.width,
69
+ currentSize: canvasSize,
70
+ height: style === null || style === void 0 ? void 0 : style.height,
71
+ width: style === null || style === void 0 ? void 0 : style.width,
72
+ }),
73
+ ...style,
74
+ };
75
+ };
76
+ const calculateContainerStyle = ({ config, canvasSize, layout, scale, }) => {
77
+ if (!config || !canvasSize || !layout) {
78
+ return {};
79
+ }
80
+ return {
81
+ position: 'absolute',
82
+ width: config.width,
83
+ height: config.height,
84
+ display: 'flex',
85
+ transform: `scale(${scale})`,
86
+ marginLeft: layout.xCorrection,
87
+ marginTop: layout.yCorrection,
88
+ overflow: 'hidden',
89
+ };
90
+ };
91
+ const calculateOuter = ({ layout, scale, config, }) => {
92
+ if (!layout || !config) {
93
+ return {};
94
+ }
95
+ const { centerX, centerY } = layout;
96
+ return {
97
+ width: config.width * scale,
98
+ height: config.height * scale,
99
+ display: 'flex',
100
+ flexDirection: 'column',
101
+ position: 'absolute',
102
+ left: centerX,
103
+ top: centerY,
104
+ overflow: 'hidden',
105
+ };
106
+ };
107
+
108
+ const PlayerEventEmitterContext = React.createContext(undefined);
109
+ const ThumbnailEmitterContext = React.createContext(undefined);
110
+
111
+ class PlayerEmitter {
112
+ constructor() {
113
+ this.listeners = {
114
+ ended: [],
115
+ error: [],
116
+ pause: [],
117
+ play: [],
118
+ ratechange: [],
119
+ scalechange: [],
120
+ seeked: [],
121
+ timeupdate: [],
122
+ frameupdate: [],
123
+ fullscreenchange: [],
124
+ volumechange: [],
125
+ mutechange: [],
126
+ };
127
+ }
128
+ addEventListener(name, callback) {
129
+ this.listeners[name].push(callback);
130
+ }
131
+ removeEventListener(name, callback) {
132
+ this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
133
+ }
134
+ dispatchEvent(dispatchName, context) {
135
+ this.listeners[dispatchName].forEach((callback) => {
136
+ callback({ detail: context });
137
+ });
138
+ }
139
+ dispatchSeek(frame) {
140
+ this.dispatchEvent('seeked', {
141
+ frame,
142
+ });
143
+ }
144
+ dispatchVolumeChange(volume) {
145
+ this.dispatchEvent('volumechange', {
146
+ volume,
147
+ });
148
+ }
149
+ dispatchPause() {
150
+ this.dispatchEvent('pause', undefined);
151
+ }
152
+ dispatchPlay() {
153
+ this.dispatchEvent('play', undefined);
154
+ }
155
+ dispatchEnded() {
156
+ this.dispatchEvent('ended', undefined);
157
+ }
158
+ dispatchRateChange(playbackRate) {
159
+ this.dispatchEvent('ratechange', {
160
+ playbackRate,
161
+ });
162
+ }
163
+ dispatchScaleChange(scale) {
164
+ this.dispatchEvent('scalechange', {
165
+ scale,
166
+ });
167
+ }
168
+ dispatchError(error) {
169
+ this.dispatchEvent('error', {
170
+ error,
171
+ });
172
+ }
173
+ dispatchTimeUpdate(event) {
174
+ this.dispatchEvent('timeupdate', event);
175
+ }
176
+ dispatchFrameUpdate(event) {
177
+ this.dispatchEvent('frameupdate', event);
178
+ }
179
+ dispatchFullscreenChange(event) {
180
+ this.dispatchEvent('fullscreenchange', event);
181
+ }
182
+ dispatchMuteChange(event) {
183
+ this.dispatchEvent('mutechange', event);
184
+ }
185
+ }
186
+ class ThumbnailEmitter {
187
+ constructor() {
188
+ this.listeners = {
189
+ error: [],
190
+ };
191
+ }
192
+ addEventListener(name, callback) {
193
+ this.listeners[name].push(callback);
194
+ }
195
+ removeEventListener(name, callback) {
196
+ this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
197
+ }
198
+ dispatchEvent(dispatchName, context) {
199
+ this.listeners[dispatchName].forEach((callback) => {
200
+ callback({ detail: context });
201
+ });
202
+ }
203
+ dispatchError(error) {
204
+ this.dispatchEvent('error', {
205
+ error,
206
+ });
207
+ }
208
+ }
209
+
210
+ const useHoverState = (ref) => {
211
+ const [hovered, stetHovered] = useState(false);
212
+ useEffect(() => {
213
+ const { current } = ref;
214
+ if (!current) {
215
+ return;
216
+ }
217
+ const onHover = () => {
218
+ stetHovered(true);
219
+ };
220
+ const onLeave = () => {
221
+ stetHovered(false);
222
+ };
223
+ current.addEventListener('mouseenter', onHover);
224
+ current.addEventListener('mouseleave', onLeave);
225
+ return () => {
226
+ current.removeEventListener('mouseenter', onHover);
227
+ current.removeEventListener('mouseenter', onLeave);
228
+ };
229
+ }, [ref]);
230
+ return hovered;
231
+ };
232
+
233
+ const calculateNextFrame = ({ time, currentFrame: startFrame, playbackSpeed, fps, actualLastFrame, actualFirstFrame, framesAdvanced, shouldLoop, }) => {
234
+ const op = playbackSpeed < 0 ? Math.ceil : Math.floor;
235
+ const framesToAdvance = op((time * playbackSpeed) / (1000 / fps)) - framesAdvanced;
236
+ const nextFrame = framesToAdvance + startFrame;
237
+ const isCurrentFrameOutside = startFrame > actualLastFrame || startFrame < actualFirstFrame;
238
+ const isNextFrameOutside = nextFrame > actualLastFrame || nextFrame < actualFirstFrame;
239
+ const hasEnded = !shouldLoop && isNextFrameOutside && !isCurrentFrameOutside;
240
+ if (playbackSpeed > 0) {
241
+ // Play forwards
242
+ if (isNextFrameOutside) {
243
+ return {
244
+ nextFrame: actualFirstFrame,
245
+ framesToAdvance,
246
+ hasEnded,
247
+ };
248
+ }
249
+ return { nextFrame, framesToAdvance, hasEnded };
250
+ }
251
+ // Reverse playback
252
+ if (isNextFrameOutside) {
253
+ return { nextFrame: actualLastFrame, framesToAdvance, hasEnded };
254
+ }
255
+ return { nextFrame, framesToAdvance, hasEnded };
256
+ };
257
+
258
+ const getIsBackgrounded = () => {
259
+ if (typeof document === 'undefined') {
260
+ return false;
261
+ }
262
+ return document.visibilityState === 'hidden';
263
+ };
264
+ const useIsBackgrounded = () => {
265
+ const isBackgrounded = useRef(getIsBackgrounded());
266
+ useEffect(() => {
267
+ const onVisibilityChange = () => {
268
+ isBackgrounded.current = getIsBackgrounded();
269
+ };
270
+ document.addEventListener('visibilitychange', onVisibilityChange);
271
+ return () => {
272
+ document.removeEventListener('visibilitychange', onVisibilityChange);
273
+ };
274
+ }, []);
275
+ return isBackgrounded;
276
+ };
277
+
278
+ const usePlayer = () => {
279
+ var _a;
280
+ const [playing, setPlaying, imperativePlaying] = Internals.Timeline.usePlayingState();
281
+ const [hasPlayed, setHasPlayed] = useState(false);
282
+ const frame = Internals.Timeline.useTimelinePosition();
283
+ const playStart = useRef(frame);
284
+ const setFrame = Internals.Timeline.useTimelineSetFrame();
285
+ const setTimelinePosition = Internals.Timeline.useTimelineSetFrame();
286
+ const audioContext = useContext(Internals.SharedAudioContext);
287
+ const { audioAndVideoTags } = useContext(Internals.Timeline.TimelineContext);
288
+ const frameRef = useRef();
289
+ frameRef.current = frame;
290
+ const video = Internals.useVideo();
291
+ const config = Internals.useUnsafeVideoConfig();
292
+ const emitter = useContext(PlayerEventEmitterContext);
293
+ const lastFrame = ((_a = config === null || config === void 0 ? void 0 : config.durationInFrames) !== null && _a !== void 0 ? _a : 1) - 1;
294
+ const isLastFrame = frame === lastFrame;
295
+ const isFirstFrame = frame === 0;
296
+ if (!emitter) {
297
+ throw new TypeError('Expected Player event emitter context');
298
+ }
299
+ const seek = useCallback((newFrame) => {
300
+ setTimelinePosition(newFrame);
301
+ emitter.dispatchSeek(newFrame);
302
+ }, [emitter, setTimelinePosition]);
303
+ const play = useCallback((e) => {
304
+ if (imperativePlaying.current) {
305
+ return;
306
+ }
307
+ setHasPlayed(true);
308
+ if (isLastFrame) {
309
+ seek(0);
310
+ }
311
+ /**
312
+ * Play silent audio tags to warm them up for autoplay
313
+ */
314
+ if (audioContext && audioContext.numberOfAudioTags > 0 && e) {
315
+ audioContext.playAllAudios();
316
+ }
317
+ /**
318
+ * Play audios and videos directly here so they can benefit from
319
+ * being triggered by a click
320
+ */
321
+ audioAndVideoTags.current.forEach((a) => a.play());
322
+ imperativePlaying.current = true;
323
+ setPlaying(true);
324
+ playStart.current = frameRef.current;
325
+ emitter.dispatchPlay();
326
+ }, [
327
+ imperativePlaying,
328
+ isLastFrame,
329
+ audioContext,
330
+ setPlaying,
331
+ emitter,
332
+ seek,
333
+ audioAndVideoTags,
334
+ ]);
335
+ const pause = useCallback(() => {
336
+ if (imperativePlaying.current) {
337
+ imperativePlaying.current = false;
338
+ setPlaying(false);
339
+ emitter.dispatchPause();
340
+ }
341
+ }, [emitter, imperativePlaying, setPlaying]);
342
+ const pauseAndReturnToPlayStart = useCallback(() => {
343
+ if (imperativePlaying.current) {
344
+ imperativePlaying.current = false;
345
+ setTimelinePosition(playStart.current);
346
+ setPlaying(false);
347
+ emitter.dispatchPause();
348
+ }
349
+ }, [emitter, imperativePlaying, setPlaying, setTimelinePosition]);
350
+ const hasVideo = Boolean(video);
351
+ const frameBack = useCallback((frames) => {
352
+ if (!hasVideo) {
353
+ return null;
354
+ }
355
+ if (imperativePlaying.current) {
356
+ return;
357
+ }
358
+ setFrame((f) => {
359
+ return Math.max(0, f - frames);
360
+ });
361
+ }, [hasVideo, imperativePlaying, setFrame]);
362
+ const frameForward = useCallback((frames) => {
363
+ if (!hasVideo) {
364
+ return null;
365
+ }
366
+ if (imperativePlaying.current) {
367
+ return;
368
+ }
369
+ setFrame((f) => Math.min(lastFrame, f + frames));
370
+ }, [hasVideo, imperativePlaying, lastFrame, setFrame]);
371
+ const returnValue = useMemo(() => {
372
+ return {
373
+ frameBack,
374
+ frameForward,
375
+ isLastFrame,
376
+ emitter,
377
+ playing,
378
+ play,
379
+ pause,
380
+ seek,
381
+ isFirstFrame,
382
+ getCurrentFrame: () => frameRef.current,
383
+ isPlaying: () => imperativePlaying.current,
384
+ pauseAndReturnToPlayStart,
385
+ hasPlayed,
386
+ };
387
+ }, [
388
+ frameBack,
389
+ frameForward,
390
+ isLastFrame,
391
+ emitter,
392
+ playing,
393
+ play,
394
+ pause,
395
+ seek,
396
+ isFirstFrame,
397
+ pauseAndReturnToPlayStart,
398
+ imperativePlaying,
399
+ hasPlayed,
400
+ ]);
401
+ return returnValue;
402
+ };
403
+
404
+ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, }) => {
405
+ const frame = Internals.Timeline.useTimelinePosition();
406
+ const config = Internals.useUnsafeVideoConfig();
407
+ const { playing, pause, emitter } = usePlayer();
408
+ const setFrame = Internals.Timeline.useTimelineSetFrame();
409
+ // requestAnimationFrame() does not work if the tab is not active.
410
+ // This means that audio will keep playing even if it has ended.
411
+ // In that case, we use setTimeout() instead.
412
+ const isBackgroundedRef = useIsBackgrounded();
413
+ const frameRef = useRef(frame);
414
+ frameRef.current = frame;
415
+ const lastTimeUpdateEvent = useRef(null);
416
+ useEffect(() => {
417
+ if (!config) {
418
+ return;
419
+ }
420
+ if (!playing) {
421
+ return;
422
+ }
423
+ let hasBeenStopped = false;
424
+ let reqAnimFrameCall = null;
425
+ const startedTime = performance.now();
426
+ let framesAdvanced = 0;
427
+ const cancelQueuedFrame = () => {
428
+ if (reqAnimFrameCall !== null) {
429
+ if (reqAnimFrameCall.type === 'raf') {
430
+ cancelAnimationFrame(reqAnimFrameCall.id);
431
+ }
432
+ else {
433
+ clearTimeout(reqAnimFrameCall.id);
434
+ }
435
+ }
436
+ };
437
+ const stop = () => {
438
+ hasBeenStopped = true;
439
+ cancelQueuedFrame();
440
+ };
441
+ const callback = () => {
442
+ const time = performance.now() - startedTime;
443
+ const actualLastFrame = outFrame !== null && outFrame !== void 0 ? outFrame : config.durationInFrames - 1;
444
+ const actualFirstFrame = inFrame !== null && inFrame !== void 0 ? inFrame : 0;
445
+ const { nextFrame, framesToAdvance, hasEnded } = calculateNextFrame({
446
+ time,
447
+ currentFrame: frameRef.current,
448
+ playbackSpeed: playbackRate,
449
+ fps: config.fps,
450
+ actualFirstFrame,
451
+ actualLastFrame,
452
+ framesAdvanced,
453
+ shouldLoop: loop,
454
+ });
455
+ framesAdvanced += framesToAdvance;
456
+ if (nextFrame !== frameRef.current &&
457
+ (!hasEnded || moveToBeginningWhenEnded)) {
458
+ setFrame(nextFrame);
459
+ }
460
+ if (hasEnded) {
461
+ stop();
462
+ pause();
463
+ emitter.dispatchEnded();
464
+ return;
465
+ }
466
+ if (!hasBeenStopped) {
467
+ queueNextFrame();
468
+ }
469
+ };
470
+ const queueNextFrame = () => {
471
+ if (isBackgroundedRef.current) {
472
+ reqAnimFrameCall = {
473
+ type: 'timeout',
474
+ // Note: Most likely, this will not be 1000 / fps, but the browser will throttle it to ~1/sec.
475
+ id: setTimeout(callback, 1000 / config.fps),
476
+ };
477
+ }
478
+ else {
479
+ reqAnimFrameCall = { type: 'raf', id: requestAnimationFrame(callback) };
480
+ }
481
+ };
482
+ queueNextFrame();
483
+ const onVisibilityChange = () => {
484
+ if (document.visibilityState === 'visible') {
485
+ return;
486
+ }
487
+ // If tab goes into the background, cancel requestAnimationFrame() and update immediately.
488
+ // , so the transition to setTimeout() can be fulfilled.
489
+ cancelQueuedFrame();
490
+ callback();
491
+ };
492
+ window.addEventListener('visibilitychange', onVisibilityChange);
493
+ return () => {
494
+ window.removeEventListener('visibilitychange', onVisibilityChange);
495
+ stop();
496
+ };
497
+ }, [
498
+ config,
499
+ loop,
500
+ pause,
501
+ playing,
502
+ setFrame,
503
+ emitter,
504
+ playbackRate,
505
+ inFrame,
506
+ outFrame,
507
+ moveToBeginningWhenEnded,
508
+ isBackgroundedRef,
509
+ ]);
510
+ useEffect(() => {
511
+ const interval = setInterval(() => {
512
+ if (lastTimeUpdateEvent.current === frameRef.current) {
513
+ return;
514
+ }
515
+ emitter.dispatchTimeUpdate({ frame: frameRef.current });
516
+ lastTimeUpdateEvent.current = frameRef.current;
517
+ }, 250);
518
+ return () => clearInterval(interval);
519
+ }, [emitter]);
520
+ useEffect(() => {
521
+ emitter.dispatchFrameUpdate({ frame });
522
+ }, [emitter, frame]);
523
+ };
524
+
525
+ let elementSizeHooks = [];
526
+ const updateAllElementsSizes = () => {
527
+ for (const listener of elementSizeHooks) {
528
+ listener();
529
+ }
530
+ };
531
+ const useElementSize = (ref, options) => {
532
+ const [size, setSize] = useState(null);
533
+ const observer = useMemo(() => {
534
+ if (typeof ResizeObserver === 'undefined') {
535
+ return null;
536
+ }
537
+ return new ResizeObserver((entries) => {
538
+ // The contentRect returns the width without any `scale()`'s being applied. The height is wrong
539
+ const { contentRect } = entries[0];
540
+ // The clientRect returns the size with `scale()` being applied.
541
+ const newSize = entries[0].target.getClientRects();
542
+ if (!(newSize === null || newSize === void 0 ? void 0 : newSize[0])) {
543
+ setSize(null);
544
+ return;
545
+ }
546
+ const probableCssParentScale = contentRect.width === 0 ? 1 : newSize[0].width / contentRect.width;
547
+ const width = options.shouldApplyCssTransforms
548
+ ? newSize[0].width
549
+ : newSize[0].width * (1 / probableCssParentScale);
550
+ const height = options.shouldApplyCssTransforms
551
+ ? newSize[0].height
552
+ : newSize[0].height * (1 / probableCssParentScale);
553
+ setSize({
554
+ width,
555
+ height,
556
+ left: newSize[0].x,
557
+ top: newSize[0].y,
558
+ windowSize: {
559
+ height: window.innerHeight,
560
+ width: window.innerWidth,
561
+ },
562
+ });
563
+ });
564
+ }, [options.shouldApplyCssTransforms]);
565
+ const updateSize = useCallback(() => {
566
+ if (!ref.current) {
567
+ return;
568
+ }
569
+ const rect = ref.current.getClientRects();
570
+ if (!rect[0]) {
571
+ setSize(null);
572
+ return;
573
+ }
574
+ setSize((prevState) => {
575
+ const isSame = prevState &&
576
+ prevState.width === rect[0].width &&
577
+ prevState.height === rect[0].height &&
578
+ prevState.left === rect[0].x &&
579
+ prevState.top === rect[0].y &&
580
+ prevState.windowSize.height === window.innerHeight &&
581
+ prevState.windowSize.width === window.innerWidth;
582
+ if (isSame) {
583
+ return prevState;
584
+ }
585
+ return {
586
+ width: rect[0].width,
587
+ height: rect[0].height,
588
+ left: rect[0].x,
589
+ top: rect[0].y,
590
+ windowSize: {
591
+ height: window.innerHeight,
592
+ width: window.innerWidth,
593
+ },
594
+ };
595
+ });
596
+ }, [ref]);
597
+ useEffect(() => {
598
+ if (!observer) {
599
+ return;
600
+ }
601
+ updateSize();
602
+ const { current } = ref;
603
+ if (ref.current) {
604
+ observer.observe(ref.current);
605
+ }
606
+ return () => {
607
+ if (current) {
608
+ observer.unobserve(current);
609
+ }
610
+ };
611
+ }, [observer, ref, updateSize]);
612
+ useEffect(() => {
613
+ if (!options.triggerOnWindowResize) {
614
+ return;
615
+ }
616
+ window.addEventListener('resize', updateSize);
617
+ return () => {
618
+ window.removeEventListener('resize', updateSize);
619
+ };
620
+ }, [options.triggerOnWindowResize, updateSize]);
621
+ useEffect(() => {
622
+ elementSizeHooks.push(updateSize);
623
+ return () => {
624
+ elementSizeHooks = elementSizeHooks.filter((e) => e !== updateSize);
625
+ };
626
+ }, [updateSize]);
627
+ return useMemo(() => {
628
+ if (!size) {
629
+ return null;
630
+ }
631
+ return { ...size, refresh: updateSize };
632
+ }, [size, updateSize]);
633
+ };
634
+
635
+ const PLAYER_CSS_CLASSNAME = '__remotion-player';
636
+
637
+ const errorStyle = {
638
+ display: 'flex',
639
+ justifyContent: 'center',
640
+ alignItems: 'center',
641
+ flex: 1,
642
+ height: '100%',
643
+ width: '100%',
644
+ };
645
+ class ErrorBoundary extends React.Component {
646
+ constructor() {
647
+ super(...arguments);
648
+ this.state = { hasError: null };
649
+ }
650
+ static getDerivedStateFromError(error) {
651
+ // Update state so the next render will show the fallback UI.
652
+ return { hasError: error };
653
+ }
654
+ componentDidCatch(error) {
655
+ this.props.onError(error);
656
+ }
657
+ render() {
658
+ if (this.state.hasError) {
659
+ // You can render any custom fallback UI
660
+ return (jsx("div", { style: errorStyle, children: this.props.errorFallback({
661
+ error: this.state.hasError,
662
+ }) }));
663
+ }
664
+ return this.props.children;
665
+ }
666
+ }
667
+
668
+ const formatTime = (timeInSeconds) => {
669
+ const minutes = Math.floor(timeInSeconds / 60);
670
+ const seconds = Math.floor(timeInSeconds - minutes * 60);
671
+ return `${String(minutes)}:${String(seconds).padStart(2, '0')}`;
672
+ };
673
+
674
+ const ICON_SIZE = 25;
675
+ const fullscreenIconSize = 16;
676
+ const rotate = {
677
+ transform: `rotate(90deg)`,
678
+ };
679
+ const PlayIcon = () => {
680
+ return (jsx("svg", { width: ICON_SIZE, height: ICON_SIZE, viewBox: "-100 -100 400 400", style: rotate, children: jsx("path", { fill: "#fff", stroke: "#fff", strokeWidth: "100", strokeLinejoin: "round", d: "M 2 172 a 196 100 0 0 0 195 5 A 196 240 0 0 0 100 2.259 A 196 240 0 0 0 2 172 z" }) }));
681
+ };
682
+ const PauseIcon = () => {
683
+ return (jsxs("svg", { viewBox: "0 0 100 100", width: ICON_SIZE, height: ICON_SIZE, children: [jsx("rect", { x: "25", y: "20", width: "20", height: "60", fill: "#fff", ry: "5", rx: "5" }), jsx("rect", { x: "55", y: "20", width: "20", height: "60", fill: "#fff", ry: "5", rx: "5" })] }));
684
+ };
685
+ const FullscreenIcon = ({ isFullscreen, }) => {
686
+ const strokeWidth = 6;
687
+ const viewSize = 32;
688
+ const out = isFullscreen ? 0 : strokeWidth / 2;
689
+ const middleInset = isFullscreen ? strokeWidth * 1.6 : strokeWidth / 2;
690
+ const inset = isFullscreen ? strokeWidth * 1.6 : strokeWidth * 2;
691
+ return (jsxs("svg", { viewBox: `0 0 ${viewSize} ${viewSize}`, height: fullscreenIconSize, width: fullscreenIconSize, children: [jsx("path", { d: `
692
+ M ${out} ${inset}
693
+ L ${middleInset} ${middleInset}
694
+ L ${inset} ${out}
695
+ `, stroke: "#fff", strokeWidth: strokeWidth, fill: "none" }), jsx("path", { d: `
696
+ M ${viewSize - out} ${inset}
697
+ L ${viewSize - middleInset} ${middleInset}
698
+ L ${viewSize - inset} ${out}
699
+ `, stroke: "#fff", strokeWidth: strokeWidth, fill: "none" }), jsx("path", { d: `
700
+ M ${out} ${viewSize - inset}
701
+ L ${middleInset} ${viewSize - middleInset}
702
+ L ${inset} ${viewSize - out}
703
+ `, stroke: "#fff", strokeWidth: strokeWidth, fill: "none" }), jsx("path", { d: `
704
+ M ${viewSize - out} ${viewSize - inset}
705
+ L ${viewSize - middleInset} ${viewSize - middleInset}
706
+ L ${viewSize - inset} ${viewSize - out}
707
+ `, stroke: "#fff", strokeWidth: strokeWidth, fill: "none" })] }));
708
+ };
709
+ const VolumeOffIcon = () => {
710
+ return (jsx("svg", { width: ICON_SIZE, height: ICON_SIZE, viewBox: "0 0 24 24", children: jsx("path", { d: "M3.63 3.63a.996.996 0 000 1.41L7.29 8.7 7 9H4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71v-4.17l4.18 4.18c-.49.37-1.02.68-1.6.91-.36.15-.58.53-.58.92 0 .72.73 1.18 1.39.91.8-.33 1.55-.77 2.22-1.31l1.34 1.34a.996.996 0 101.41-1.41L5.05 3.63c-.39-.39-1.02-.39-1.42 0zM19 12c0 .82-.15 1.61-.41 2.34l1.53 1.53c.56-1.17.88-2.48.88-3.87 0-3.83-2.4-7.11-5.78-8.4-.59-.23-1.22.23-1.22.86v.19c0 .38.25.71.61.85C17.18 6.54 19 9.06 19 12zm-8.71-6.29l-.17.17L12 7.76V6.41c0-.89-1.08-1.33-1.71-.7zM16.5 12A4.5 4.5 0 0014 7.97v1.79l2.48 2.48c.01-.08.02-.16.02-.24z", fill: "#fff" }) }));
711
+ };
712
+ const VolumeOnIcon = () => {
713
+ return (jsx("svg", { width: ICON_SIZE, height: ICON_SIZE, viewBox: "0 0 24 24", children: jsx("path", { d: "M3 10v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71V6.41c0-.89-1.08-1.34-1.71-.71L7 9H4c-.55 0-1 .45-1 1zm13.5 2A4.5 4.5 0 0014 7.97v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 4.45v.2c0 .38.25.71.6.85C17.18 6.53 19 9.06 19 12s-1.82 5.47-4.4 6.5c-.36.14-.6.47-.6.85v.2c0 .63.63 1.07 1.21.85C18.6 19.11 21 15.84 21 12s-2.4-7.11-5.79-8.4c-.58-.23-1.21.22-1.21.85z", fill: "#fff" }) }));
714
+ };
715
+
716
+ const BAR_HEIGHT$1 = 5;
717
+ const KNOB_SIZE$1 = 12;
718
+ const VOLUME_SLIDER_WIDTH = 100;
719
+ const MediaVolumeSlider = ({ displayVerticalVolumeSlider }) => {
720
+ const [mediaMuted, setMediaMuted] = Internals.useMediaMutedState();
721
+ const [mediaVolume, setMediaVolume] = Internals.useMediaVolumeState();
722
+ const [focused, setFocused] = useState(false);
723
+ const parentDivRef = useRef(null);
724
+ const inputRef = useRef(null);
725
+ const hover = useHoverState(parentDivRef);
726
+ // Need to import it from React to fix React 17 ESM support.
727
+ const randomId =
728
+ // eslint-disable-next-line react-hooks/rules-of-hooks
729
+ typeof React.useId === 'undefined' ? 'volume-slider' : React.useId();
730
+ const [randomClass] = useState(() => `__remotion-volume-slider-${random(randomId)}`.replace('.', ''));
731
+ const isMutedOrZero = mediaMuted || mediaVolume === 0;
732
+ const onVolumeChange = (e) => {
733
+ setMediaVolume(parseFloat(e.target.value));
734
+ };
735
+ const onBlur = () => {
736
+ setTimeout(() => {
737
+ // We need a small delay to check which element was focused next,
738
+ // and if it wasn't the volume slider, we hide it
739
+ if (document.activeElement !== inputRef.current) {
740
+ setFocused(false);
741
+ }
742
+ }, 10);
743
+ };
744
+ const onClick = useCallback(() => {
745
+ if (mediaVolume === 0) {
746
+ setMediaVolume(1);
747
+ setMediaMuted(false);
748
+ return;
749
+ }
750
+ setMediaMuted((mute) => !mute);
751
+ }, [mediaVolume, setMediaMuted, setMediaVolume]);
752
+ const parentDivStyle = useMemo(() => {
753
+ return {
754
+ display: 'inline-flex',
755
+ background: 'none',
756
+ border: 'none',
757
+ justifyContent: 'center',
758
+ alignItems: 'center',
759
+ touchAction: 'none',
760
+ ...(displayVerticalVolumeSlider && { position: 'relative' }),
761
+ };
762
+ }, [displayVerticalVolumeSlider]);
763
+ const volumeContainer = useMemo(() => {
764
+ return {
765
+ display: 'inline',
766
+ width: ICON_SIZE,
767
+ height: ICON_SIZE,
768
+ cursor: 'pointer',
769
+ appearance: 'none',
770
+ background: 'none',
771
+ border: 'none',
772
+ padding: 0,
773
+ };
774
+ }, []);
775
+ const inputStyle = useMemo(() => {
776
+ const commonStyle = {
777
+ WebkitAppearance: 'none',
778
+ backgroundColor: 'rgba(255, 255, 255, 0.5)',
779
+ borderRadius: BAR_HEIGHT$1 / 2,
780
+ cursor: 'pointer',
781
+ height: BAR_HEIGHT$1,
782
+ width: VOLUME_SLIDER_WIDTH,
783
+ backgroundImage: `linear-gradient(
784
+ to right,
785
+ white ${mediaVolume * 100}%, rgba(255, 255, 255, 0) ${mediaVolume * 100}%
786
+ )`,
787
+ };
788
+ if (displayVerticalVolumeSlider) {
789
+ return {
790
+ ...commonStyle,
791
+ transform: `rotate(-90deg)`,
792
+ position: 'absolute',
793
+ bottom: ICON_SIZE + VOLUME_SLIDER_WIDTH / 2 + 5,
794
+ };
795
+ }
796
+ return {
797
+ ...commonStyle,
798
+ marginLeft: 5,
799
+ };
800
+ }, [displayVerticalVolumeSlider, mediaVolume]);
801
+ const sliderStyle = `
802
+ .${randomClass}::-webkit-slider-thumb {
803
+ -webkit-appearance: none;
804
+ background-color: white;
805
+ border-radius: ${KNOB_SIZE$1 / 2}px;
806
+ box-shadow: 0 0 2px black;
807
+ height: ${KNOB_SIZE$1}px;
808
+ width: ${KNOB_SIZE$1}px;
809
+ }
810
+
811
+ .${randomClass}::-moz-range-thumb {
812
+ -webkit-appearance: none;
813
+ background-color: white;
814
+ border-radius: ${KNOB_SIZE$1 / 2}px;
815
+ box-shadow: 0 0 2px black;
816
+ height: ${KNOB_SIZE$1}px;
817
+ width: ${KNOB_SIZE$1}px;
818
+ }
819
+ `;
820
+ return (jsxs("div", { ref: parentDivRef, style: parentDivStyle, children: [jsx("style", {
821
+ // eslint-disable-next-line react/no-danger
822
+ dangerouslySetInnerHTML: {
823
+ __html: sliderStyle,
824
+ } }), jsx("button", { "aria-label": isMutedOrZero ? 'Unmute sound' : 'Mute sound', title: isMutedOrZero ? 'Unmute sound' : 'Mute sound', onClick: onClick, onBlur: onBlur, onFocus: () => setFocused(true), style: volumeContainer, type: "button", children: isMutedOrZero ? jsx(VolumeOffIcon, {}) : jsx(VolumeOnIcon, {}) }), (focused || hover) && !mediaMuted ? (jsx("input", { ref: inputRef, "aria-label": "Change volume", className: randomClass, max: 1, min: 0, onBlur: () => setFocused(false), onChange: onVolumeChange, step: 0.01, type: "range", value: mediaVolume, style: inputStyle })) : null] }));
825
+ };
826
+
827
+ // hook to hide a popup/modal when clicked outside
828
+ function useComponentVisible(initialIsVisible) {
829
+ const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
830
+ const ref = useRef(null);
831
+ useEffect(() => {
832
+ const handleClickOutside = (event) => {
833
+ if (ref.current && !ref.current.contains(event.target)) {
834
+ setIsComponentVisible(false);
835
+ }
836
+ };
837
+ document.addEventListener('pointerup', handleClickOutside, true);
838
+ return () => {
839
+ document.removeEventListener('pointerup', handleClickOutside, true);
840
+ };
841
+ }, []);
842
+ return { ref, isComponentVisible, setIsComponentVisible };
843
+ }
844
+
845
+ // To align
846
+ const BOTTOM = 35;
847
+ // Arbitrary to clamp the height of the popup
848
+ const THRESHOLD = 70;
849
+ const rateDiv = {
850
+ height: 30,
851
+ paddingRight: 15,
852
+ paddingLeft: 12,
853
+ display: 'flex',
854
+ flexDirection: 'row',
855
+ alignItems: 'center',
856
+ };
857
+ const checkmarkContainer = {
858
+ width: 22,
859
+ display: 'flex',
860
+ alignItems: 'center',
861
+ };
862
+ const checkmarkStyle = {
863
+ width: 14,
864
+ height: 14,
865
+ color: 'black',
866
+ };
867
+ const Checkmark = () => (jsx("svg", { viewBox: "0 0 512 512", style: checkmarkStyle, children: jsx("path", { fill: "currentColor", d: "M435.848 83.466L172.804 346.51l-96.652-96.652c-4.686-4.686-12.284-4.686-16.971 0l-28.284 28.284c-4.686 4.686-4.686 12.284 0 16.971l133.421 133.421c4.686 4.686 12.284 4.686 16.971 0l299.813-299.813c4.686-4.686 4.686-12.284 0-16.971l-28.284-28.284c-4.686-4.686-12.284-4.686-16.97 0z" }) }));
868
+ const PlaybackPopup = ({ setIsComponentVisible, playbackRates, canvasSize }) => {
869
+ const { setPlaybackRate, playbackRate } = useContext(Internals.Timeline.TimelineContext);
870
+ const [keyboardSelectedRate, setKeyboardSelectedRate] = useState(playbackRate);
871
+ useEffect(() => {
872
+ const listener = (e) => {
873
+ e.preventDefault();
874
+ if (e.key === 'ArrowUp') {
875
+ const currentIndex = playbackRates.findIndex((rate) => rate === keyboardSelectedRate);
876
+ if (currentIndex === 0) {
877
+ return;
878
+ }
879
+ if (currentIndex === -1) {
880
+ setKeyboardSelectedRate(playbackRates[0]);
881
+ }
882
+ else {
883
+ setKeyboardSelectedRate(playbackRates[currentIndex - 1]);
884
+ }
885
+ }
886
+ else if (e.key === 'ArrowDown') {
887
+ const currentIndex = playbackRates.findIndex((rate) => rate === keyboardSelectedRate);
888
+ if (currentIndex === playbackRates.length - 1) {
889
+ return;
890
+ }
891
+ if (currentIndex === -1) {
892
+ setKeyboardSelectedRate(playbackRates[playbackRates.length - 1]);
893
+ }
894
+ else {
895
+ setKeyboardSelectedRate(playbackRates[currentIndex + 1]);
896
+ }
897
+ }
898
+ else if (e.key === 'Enter') {
899
+ setPlaybackRate(keyboardSelectedRate);
900
+ setIsComponentVisible(false);
901
+ }
902
+ };
903
+ window.addEventListener('keydown', listener);
904
+ return () => {
905
+ window.removeEventListener('keydown', listener);
906
+ };
907
+ }, [
908
+ playbackRates,
909
+ keyboardSelectedRate,
910
+ setPlaybackRate,
911
+ setIsComponentVisible,
912
+ ]);
913
+ const onSelect = useCallback((rate) => {
914
+ setPlaybackRate(rate);
915
+ setIsComponentVisible(false);
916
+ }, [setIsComponentVisible, setPlaybackRate]);
917
+ const playbackPopup = useMemo(() => {
918
+ return {
919
+ position: 'absolute',
920
+ right: 0,
921
+ width: 125,
922
+ maxHeight: canvasSize.height - THRESHOLD - BOTTOM,
923
+ bottom: 35,
924
+ background: '#fff',
925
+ borderRadius: 4,
926
+ overflow: 'auto',
927
+ color: 'black',
928
+ textAlign: 'left',
929
+ };
930
+ }, [canvasSize.height]);
931
+ return (jsx("div", { style: playbackPopup, children: playbackRates.map((rate) => {
932
+ return (jsx(PlaybackrateOption, { selectedRate: playbackRate, onSelect: onSelect, rate: rate, keyboardSelectedRate: keyboardSelectedRate }, rate));
933
+ }) }));
934
+ };
935
+ const PlaybackrateOption = ({ rate, onSelect, selectedRate, keyboardSelectedRate }) => {
936
+ const onClick = useCallback((e) => {
937
+ e.stopPropagation();
938
+ e.preventDefault();
939
+ onSelect(rate);
940
+ }, [onSelect, rate]);
941
+ const [hovered, setHovered] = useState(false);
942
+ const onMouseEnter = useCallback(() => {
943
+ setHovered(true);
944
+ }, []);
945
+ const onMouseLeave = useCallback(() => {
946
+ setHovered(false);
947
+ }, []);
948
+ const actualStyle = useMemo(() => {
949
+ return {
950
+ ...rateDiv,
951
+ backgroundColor: hovered || keyboardSelectedRate === rate ? '#eee' : 'transparent',
952
+ };
953
+ }, [hovered, keyboardSelectedRate, rate]);
954
+ return (jsxs("div", { onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, tabIndex: 0, style: actualStyle, onClick: onClick, children: [jsx("div", { style: checkmarkContainer, children: rate === selectedRate ? jsx(Checkmark, {}) : null }), rate.toFixed(1), "x"] }, rate));
955
+ };
956
+ const label = {
957
+ fontSize: 13,
958
+ fontWeight: 'bold',
959
+ color: 'white',
960
+ border: '2px solid white',
961
+ borderRadius: 20,
962
+ paddingLeft: 8,
963
+ paddingRight: 8,
964
+ paddingTop: 2,
965
+ paddingBottom: 2,
966
+ };
967
+ const playerButtonStyle = {
968
+ appearance: 'none',
969
+ backgroundColor: 'transparent',
970
+ border: 'none',
971
+ cursor: 'pointer',
972
+ paddingLeft: 0,
973
+ paddingRight: 0,
974
+ paddingTop: 6,
975
+ paddingBottom: 6,
976
+ height: 37,
977
+ display: 'inline-flex',
978
+ marginBottom: 0,
979
+ marginTop: 0,
980
+ alignItems: 'center',
981
+ };
982
+ const button = {
983
+ ...playerButtonStyle,
984
+ position: 'relative',
985
+ };
986
+ const PlaybackrateControl = ({ playbackRates, canvasSize }) => {
987
+ const { ref, isComponentVisible, setIsComponentVisible } = useComponentVisible(false);
988
+ const { playbackRate } = useContext(Internals.Timeline.TimelineContext);
989
+ const onClick = useCallback((e) => {
990
+ e.stopPropagation();
991
+ e.preventDefault();
992
+ setIsComponentVisible(!isComponentVisible);
993
+ }, [isComponentVisible, setIsComponentVisible]);
994
+ return (jsx("div", { ref: ref, children: jsxs("button", { type: "button", "aria-label": "Change playback rate", style: button, onClick: onClick, children: [jsxs("div", { style: label, children: [playbackRate, "x"] }), isComponentVisible && (jsx(PlaybackPopup, { canvasSize: canvasSize, playbackRates: playbackRates, setIsComponentVisible: setIsComponentVisible }))] }) }));
995
+ };
996
+
997
+ const getFrameFromX = (clientX, durationInFrames, width) => {
998
+ var _a;
999
+ const pos = clientX;
1000
+ const frame = Math.round(interpolate(pos, [0, width], [0, (_a = durationInFrames - 1) !== null && _a !== void 0 ? _a : 0], {
1001
+ extrapolateLeft: 'clamp',
1002
+ extrapolateRight: 'clamp',
1003
+ }));
1004
+ return frame;
1005
+ };
1006
+ const BAR_HEIGHT = 5;
1007
+ const KNOB_SIZE = 12;
1008
+ const VERTICAL_PADDING = 4;
1009
+ const containerStyle$1 = {
1010
+ userSelect: 'none',
1011
+ paddingTop: VERTICAL_PADDING,
1012
+ paddingBottom: VERTICAL_PADDING,
1013
+ boxSizing: 'border-box',
1014
+ cursor: 'pointer',
1015
+ position: 'relative',
1016
+ touchAction: 'none',
1017
+ };
1018
+ const barBackground = {
1019
+ height: BAR_HEIGHT,
1020
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
1021
+ width: '100%',
1022
+ borderRadius: BAR_HEIGHT / 2,
1023
+ };
1024
+ const findBodyInWhichDivIsLocated = (div) => {
1025
+ let current = div;
1026
+ while (current.parentElement) {
1027
+ current = current.parentElement;
1028
+ }
1029
+ return current;
1030
+ };
1031
+ const PlayerSeekBar = ({ durationInFrames, onSeekEnd, onSeekStart, inFrame, outFrame }) => {
1032
+ const containerRef = useRef(null);
1033
+ const barHovered = useHoverState(containerRef);
1034
+ const size = useElementSize(containerRef, {
1035
+ triggerOnWindowResize: true,
1036
+ shouldApplyCssTransforms: true,
1037
+ });
1038
+ const { seek, play, pause, playing } = usePlayer();
1039
+ const frame = Internals.Timeline.useTimelinePosition();
1040
+ const [dragging, setDragging] = useState({
1041
+ dragging: false,
1042
+ });
1043
+ const onPointerDown = useCallback((e) => {
1044
+ if (!size) {
1045
+ throw new Error('Player has no size');
1046
+ }
1047
+ const _frame = getFrameFromX(e.clientX - size.left, durationInFrames, size.width);
1048
+ pause();
1049
+ seek(_frame);
1050
+ setDragging({
1051
+ dragging: true,
1052
+ wasPlaying: playing,
1053
+ });
1054
+ onSeekStart();
1055
+ }, [size, durationInFrames, pause, seek, playing, onSeekStart]);
1056
+ const onPointerMove = useCallback((e) => {
1057
+ var _a;
1058
+ if (!size) {
1059
+ throw new Error('Player has no size');
1060
+ }
1061
+ if (!dragging.dragging) {
1062
+ return;
1063
+ }
1064
+ const _frame = getFrameFromX(e.clientX - ((_a = size === null || size === void 0 ? void 0 : size.left) !== null && _a !== void 0 ? _a : 0), durationInFrames, size.width);
1065
+ seek(_frame);
1066
+ }, [dragging.dragging, durationInFrames, seek, size]);
1067
+ const onPointerUp = useCallback(() => {
1068
+ setDragging({
1069
+ dragging: false,
1070
+ });
1071
+ if (!dragging.dragging) {
1072
+ return;
1073
+ }
1074
+ if (dragging.wasPlaying) {
1075
+ play();
1076
+ }
1077
+ else {
1078
+ pause();
1079
+ }
1080
+ onSeekEnd();
1081
+ }, [dragging, onSeekEnd, pause, play]);
1082
+ useEffect(() => {
1083
+ if (!dragging.dragging) {
1084
+ return;
1085
+ }
1086
+ const body = findBodyInWhichDivIsLocated(containerRef.current);
1087
+ body.addEventListener('pointermove', onPointerMove);
1088
+ body.addEventListener('pointerup', onPointerUp);
1089
+ return () => {
1090
+ body.removeEventListener('pointermove', onPointerMove);
1091
+ body.removeEventListener('pointerup', onPointerUp);
1092
+ };
1093
+ }, [dragging.dragging, onPointerMove, onPointerUp]);
1094
+ const knobStyle = useMemo(() => {
1095
+ var _a;
1096
+ return {
1097
+ height: KNOB_SIZE,
1098
+ width: KNOB_SIZE,
1099
+ borderRadius: KNOB_SIZE / 2,
1100
+ position: 'absolute',
1101
+ top: VERTICAL_PADDING - KNOB_SIZE / 2 + 5 / 2,
1102
+ backgroundColor: 'white',
1103
+ left: Math.max(0, (frame / Math.max(1, durationInFrames - 1)) * ((_a = size === null || size === void 0 ? void 0 : size.width) !== null && _a !== void 0 ? _a : 0) -
1104
+ KNOB_SIZE / 2),
1105
+ boxShadow: '0 0 2px black',
1106
+ opacity: Number(barHovered),
1107
+ };
1108
+ }, [barHovered, durationInFrames, frame, size]);
1109
+ const fillStyle = useMemo(() => {
1110
+ return {
1111
+ height: BAR_HEIGHT,
1112
+ backgroundColor: 'rgba(255, 255, 255, 1)',
1113
+ width: ((frame - (inFrame !== null && inFrame !== void 0 ? inFrame : 0)) / (durationInFrames - 1)) * 100 + '%',
1114
+ marginLeft: ((inFrame !== null && inFrame !== void 0 ? inFrame : 0) / (durationInFrames - 1)) * 100 + '%',
1115
+ borderRadius: BAR_HEIGHT / 2,
1116
+ };
1117
+ }, [durationInFrames, frame, inFrame]);
1118
+ const active = useMemo(() => {
1119
+ return {
1120
+ height: BAR_HEIGHT,
1121
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
1122
+ width: (((outFrame !== null && outFrame !== void 0 ? outFrame : durationInFrames - 1) - (inFrame !== null && inFrame !== void 0 ? inFrame : 0)) /
1123
+ (durationInFrames - 1)) *
1124
+ 100 +
1125
+ '%',
1126
+ marginLeft: ((inFrame !== null && inFrame !== void 0 ? inFrame : 0) / (durationInFrames - 1)) * 100 + '%',
1127
+ borderRadius: BAR_HEIGHT / 2,
1128
+ position: 'absolute',
1129
+ };
1130
+ }, [durationInFrames, inFrame, outFrame]);
1131
+ return (jsxs("div", { ref: containerRef, onPointerDown: onPointerDown, style: containerStyle$1, children: [jsxs("div", { style: barBackground, children: [jsx("div", { style: active }), jsx("div", { style: fillStyle })] }), jsx("div", { style: knobStyle })] }));
1132
+ };
1133
+
1134
+ const X_SPACER = 10;
1135
+ const X_PADDING = 12;
1136
+ const useVideoControlsResize = ({ allowFullscreen: allowFullScreen, playerWidth, }) => {
1137
+ const resizeInfo = useMemo(() => {
1138
+ const playPauseIconSize = ICON_SIZE;
1139
+ const volumeIconSize = ICON_SIZE;
1140
+ const _fullscreenIconSize = allowFullScreen ? fullscreenIconSize : 0;
1141
+ const elementsSize = volumeIconSize +
1142
+ playPauseIconSize +
1143
+ _fullscreenIconSize +
1144
+ X_PADDING * 2 +
1145
+ X_SPACER * 2;
1146
+ const maxTimeLabelWidth = playerWidth - elementsSize;
1147
+ const maxTimeLabelWidthWithoutNegativeValue = Math.max(maxTimeLabelWidth, 0);
1148
+ const availableTimeLabelWidthIfVolumeOpen = maxTimeLabelWidthWithoutNegativeValue - VOLUME_SLIDER_WIDTH;
1149
+ // If max label width is lower than the volume width
1150
+ // then it means we need to take it's width as the max label width
1151
+ // otherwise we took the available width when volume open
1152
+ const computedLabelWidth = availableTimeLabelWidthIfVolumeOpen < VOLUME_SLIDER_WIDTH
1153
+ ? maxTimeLabelWidthWithoutNegativeValue
1154
+ : availableTimeLabelWidthIfVolumeOpen;
1155
+ const minWidthForHorizontalDisplay = computedLabelWidth + elementsSize + VOLUME_SLIDER_WIDTH;
1156
+ const displayVerticalVolumeSlider = playerWidth < minWidthForHorizontalDisplay;
1157
+ return {
1158
+ maxTimeLabelWidth: maxTimeLabelWidthWithoutNegativeValue === 0
1159
+ ? null
1160
+ : maxTimeLabelWidthWithoutNegativeValue,
1161
+ displayVerticalVolumeSlider,
1162
+ };
1163
+ }, [allowFullScreen, playerWidth]);
1164
+ return resizeInfo;
1165
+ };
1166
+
1167
+ const gradientSteps = [
1168
+ 0, 0.013, 0.049, 0.104, 0.175, 0.259, 0.352, 0.45, 0.55, 0.648, 0.741, 0.825,
1169
+ 0.896, 0.951, 0.987,
1170
+ ];
1171
+ const gradientOpacities = [
1172
+ 0, 8.1, 15.5, 22.5, 29, 35.3, 41.2, 47.1, 52.9, 58.8, 64.7, 71, 77.5, 84.5,
1173
+ 91.9,
1174
+ ];
1175
+ const globalGradientOpacity = 1 / 0.7;
1176
+ const containerStyle = {
1177
+ boxSizing: 'border-box',
1178
+ position: 'absolute',
1179
+ bottom: 0,
1180
+ width: '100%',
1181
+ paddingTop: 40,
1182
+ paddingBottom: 10,
1183
+ backgroundImage: `linear-gradient(to bottom,${gradientSteps
1184
+ .map((g, i) => {
1185
+ return `hsla(0, 0%, 0%, ${g}) ${gradientOpacities[i] * globalGradientOpacity}%`;
1186
+ })
1187
+ .join(', ')}, hsl(0, 0%, 0%) 100%)`,
1188
+ backgroundSize: 'auto 145px',
1189
+ display: 'flex',
1190
+ paddingRight: X_PADDING,
1191
+ paddingLeft: X_PADDING,
1192
+ flexDirection: 'column',
1193
+ transition: 'opacity 0.3s',
1194
+ };
1195
+ const controlsRow = {
1196
+ display: 'flex',
1197
+ flexDirection: 'row',
1198
+ width: '100%',
1199
+ alignItems: 'center',
1200
+ justifyContent: 'center',
1201
+ userSelect: 'none',
1202
+ };
1203
+ const leftPartStyle = {
1204
+ display: 'flex',
1205
+ flexDirection: 'row',
1206
+ userSelect: 'none',
1207
+ alignItems: 'center',
1208
+ };
1209
+ const xSpacer = {
1210
+ width: 12,
1211
+ };
1212
+ const ySpacer = {
1213
+ height: 8,
1214
+ };
1215
+ const flex1 = {
1216
+ flex: 1,
1217
+ };
1218
+ const fullscreen = {};
1219
+ const PlayPauseButton = ({ playing }) => playing ? jsx(PauseIcon, {}) : jsx(PlayIcon, {});
1220
+ const Controls = ({ durationInFrames, hovered, isFullscreen, fps, player, showVolumeControls, onFullscreenButtonClick, allowFullscreen, onExitFullscreenButtonClick, spaceKeyToPlayOrPause, onSeekEnd, onSeekStart, inFrame, outFrame, initiallyShowControls, canvasSize, renderPlayPauseButton, renderFullscreenButton, alwaysShowControls, showPlaybackRateControl, }) => {
1221
+ var _a;
1222
+ const playButtonRef = useRef(null);
1223
+ const frame = Internals.Timeline.useTimelinePosition();
1224
+ const [supportsFullscreen, setSupportsFullscreen] = useState(false);
1225
+ const { maxTimeLabelWidth, displayVerticalVolumeSlider } = useVideoControlsResize({
1226
+ allowFullscreen,
1227
+ playerWidth: (_a = canvasSize === null || canvasSize === void 0 ? void 0 : canvasSize.width) !== null && _a !== void 0 ? _a : 0,
1228
+ });
1229
+ const [shouldShowInitially, setInitiallyShowControls] = useState(() => {
1230
+ if (typeof initiallyShowControls === 'boolean') {
1231
+ return initiallyShowControls;
1232
+ }
1233
+ if (typeof initiallyShowControls === 'number') {
1234
+ if (initiallyShowControls % 1 !== 0) {
1235
+ throw new Error('initiallyShowControls must be an integer or a boolean');
1236
+ }
1237
+ if (Number.isNaN(initiallyShowControls)) {
1238
+ throw new Error('initiallyShowControls must not be NaN');
1239
+ }
1240
+ if (!Number.isFinite(initiallyShowControls)) {
1241
+ throw new Error('initiallyShowControls must be finite');
1242
+ }
1243
+ if (initiallyShowControls <= 0) {
1244
+ throw new Error('initiallyShowControls must be a positive integer');
1245
+ }
1246
+ return initiallyShowControls;
1247
+ }
1248
+ throw new TypeError('initiallyShowControls must be a number or a boolean');
1249
+ });
1250
+ const containerCss = useMemo(() => {
1251
+ // Hide if playing and mouse outside
1252
+ const shouldShow = hovered || !player.playing || shouldShowInitially || alwaysShowControls;
1253
+ return {
1254
+ ...containerStyle,
1255
+ opacity: Number(shouldShow),
1256
+ };
1257
+ }, [hovered, shouldShowInitially, player.playing, alwaysShowControls]);
1258
+ useEffect(() => {
1259
+ if (playButtonRef.current && spaceKeyToPlayOrPause) {
1260
+ // This switches focus to play button when player.playing flag changes
1261
+ playButtonRef.current.focus({
1262
+ preventScroll: true,
1263
+ });
1264
+ }
1265
+ }, [player.playing, spaceKeyToPlayOrPause]);
1266
+ useEffect(() => {
1267
+ var _a;
1268
+ // Must be handled client-side to avoid SSR hydration mismatch
1269
+ setSupportsFullscreen((_a = (typeof document !== 'undefined' &&
1270
+ (document.fullscreenEnabled || document.webkitFullscreenEnabled))) !== null && _a !== void 0 ? _a : false);
1271
+ }, []);
1272
+ useEffect(() => {
1273
+ if (shouldShowInitially === false) {
1274
+ return;
1275
+ }
1276
+ const time = shouldShowInitially === true ? 2000 : shouldShowInitially;
1277
+ const timeout = setTimeout(() => {
1278
+ setInitiallyShowControls(false);
1279
+ }, time);
1280
+ return () => {
1281
+ clearInterval(timeout);
1282
+ };
1283
+ }, [shouldShowInitially]);
1284
+ const timeLabel = useMemo(() => {
1285
+ return {
1286
+ color: 'white',
1287
+ fontFamily: 'sans-serif',
1288
+ fontSize: 14,
1289
+ maxWidth: maxTimeLabelWidth === null ? undefined : maxTimeLabelWidth,
1290
+ overflow: 'hidden',
1291
+ textOverflow: 'ellipsis',
1292
+ };
1293
+ }, [maxTimeLabelWidth]);
1294
+ const playbackRates = useMemo(() => {
1295
+ if (showPlaybackRateControl === true) {
1296
+ return [0.5, 0.8, 1, 1.2, 1.5, 1.8, 2, 2.5, 3];
1297
+ }
1298
+ if (Array.isArray(showPlaybackRateControl)) {
1299
+ for (const rate of showPlaybackRateControl) {
1300
+ if (typeof rate !== 'number') {
1301
+ throw new Error('Every item in showPlaybackRateControl must be a number');
1302
+ }
1303
+ if (rate <= 0) {
1304
+ throw new Error('Every item in showPlaybackRateControl must be positive');
1305
+ }
1306
+ }
1307
+ return showPlaybackRateControl;
1308
+ }
1309
+ return null;
1310
+ }, [showPlaybackRateControl]);
1311
+ return (jsxs("div", { style: containerCss, children: [jsxs("div", { style: controlsRow, children: [jsxs("div", { style: leftPartStyle, children: [jsx("button", { ref: playButtonRef, type: "button", style: playerButtonStyle, onClick: player.playing ? player.pause : player.play, "aria-label": player.playing ? 'Pause video' : 'Play video', title: player.playing ? 'Pause video' : 'Play video', children: renderPlayPauseButton === null ? (jsx(PlayPauseButton, { playing: player.playing })) : (renderPlayPauseButton({ playing: player.playing })) }), showVolumeControls ? (jsxs(Fragment, { children: [jsx("div", { style: xSpacer }), jsx(MediaVolumeSlider, { displayVerticalVolumeSlider: displayVerticalVolumeSlider })] })) : null, jsx("div", { style: xSpacer }), jsxs("div", { style: timeLabel, children: [formatTime(frame / fps), " / ", formatTime(durationInFrames / fps)] }), jsx("div", { style: xSpacer })] }), jsx("div", { style: flex1 }), playbackRates && canvasSize && (jsx(PlaybackrateControl, { canvasSize: canvasSize, playbackRates: playbackRates })), playbackRates && supportsFullscreen && allowFullscreen ? (jsx("div", { style: xSpacer })) : null, jsx("div", { style: fullscreen, children: supportsFullscreen && allowFullscreen ? (jsx("button", { type: "button", "aria-label": isFullscreen ? 'Exit fullscreen' : 'Enter Fullscreen', title: isFullscreen ? 'Exit fullscreen' : 'Enter Fullscreen', style: playerButtonStyle, onClick: isFullscreen
1312
+ ? onExitFullscreenButtonClick
1313
+ : onFullscreenButtonClick, children: renderFullscreenButton === null ? (jsx(FullscreenIcon, { isFullscreen: isFullscreen })) : (renderFullscreenButton({ isFullscreen })) })) : null })] }), jsx("div", { style: ySpacer }), jsx(PlayerSeekBar, { onSeekEnd: onSeekEnd, onSeekStart: onSeekStart, durationInFrames: durationInFrames, inFrame: inFrame, outFrame: outFrame })] }));
1314
+ };
1315
+
1316
+ const IS_NODE = typeof document === 'undefined';
1317
+
1318
+ const cancellablePromise = (promise) => {
1319
+ let isCanceled = false;
1320
+ const wrappedPromise = new Promise((resolve, reject) => {
1321
+ promise
1322
+ .then((value) => {
1323
+ if (isCanceled) {
1324
+ reject({ isCanceled, value });
1325
+ return;
1326
+ }
1327
+ resolve(value);
1328
+ })
1329
+ .catch((error) => {
1330
+ reject({ isCanceled, error });
1331
+ });
1332
+ });
1333
+ return {
1334
+ promise: wrappedPromise,
1335
+ cancel: () => {
1336
+ isCanceled = true;
1337
+ },
1338
+ };
1339
+ };
1340
+
1341
+ /* eslint-disable no-promise-executor-return */
1342
+ const delay = (n) => new Promise((resolve) => setTimeout(resolve, n));
1343
+
1344
+ const useCancellablePromises = () => {
1345
+ const pendingPromises = useRef([]);
1346
+ const appendPendingPromise = useCallback((promise) => {
1347
+ pendingPromises.current = [...pendingPromises.current, promise];
1348
+ }, []);
1349
+ const removePendingPromise = useCallback((promise) => {
1350
+ pendingPromises.current = pendingPromises.current.filter((p) => p !== promise);
1351
+ }, []);
1352
+ const clearPendingPromises = useCallback(() => pendingPromises.current.map((p) => p.cancel()), []);
1353
+ const api = useMemo(() => ({
1354
+ appendPendingPromise,
1355
+ removePendingPromise,
1356
+ clearPendingPromises,
1357
+ }), [appendPendingPromise, clearPendingPromises, removePendingPromise]);
1358
+ return api;
1359
+ };
1360
+
1361
+ const useClickPreventionOnDoubleClick = (onClick, onDoubleClick, doubleClickToFullscreen) => {
1362
+ const api = useCancellablePromises();
1363
+ const handleClick = useCallback(async (e) => {
1364
+ api.clearPendingPromises();
1365
+ const waitForClick = cancellablePromise(delay(200));
1366
+ api.appendPendingPromise(waitForClick);
1367
+ try {
1368
+ await waitForClick.promise;
1369
+ api.removePendingPromise(waitForClick);
1370
+ onClick(e);
1371
+ }
1372
+ catch (errorInfo) {
1373
+ const info = errorInfo;
1374
+ api.removePendingPromise(waitForClick);
1375
+ if (!info.isCanceled) {
1376
+ throw info.error;
1377
+ }
1378
+ }
1379
+ }, [api, onClick]);
1380
+ const handleDoubleClick = useCallback(() => {
1381
+ api.clearPendingPromises();
1382
+ onDoubleClick();
1383
+ }, [api, onDoubleClick]);
1384
+ const returnValue = useMemo(() => {
1385
+ if (!doubleClickToFullscreen) {
1386
+ return [onClick, onDoubleClick];
1387
+ }
1388
+ return [handleClick, handleDoubleClick];
1389
+ }, [
1390
+ doubleClickToFullscreen,
1391
+ handleClick,
1392
+ handleDoubleClick,
1393
+ onClick,
1394
+ onDoubleClick,
1395
+ ]);
1396
+ return returnValue;
1397
+ };
1398
+
1399
+ const reactVersion$1 = React.version.split('.')[0];
1400
+ if (reactVersion$1 === '0') {
1401
+ throw new Error(`Version ${reactVersion$1} of "react" is not supported by Remotion`);
1402
+ }
1403
+ const doesReactVersionSupportSuspense$1 = parseInt(reactVersion$1, 10) >= 18;
1404
+ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, doubleClickToFullscreen, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, renderPoster, className, moveToBeginningWhenEnded, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, inFrame, outFrame, initiallyShowControls, renderFullscreen: renderFullscreenButton, renderPlayPauseButton, alwaysShowControls, showPlaybackRateControl, }, ref) => {
1405
+ var _a, _b, _c;
1406
+ const config = Internals.useUnsafeVideoConfig();
1407
+ const video = Internals.useVideo();
1408
+ const container = useRef(null);
1409
+ const hovered = useHoverState(container);
1410
+ const canvasSize = useElementSize(container, {
1411
+ triggerOnWindowResize: false,
1412
+ shouldApplyCssTransforms: false,
1413
+ });
1414
+ const [hasPausedToResume, setHasPausedToResume] = useState(false);
1415
+ const [shouldAutoplay, setShouldAutoPlay] = useState(autoPlay);
1416
+ const [isFullscreen, setIsFullscreen] = useState(() => false);
1417
+ const [seeking, setSeeking] = useState(false);
1418
+ usePlayback({
1419
+ loop,
1420
+ playbackRate,
1421
+ moveToBeginningWhenEnded,
1422
+ inFrame,
1423
+ outFrame,
1424
+ });
1425
+ const player = usePlayer();
1426
+ useEffect(() => {
1427
+ if (hasPausedToResume && !player.playing) {
1428
+ setHasPausedToResume(false);
1429
+ player.play();
1430
+ }
1431
+ }, [hasPausedToResume, player]);
1432
+ useEffect(() => {
1433
+ const { current } = container;
1434
+ if (!current) {
1435
+ return;
1436
+ }
1437
+ const onFullscreenChange = () => {
1438
+ setIsFullscreen(document.fullscreenElement === current ||
1439
+ document.webkitFullscreenElement === current);
1440
+ };
1441
+ document.addEventListener('fullscreenchange', onFullscreenChange);
1442
+ document.addEventListener('webkitfullscreenchange', onFullscreenChange);
1443
+ return () => {
1444
+ document.removeEventListener('fullscreenchange', onFullscreenChange);
1445
+ document.removeEventListener('webkitfullscreenchange', onFullscreenChange);
1446
+ };
1447
+ }, []);
1448
+ const toggle = useCallback((e) => {
1449
+ if (player.isPlaying()) {
1450
+ player.pause();
1451
+ }
1452
+ else {
1453
+ player.play(e);
1454
+ }
1455
+ }, [player]);
1456
+ const requestFullscreen = useCallback(() => {
1457
+ if (!allowFullscreen) {
1458
+ throw new Error('allowFullscreen is false');
1459
+ }
1460
+ const supportsFullScreen = document.fullscreenEnabled || document.webkitFullscreenEnabled;
1461
+ if (!supportsFullScreen) {
1462
+ throw new Error('Browser doesnt support fullscreen');
1463
+ }
1464
+ if (!container.current) {
1465
+ throw new Error('No player ref found');
1466
+ }
1467
+ if (container.current.webkitRequestFullScreen) {
1468
+ container.current.webkitRequestFullScreen();
1469
+ }
1470
+ else {
1471
+ container.current.requestFullscreen();
1472
+ }
1473
+ }, [allowFullscreen]);
1474
+ const exitFullscreen = useCallback(() => {
1475
+ if (document.webkitExitFullscreen) {
1476
+ document.webkitExitFullscreen();
1477
+ }
1478
+ else {
1479
+ document.exitFullscreen();
1480
+ }
1481
+ }, []);
1482
+ useEffect(() => {
1483
+ const { current } = container;
1484
+ if (!current) {
1485
+ return;
1486
+ }
1487
+ const fullscreenChange = () => {
1488
+ var _a;
1489
+ const element = (_a = document.webkitFullscreenElement) !== null && _a !== void 0 ? _a : document.fullscreenElement;
1490
+ if (element && element === container.current) {
1491
+ player.emitter.dispatchFullscreenChange({
1492
+ isFullscreen: true,
1493
+ });
1494
+ }
1495
+ else {
1496
+ player.emitter.dispatchFullscreenChange({
1497
+ isFullscreen: false,
1498
+ });
1499
+ }
1500
+ };
1501
+ current.addEventListener('webkitfullscreenchange', fullscreenChange);
1502
+ current.addEventListener('fullscreenchange', fullscreenChange);
1503
+ return () => {
1504
+ current.removeEventListener('webkitfullscreenchange', fullscreenChange);
1505
+ current.removeEventListener('fullscreenchange', fullscreenChange);
1506
+ };
1507
+ }, [player.emitter]);
1508
+ const durationInFrames = (_a = config === null || config === void 0 ? void 0 : config.durationInFrames) !== null && _a !== void 0 ? _a : 1;
1509
+ const layout = useMemo(() => {
1510
+ if (!config || !canvasSize) {
1511
+ return null;
1512
+ }
1513
+ return calculateCanvasTransformation({
1514
+ canvasSize,
1515
+ compositionHeight: config.height,
1516
+ compositionWidth: config.width,
1517
+ previewSize: 'auto',
1518
+ });
1519
+ }, [canvasSize, config]);
1520
+ const scale = (_b = layout === null || layout === void 0 ? void 0 : layout.scale) !== null && _b !== void 0 ? _b : 1;
1521
+ const initialScaleIgnored = useRef(false);
1522
+ useEffect(() => {
1523
+ if (!initialScaleIgnored.current) {
1524
+ initialScaleIgnored.current = true;
1525
+ return;
1526
+ }
1527
+ player.emitter.dispatchScaleChange(scale);
1528
+ }, [player.emitter, scale]);
1529
+ const { setMediaVolume, setMediaMuted } = useContext(Internals.SetMediaVolumeContext);
1530
+ const { mediaMuted, mediaVolume } = useContext(Internals.MediaVolumeContext);
1531
+ useEffect(() => {
1532
+ player.emitter.dispatchVolumeChange(mediaVolume);
1533
+ }, [player.emitter, mediaVolume]);
1534
+ const isMuted = mediaMuted || mediaVolume === 0;
1535
+ useEffect(() => {
1536
+ player.emitter.dispatchMuteChange({
1537
+ isMuted,
1538
+ });
1539
+ }, [player.emitter, isMuted]);
1540
+ useImperativeHandle(ref, () => {
1541
+ const methods = {
1542
+ play: player.play,
1543
+ pause: player.pause,
1544
+ toggle,
1545
+ getContainerNode: () => container.current,
1546
+ getCurrentFrame: player.getCurrentFrame,
1547
+ isPlaying: () => player.playing,
1548
+ seekTo: (f) => {
1549
+ const lastFrame = durationInFrames - 1;
1550
+ const frameToSeekTo = Math.max(0, Math.min(lastFrame, f));
1551
+ if (player.isPlaying()) {
1552
+ const pauseToResume = frameToSeekTo !== lastFrame || loop;
1553
+ setHasPausedToResume(pauseToResume);
1554
+ player.pause();
1555
+ }
1556
+ if (frameToSeekTo === lastFrame && !loop) {
1557
+ player.emitter.dispatchEnded();
1558
+ }
1559
+ player.seek(frameToSeekTo);
1560
+ },
1561
+ isFullscreen: () => isFullscreen,
1562
+ requestFullscreen,
1563
+ exitFullscreen,
1564
+ getVolume: () => {
1565
+ if (mediaMuted) {
1566
+ return 0;
1567
+ }
1568
+ return mediaVolume;
1569
+ },
1570
+ setVolume: (vol) => {
1571
+ if (typeof vol !== 'number') {
1572
+ throw new TypeError(`setVolume() takes a number, got value of type ${typeof vol}`);
1573
+ }
1574
+ if (isNaN(vol)) {
1575
+ throw new TypeError(`setVolume() got a number that is NaN. Volume must be between 0 and 1.`);
1576
+ }
1577
+ if (vol < 0 || vol > 1) {
1578
+ throw new TypeError(`setVolume() got a number that is out of range. Must be between 0 and 1, got ${typeof vol}`);
1579
+ }
1580
+ setMediaVolume(vol);
1581
+ },
1582
+ isMuted: () => isMuted,
1583
+ mute: () => {
1584
+ setMediaMuted(true);
1585
+ },
1586
+ unmute: () => {
1587
+ setMediaMuted(false);
1588
+ },
1589
+ getScale: () => scale,
1590
+ };
1591
+ return Object.assign(player.emitter, methods);
1592
+ }, [
1593
+ durationInFrames,
1594
+ exitFullscreen,
1595
+ isFullscreen,
1596
+ loop,
1597
+ mediaMuted,
1598
+ isMuted,
1599
+ mediaVolume,
1600
+ player,
1601
+ requestFullscreen,
1602
+ setMediaMuted,
1603
+ setMediaVolume,
1604
+ toggle,
1605
+ scale,
1606
+ ]);
1607
+ const VideoComponent = video ? video.component : null;
1608
+ const outerStyle = useMemo(() => {
1609
+ return calculateOuterStyle({ canvasSize, config, style });
1610
+ }, [canvasSize, config, style]);
1611
+ const outer = useMemo(() => {
1612
+ return calculateOuter({ config, layout, scale });
1613
+ }, [config, layout, scale]);
1614
+ const containerStyle = useMemo(() => {
1615
+ return calculateContainerStyle({ canvasSize, config, layout, scale });
1616
+ }, [canvasSize, config, layout, scale]);
1617
+ const onError = useCallback((error) => {
1618
+ player.pause();
1619
+ // Pay attention to `this context`
1620
+ player.emitter.dispatchError(error);
1621
+ }, [player]);
1622
+ const onFullscreenButtonClick = useCallback((e) => {
1623
+ e.stopPropagation();
1624
+ requestFullscreen();
1625
+ }, [requestFullscreen]);
1626
+ const onExitFullscreenButtonClick = useCallback((e) => {
1627
+ e.stopPropagation();
1628
+ exitFullscreen();
1629
+ }, [exitFullscreen]);
1630
+ const onSingleClick = useCallback((e) => {
1631
+ toggle(e);
1632
+ }, [toggle]);
1633
+ const onSeekStart = useCallback(() => {
1634
+ setSeeking(true);
1635
+ }, []);
1636
+ const onSeekEnd = useCallback(() => {
1637
+ setSeeking(false);
1638
+ }, []);
1639
+ const onDoubleClick = useCallback(() => {
1640
+ if (isFullscreen) {
1641
+ exitFullscreen();
1642
+ }
1643
+ else {
1644
+ requestFullscreen();
1645
+ }
1646
+ }, [exitFullscreen, isFullscreen, requestFullscreen]);
1647
+ const [handleClick, handleDoubleClick] = useClickPreventionOnDoubleClick(onSingleClick, onDoubleClick, doubleClickToFullscreen);
1648
+ useEffect(() => {
1649
+ if (shouldAutoplay) {
1650
+ player.play();
1651
+ setShouldAutoPlay(false);
1652
+ }
1653
+ }, [shouldAutoplay, player]);
1654
+ const loadingMarkup = useMemo(() => {
1655
+ return renderLoading
1656
+ ? renderLoading({
1657
+ height: outerStyle.height,
1658
+ width: outerStyle.width,
1659
+ })
1660
+ : null;
1661
+ }, [outerStyle.height, outerStyle.width, renderLoading]);
1662
+ if (!config) {
1663
+ return null;
1664
+ }
1665
+ const poster = renderPoster
1666
+ ? renderPoster({
1667
+ height: outerStyle.height,
1668
+ width: outerStyle.width,
1669
+ })
1670
+ : null;
1671
+ if (poster === undefined) {
1672
+ throw new TypeError('renderPoster() must return a React element, but undefined was returned');
1673
+ }
1674
+ const shouldShowPoster = poster &&
1675
+ [
1676
+ showPosterWhenPaused && !player.isPlaying() && !seeking,
1677
+ showPosterWhenEnded && player.isLastFrame && !player.isPlaying(),
1678
+ showPosterWhenUnplayed && !player.hasPlayed && !player.isPlaying(),
1679
+ ].some(Boolean);
1680
+ const content = (jsxs(Fragment, { children: [jsx("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: jsx("div", { style: containerStyle, className: PLAYER_CSS_CLASSNAME, children: VideoComponent ? (jsx(ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: jsx(VideoComponent, { ...((_c = video === null || video === void 0 ? void 0 : video.defaultProps) !== null && _c !== void 0 ? _c : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) })) : null }) }), shouldShowPoster ? (jsx("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null, controls ? (jsx(Controls, { fps: config.fps, durationInFrames: config.durationInFrames, hovered: hovered, player: player, onFullscreenButtonClick: onFullscreenButtonClick, isFullscreen: isFullscreen, allowFullscreen: allowFullscreen, showVolumeControls: showVolumeControls, onExitFullscreenButtonClick: onExitFullscreenButtonClick, spaceKeyToPlayOrPause: spaceKeyToPlayOrPause, onSeekEnd: onSeekEnd, onSeekStart: onSeekStart, inFrame: inFrame, outFrame: outFrame, initiallyShowControls: initiallyShowControls, canvasSize: canvasSize, renderFullscreenButton: renderFullscreenButton, renderPlayPauseButton: renderPlayPauseButton, alwaysShowControls: alwaysShowControls, showPlaybackRateControl: showPlaybackRateControl })) : null] }));
1681
+ if (IS_NODE && !doesReactVersionSupportSuspense$1) {
1682
+ return (jsx("div", { ref: container, style: outerStyle, className: className, children: content }));
1683
+ }
1684
+ return (jsx("div", { ref: container, style: outerStyle, className: className, children: jsx(Suspense, { fallback: loadingMarkup, children: content }) }));
1685
+ };
1686
+ var PlayerUI$1 = forwardRef(PlayerUI);
1687
+
1688
+ const VOLUME_PERSISTANCE_KEY = 'remotion.volumePreference';
1689
+ const persistVolume = (volume) => {
1690
+ if (typeof window === 'undefined') {
1691
+ return;
1692
+ }
1693
+ window.localStorage.setItem(VOLUME_PERSISTANCE_KEY, String(volume));
1694
+ };
1695
+ const getPreferredVolume = () => {
1696
+ if (typeof window === 'undefined') {
1697
+ return 1;
1698
+ }
1699
+ const val = window.localStorage.getItem(VOLUME_PERSISTANCE_KEY);
1700
+ return val ? Number(val) : 1;
1701
+ };
1702
+
1703
+ const SharedPlayerContexts = ({ children, timelineContext, inputProps, fps, compositionHeight, compositionWidth, durationInFrames, component, numberOfSharedAudioTags, initiallyMuted, }) => {
1704
+ const compositionManagerContext = useMemo(() => {
1705
+ return {
1706
+ compositions: [
1707
+ {
1708
+ component: component,
1709
+ durationInFrames,
1710
+ height: compositionHeight,
1711
+ width: compositionWidth,
1712
+ fps,
1713
+ id: 'player-comp',
1714
+ props: inputProps,
1715
+ nonce: 777,
1716
+ scale: 1,
1717
+ folderName: null,
1718
+ defaultProps: undefined,
1719
+ parentFolderName: null,
1720
+ schema: null,
1721
+ calculateMetadata: null,
1722
+ },
1723
+ ],
1724
+ folders: [],
1725
+ registerFolder: () => undefined,
1726
+ unregisterFolder: () => undefined,
1727
+ currentComposition: 'player-comp',
1728
+ registerComposition: () => undefined,
1729
+ registerSequence: () => undefined,
1730
+ sequences: [],
1731
+ setCurrentComposition: () => undefined,
1732
+ unregisterComposition: () => undefined,
1733
+ unregisterSequence: () => undefined,
1734
+ registerAsset: () => undefined,
1735
+ unregisterAsset: () => undefined,
1736
+ currentCompositionMetadata: null,
1737
+ setCurrentCompositionMetadata: () => undefined,
1738
+ assets: [],
1739
+ setClipRegion: () => undefined,
1740
+ resolved: null,
1741
+ };
1742
+ }, [
1743
+ component,
1744
+ durationInFrames,
1745
+ compositionHeight,
1746
+ compositionWidth,
1747
+ fps,
1748
+ inputProps,
1749
+ ]);
1750
+ const [mediaMuted, setMediaMuted] = useState(() => initiallyMuted);
1751
+ const [mediaVolume, setMediaVolume] = useState(() => getPreferredVolume());
1752
+ const mediaVolumeContextValue = useMemo(() => {
1753
+ return {
1754
+ mediaMuted,
1755
+ mediaVolume,
1756
+ };
1757
+ }, [mediaMuted, mediaVolume]);
1758
+ const setMediaVolumeAndPersist = useCallback((vol) => {
1759
+ setMediaVolume(vol);
1760
+ persistVolume(vol);
1761
+ }, []);
1762
+ const setMediaVolumeContextValue = useMemo(() => {
1763
+ return {
1764
+ setMediaMuted,
1765
+ setMediaVolume: setMediaVolumeAndPersist,
1766
+ };
1767
+ }, [setMediaVolumeAndPersist]);
1768
+ return (jsx(Internals.CanUseRemotionHooksProvider, { children: jsx(Internals.Timeline.TimelineContext.Provider, { value: timelineContext, children: jsx(Internals.CompositionManager.Provider, { value: compositionManagerContext, children: jsx(Internals.ResolveCompositionConfig, { children: jsx(Internals.PrefetchProvider, { children: jsx(Internals.DurationsContextProvider, { children: jsx(Internals.MediaVolumeContext.Provider, { value: mediaVolumeContextValue, children: jsx(Internals.SetMediaVolumeContext.Provider, { value: setMediaVolumeContextValue, children: jsx(Internals.SharedAudioContextProvider, { numberOfAudioTags: numberOfSharedAudioTags, component: component, children: children }) }) }) }) }) }) }) }) }));
1769
+ };
1770
+
1771
+ const validateSingleFrame = (frame, variableName) => {
1772
+ if (typeof frame === 'undefined' || frame === null) {
1773
+ return frame !== null && frame !== void 0 ? frame : null;
1774
+ }
1775
+ if (typeof frame !== 'number') {
1776
+ throw new TypeError(`"${variableName}" must be a number, but is ${JSON.stringify(frame)}`);
1777
+ }
1778
+ if (Number.isNaN(frame)) {
1779
+ throw new TypeError(`"${variableName}" must not be NaN, but is ${JSON.stringify(frame)}`);
1780
+ }
1781
+ if (!Number.isFinite(frame)) {
1782
+ throw new TypeError(`"${variableName}" must be finite, but is ${JSON.stringify(frame)}`);
1783
+ }
1784
+ if (frame % 1 !== 0) {
1785
+ throw new TypeError(`"${variableName}" must be an integer, but is ${JSON.stringify(frame)}`);
1786
+ }
1787
+ return frame;
1788
+ };
1789
+ const validateInOutFrames = ({ inFrame, durationInFrames, outFrame, }) => {
1790
+ const validatedInFrame = validateSingleFrame(inFrame, 'inFrame');
1791
+ const validatedOutFrame = validateSingleFrame(outFrame, 'outFrame');
1792
+ if (validatedInFrame === null && validatedOutFrame === null) {
1793
+ return;
1794
+ }
1795
+ // Must not be over the duration
1796
+ if (validatedInFrame !== null && validatedInFrame > durationInFrames - 1) {
1797
+ throw new Error('inFrame must be less than (durationInFrames - 1), but is ' +
1798
+ validatedInFrame);
1799
+ }
1800
+ if (validatedOutFrame !== null && validatedOutFrame > durationInFrames - 1) {
1801
+ throw new Error('outFrame must be less than (durationInFrames - 1), but is ' +
1802
+ validatedOutFrame);
1803
+ }
1804
+ // Must not be under 0
1805
+ if (validatedInFrame !== null && validatedInFrame < 0) {
1806
+ throw new Error('inFrame must be greater than 0, but is ' + validatedInFrame);
1807
+ }
1808
+ if (validatedOutFrame !== null && validatedOutFrame <= 0) {
1809
+ throw new Error(`outFrame must be greater than 0, but is ${validatedOutFrame}. If you want to render a single frame, use <Thumbnail /> instead.`);
1810
+ }
1811
+ if (validatedOutFrame !== null &&
1812
+ validatedInFrame !== null &&
1813
+ validatedOutFrame <= validatedInFrame) {
1814
+ throw new Error('outFrame must be greater than inFrame, but is ' +
1815
+ validatedOutFrame +
1816
+ ' <= ' +
1817
+ validatedInFrame);
1818
+ }
1819
+ };
1820
+
1821
+ const validateInitialFrame = ({ initialFrame, durationInFrames, }) => {
1822
+ if (typeof durationInFrames !== 'number') {
1823
+ throw new Error(`\`durationInFrames\` must be a number, but is ${JSON.stringify(durationInFrames)}`);
1824
+ }
1825
+ if (typeof initialFrame === 'undefined') {
1826
+ return;
1827
+ }
1828
+ if (typeof initialFrame !== 'number') {
1829
+ throw new Error(`\`initialFrame\` must be a number, but is ${JSON.stringify(initialFrame)}`);
1830
+ }
1831
+ if (Number.isNaN(initialFrame)) {
1832
+ throw new Error(`\`initialFrame\` must be a number, but is NaN`);
1833
+ }
1834
+ if (!Number.isFinite(initialFrame)) {
1835
+ throw new Error(`\`initialFrame\` must be a number, but is Infinity`);
1836
+ }
1837
+ if (initialFrame % 1 !== 0) {
1838
+ throw new Error(`\`initialFrame\` must be an integer, but is ${JSON.stringify(initialFrame)}`);
1839
+ }
1840
+ if (initialFrame > durationInFrames - 1) {
1841
+ throw new Error(`\`initialFrame\` must be less or equal than \`durationInFrames - 1\`, but is ${JSON.stringify(initialFrame)}`);
1842
+ }
1843
+ };
1844
+
1845
+ const validatePlaybackRate = (playbackRate) => {
1846
+ if (playbackRate === undefined) {
1847
+ return;
1848
+ }
1849
+ if (playbackRate > 4) {
1850
+ throw new Error(`The highest possible playback rate is 4. You passed: ${playbackRate}`);
1851
+ }
1852
+ if (playbackRate < -4) {
1853
+ throw new Error(`The lowest possible playback rate is -4. You passed: ${playbackRate}`);
1854
+ }
1855
+ if (playbackRate === 0) {
1856
+ throw new Error(`A playback rate of 0 is not supported.`);
1857
+ }
1858
+ };
1859
+
1860
+ const componentOrNullIfLazy = (props) => {
1861
+ if ('component' in props) {
1862
+ return props.component;
1863
+ }
1864
+ return null;
1865
+ };
1866
+ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls = false, loop = false, autoPlay = false, showVolumeControls = true, allowFullscreen = true, clickToPlay, doubleClickToFullscreen = false, spaceKeyToPlayOrPause = true, moveToBeginningWhenEnded = true, numberOfSharedAudioTags = 5, errorFallback = () => '⚠️', playbackRate = 1, renderLoading, className, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, initialFrame, renderPoster, inFrame, outFrame, initiallyShowControls, renderFullscreenButton, renderPlayPauseButton, alwaysShowControls = false, initiallyMuted = false, showPlaybackRateControl = false, ...componentProps }, ref) => {
1867
+ if (typeof window !== 'undefined') {
1868
+ // eslint-disable-next-line react-hooks/rules-of-hooks
1869
+ useLayoutEffect(() => {
1870
+ window.remotion_isPlayer = true;
1871
+ }, []);
1872
+ }
1873
+ // @ts-expect-error
1874
+ if (componentProps.defaultProps !== undefined) {
1875
+ throw new Error('The <Player /> component does not accept `defaultProps`, but some were passed. Use `inputProps` instead.');
1876
+ }
1877
+ const componentForValidation = componentOrNullIfLazy(componentProps);
1878
+ // @ts-expect-error
1879
+ if ((componentForValidation === null || componentForValidation === void 0 ? void 0 : componentForValidation.type) === Composition) {
1880
+ throw new TypeError(`'component' should not be an instance of <Composition/>. Pass the React component directly, and set the duration, fps and dimensions as separate props. See https://www.remotion.dev/docs/player/examples for an example.`);
1881
+ }
1882
+ if (componentForValidation === Composition) {
1883
+ throw new TypeError(`'component' must not be the 'Composition' component. Pass your own React component directly, and set the duration, fps and dimensions as separate props. See https://www.remotion.dev/docs/player/examples for an example.`);
1884
+ }
1885
+ const component = Internals.useLazyComponent(componentProps);
1886
+ validateInitialFrame({ initialFrame, durationInFrames });
1887
+ const [frame, setFrame] = useState(() => initialFrame !== null && initialFrame !== void 0 ? initialFrame : 0);
1888
+ const [playing, setPlaying] = useState(false);
1889
+ const [rootId] = useState('player-comp');
1890
+ const [emitter] = useState(() => new PlayerEmitter());
1891
+ const rootRef = useRef(null);
1892
+ const audioAndVideoTags = useRef([]);
1893
+ const imperativePlaying = useRef(false);
1894
+ const [currentPlaybackRate, setCurrentPlaybackRate] = useState(playbackRate);
1895
+ if (typeof compositionHeight !== 'number') {
1896
+ throw new TypeError(`'compositionHeight' must be a number but got '${typeof compositionHeight}' instead`);
1897
+ }
1898
+ if (typeof compositionWidth !== 'number') {
1899
+ throw new TypeError(`'compositionWidth' must be a number but got '${typeof compositionWidth}' instead`);
1900
+ }
1901
+ Internals.validateDimension(compositionHeight, 'compositionHeight', 'of the <Player /> component');
1902
+ Internals.validateDimension(compositionWidth, 'compositionWidth', 'of the <Player /> component');
1903
+ Internals.validateDurationInFrames({
1904
+ durationInFrames,
1905
+ component: 'of the <Player/> component',
1906
+ allowFloats: false,
1907
+ });
1908
+ Internals.validateFps(fps, 'as a prop of the <Player/> component', false);
1909
+ Internals.validateDefaultAndInputProps(inputProps, 'inputProps', null);
1910
+ validateInOutFrames({
1911
+ durationInFrames,
1912
+ inFrame,
1913
+ outFrame,
1914
+ });
1915
+ if (typeof controls !== 'boolean' && typeof controls !== 'undefined') {
1916
+ throw new TypeError(`'controls' must be a boolean or undefined but got '${typeof controls}' instead`);
1917
+ }
1918
+ if (typeof autoPlay !== 'boolean' && typeof autoPlay !== 'undefined') {
1919
+ throw new TypeError(`'autoPlay' must be a boolean or undefined but got '${typeof autoPlay}' instead`);
1920
+ }
1921
+ if (typeof loop !== 'boolean' && typeof loop !== 'undefined') {
1922
+ throw new TypeError(`'loop' must be a boolean or undefined but got '${typeof loop}' instead`);
1923
+ }
1924
+ if (typeof doubleClickToFullscreen !== 'boolean' &&
1925
+ typeof doubleClickToFullscreen !== 'undefined') {
1926
+ throw new TypeError(`'doubleClickToFullscreen' must be a boolean or undefined but got '${typeof doubleClickToFullscreen}' instead`);
1927
+ }
1928
+ if (typeof showVolumeControls !== 'boolean' &&
1929
+ typeof showVolumeControls !== 'undefined') {
1930
+ throw new TypeError(`'showVolumeControls' must be a boolean or undefined but got '${typeof showVolumeControls}' instead`);
1931
+ }
1932
+ if (typeof allowFullscreen !== 'boolean' &&
1933
+ typeof allowFullscreen !== 'undefined') {
1934
+ throw new TypeError(`'allowFullscreen' must be a boolean or undefined but got '${typeof allowFullscreen}' instead`);
1935
+ }
1936
+ if (typeof clickToPlay !== 'boolean' && typeof clickToPlay !== 'undefined') {
1937
+ throw new TypeError(`'clickToPlay' must be a boolean or undefined but got '${typeof clickToPlay}' instead`);
1938
+ }
1939
+ if (typeof spaceKeyToPlayOrPause !== 'boolean' &&
1940
+ typeof spaceKeyToPlayOrPause !== 'undefined') {
1941
+ throw new TypeError(`'spaceKeyToPlayOrPause' must be a boolean or undefined but got '${typeof spaceKeyToPlayOrPause}' instead`);
1942
+ }
1943
+ if (typeof numberOfSharedAudioTags !== 'number' ||
1944
+ numberOfSharedAudioTags % 1 !== 0 ||
1945
+ !Number.isFinite(numberOfSharedAudioTags) ||
1946
+ Number.isNaN(numberOfSharedAudioTags) ||
1947
+ numberOfSharedAudioTags < 0) {
1948
+ throw new TypeError(`'numberOfSharedAudioTags' must be an integer but got '${numberOfSharedAudioTags}' instead`);
1949
+ }
1950
+ validatePlaybackRate(currentPlaybackRate);
1951
+ useEffect(() => {
1952
+ emitter.dispatchRateChange(currentPlaybackRate);
1953
+ }, [emitter, currentPlaybackRate]);
1954
+ useEffect(() => {
1955
+ setCurrentPlaybackRate(playbackRate);
1956
+ }, [playbackRate]);
1957
+ useImperativeHandle(ref, () => rootRef.current, []);
1958
+ const timelineContextValue = useMemo(() => {
1959
+ return {
1960
+ frame,
1961
+ playing,
1962
+ rootId,
1963
+ shouldRegisterSequences: false,
1964
+ playbackRate: currentPlaybackRate,
1965
+ imperativePlaying,
1966
+ setPlaybackRate: (rate) => {
1967
+ setCurrentPlaybackRate(rate);
1968
+ },
1969
+ audioAndVideoTags,
1970
+ };
1971
+ }, [frame, currentPlaybackRate, playing, rootId]);
1972
+ const setTimelineContextValue = useMemo(() => {
1973
+ return {
1974
+ setFrame,
1975
+ setPlaying,
1976
+ };
1977
+ }, [setFrame]);
1978
+ const passedInputProps = useMemo(() => {
1979
+ return inputProps !== null && inputProps !== void 0 ? inputProps : {};
1980
+ }, [inputProps]);
1981
+ if (typeof window !== 'undefined') {
1982
+ // eslint-disable-next-line react-hooks/rules-of-hooks
1983
+ useLayoutEffect(() => {
1984
+ // Inject CSS only on client, and also only after the Player has hydrated
1985
+ Internals.CSSUtils.injectCSS(Internals.CSSUtils.makeDefaultCSS(`.${PLAYER_CSS_CLASSNAME}`, '#fff'));
1986
+ }, []);
1987
+ }
1988
+ const actualInputProps = useMemo(() => inputProps !== null && inputProps !== void 0 ? inputProps : {}, [inputProps]);
1989
+ return (jsx(Internals.IsPlayerContextProvider, { children: jsx(SharedPlayerContexts, { timelineContext: timelineContextValue, component: component, compositionHeight: compositionHeight, compositionWidth: compositionWidth, durationInFrames: durationInFrames, fps: fps, inputProps: actualInputProps, numberOfSharedAudioTags: numberOfSharedAudioTags, initiallyMuted: initiallyMuted, children: jsx(Internals.Timeline.SetTimelineContext.Provider, { value: setTimelineContextValue, children: jsx(PlayerEventEmitterContext.Provider, { value: emitter, children: jsx(PlayerUI$1, { ref: rootRef, renderLoading: renderLoading, autoPlay: Boolean(autoPlay), loop: Boolean(loop), controls: Boolean(controls), errorFallback: errorFallback, style: style, inputProps: passedInputProps, allowFullscreen: Boolean(allowFullscreen), moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded), clickToPlay: typeof clickToPlay === 'boolean'
1990
+ ? clickToPlay
1991
+ : Boolean(controls), showVolumeControls: Boolean(showVolumeControls), doubleClickToFullscreen: Boolean(doubleClickToFullscreen), spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause), playbackRate: currentPlaybackRate, className: className !== null && className !== void 0 ? className : undefined, showPosterWhenUnplayed: Boolean(showPosterWhenUnplayed), showPosterWhenEnded: Boolean(showPosterWhenEnded), showPosterWhenPaused: Boolean(showPosterWhenPaused), renderPoster: renderPoster, inFrame: inFrame !== null && inFrame !== void 0 ? inFrame : null, outFrame: outFrame !== null && outFrame !== void 0 ? outFrame : null, initiallyShowControls: initiallyShowControls !== null && initiallyShowControls !== void 0 ? initiallyShowControls : true, renderFullscreen: renderFullscreenButton !== null && renderFullscreenButton !== void 0 ? renderFullscreenButton : null, renderPlayPauseButton: renderPlayPauseButton !== null && renderPlayPauseButton !== void 0 ? renderPlayPauseButton : null, alwaysShowControls: alwaysShowControls, showPlaybackRateControl: showPlaybackRateControl }) }) }) }) }));
1992
+ };
1993
+ const forward$1 = forwardRef;
1994
+ /**
1995
+ * @description A component which can be rendered in a regular React App (for example: Create React App, Next.js) to display a Remotion video.
1996
+ * @see [Documentation](https://www.remotion.dev/docs/player/player)
1997
+ */
1998
+ const Player = forward$1(PlayerFn);
1999
+
2000
+ const useThumbnail = () => {
2001
+ const emitter = useContext(ThumbnailEmitterContext);
2002
+ if (!emitter) {
2003
+ throw new TypeError('Expected Player event emitter context');
2004
+ }
2005
+ const returnValue = useMemo(() => {
2006
+ return {
2007
+ emitter,
2008
+ };
2009
+ }, [emitter]);
2010
+ return returnValue;
2011
+ };
2012
+
2013
+ const reactVersion = React.version.split('.')[0];
2014
+ if (reactVersion === '0') {
2015
+ throw new Error(`Version ${reactVersion} of "react" is not supported by Remotion`);
2016
+ }
2017
+ const doesReactVersionSupportSuspense = parseInt(reactVersion, 10) >= 18;
2018
+ const ThumbnailUI = ({ style, inputProps, errorFallback, renderLoading, className }, ref) => {
2019
+ var _a, _b;
2020
+ const config = Internals.useUnsafeVideoConfig();
2021
+ const video = Internals.useVideo();
2022
+ const container = useRef(null);
2023
+ const canvasSize = useElementSize(container, {
2024
+ triggerOnWindowResize: false,
2025
+ shouldApplyCssTransforms: false,
2026
+ });
2027
+ const layout = useMemo(() => {
2028
+ if (!config || !canvasSize) {
2029
+ return null;
2030
+ }
2031
+ return calculateCanvasTransformation({
2032
+ canvasSize,
2033
+ compositionHeight: config.height,
2034
+ compositionWidth: config.width,
2035
+ previewSize: 'auto',
2036
+ });
2037
+ }, [canvasSize, config]);
2038
+ const scale = (_a = layout === null || layout === void 0 ? void 0 : layout.scale) !== null && _a !== void 0 ? _a : 1;
2039
+ const thumbnail = useThumbnail();
2040
+ useImperativeHandle(ref, () => {
2041
+ const methods = {
2042
+ getContainerNode: () => container.current,
2043
+ getScale: () => scale,
2044
+ };
2045
+ return Object.assign(thumbnail.emitter, methods);
2046
+ }, [scale, thumbnail.emitter]);
2047
+ const VideoComponent = video ? video.component : null;
2048
+ const outerStyle = useMemo(() => {
2049
+ return calculateOuterStyle({ config, style, canvasSize });
2050
+ }, [canvasSize, config, style]);
2051
+ const outer = useMemo(() => {
2052
+ return calculateOuter({ config, layout, scale });
2053
+ }, [config, layout, scale]);
2054
+ const containerStyle = useMemo(() => {
2055
+ return calculateContainerStyle({
2056
+ canvasSize,
2057
+ config,
2058
+ layout,
2059
+ scale,
2060
+ });
2061
+ }, [canvasSize, config, layout, scale]);
2062
+ const onError = useCallback((error) => {
2063
+ // Pay attention to `this context`
2064
+ thumbnail.emitter.dispatchError(error);
2065
+ }, [thumbnail.emitter]);
2066
+ const rootRef = useRef(null);
2067
+ useImperativeHandle(ref, () => rootRef.current, []);
2068
+ const loadingMarkup = useMemo(() => {
2069
+ return renderLoading
2070
+ ? renderLoading({
2071
+ height: outerStyle.height,
2072
+ width: outerStyle.width,
2073
+ })
2074
+ : null;
2075
+ }, [outerStyle.height, outerStyle.width, renderLoading]);
2076
+ if (!config) {
2077
+ return null;
2078
+ }
2079
+ const content = (jsx("div", { style: outer, children: jsx("div", { style: containerStyle, className: PLAYER_CSS_CLASSNAME, children: VideoComponent ? (jsx(ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: jsx(VideoComponent, { ...((_b = video === null || video === void 0 ? void 0 : video.defaultProps) !== null && _b !== void 0 ? _b : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) })) : null }) }));
2080
+ if (IS_NODE && !doesReactVersionSupportSuspense) {
2081
+ return (jsx("div", { ref: container, style: outerStyle, className: className, children: content }));
2082
+ }
2083
+ return (jsx("div", { ref: container, style: outerStyle, className: className, children: jsx(Suspense, { fallback: loadingMarkup, children: content }) }));
2084
+ };
2085
+ var ThumbnailUI$1 = forwardRef(ThumbnailUI);
2086
+
2087
+ const ThumbnailFn = ({ frameToDisplay, style, inputProps, compositionHeight, compositionWidth, durationInFrames, fps, className, errorFallback = () => '⚠️', renderLoading, ...componentProps }, ref) => {
2088
+ const [thumbnailId] = useState(() => String(random(null)));
2089
+ const rootRef = useRef(null);
2090
+ const timelineState = useMemo(() => {
2091
+ return {
2092
+ playing: false,
2093
+ frame: frameToDisplay,
2094
+ rootId: thumbnailId,
2095
+ imperativePlaying: {
2096
+ current: false,
2097
+ },
2098
+ playbackRate: 1,
2099
+ setPlaybackRate: () => {
2100
+ throw new Error('thumbnail');
2101
+ },
2102
+ audioAndVideoTags: { current: [] },
2103
+ };
2104
+ }, [frameToDisplay, thumbnailId]);
2105
+ useImperativeHandle(ref, () => rootRef.current, []);
2106
+ const Component = Internals.useLazyComponent(componentProps);
2107
+ const [emitter] = useState(() => new ThumbnailEmitter());
2108
+ const passedInputProps = useMemo(() => {
2109
+ return inputProps !== null && inputProps !== void 0 ? inputProps : {};
2110
+ }, [inputProps]);
2111
+ return (jsx(Internals.IsPlayerContextProvider, { children: jsx(SharedPlayerContexts, { timelineContext: timelineState, component: Component, compositionHeight: compositionHeight, compositionWidth: compositionWidth, durationInFrames: durationInFrames, fps: fps, inputProps: passedInputProps, numberOfSharedAudioTags: 0, initiallyMuted: true, children: jsx(ThumbnailEmitterContext.Provider, { value: emitter, children: jsx(ThumbnailUI$1, { className: className, errorFallback: errorFallback, inputProps: passedInputProps, renderLoading: renderLoading, style: style }) }) }) }));
2112
+ };
2113
+ const forward = forwardRef;
2114
+ /**
2115
+ * @description A component which can be rendered in a regular React App (for example: Create React App, Next.js) to display a single frame of a video.
2116
+ * @see [Documentation](https://www.remotion.dev/docs/player/thumbnail)
2117
+ */
2118
+ const Thumbnail = forward(ThumbnailFn);
2119
+
2120
+ const PlayerInternals = {
2121
+ PlayerEventEmitterContext,
2122
+ PlayerEmitter,
2123
+ usePlayer,
2124
+ usePlayback,
2125
+ useElementSize,
2126
+ calculateCanvasTransformation,
2127
+ useHoverState,
2128
+ updateAllElementsSizes,
2129
+ calculateScale,
2130
+ };
2131
+
2132
+ export { Player, PlayerInternals, Thumbnail };