The Player buffer state
available from 4.0.111
Just like regular video players, it is possible that the content being displayed in the Player is not yet fully loaded.
In this case, the best practice is to briefly pause the video, let the content load and then resume playback.
Remotion has a native buffer state, which can be used to pause the video when the buffer is empty.
TL;DR
You can add a pauseWhenBuffering
prop to your <Video>
, <OffthreadVideo>
, <Audio>
tags.
The prop is called pauseWhenLoading
for <Img>
tags.
By doing so, the Player will briefly pause until your media is loaded.
Mechanism
Activating the buffer state
A component can tell the player to switch into a buffer state by first using the useBufferState()
hook and then calling buffer.delayPlayback()
:
MyComp.tsxtsx
importReact from 'react';import {useBufferState } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();React .useEffect (() => {constdelayHandle =buffer .delayPlayback ();setTimeout (() => {delayHandle .unblock ();}, 5000);return () => {delayHandle .unblock ();};}, []);return null;};
MyComp.tsxtsx
importReact from 'react';import {useBufferState } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();React .useEffect (() => {constdelayHandle =buffer .delayPlayback ();setTimeout (() => {delayHandle .unblock ();}, 5000);return () => {delayHandle .unblock ();};}, []);return null;};
To clear the handle, call .unblock()
on the return value of delayPlayback()
.
When activating the buffer state, pay attention to the following:
Clear the handle when the component unmounts
The user might seek to a different portion of the video which is immediately available.
Use the cleanup function of useEffect()
to clear the handle when your component is unmounted.
❌ Causes problems with React strict modetsx
importReact , {useState } from 'react';import {useBufferState } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();const [delayHandle ] =useState (() =>buffer .delayPlayback ()); // 💥React .useEffect (() => {setTimeout (() => {delayHandle .unblock ();}, 5000);}, []);return <></>;};
❌ Causes problems with React strict modetsx
importReact , {useState } from 'react';import {useBufferState } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();const [delayHandle ] =useState (() =>buffer .delayPlayback ()); // 💥React .useEffect (() => {setTimeout (() => {delayHandle .unblock ();}, 5000);}, []);return <></>;};
Don't use delayPlayback()
inside a useState()
While the following implementation works in production, it fails in React Strict mode, because the useState()
hook is called twice, which causes the first invocation of the buffer to never be cleared.
❌ Doesn't clear the buffer handle when seeking to a different portion of a videotsx
importReact , {useState } from 'react';import {useBufferState } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();const [delayHandle ] =useState (() =>buffer .delayPlayback ()); // 💥React .useEffect (() => {setTimeout (() => {delayHandle .unblock ();}, 5000);return () => {delayHandle .unblock ();};}, []);return <></>;};
❌ Doesn't clear the buffer handle when seeking to a different portion of a videotsx
importReact , {useState } from 'react';import {useBufferState } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();const [delayHandle ] =useState (() =>buffer .delayPlayback ()); // 💥React .useEffect (() => {setTimeout (() => {delayHandle .unblock ();}, 5000);return () => {delayHandle .unblock ();};}, []);return <></>;};
Details
It doesn't replace delayRender()
delayRender() is a different API which controls when a screenshot is taken during rendering.If you are loading data, you might want to delay the screenshotting of your component during rendering and delay the playback of the video in preview, in which case you need to use both APIs together.
Using delayRender() and delayPlayback() togethertsx
importReact from 'react';import {useBufferState ,delayRender ,continueRender } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();const [handle ] =React .useState (() =>delayRender ());React .useEffect (() => {constdelayHandle =buffer .delayPlayback ();setTimeout (() => {delayHandle .unblock ();continueRender (handle );}, 5000);return () => {delayHandle .unblock ();};}, []);return <></>;};
Using delayRender() and delayPlayback() togethertsx
importReact from 'react';import {useBufferState ,delayRender ,continueRender } from 'remotion';constMyComp :React .FC = () => {constbuffer =useBufferState ();const [handle ] =React .useState (() =>delayRender ());React .useEffect (() => {constdelayHandle =buffer .delayPlayback ();setTimeout (() => {delayHandle .unblock ();continueRender (handle );}, 5000);return () => {delayHandle .unblock ();};}, []);return <></>;};
Possible states
Whether a player is buffering does not internally change the playing
/ paused
state.
Therefore, a player can be in four playback states:
playing && !buffering
playing && buffering
paused && !buffering
paused && buffering
Only in state
By default, Remotion will display the following UI based on the state of the player:
When in State
When in State
Otherwise, the Play button is shown.
You may add additional UI to this, for example by overlaying the Player with a spinner when the Player is buffering.
Listening to buffer events
If the <Player />
is entering a buffer state, it will emit the waiting
event.
Once it resumes, it emits the resume
event.
Listening to waiting and resume eventstsx
import {Player ,PlayerRef } from '@remotion/player';import {useEffect ,useRef ,useState } from 'react';import {MyVideo } from './remotion/MyVideo';export constApp :React .FC = () => {constplayerRef =useRef <PlayerRef >(null);const [buffering ,setBuffering ] =useState (false);useEffect (() => {const {current } =playerRef ;if (!current ) {return;}constonBuffering = () => {setBuffering (true);};constonResume = () => {setBuffering (false);};current .addEventListener ('waiting',onBuffering );current .addEventListener ('resume',onResume );return () => {current .removeEventListener ('waiting',onBuffering );current .removeEventListener ('resume',onResume );};}, [setBuffering ]);return (<Player ref ={playerRef }component ={MyVideo }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}/>);};
Listening to waiting and resume eventstsx
import {Player ,PlayerRef } from '@remotion/player';import {useEffect ,useRef ,useState } from 'react';import {MyVideo } from './remotion/MyVideo';export constApp :React .FC = () => {constplayerRef =useRef <PlayerRef >(null);const [buffering ,setBuffering ] =useState (false);useEffect (() => {const {current } =playerRef ;if (!current ) {return;}constonBuffering = () => {setBuffering (true);};constonResume = () => {setBuffering (false);};current .addEventListener ('waiting',onBuffering );current .addEventListener ('resume',onResume );return () => {current .removeEventListener ('waiting',onBuffering );current .removeEventListener ('resume',onResume );};}, [setBuffering ]);return (<Player ref ={playerRef }component ={MyVideo }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}/>);};
Components with built-in buffering
You can enable buffering on the following components:
<Audio>
- add thepauseWhenBuffering
prop<Video>
- add thepauseWhenBuffering
prop<OffthreadVideo>
- add thepauseWhenBuffering
prop<Img>
- add thepauseWhenLoading
prop
Indicating buffering in the UI
When the Player is buffering, by default the Play button will be replaced with a spinner.
To prevent a janky UI, this spinner will only be shown after the Player has been in a buffering state for 300ms.
You may customize the timeout of 300
milliseconds by passing the bufferStateDelayInMilliseconds
prop to the <Player />
component.
Setting the delay until the spinner is showntsx
import {Player ,PlayerRef } from '@remotion/player';import {useEffect ,useRef ,useState } from 'react';import {MyVideo } from './remotion/MyVideo';export constApp :React .FC = () => {return (<Player component ={MyVideo }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}bufferStateDelayInMilliseconds ={1000} // Or set to `0` to immediately show the spinner/>);};
Setting the delay until the spinner is showntsx
import {Player ,PlayerRef } from '@remotion/player';import {useEffect ,useRef ,useState } from 'react';import {MyVideo } from './remotion/MyVideo';export constApp :React .FC = () => {return (<Player component ={MyVideo }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}bufferStateDelayInMilliseconds ={1000} // Or set to `0` to immediately show the spinner/>);};
In the Studio, you can change the delay in the config file:
remotion.config.tsts
import {Config } from '@remotion/cli/config';Config .setBufferStateDelayInMilliseconds (0);
remotion.config.tsts
import {Config } from '@remotion/cli/config';Config .setBufferStateDelayInMilliseconds (0);
To customize the spinner that is shown in place of the Play button, you can pass a renderPlayPauseButton()
prop:
Rendering a custom spinner inside the Play buttontsx
import {Player ,RenderPlayPauseButton } from '@remotion/player';import {useCallback } from 'react';export constApp :React .FC = () => {constrenderPlayPauseButton :RenderPlayPauseButton =useCallback (({playing ,isBuffering }) => {if (playing &&isBuffering ) {return <MySpinner />;}return null;},[],);return (<Player component ={MyVideo }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}renderPlayPauseButton ={renderPlayPauseButton }/>);};
Rendering a custom spinner inside the Play buttontsx
import {Player ,RenderPlayPauseButton } from '@remotion/player';import {useCallback } from 'react';export constApp :React .FC = () => {constrenderPlayPauseButton :RenderPlayPauseButton =useCallback (({playing ,isBuffering }) => {if (playing &&isBuffering ) {return <MySpinner />;}return null;},[],);return (<Player component ={MyVideo }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}renderPlayPauseButton ={renderPlayPauseButton }/>);};
To display a loading UI layered on top of the Player (e.g. a spinner), you can set showPosterWhenBuffering
to true
and pass a renderPoster()
prop:
Rendering a custom spinner on top of the Playertsx
import type {RenderPoster } from '@remotion/player';import {Player } from '@remotion/player';constMyApp :React .FC = () => {constrenderPoster :RenderPoster =useCallback (({isBuffering }) => {if (isBuffering ) {return (<AbsoluteFill style ={{justifyContent : 'center',alignItems : 'center'}}><Spinner /></AbsoluteFill >);}return null;}, []);return (<Player fps ={30}component ={Component }durationInFrames ={100}compositionWidth ={1080}compositionHeight ={1080}renderPoster ={renderPoster }showPosterWhenBuffering />);};
Rendering a custom spinner on top of the Playertsx
import type {RenderPoster } from '@remotion/player';import {Player } from '@remotion/player';constMyApp :React .FC = () => {constrenderPoster :RenderPoster =useCallback (({isBuffering }) => {if (isBuffering ) {return (<AbsoluteFill style ={{justifyContent : 'center',alignItems : 'center'}}><Spinner /></AbsoluteFill >);}return null;}, []);return (<Player fps ={30}component ={Component }durationInFrames ={100}compositionWidth ={1080}compositionHeight ={1080}renderPoster ={renderPoster }showPosterWhenBuffering />);};
Upcoming changes in Remotion 5.0
In Remotion 4.0, media tags such as <Audio>
, <OffthreadVideo>
tags will need to opt-in to use the buffer state.
In Remotion 5.0, it is planned that <Audio>
, <Video>
and <OffthreadVideo>
will automatically use the buffer state, but they can opt out of it.