packages/docs/docs/player/current-time.mdx
When rendering a <Player> in your app, special considerations must be taken to prevent constant re-renders of the app or <Player> if the time changes.
This is why the useCurrentFrame() hook does not work outside a composition.
:::warning
Do not put this hook into the same component in which the <Player> is rendered, otherwise you'll see constant re-renders. Instead, put it inside a component that is rendered adjacent to the component in which the Player is rendered.
:::
If you want to display a component that synchronizes with the time of the player, for example the cursor of a timeline component or a custom time display, you can use the following hook:
import {CallbackListener, PlayerRef} from '@remotion/player';
import {useCallback, useSyncExternalStore} from 'react';
export const useCurrentPlayerFrame = (
ref: React.RefObject<PlayerRef | null>,
) => {
const subscribe = useCallback(
(onStoreChange: () => void) => {
const {current} = ref;
if (!current) {
return () => undefined;
}
const updater: CallbackListener<'frameupdate'> = ({detail}) => {
onStoreChange();
};
current.addEventListener('frameupdate', updater);
return () => {
current.removeEventListener('frameupdate', updater);
};
},
[ref],
);
const data = useSyncExternalStore<number>(
subscribe,
() => ref.current?.getCurrentFrame() ?? 0,
() => 0,
);
return data;
};
Add a ref to a React Player and pass it to another component:
// @allowUmdGlobalAccess
// @filename: ./remotion/MyVideo.tsx
export const MyVideo = () => <></>;
// @filename: ./remotion/TimeDisplay.tsx
export const TimeDisplay: React.FC<{
playerRef: React.RefObject<PlayerRef | null>;
}> = () => <></>;
// @filename: index.tsx
// ---cut---
import {Player, PlayerRef} from '@remotion/player';
import {useRef} from 'react';
import {MyVideo} from './remotion/MyVideo';
import {TimeDisplay} from './remotion/TimeDisplay';
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
return (
<>
<Player
ref={playerRef}
component={MyVideo}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
/>
<TimeDisplay playerRef={playerRef} />
</>
);
};
This is how a component could access the current time:
// @filename: ./use-current-player-frame.ts
import {CallbackListener, PlayerRef} from '@remotion/player';
import {useCallback, useSyncExternalStore} from 'react';
export const useCurrentPlayerFrame = (
ref: React.RefObject<PlayerRef | null>,
) => {
const subscribe = useCallback(
(onStoreChange: (newVal: number) => void) => {
const {current} = ref;
if (!current) {
return () => undefined;
}
const updater: CallbackListener<'frameupdate'> = ({detail}) => {
onStoreChange(detail.frame);
};
current.addEventListener('frameupdate', updater);
return () => {
current.removeEventListener('frameupdate', updater);
};
},
[ref],
);
const data = useSyncExternalStore<number>(
subscribe,
() => ref.current?.getCurrentFrame() ?? 0,
() => 0,
);
return data;
};
// @filename: TimeDisplay.tsx
// ---cut---
import React from 'react';
import {PlayerRef} from '@remotion/player';
import {useCurrentPlayerFrame} from './use-current-player-frame';
export const TimeDisplay: React.FC<{
playerRef: React.RefObject<PlayerRef | null>;
}> = ({playerRef}) => {
const frame = useCurrentPlayerFrame(playerRef);
return <div>current frame: {frame}</div>;
};
This approach is efficient, because only the video itself and the component relying on the time are re-rendering, but the <App> component is not.