Skip to main content

显示缓冲状态

warning

自 Remotion v4.0.111 起,Remotion 具有 本机缓冲状态。本页描述的技术仅适用于 Remotion 的旧版本。

在您的 <Player> 中,您可能会有视频和其他资产,这些资产在进入场景后可能需要一些时间来加载。 您可以预加载这些资产,但有时浏览器策略会阻止预加载,并且在浏览器需要解码视频之前播放时可能会出现短暂的闪烁

在这种情况下,如果媒体正在加载,您可能希望暂停播放器并显示一个旋转图标,一旦媒体准备好播放,就恢复视频播放。这可以使用常规 Web API 和 React 原语来实现。

参考应用程序

访问此 GitHub 存储库 查看此技术的完全功能示例。

实现缓冲状态

我们创建一个新的 React 上下文,可以处理播放器内媒体的缓冲状态。我们实现了默认函数,因为在渲染期间不需要缓冲状态。

BufferManager.tsx
tsx
import { createContext } from "react";
type BufferState = { [key: string]: boolean };
type BufferContextType = {
canPlay: (id: string) => void;
needsToBuffer: (id: string) => void;
};
export const BufferContext = createContext<BufferContextType>({
// By default, do nothing if the context is not set, for example in rendering
canPlay: () => {},
needsToBuffer: () => {},
});
BufferManager.tsx
tsx
import { createContext } from "react";
type BufferState = { [key: string]: boolean };
type BufferContextType = {
canPlay: (id: string) => void;
needsToBuffer: (id: string) => void;
};
export const BufferContext = createContext<BufferContextType>({
// By default, do nothing if the context is not set, for example in rendering
canPlay: () => {},
needsToBuffer: () => {},
});

可以将以下组件包装在播放器周围,以为其提供 onBufferonContinue 函数。通过使用上下文,我们不必将这些函数作为 props 传递给每个媒体元素,尽管这也是可能的。

如果一个媒体元素正在缓冲,它可以使用 onBuffer() 将其注册到管理器中。如果所有媒体元素都已加载,缓冲管理器将调用 onContinue() 事件。

BufferManager.tsx
tsx
import { useCallback, useMemo, useRef } from "react";
 
export const BufferManager: React.FC<{
children: React.ReactNode;
onBuffer: () => void;
onContinue: () => void;
}> = ({ children, onBuffer, onContinue }) => {
const bufferState = useRef<BufferState>({});
const currentState = useRef(false);
 
const sendEvents = useCallback(() => {
let previousState = currentState.current;
currentState.current = Object.values(bufferState.current).some(Boolean);
 
if (currentState.current && !previousState) {
onBuffer();
} else if (!currentState.current && previousState) {
onContinue();
}
}, [onBuffer, onContinue]);
 
const canPlay = useCallback(
(id: string) => {
bufferState.current[id] = false;
sendEvents();
},
[sendEvents],
);
 
const needsToBuffer = useCallback(
(id: string) => {
bufferState.current[id] = true;
sendEvents();
},
[sendEvents],
);
 
const bufferEvents = useMemo(() => {
return {
canPlay,
needsToBuffer,
};
}, [canPlay, needsToBuffer]);
 
return (
<BufferContext.Provider value={bufferEvents}>
{children}
</BufferContext.Provider>
);
};
BufferManager.tsx
tsx
import { useCallback, useMemo, useRef } from "react";
 
export const BufferManager: React.FC<{
children: React.ReactNode;
onBuffer: () => void;
onContinue: () => void;
}> = ({ children, onBuffer, onContinue }) => {
const bufferState = useRef<BufferState>({});
const currentState = useRef(false);
 
const sendEvents = useCallback(() => {
let previousState = currentState.current;
currentState.current = Object.values(bufferState.current).some(Boolean);
 
if (currentState.current && !previousState) {
onBuffer();
} else if (!currentState.current && previousState) {
onContinue();
}
}, [onBuffer, onContinue]);
 
const canPlay = useCallback(
(id: string) => {
bufferState.current[id] = false;
sendEvents();
},
[sendEvents],
);
 
const needsToBuffer = useCallback(
(id: string) => {
bufferState.current[id] = true;
sendEvents();
},
[sendEvents],
);
 
const bufferEvents = useMemo(() => {
return {
canPlay,
needsToBuffer,
};
}, [canPlay, needsToBuffer]);
 
return (
<BufferContext.Provider value={bufferEvents}>
{children}
</BufferContext.Provider>
);
};

使 <Video> 报告缓冲

