Skip to main content

播放器缓冲状态

从4.0.111版本开始可用

就像常规视频播放器一样,播放器中显示的内容可能尚未完全加载。
在这种情况下,最佳实践是暂停视频,让内容加载完毕,然后恢复播放。

Remotion具有本机缓冲状态,可以在缓冲区为空时暂停视频。

简而言之

您可以为<Video><OffthreadVideo><Audio>标签添加pauseWhenBuffering属性。
对于<Img>标签,该属性称为pauseWhenLoading
通过这样做,播放器将在媒体加载完毕前暂停一小段时间。

机制

激活缓冲状态

组件可以通过首先使用useBufferState()钩子,然后调用buffer.delayPlayback()来告诉播放器切换到缓冲状态:

MyComp.tsx
tsx
import React from 'react';
import {useBufferState} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
 
React.useEffect(() => {
const delayHandle = buffer.delayPlayback();
 
setTimeout(() => {
delayHandle.unblock();
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return null;
};
MyComp.tsx
tsx
import React from 'react';
import {useBufferState} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
 
React.useEffect(() => {
const delayHandle = buffer.delayPlayback();
 
setTimeout(() => {
delayHandle.unblock();
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return null;
};

要清除句柄,请在delayPlayback()的返回值上调用.unblock()

在激活缓冲状态时,请注意以下事项:

组件卸载时清除句柄

用户可能会寻找视频的不同部分,这些部分可能会立即可用。
使用useEffect()的清理函数在组件卸载时清除句柄。

❌ 与React严格模式冲突
tsx
import React, {useState} from 'react';
import {useBufferState} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const [delayHandle] = useState(() => buffer.delayPlayback()); // 💥
 
React.useEffect(() => {
setTimeout(() => {
delayHandle.unblock();
}, 5000);
}, []);
 
return <></>;
};
❌ 与React严格模式冲突
tsx
import React, {useState} from 'react';
import {useBufferState} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const [delayHandle] = useState(() => buffer.delayPlayback()); // 💥
 
React.useEffect(() => {
setTimeout(() => {
delayHandle.unblock();
}, 5000);
}, []);
 
return <></>;
};
不要在useState()内部使用delayPlayback()

虽然以下实现在生产环境中有效,但在React严格模式下会失败,因为useState()钩子被调用两次,导致第一次调用的缓冲永远不会被清除。

❌ 当寻找视频的不同部分时不清除缓冲句柄
tsx
import React, {useState} from 'react';
import {useBufferState} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const [delayHandle] = useState(() => buffer.delayPlayback()); // 💥
 
React.useEffect(() => {
setTimeout(() => {
delayHandle.unblock();
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return <></>;
};
❌ 当寻找视频的不同部分时不清除缓冲句柄
tsx
import React, {useState} from 'react';
import {useBufferState} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const [delayHandle] = useState(() => buffer.delayPlayback()); // 💥
 
React.useEffect(() => {
setTimeout(() => {
delayHandle.unblock();
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return <></>;
};
Details

它不会替换delayRender() delayRender()是一个不同的API,它控制了在渲染过程中何时拍摄屏幕截图。

如果您正在加载数据,您可能希望在渲染过程中延迟组件的截图,并延迟预览视频的播放,在这种情况下,您需要同时使用这两个API。

同时使用delayRender()和delayPlayback()
tsx
import React from 'react';
import {useBufferState, delayRender, continueRender} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const [handle] = React.useState(() => delayRender());
 
React.useEffect(() => {
const delayHandle = buffer.delayPlayback();
 
setTimeout(() => {
delayHandle.unblock();
continueRender(handle);
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return <></>;
};
同时使用delayRender()和delayPlayback()
tsx
import React from 'react';
import {useBufferState, delayRender, continueRender} from 'remotion';
 
const MyComp: React.FC = () => {
const buffer = useBufferState();
const [handle] = React.useState(() => delayRender());
 
React.useEffect(() => {
const delayHandle = buffer.delayPlayback();
 
setTimeout(() => {
delayHandle.unblock();
continueRender(handle);
}, 5000);
 
return () => {
delayHandle.unblock();
};
}, []);
 
return <></>;
};

可能的状态

播放器是否正在缓冲不会在内部更改playing / paused状态。
因此,播放器可以处于四种播放状态:

1
playing && !buffering
2
playing && buffering
3
paused && !buffering
4
paused && buffering

只有在状态

1
时时间才会向前推进。


默认情况下,Remotion将根据播放器的状态显示以下UI:

当处于状态

1
时,会显示一个暂停按钮。

当处于状态

2
时,首先显示一个暂停按钮,然后经过 延迟后,显示一个 可自定义的旋转器

否则,将显示播放按钮。

您可以向此添加其他UI,例如在播放器缓冲时在播放器上叠加一个旋转器。

监听缓冲事件

如果<Player />进入缓冲状态,它将发出waiting事件。
一旦恢复,它会发出resume事件。

监听waiting和resume事件
tsx
import {Player, PlayerRef} from '@remotion/player';
import {useEffect, useRef, useState} from 'react';
import {MyVideo} from './remotion/MyVideo';
 
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
const [buffering, setBuffering] = useState(false);
 
useEffect(() => {
const {current} = playerRef;
if (!current) {
return;
}
 
const onBuffering = () => {
setBuffering(true);
};
const onResume = () => {
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}
/>
);
};
监听waiting和resume事件
tsx
import {Player, PlayerRef} from '@remotion/player';
import {useEffect, useRef, useState} from 'react';
import {MyVideo} from './remotion/MyVideo';
 
export const App: React.FC = () => {
const playerRef = useRef<PlayerRef>(null);
const [buffering, setBuffering] = useState(false);
 
useEffect(() => {
const {current} = playerRef;
if (!current) {
return;
}
 
const onBuffering = () => {
setBuffering(true);
};
const onResume = () => {
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}
/>
);
};

具有内置缓冲的组件

您可以在以下组件上启用缓冲:

在 UI 中指示缓冲

当播放器正在缓冲时,默认情况下,播放按钮将被一个旋转器替换。
为了防止 UI 闪烁,此旋转器只会在播放器处于缓冲状态超过 300 毫秒后显示。

您可以通过将 bufferStateDelayInMilliseconds 属性传递给 <Player /> 组件来自定义 300 毫秒的超时时间。

设置显示旋转器的延迟时间
tsx
import {Player, PlayerRef} from '@remotion/player';
import {useEffect, useRef, useState} from 'react';
import {MyVideo} from './remotion/MyVideo';
 
export const App: 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
/>
);
};
设置显示旋转器的延迟时间
tsx
import {Player, PlayerRef} from '@remotion/player';
import {useEffect, useRef, useState} from 'react';
import {MyVideo} from './remotion/MyVideo';
 
export const App: 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
/>
);
};

在 Studio 中,您可以在 配置文件 中更改延迟:

remotion.config.ts
ts
import {Config} from '@remotion/cli/config';
 
Config.setBufferStateDelayInMilliseconds(0);
remotion.config.ts
ts
import {Config} from '@remotion/cli/config';
 
Config.setBufferStateDelayInMilliseconds(0);

要自定义显示在播放按钮位置的旋转器,您可以传递一个 renderPlayPauseButton() 属性:

在播放按钮内部渲染自定义旋转器
tsx
import {Player, RenderPlayPauseButton} from '@remotion/player';
import {useCallback} from 'react';
 
export const App: React.FC = () => {
const renderPlayPauseButton: 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}
/>
);
};
在播放按钮内部渲染自定义旋转器
tsx
import {Player, RenderPlayPauseButton} from '@remotion/player';
import {useCallback} from 'react';
 
export const App: React.FC = () => {
const renderPlayPauseButton: 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}
/>
);
};

要在播放器上方显示一个叠加的加载 UI(例如旋转器),您可以将 showPosterWhenBuffering 设置为 true 并传递一个 renderPoster() 属性:

在播放器上方渲染自定义旋转器
tsx
import type {RenderPoster} from '@remotion/player';
import {Player} from '@remotion/player';
 
const MyApp: React.FC = () => {
const renderPoster: 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
/>
);
};
在播放器上方渲染自定义旋转器
tsx
import type {RenderPoster} from '@remotion/player';
import {Player} from '@remotion/player';
 
const MyApp: React.FC = () => {
const renderPoster: 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
/>
);
};

Remotion 5.0 中即将发生的更改

在 Remotion 4.0 中,媒体标签如 <Audio><OffthreadVideo> 标签需要选择使用缓冲状态。

在 Remotion 5.0 中,计划让 <Audio><Video><OffthreadVideo> 自动使用缓冲状态,但它们可以选择退出。

另请参阅