@remotion/player 4.0.110 → 4.0.112

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 (46) hide show
  1. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/Player.d.ts +2 -0
  2. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/Player.js +4 -9
  3. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/PlayerControls.d.ts +3 -1
  4. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/PlayerControls.js +8 -5
  5. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/PlayerUI.d.ts +3 -0
  6. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/PlayerUI.js +51 -4
  7. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/SharedPlayerContext.js +1 -1
  8. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/ThumbnailUI.js +8 -1
  9. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/calculate-scale.d.ts +1 -8
  10. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/calculate-scale.js +2 -7
  11. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/event-emitter.d.ts +6 -0
  12. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/event-emitter.js +8 -0
  13. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/icons.js +1 -1
  14. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/index.d.ts +8 -7
  15. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/index.js +5 -2
  16. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/use-playback.js +35 -3
  17. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/use-player.d.ts +1 -0
  18. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/esm/use-player.js +8 -1
  19. package/.rollup.cache/Users/jonathanburger/remotion/packages/player/dist/tsconfig-esm.tsbuildinfo +1 -1
  20. package/dist/cjs/Player.d.ts +2 -0
  21. package/dist/cjs/Player.js +4 -9
  22. package/dist/cjs/PlayerControls.d.ts +3 -1
  23. package/dist/cjs/PlayerControls.js +7 -4
  24. package/dist/cjs/PlayerUI.d.ts +3 -0
  25. package/dist/cjs/PlayerUI.js +51 -4
  26. package/dist/cjs/SharedPlayerContext.js +1 -1
  27. package/dist/cjs/ThumbnailUI.js +8 -1
  28. package/dist/cjs/calculate-scale.d.ts +1 -8
  29. package/dist/cjs/calculate-scale.js +3 -9
  30. package/dist/cjs/event-emitter.d.ts +6 -0
  31. package/dist/cjs/event-emitter.js +8 -0
  32. package/dist/cjs/icons.js +1 -1
  33. package/dist/cjs/index.d.ts +8 -8
  34. package/dist/cjs/index.js +4 -1
  35. package/dist/cjs/use-playback.js +34 -2
  36. package/dist/cjs/use-player.d.ts +1 -0
  37. package/dist/cjs/use-player.js +8 -1
  38. package/dist/esm/Player.d.ts +2 -0
  39. package/dist/esm/PlayerControls.d.ts +3 -1
  40. package/dist/esm/PlayerUI.d.ts +3 -0
  41. package/dist/esm/calculate-scale.d.ts +1 -8
  42. package/dist/esm/event-emitter.d.ts +6 -0
  43. package/dist/esm/index.d.ts +8 -7
  44. package/dist/esm/index.mjs +237 -69
  45. package/dist/esm/use-player.d.ts +1 -0
  46. package/package.json +2 -2
