Skip to main content

Player - 最佳实践

避免 <Player> 的重新渲染

以下模式并不理想,因为每次时间更新时,<Player> 都会被重新渲染:

❌ 有问题
tsx
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
const [currentTime, setCurrentTime] = useState(0);
 
useEffect(() => {
playerRef.current?.addEventListener("timeupdate", (e) => {
setCurrentTime(e.detail.frame);
});
}, []);
 
return (
<div>
<Player ref={playerRef} component={MyVideo} {...otherProps} />
<div>Current time: {currentTime}</div>
</div>
);
};
❌ 有问题
tsx
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
const [currentTime, setCurrentTime] = useState(0);
 
useEffect(() => {
playerRef.current?.addEventListener("timeupdate", (e) => {
setCurrentTime(e.detail.frame);
});
}, []);
 
return (
<div>
<Player ref={playerRef} component={MyVideo} {...otherProps} />
<div>Current time: {currentTime}</div>
</div>
);
};

我们建议将您的控件和用户界面作为 <Player> 渲染的兄弟元素,并将一个 ref 作为 prop 传递给播放器:

✅ 更好
tsx
const PlayerOnly: React.FC<{
playerRef: React.RefObject<PlayerRef>;
}> = ({ playerRef }) => {
return <Player ref={playerRef} component={MyVideo} {...otherProps} />;
};
 
const ControlsOnly: React.FC<{
playerRef: React.RefObject<PlayerRef>;
}> = ({ playerRef }) => {
const [currentTime, setCurrentTime] = useState(0);
 
useEffect(() => {
playerRef.current?.addEventListener("timeupdate", (e) => {
setCurrentTime(e.detail.frame);
});
}, []);
 
return <div>Current time: {currentTime}</div>;
};
 
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
 
return (
<>
<PlayerOnly playerRef={playerRef} />
<ControlsOnly playerRef={playerRef} />
</>
);
};
✅ 更好
tsx
const PlayerOnly: React.FC<{
playerRef: React.RefObject<PlayerRef>;
}> = ({ playerRef }) => {
return <Player ref={playerRef} component={MyVideo} {...otherProps} />;
};
 
const ControlsOnly: React.FC<{
playerRef: React.RefObject<PlayerRef>;
}> = ({ playerRef }) => {
const [currentTime, setCurrentTime] = useState(0);
 
useEffect(() => {
playerRef.current?.addEventListener("timeupdate", (e) => {
setCurrentTime(e.detail.frame);
});
}, []);
 
return <div>Current time: {currentTime}</div>;
};
 
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
 
return (
<>
<PlayerOnly playerRef={playerRef} />
<ControlsOnly playerRef={playerRef} />
</>
);
};

这样做更高效,因为 <Player> 的重新渲染次数更少。

这个建议主要针对像当前时间这样频繁更新的状态。在父组件中保留像 loop 切换这样的状态是可以的,因为不会经常更改。

这里可以找到一个更复杂的示例。

将用户交互事件传递给 play()

当您监听 onClick() 事件时,浏览器会给您一个 event 参数。
将其传递给 .play().toggle() 可以最大程度地减少浏览器可能施加的自动播放限制。

点击这里了解更多详情。

inputProps 进行记忆化处理

不对 inputProps 进行记忆化处理会导致整个树重新渲染太多次,从而创建瓶颈。

Player.tsx
tsx
import { Player } from "@remotion/player";
import { useState, useMemo } from "react";
import { MyVideo } from "./remotion/MyVideo";
 
export const App: React.FC = () => {
const [text, setText] = useState("world");
const inputProps = useMemo(() => {
return {
text,
};
}, [text]);
 
return (
<Player
component={MyVideo}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
inputProps={inputProps}
/>
);
};
Player.tsx
tsx
import { Player } from "@remotion/player";
import { useState, useMemo } from "react";
import { MyVideo } from "./remotion/MyVideo";
 
export const App: React.FC = () => {
const [text, setText] = useState("world");
const inputProps = useMemo(() => {
return {
text,
};
}, [text]);
 
return (
<Player
component={MyVideo}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
inputProps={inputProps}
/>
);
};

参见