取得與顯示 Player 目前時間
學習如何在 Remotion Player 外部取得並顯示目前播放時間,同時避免不必要的重新渲染。
取得與顯示 Player 目前時間
在應用程式中渲染 <Player> 時,必須特別注意,避免在時間變化時造成整個應用程式或 <Player> 元件持續重新渲染。
這也是為什麼 useCurrentFrame() hook 無法在組合外部使用的原因。
警告:不要將此 hook 放入渲染
<Player>的同一個元件中,否則你將看到持續的重新渲染。請將其放入一個與渲染 Player 的元件相鄰的元件中。
與 Player 時間同步元件
如果你想顯示一個與 Player 時間同步的元件,例如時間軸元件的游標或自訂時間顯示,可以使用以下 hook:
import { CallbackListener, PlayerRef } from '@remotion/player';
import { useCallback, useSyncExternalStore } from 'react';
export const useCurrentPlayerFrame = (
ref: React.RefObject<PlayerRef>,
) => {
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(
subscribe,
() => ref.current?.getCurrentFrame() ?? 0,
() => 0,
);
return data;
};使用範例
在 React Player 上新增 ref,並將其傳遞給另一個元件:
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} />
</>
);
};以下是元件存取目前時間的方式:
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>;
}> = ({ playerRef }) => {
const frame = useCurrentPlayerFrame(playerRef);
return <div>目前幀:{frame}</div>;
};這個方法很有效率,因為只有影片本身和依賴時間的元件會重新渲染,<Player> 元件本身不會重新渲染。
為什麼不直接使用 useState?
若直接在父層元件監聽事件並使用 useState 儲存幀數,每次時間更新(每秒最多 60 次)都會導致整個元件樹重新渲染,包含 <Player> 本身,造成效能問題。
useSyncExternalStore 可以讓訂閱邏輯只在需要該值的元件中觸發重新渲染,因此是推薦做法。
使用 seekTo() 設定目前時間
透過 PlayerRef,你也可以程式化地跳轉到特定幀:
import { Player, PlayerRef } from '@remotion/player';
import { useRef } from 'react';
import { MyVideo } from './remotion/MyVideo';
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
const seekToMiddle = () => {
playerRef.current?.seekTo(60);
};
return (
<>
<Player
ref={playerRef}
component={MyVideo}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
/>
<button onClick={seekToMiddle}>跳至中間</button>
</>
);
};監聽 frameupdate 事件
除了 hook,你也可以直接監聽 frameupdate 事件:
import { useEffect, useRef } from 'react';
import { Player, PlayerRef } from '@remotion/player';
import { MyVideo } from './remotion/MyVideo';
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
useEffect(() => {
const { current } = playerRef;
if (!current) return;
const onFrameUpdate = () => {
const frame = current.getCurrentFrame();
console.log('目前幀:', frame);
};
current.addEventListener('frameupdate', onFrameUpdate);
return () => {
current.removeEventListener('frameupdate', onFrameUpdate);
};
}, []);
return (
<Player
ref={playerRef}
component={MyVideo}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
/>
);
};