@@ -34,6 +34,7 @@ export type PlayerProps<Schema extends AnyZodObject, Props> = {
34
34
  showPosterWhenPaused?: boolean;
35
35
  showPosterWhenEnded?: boolean;
36
36
  showPosterWhenUnplayed?: boolean;
37
+ showPosterWhenBuffering?: boolean;
37
38
  inFrame?: number | null;
38
39
  outFrame?: number | null;
39
40
  initiallyShowControls?: number | boolean;
@@ -44,6 +45,7 @@ export type PlayerProps<Schema extends AnyZodObject, Props> = {
44
45
  initiallyMuted?: boolean;
45
46
  showPlaybackRateControl?: boolean | number[];
46
47
  posterFillMode?: PosterFillMode;
48
+ bufferStateDelayInMilliseconds?: number;
47
49
  } & CompProps<Props> & PropsIfHasProps<Schema, Props>;
48
50
  export declare const componentOrNullIfLazy: <Props>(props: CompProps<Props>) => ComponentType<Props> | null;
49
51
  /**
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { Composition, Internals } from 'remotion';
4
- import { PlayerEventEmitterContext } from './emitter-context.js';
5
- import { PlayerEmitter } from './event-emitter.js';
4
+ import { PlayerEmitterProvider } from './EmitterProvider.js';
6
5
  import { PLAYER_CSS_CLASSNAME } from './player-css-classname.js';
7
6
  import PlayerUI from './PlayerUI.js';
8
7
  import { PLAYER_COMP_ID, SharedPlayerContexts } from './SharedPlayerContext.js';
@@ -16,7 +15,7 @@ export const componentOrNullIfLazy = (props) => {
16
15
  }
17
16
  return null;
18
17
  };
19
- 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, posterFillMode = 'player-size', ...componentProps }, ref) => {
18
+ 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, showPosterWhenBuffering, initialFrame, renderPoster, inFrame, outFrame, initiallyShowControls, renderFullscreenButton, renderPlayPauseButton, alwaysShowControls = false, initiallyMuted = false, showPlaybackRateControl = false, posterFillMode = 'player-size', bufferStateDelayInMilliseconds, ...componentProps }, ref) => {
20
19
  if (typeof window !== 'undefined') {
21
20
  // eslint-disable-next-line react-hooks/rules-of-hooks
22
21
  useLayoutEffect(() => {
@@ -42,7 +41,6 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
42
41
  }));
43
42
  const [playing, setPlaying] = useState(false);
44
43
  const [rootId] = useState('player-comp');
45
- const [emitter] = useState(() => new PlayerEmitter());
46
44
  const rootRef = useRef(null);
47
45
  const audioAndVideoTags = useRef([]);
48
46
  const imperativePlaying = useRef(false);
@@ -102,9 +100,6 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
102
100
  throw new TypeError(`'numberOfSharedAudioTags' must be an integer but got '${numberOfSharedAudioTags}' instead`);
103
101
  }
104
102
  validatePlaybackRate(currentPlaybackRate);
105
- useEffect(() => {
106
- emitter.dispatchRateChange(currentPlaybackRate);
107
- }, [emitter, currentPlaybackRate]);
108
103
  useEffect(() => {
109
104
  setCurrentPlaybackRate(playbackRate);
110
105
  }, [playbackRate]);
@@ -136,9 +131,9 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
136
131
  }, []);
137
132
  }
138
133
  const actualInputProps = useMemo(() => inputProps !== null && inputProps !== void 0 ? inputProps : {}, [inputProps]);
139
- return (_jsx(Internals.IsPlayerContextProvider, { children: _jsx(SharedPlayerContexts, { timelineContext: timelineContextValue, component: component, compositionHeight: compositionHeight, compositionWidth: compositionWidth, durationInFrames: durationInFrames, fps: fps, numberOfSharedAudioTags: numberOfSharedAudioTags, initiallyMuted: initiallyMuted, children: _jsx(Internals.Timeline.SetTimelineContext.Provider, { value: setTimelineContextValue, children: _jsx(PlayerEventEmitterContext.Provider, { value: emitter, children: _jsx(PlayerUI, { ref: rootRef, posterFillMode: posterFillMode, renderLoading: renderLoading, autoPlay: Boolean(autoPlay), loop: Boolean(loop), controls: Boolean(controls), errorFallback: errorFallback, style: style, inputProps: actualInputProps, allowFullscreen: Boolean(allowFullscreen), moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded), clickToPlay: typeof clickToPlay === 'boolean'
134
+ return (_jsx(Internals.IsPlayerContextProvider, { children: _jsx(SharedPlayerContexts, { timelineContext: timelineContextValue, component: component, compositionHeight: compositionHeight, compositionWidth: compositionWidth, durationInFrames: durationInFrames, fps: fps, numberOfSharedAudioTags: numberOfSharedAudioTags, initiallyMuted: initiallyMuted, children: _jsx(Internals.Timeline.SetTimelineContext.Provider, { value: setTimelineContextValue, children: _jsx(PlayerEmitterProvider, { currentPlaybackRate: currentPlaybackRate, children: _jsx(PlayerUI, { ref: rootRef, posterFillMode: posterFillMode, renderLoading: renderLoading, autoPlay: Boolean(autoPlay), loop: Boolean(loop), controls: Boolean(controls), errorFallback: errorFallback, style: style, inputProps: actualInputProps, allowFullscreen: Boolean(allowFullscreen), moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded), clickToPlay: typeof clickToPlay === 'boolean'
140
135
  ? clickToPlay
141
- : 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 }) }) }) }) }));
136
+ : 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), showPosterWhenBuffering: Boolean(showPosterWhenBuffering), 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, bufferStateDelayInMilliseconds: bufferStateDelayInMilliseconds !== null && bufferStateDelayInMilliseconds !== void 0 ? bufferStateDelayInMilliseconds : 300 }) }) }) }) }));
142
137
  };
143
138
  const forward = forwardRef;
144
139
  /**
@@ -4,7 +4,8 @@ import type { usePlayer } from './use-player.js';
4
4
  import type { Size } from './utils/use-element-size.js';
5
5
  export type RenderPlayPauseButton = (props: {
6
6
  playing: boolean;
7
- }) => ReactNode;
7
+ isBuffering: boolean;
8
+ }) => ReactNode | null;
8
9
  export type RenderFullscreenButton = (props: {
9
10
  isFullscreen: boolean;
10
11
  }) => ReactNode;
@@ -39,4 +40,5 @@ export declare const Controls: React.FC<{
39
40
  alwaysShowControls: boolean;
40
41
  showPlaybackRateControl: boolean | number[];
41
42
  containerRef: React.RefObject<HTMLDivElement>;
43
+ buffering: boolean;
42
44
  }>;
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Internals } from 'remotion';
4
+ import { DefaultPlayPauseButton } from './DefaultPlayPauseButton.js';
4
5
  import { formatTime } from './format-time.js';
5
- import { FullscreenIcon, PauseIcon, PlayIcon } from './icons.js';
6
+ import { FullscreenIcon } from './icons.js';
6
7
  import { MediaVolumeSlider } from './MediaVolumeSlider.js';
7
8
  import { PlaybackrateControl, playerButtonStyle } from './PlaybackrateControl.js';
8
9
  import { PlayerSeekBar } from './PlayerSeekBar.js';
@@ -60,9 +61,8 @@ const flex1 = {
60
61
  flex: 1,
61
62
  };
62
63
  const fullscreen = {};
63
- const PlayPauseButton = ({ playing }) => playing ? _jsx(PauseIcon, {}) : _jsx(PlayIcon, {});
64
- export const Controls = ({ durationInFrames, isFullscreen, fps, player, showVolumeControls, onFullscreenButtonClick, allowFullscreen, onExitFullscreenButtonClick, spaceKeyToPlayOrPause, onSeekEnd, onSeekStart, inFrame, outFrame, initiallyShowControls, canvasSize, renderPlayPauseButton, renderFullscreenButton, alwaysShowControls, showPlaybackRateControl, containerRef, }) => {
65
- var _a;
64
+ export const Controls = ({ durationInFrames, isFullscreen, fps, player, showVolumeControls, onFullscreenButtonClick, allowFullscreen, onExitFullscreenButtonClick, spaceKeyToPlayOrPause, onSeekEnd, onSeekStart, inFrame, outFrame, initiallyShowControls, canvasSize, renderPlayPauseButton, renderFullscreenButton, alwaysShowControls, showPlaybackRateControl, containerRef, buffering, }) => {
65
+ var _a, _b;
66
66
  const playButtonRef = useRef(null);
67
67
  const frame = Internals.Timeline.useTimelinePosition();
68
68
  const [supportsFullscreen, setSupportsFullscreen] = useState(false);
@@ -153,7 +153,10 @@ export const Controls = ({ durationInFrames, isFullscreen, fps, player, showVolu
153
153
  }
154
154
  return null;
155
155
  }, [showPlaybackRateControl]);
156
- 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
156
+ 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(DefaultPlayPauseButton, { buffering: buffering, playing: player.playing })) : ((_b = renderPlayPauseButton({
157
+ playing: player.playing,
158
+ isBuffering: buffering,
159
+ })) !== null && _b !== void 0 ? _b : (_jsx(DefaultPlayPauseButton, { buffering: buffering, 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
157
160
  ? onExitFullscreenButtonClick
158
161
  : 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 })] }));
159
162
  };
@@ -7,6 +7,7 @@ export type ErrorFallback = (info: {
7
7
  export type RenderLoading = (canvas: {
8
8
  height: number;
9
9
  width: number;
10
+ isBuffering: boolean;
10
11
  }) => React.ReactNode;
11
12
  export type RenderPoster = RenderLoading;
12
13
  export type PosterFillMode = 'player-size' | 'composition-size';
@@ -30,6 +31,7 @@ declare const _default: React.ForwardRefExoticComponent<{
30
31
  showPosterWhenPaused: boolean;
31
32
  showPosterWhenEnded: boolean;
32
33
  showPosterWhenUnplayed: boolean;
34
+ showPosterWhenBuffering: boolean;
33
35
  inFrame: number | null;
34
36
  outFrame: number | null;
35
37
  initiallyShowControls: number | boolean;
@@ -38,5 +40,6 @@ declare const _default: React.ForwardRefExoticComponent<{
38
40
  alwaysShowControls: boolean;
39
41
  showPlaybackRateControl: boolean | number[];
40
42
  posterFillMode: PosterFillMode;
43
+ bufferStateDelayInMilliseconds: number;
41
44
  } & React.RefAttributes<PlayerRef>>;
42
45
  export default _default;
@@ -15,7 +15,7 @@ if (reactVersion === '0') {
15
15
  throw new Error(`Version ${reactVersion} of "react" is not supported by Remotion`);
16
16
  }
17
17
  const doesReactVersionSupportSuspense = parseInt(reactVersion, 10) >= 18;
18
- 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, posterFillMode, }, ref) => {
18
+ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, doubleClickToFullscreen, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, renderPoster, className, moveToBeginningWhenEnded, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, inFrame, outFrame, initiallyShowControls, renderFullscreen: renderFullscreenButton, renderPlayPauseButton, alwaysShowControls, showPlaybackRateControl, posterFillMode, bufferStateDelayInMilliseconds, }, ref) => {
19
19
  var _a, _b, _c;
20
20
  const config = Internals.useUnsafeVideoConfig();
21
21
  const video = Internals.useVideo();
@@ -151,6 +151,44 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
151
151
  isMuted,
152
152
  });
153
153
  }, [player.emitter, isMuted]);
154
+ const [showBufferIndicator, setShowBufferState] = useState(false);
155
+ useEffect(() => {
156
+ let timeout = null;
157
+ let stopped = false;
158
+ const onBuffer = () => {
159
+ requestAnimationFrame(() => {
160
+ if (bufferStateDelayInMilliseconds === 0) {
161
+ setShowBufferState(true);
162
+ }
163
+ else {
164
+ timeout = setTimeout(() => {
165
+ if (!stopped) {
166
+ setShowBufferState(true);
167
+ }
168
+ }, bufferStateDelayInMilliseconds);
169
+ }
170
+ });
171
+ };
172
+ const onResume = () => {
173
+ requestAnimationFrame(() => {
174
+ setShowBufferState(false);
175
+ if (timeout) {
176
+ clearTimeout(timeout);
177
+ }
178
+ });
179
+ };
180
+ player.emitter.addEventListener('waiting', onBuffer);
181
+ player.emitter.addEventListener('resume', onResume);
182
+ return () => {
183
+ player.emitter.removeEventListener('waiting', onBuffer);
184
+ player.emitter.removeEventListener('resume', onResume);
185
+ setShowBufferState(false);
186
+ if (timeout) {
187
+ clearTimeout(timeout);
188
+ }
189
+ stopped = true;
190
+ };
191
+ }, [bufferStateDelayInMilliseconds, player.emitter]);
154
192
  useImperativeHandle(ref, () => {
155
193
  const methods = {
156
194
  play: player.play,
@@ -278,9 +316,16 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
278
316
  ? renderLoading({
279
317
  height: outerStyle.height,
280
318
  width: outerStyle.width,
319
+ isBuffering: showBufferIndicator,
281
320
  })
282
321
  : null;
283
- }, [outerStyle.height, outerStyle.width, renderLoading]);
322
+ }, [outerStyle.height, outerStyle.width, renderLoading, showBufferIndicator]);
323
+ const currentScale = useMemo(() => {
324
+ return {
325
+ type: 'scale',
326
+ scale,
327
+ };
328
+ }, [scale]);
284
329
  if (!config) {
285
330
  return null;
286
331
  }
@@ -292,6 +337,7 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
292
337
  width: posterFillMode === 'player-size'
293
338
  ? outerStyle.width
294
339
  : config.width,
340
+ isBuffering: showBufferIndicator,
295
341
  })
296
342
  : null;
297
343
  if (poster === undefined) {
@@ -302,13 +348,14 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
302
348
  showPosterWhenPaused && !player.isPlaying() && !seeking,
303
349
  showPosterWhenEnded && player.isLastFrame && !player.isPlaying(),
304
350
  showPosterWhenUnplayed && !player.hasPlayed && !player.isPlaying(),
351
+ showPosterWhenBuffering && showBufferIndicator && player.isPlaying(),
305
352
  ].some(Boolean);
306
353
  const { left, top, width, height, ...outerWithoutScale } = outer;
307
- const content = (_jsxs(_Fragment, { children: [_jsx("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: _jsxs("div", { style: containerStyle, className: PLAYER_CSS_CLASSNAME, children: [VideoComponent ? (_jsx(ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: _jsx(Internals.ClipComposition, { children: _jsx(VideoComponent, { ...((_c = video === null || video === void 0 ? void 0 : video.props) !== null && _c !== void 0 ? _c : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) }) })) : null, shouldShowPoster && posterFillMode === 'composition-size' ? (_jsx("div", { style: {
354
+ const content = (_jsxs(_Fragment, { children: [_jsx("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: _jsxs("div", { style: containerStyle, className: PLAYER_CSS_CLASSNAME, children: [VideoComponent ? (_jsx(ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: _jsx(Internals.ClipComposition, { children: _jsx(Internals.CurrentScaleContext.Provider, { value: currentScale, children: _jsx(VideoComponent, { ...((_c = video === null || video === void 0 ? void 0 : video.props) !== null && _c !== void 0 ? _c : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) }) }) })) : null, shouldShowPoster && posterFillMode === 'composition-size' ? (_jsx("div", { style: {
308
355
  ...outerWithoutScale,
309
356
  width: config.width,
310
357
  height: config.height,
311
- }, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null] }) }), shouldShowPoster && posterFillMode === 'player-size' ? (_jsx("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null, controls ? (_jsx(Controls, { fps: config.fps, durationInFrames: config.durationInFrames, player: player, containerRef: container, 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] }));
358
+ }, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null] }) }), shouldShowPoster && posterFillMode === 'player-size' ? (_jsx("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null, controls ? (_jsx(Controls, { fps: config.fps, durationInFrames: config.durationInFrames, player: player, containerRef: container, 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, buffering: showBufferIndicator })) : null] }));
312
359
  if (IS_NODE && !doesReactVersionSupportSuspense) {
313
360
  return (_jsx("div", { ref: container, style: outerStyle, className: className, children: content }));
314
361
  }
@@ -51,5 +51,5 @@ export const SharedPlayerContexts = ({ children, timelineContext, fps, compositi
51
51
  setMediaVolume: setMediaVolumeAndPersist,
52
52
  };
53
53
  }, [setMediaVolumeAndPersist]);
54
- 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.NativeLayersProvider, { children: _jsx(Internals.SetMediaVolumeContext.Provider, { value: setMediaVolumeContextValue, children: _jsx(Internals.SharedAudioContextProvider, { numberOfAudioTags: numberOfSharedAudioTags, component: component, children: children }) }) }) }) }) }) }) }) }) }));
54
+ 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.NativeLayersProvider, { children: _jsx(Internals.SetMediaVolumeContext.Provider, { value: setMediaVolumeContextValue, children: _jsx(Internals.SharedAudioContextProvider, { numberOfAudioTags: numberOfSharedAudioTags, component: component, children: _jsx(Internals.BufferingProvider, { children: children }) }) }) }) }) }) }) }) }) }) }));
55
55
  };
@@ -67,13 +67,20 @@ const ThumbnailUI = ({ style, inputProps, errorFallback, renderLoading, classNam
67
67
  ? renderLoading({
68
68
  height: outerStyle.height,
69
69
  width: outerStyle.width,
70
+ isBuffering: false,
70
71
  })
71
72
  : null;
72
73
  }, [outerStyle.height, outerStyle.width, renderLoading]);
74
+ const currentScaleContext = useMemo(() => {
75
+ return {
76
+ type: 'scale',
77
+ scale,
78
+ };
79
+ }, [scale]);
73
80
  if (!config) {
74
81
  return null;
75
82
  }
76
- 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.props) !== null && _b !== void 0 ? _b : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) })) : null }) }));
83
+ 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(Internals.CurrentScaleContext.Provider, { value: currentScaleContext, children: _jsx(VideoComponent, { ...((_b = video === null || video === void 0 ? void 0 : video.props) !== null && _b !== void 0 ? _b : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) }) })) : null }) }));
77
84
  if (IS_NODE && !doesReactVersionSupportSuspense) {
78
85
  return (_jsx("div", { ref: container, style: outerStyle, className: className, children: content }));
79
86
  }
@@ -1,12 +1,5 @@
1
- import type { VideoConfig } from 'remotion';
2
- import type { PreviewSize } from './utils/preview-size.js';
1
+ import type { PreviewSize, VideoConfig } from 'remotion';
3
2
  import type { Size } from './utils/use-element-size.js';
4
- export declare const calculateScale: ({ canvasSize, compositionHeight, compositionWidth, previewSize, }: {
5
- previewSize: PreviewSize['size'];
6
- compositionWidth: number;
7
- compositionHeight: number;
8
- canvasSize: Size;
9
- }) => number;
10
3
  type Layout = {
11
4
  centerX: number;
12
5
  centerY: number;
@@ -1,12 +1,7 @@
1
+ import { Internals } from 'remotion';
1
2
  import { calculatePlayerSize } from './utils/calculate-player-size.js';
2
- export const calculateScale = ({ canvasSize, compositionHeight, compositionWidth, previewSize, }) => {
3
- const heightRatio = canvasSize.height / compositionHeight;
4
- const widthRatio = canvasSize.width / compositionWidth;
5
- const ratio = Math.min(heightRatio, widthRatio);
6
- return previewSize === 'auto' ? ratio : Number(previewSize);
7
- };
8
3
  export const calculateCanvasTransformation = ({ previewSize, compositionWidth, compositionHeight, canvasSize, }) => {
9
- const scale = calculateScale({
4
+ const scale = Internals.calculateScale({
10
5
  canvasSize,
11
6
  compositionHeight,
12
7
  compositionWidth,
@@ -25,6 +25,8 @@ type FullscreenChangeEventPayload = {
25
25
  type MuteChangeEventPayload = {
26
26
  isMuted: boolean;
27
27
  };
28
+ type WaitingEventPayload = {};
29
+ type ResumeEventPayload = {};
28
30
  type PlayerStateEventMap = {
29
31
  seeked: SeekPayload;
30
32
  pause: undefined;
@@ -38,6 +40,8 @@ type PlayerStateEventMap = {
38
40
  frameupdate: FrameUpdateEventPayload;
39
41
  fullscreenchange: FullscreenChangeEventPayload;
40
42
  mutechange: MuteChangeEventPayload;
43
+ waiting: WaitingEventPayload;
44
+ resume: ResumeEventPayload;
41
45
  };
42
46
  type ThumbnailStateEventMap = {
43
47
  error: ErrorPayload;
@@ -70,6 +74,8 @@ export declare class PlayerEmitter {
70
74
  dispatchFrameUpdate(event: FrameUpdateEventPayload): void;
71
75
  dispatchFullscreenChange(event: FullscreenChangeEventPayload): void;
72
76
  dispatchMuteChange(event: MuteChangeEventPayload): void;
77
+ dispatchWaiting(event: WaitingEventPayload): void;
78
+ dispatchResume(event: ResumeEventPayload): void;
73
79
  }
74
80
  export declare class ThumbnailEmitter {
75
81
  listeners: ThumbnailListeners;
@@ -13,6 +13,8 @@ export class PlayerEmitter {
13
13
  fullscreenchange: [],
14
14
  volumechange: [],
15
15
  mutechange: [],
16
+ waiting: [],
17
+ resume: [],
16
18
  };
17
19
  }
18
20
  addEventListener(name, callback) {
@@ -72,6 +74,12 @@ export class PlayerEmitter {
72
74
  dispatchMuteChange(event) {
73
75
  this.dispatchEvent('mutechange', event);
74
76
  }
77
+ dispatchWaiting(event) {
78
+ this.dispatchEvent('waiting', event);
79
+ }
80
+ dispatchResume(event) {
81
+ this.dispatchEvent('resume', event);
82
+ }
75
83
  }
76
84
  export class ThumbnailEmitter {
77
85
  constructor() {
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export const ICON_SIZE = 25;
3
3
  export const fullscreenIconSize = 16;
4
4
  export const PlayIcon = () => {
5
- return (_jsx("svg", { width: ICON_SIZE, height: ICON_SIZE, viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M8 6.375C7.40904 8.17576 7.06921 10.2486 7.01438 12.3871C6.95955 14.5255 7.19163 16.6547 7.6875 18.5625C9.95364 18.2995 12.116 17.6164 14.009 16.5655C15.902 15.5147 17.4755 14.124 18.6088 12.5C17.5158 10.8949 15.9949 9.51103 14.1585 8.45082C12.3222 7.3906 10.2174 6.68116 8 6.375Z", fill: "white", stroke: "white", strokeWidth: "6.25", strokeLinejoin: "round" }) }));
5
+ return (_jsx("svg", { width: ICON_SIZE, height: ICON_SIZE, viewBox: "0 0 25 25", fill: "none", children: _jsx("path", { d: "M8 6.375C7.40904 8.17576 7.06921 10.2486 7.01438 12.3871C6.95955 14.5255 7.19163 16.6547 7.6875 18.5625C9.95364 18.2995 12.116 17.6164 14.009 16.5655C15.902 15.5147 17.4755 14.124 18.6088 12.5C17.5158 10.8949 15.9949 9.51103 14.1585 8.45082C12.3222 7.3906 10.2174 6.68116 8 6.375Z", fill: "white", stroke: "white", strokeWidth: "6.25", strokeLinejoin: "round" }) }));
6
6
  };
7
7
  export const PauseIcon = () => {
8
8
  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" })] }));
@@ -5,7 +5,6 @@ export { Player, PlayerProps } from './Player.js';
5
5
  export type { RenderFullscreenButton, RenderPlayPauseButton, } from './PlayerControls.js';
6
6
  export type { ErrorFallback, RenderLoading, RenderPoster } from './PlayerUI.js';
7
7
  export { Thumbnail } from './Thumbnail.js';
8
- export { PreviewSize, Translation } from './utils/preview-size.js';
9
8
  export { Size } from './utils/use-element-size.js';
10
9
  export type { CallbackListener, PlayerEventTypes as EventTypes };
11
10
  export declare const PlayerInternals: {
@@ -25,6 +24,7 @@ export declare const PlayerInternals: {
25
24
  getCurrentFrame: () => number;
26
25
  isPlaying: () => boolean;
27
26
  hasPlayed: boolean;
27
+ isBuffering: () => boolean;
28
28
  remotionInternal_currentFrameRef: import("react").MutableRefObject<number>;
29
29
  };
30
30
  usePlayback: ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, frameRef, }: {
@@ -53,10 +53,11 @@ export declare const PlayerInternals: {
53
53
  };
54
54
  useHoverState: (ref: import("react").RefObject<HTMLDivElement>) => boolean;
55
55
  updateAllElementsSizes: () => void;
56
- calculateScale: ({ canvasSize, compositionHeight, compositionWidth, previewSize, }: {
57
- previewSize: number | "auto";
58
- compositionWidth: number;
59
- compositionHeight: number;
60
- canvasSize: import("./utils/use-element-size.js").Size;
61
- }) => number;
56
+ PlayerEmitterProvider: import("react").FC<{
57
+ children: import("react").ReactNode;
58
+ currentPlaybackRate: number | null;
59
+ }>;
60
+ BufferingIndicator: import("react").FC<{
61
+ type: "player" | "studio";
62
+ }>;
62
63
  };
@@ -1,5 +1,7 @@
1
- import { calculateCanvasTransformation, calculateScale, } from './calculate-scale.js';
1
+ import { BufferingIndicator } from './BufferingIndicator.js';
2
+ import { calculateCanvasTransformation } from './calculate-scale.js';
2
3
  import { PlayerEventEmitterContext } from './emitter-context.js';
4
+ import { PlayerEmitterProvider } from './EmitterProvider.js';
3
5
  import { PlayerEmitter } from './event-emitter.js';
4
6
  import { useHoverState } from './use-hover-state.js';
5
7
  import { usePlayback } from './use-playback.js';
@@ -16,5 +18,6 @@ export const PlayerInternals = {
16
18
  calculateCanvasTransformation,
17
19
  useHoverState,
18
20
  updateAllElementsSizes,
19
- calculateScale,
21
+ PlayerEmitterProvider,
22
+ BufferingIndicator,
20
23
  };
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
- import { useEffect, useRef } from 'react';
2
+ import { useContext, useEffect, useRef } from 'react';
3
3
  import { Internals } from 'remotion';
4
4
  import { calculateNextFrame } from './calculate-next-frame.js';
5
5
  import { useIsBackgrounded } from './is-backgrounded.js';
@@ -9,11 +9,28 @@ export const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFr
9
9
  const frame = Internals.Timeline.useTimelinePosition();
10
10
  const { playing, pause, emitter } = usePlayer();
11
11
  const setFrame = Internals.Timeline.useTimelineSetFrame();
12
+ const buffering = useRef(null);
12
13
  // requestAnimationFrame() does not work if the tab is not active.
13
14
  // This means that audio will keep playing even if it has ended.
14
15
  // In that case, we use setTimeout() instead.
15
16
  const isBackgroundedRef = useIsBackgrounded();
16
17
  const lastTimeUpdateEvent = useRef(null);
18
+ const context = useContext(Internals.BufferingContextReact);
19
+ if (!context) {
20
+ throw new Error('Missing the buffering context. Most likely you have a Remotion version mismatch.');
21
+ }
22
+ useEffect(() => {
23
+ const onBufferClear = context.listenForBuffering(() => {
24
+ buffering.current = performance.now();
25
+ });
26
+ const onResumeClear = context.listenForResume(() => {
27
+ buffering.current = null;
28
+ });
29
+ return () => {
30
+ onBufferClear.remove();
31
+ onResumeClear.remove();
32
+ };
33
+ }, [context]);
17
34
  useEffect(() => {
18
35
  if (!config) {
19
36
  return;
@@ -23,7 +40,7 @@ export const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFr
23
40
  }
24
41
  let hasBeenStopped = false;
25
42
  let reqAnimFrameCall = null;
26
- const startedTime = performance.now();
43
+ let startedTime = performance.now();
27
44
  let framesAdvanced = 0;
28
45
  const cancelQueuedFrame = () => {
29
46
  if (reqAnimFrameCall !== null) {
@@ -43,9 +60,10 @@ export const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFr
43
60
  const time = performance.now() - startedTime;
44
61
  const actualLastFrame = outFrame !== null && outFrame !== void 0 ? outFrame : config.durationInFrames - 1;
45
62
  const actualFirstFrame = inFrame !== null && inFrame !== void 0 ? inFrame : 0;
63
+ const currentFrame = frameRef.current;
46
64
  const { nextFrame, framesToAdvance, hasEnded } = calculateNextFrame({
47
65
  time,
48
- currentFrame: frameRef.current,
66
+ currentFrame,
49
67
  playbackSpeed: playbackRate,
50
68
  fps: config.fps,
51
69
  actualFirstFrame,
@@ -69,6 +87,18 @@ export const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFr
69
87
  }
70
88
  };
71
89
  const queueNextFrame = () => {
90
+ if (buffering.current) {
91
+ const stopListening = context.listenForResume(() => {
92
+ stopListening.remove();
93
+ if (hasBeenStopped) {
94
+ return;
95
+ }
96
+ startedTime = performance.now();
97
+ framesAdvanced = 0;
98
+ callback();
99
+ });
100
+ return;
101
+ }
72
102
  if (isBackgroundedRef.current) {
73
103
  reqAnimFrameCall = {
74
104
  type: 'timeout',
@@ -108,6 +138,8 @@ export const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFr
108
138
  moveToBeginningWhenEnded,
109
139
  isBackgroundedRef,
110
140
  frameRef,
141
+ buffering,
142
+ context,
111
143
  ]);
112
144
  useEffect(() => {
113
145
  const interval = setInterval(() => {
@@ -14,6 +14,7 @@ type UsePlayerMethods = {
14
14
  getCurrentFrame: () => number;
15
15
  isPlaying: () => boolean;
16
16
  hasPlayed: boolean;
17
+ isBuffering: () => boolean;
17
18
  /**
18
19
  * @deprecated Remotion internal API
19
20
  */
@@ -22,6 +22,11 @@ export const usePlayer = () => {
22
22
  if (!emitter) {
23
23
  throw new TypeError('Expected Player event emitter context');
24
24
  }
25
+ const bufferingContext = useContext(Internals.BufferingContextReact);
26
+ if (!bufferingContext) {
27
+ throw new Error('Missing the buffering context. Most likely you have a Remotion version mismatch.');
28
+ }
29
+ const { buffering } = bufferingContext;
25
30
  const seek = useCallback((newFrame) => {
26
31
  if (video === null || video === void 0 ? void 0 : video.id) {
27
32
  setTimelinePosition((c) => ({ ...c, [video.id]: newFrame }));
@@ -128,6 +133,7 @@ export const usePlayer = () => {
128
133
  isFirstFrame,
129
134
  getCurrentFrame: () => frameRef.current,
130
135
  isPlaying: () => imperativePlaying.current,
136
+ isBuffering: () => buffering.current,
131
137
  pauseAndReturnToPlayStart,
132
138
  hasPlayed,
133
139
  remotionInternal_currentFrameRef: frameRef,
@@ -143,8 +149,9 @@ export const usePlayer = () => {
143
149
  seek,
144
150
  isFirstFrame,
145
151
  pauseAndReturnToPlayStart,
146
- imperativePlaying,
147
152
  hasPlayed,
153
+ imperativePlaying,
154
+ buffering,
148
155
  ]);
149
156
  return returnValue;
150
157
  };