以下组件 <PausableVideo> 包装了 <Video> 标签,因此您可以使用它来替代它。它获取了我们之前定义的上下文,并向 BufferManager 报告视频的缓冲和恢复。

如果您正在使用<OffthreadVideo>,则不能将引用附加到它上。
使用此技术 仅在渲染期间使用<OffthreadVideo>

将您 Remotion 组件中的 <Video> 元素替换为 <PausableVideoFunction>,以使其报告缓冲状态。

暂停视频并显示加载 UI

将您的播放器包装在新创建的 <BufferManager> 中。创建两个函数 onBufferonContinue,实现视频进入缓冲状态时应该发生的情况。将它们传递给 <BufferManager>

在此示例中,正在使用引用来跟踪视频是否因缓冲而暂停,以便在这种情况下仅恢复视频。
通过使用引用,我们消除了异步 React 状态导致竞争条件的风险。

App.tsx
tsx
import { Player, PlayerRef } from "@remotion/player";
import React, { useState, useRef, useCallback } from "react";
import { BufferManager } from "./BufferManager";
 
function App() {
const playerRef = useRef<PlayerRef>(null);
const [buffering, setBuffering] = useState(false);
const pausedBecauseOfBuffering = useRef(false);
 
const onBuffer = useCallback(() => {
setBuffering(true);
 
playerRef.current?.pause();
pausedBecauseOfBuffering.current = true;
}, []);
 
const onContinue = useCallback(() => {
setBuffering(false);
 
// Play only if we paused because of buffering
if (pausedBecauseOfBuffering.current) {
pausedBecauseOfBuffering.current = false;
playerRef.current?.play();
}
}, []);
 
return (
<BufferManager onBuffer={onBuffer} onContinue={onContinue}>
<Player
ref={playerRef}
component={MyComp}
compositionHeight={720}
compositionWidth={1280}
durationInFrames={200}
fps={30}
controls
/>
</BufferManager>
);
}
 
export default App;
App.tsx
tsx
import { Player, PlayerRef } from "@remotion/player";
import React, { useState, useRef, useCallback } from "react";
import { BufferManager } from "./BufferManager";
 
function App() {
const playerRef = useRef<PlayerRef>(null);
const [buffering, setBuffering] = useState(false);
const pausedBecauseOfBuffering = useRef(false);
 
const onBuffer = useCallback(() => {
setBuffering(true);
 
playerRef.current?.pause();
pausedBecauseOfBuffering.current = true;
}, []);
 
const onContinue = useCallback(() => {
setBuffering(false);
 
// Play only if we paused because of buffering
if (pausedBecauseOfBuffering.current) {
pausedBecauseOfBuffering.current = false;
playerRef.current?.play();
}
}, []);
 
return (
<BufferManager onBuffer={onBuffer} onContinue={onContinue}>
<Player
ref={playerRef}
component={MyComp}
compositionHeight={720}
compositionWidth={1280}
durationInFrames={200}
fps={30}
controls
/>
</BufferManager>
);
}
 
export default App;

除了暂停视频外,您还可以显示自定义 UI,该 UI 将在视频缓冲时覆盖视频。通常,您会显示一个品牌化的旋转器,在这个简化的示例中,我们显示了一个 ⏳ 表情符号。

App.tsx
tsx
import { Player, RenderPoster } from "@remotion/player";
import { useCallback, useState } from "react";
import { AbsoluteFill } from "remotion";
 
function App() {
const [buffering, setBuffering] = useState();
 
// Add this to your component rendering the <Player>
const renderPoster: RenderPoster = useCallback(() => {
if (buffering) {
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
fontSize: 100,
}}
>
</AbsoluteFill>
);
}
 
return null;
}, [buffering]);
 
return (
<Player
fps={30}
component={MyComp}
compositionHeight={720}
compositionWidth={1280}
durationInFrames={200}
// Add these two props to the Player
showPosterWhenPaused
renderPoster={renderPoster}
/>
);
}
App.tsx
tsx
import { Player, RenderPoster } from "@remotion/player";
import { useCallback, useState } from "react";
import { AbsoluteFill } from "remotion";
 
function App() {
const [buffering, setBuffering] = useState();
 
// Add this to your component rendering the <Player>
const renderPoster: RenderPoster = useCallback(() => {
if (buffering) {
return (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
fontSize: 100,
}}
>
</AbsoluteFill>
);
}
 
return null;
}, [buffering]);
 
return (
<Player
fps={30}
component={MyComp}
compositionHeight={720}
compositionWidth={1280}
durationInFrames={200}
// Add these two props to the Player
showPosterWhenPaused
renderPoster={renderPoster}
/>
);
}

另请参阅