How to Use the useBufferState Hook for Media Buffering in Remotion
Use the useBufferState hook to pause Remotion playback during async operations by calling delayPlayback(), which returns an unblock() function to resume the timeline once your data or assets are ready.
The useBufferState hook is a lightweight React utility exposed by the remotion core package in the remotion-dev/remotion repository. It provides fine-grained control over the buffering lifecycle, allowing components to temporarily halt video rendering while performing asynchronous work such as fetching remote data or preloading heavy assets.
Core Architecture of the Buffering System
Understanding how useBufferState interacts with Remotion's internal buffering infrastructure helps you implement it correctly in your projects.
BufferingContext and the Buffer Manager
At the heart of the system is BufferingContextReact, defined in [packages/core/src/buffering.tsx](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/buffering.tsx). This context provides a buffer manager object with four key capabilities:
addBlock(block)– Registers a blocking operation and returns anunblockfunction.listenForBuffering(callback)– Notifies components when at least one block is active.listenForResume(callback)– Notifies components when the last block is cleared.buffering– A mutable ref reflecting the current buffering state.
The BufferingProvider component (also in buffering.tsx) wraps your Remotion tree and ensures all nested components share the same buffer manager instance.
The useBufferState Implementation
The hook itself lives in [packages/core/src/use-buffer-state.ts](https://github.com/remotion-dev/remotion/blob/main/packages/core/src/use-buffer-state.ts). It consumes BufferingContextReact and returns a memoized API:
{
delayPlayback: () => DelayPlaybackHandle
}
When you invoke delayPlayback(), the hook calls addBlock on the context. If used outside a Remotion <Composition> or <Player>, it throws a clear error explaining the missing context.
The returned DelayPlaybackHandle contains an unblock() method. Calling this removes the block from the manager, allowing playback to continue if no other blocks remain.
Playback Flow and State Transitions
The buffering lifecycle operates through a strict state machine:
- Enter Buffering – When the first block is added via
delayPlayback(), internal effects fireonBufferingCallbacks, triggering the Player to pause rendering and display the buffering UI. - Resume – When
unblock()removes the final active block, a layout effect detects the empty block list and firesonResumeCallbacks, allowing the Player to resume timeline progression.
The Player connects to this system via [packages/player/src/use-buffer-state-emitter.ts](https://github.com/remotion-dev/remotion/blob/main/packages/player/src/use-buffer-state-emitter.ts), which bridges the core buffering context with the Player's UI state.
When to Use useBufferState for Media Buffering
Implement useBufferState when your Remotion composition depends on operations that must complete before the timeline can safely advance:
- Data-driven scenes – Fetch remote JSON, configuration files, or API responses that determine visual content or animation parameters.
- Asset preloading – Ensure high-resolution images, video files, or audio buffers are fully decoded and ready in memory before rendering begins.
- Expensive computations – Perform heavy canvas drawing, WebGL initialization, or complex data transformations that would cause frame drops if executed during active playback.
- Conditional rendering – Pause the timeline until user interactions, external API webhooks, or real-time data streams satisfy specific runtime conditions.
Implementation Examples
Basic Usage: Pausing Playback for Async Data
The standard pattern involves calling delayPlayback() at the start of an async operation and unblock() in a finally block to guarantee playback resumes even if errors occur.
import {useBufferState} from 'remotion';
import React, {useEffect, useState} from 'react';
export const MyScene = () => {
const {delayPlayback} = useBufferState();
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const {unblock} = delayPlayback(); // Pause timeline
try {
const response = await fetch('/api/scene-data');
const result = await response.json();
setData(result);
} finally {
unblock(); // Resume timeline
}
};
fetchData();
}, [delayPlayback]);
if (!data) return <div>Loading scene data…</div>;
return <div>{data.title}</div>;
};
When the component mounts, delayPlayback() registers a block. The Player remains in the buffer state until unblock() is called after the fetch completes.
Creating a Buffered Image Component
Wrap Remotion's Img component to ensure images are fully decoded before the timeline advances. This prevents visual pop-in during video export.
import {Img, useBufferState} from 'remotion';
import React, {useEffect, useState} from 'react';
export const BufferedImage = ({src, ...props}) => {
const {delayPlayback} = useBufferState();
const [isReady, setIsReady] = useState(false);
useEffect(() => {
const preload = async () => {
const {unblock} = delayPlayback();
const image = new Image();
image.src = src;
await new Promise((resolve) => {
image.onload = resolve;
image.onerror = resolve; // Proceed on error to avoid deadlock
});
setIsReady(true);
unblock();
};
preload();
}, [src, delayPlayback]);
if (!isReady) return null;
return <Img src={src} {...props} />;
};
Monitoring Buffer State Changes
For custom UI indicators or analytics, subscribe to buffering events using the internal context. This example demonstrates how to build a visual buffer indicator.
import {useContext, useEffect, useState} from 'react';
import {BufferingContextReact, useIsPlayerBuffering} from 'remotion';
export const BufferIndicator = () => {
const bufferManager = useContext(BufferingContextReact);
if (!bufferManager) {
throw new Error('BufferIndicator must be used inside a Remotion Player');
}
const isBuffering = useIsPlayerBuffering(bufferManager);
const [bufferCount, setBufferCount] = useState(0);
useEffect(() => {
// Listen for buffering events for analytics
const unsubscribe = bufferManager.listenForBuffering(() => {
setBufferCount(c => c + 1);
console.log('Playback paused for buffering');
});
return unsubscribe;
}, [bufferManager]);
if (!isBuffering) return null;
return (
<div style={{position: 'absolute', top: 20, left: 20}}>
⏸️ Buffering… (Events: {bufferCount})
</div>
);
};
Key Source Files
The buffering system is implemented across these locations in the remotion-dev/remotion repository:
| File | Role |
|---|---|
packages/core/src/use-buffer-state.ts |
Exposes the useBufferState hook; handles registration of delay blocks and returns the unblock handle. |
packages/core/src/buffering.tsx |
Defines BufferingContextReact, BufferingProvider, and the internal buffer manager with addBlock, listenForBuffering, and listenForResume methods. |
packages/player/src/use-buffer-state-emitter.ts |
Bridges the core buffering context with the Player UI, emitting events that drive the on-screen buffer indicator. |
packages/docs/docs/use-buffer-state.mdx |
Official documentation and usage guidelines. |
Summary
useBufferStateis a React hook in Remotion's core package that enables programmatic control over media buffering states.- It returns a
delayPlayback()function that registers a blocking operation; the returnedunblock()function removes the block and resumes playback. - The hook relies on
BufferingContextReact(defined inpackages/core/src/buffering.tsx) to manage the buffer state machine and coordinate with the Player UI. - Multiple simultaneous blocks are supported; playback only resumes when all active blocks are cleared.
- Use it for data fetching, asset preloading, expensive computations, or any scenario where the timeline must wait for async operations to complete.
Frequently Asked Questions
What happens if I call delayPlayback() outside a Remotion Composition?
The useBufferState hook throws a clear runtime error indicating that the buffering context is missing. This occurs because the hook attempts to access BufferingContextReact, which is only provided when your component is wrapped in a Remotion <Composition> or <Player>. Always ensure you are inside the Remotion tree before calling this hook.
Can I register multiple simultaneous buffer blocks?
Yes. The buffer manager in packages/core/src/buffering.tsx maintains a list of active blocks. Each call to delayPlayback() adds a new block to this list. Playback only resumes when all blocks have been cleared by calling their respective unblock() functions. This allows independent components to manage their own async lifecycles without interfering with each other.
How does useBufferState differ from useDelayRender?
While both hooks pause rendering, they serve different architectural purposes. useDelayRender is designed for preloading assets during the initial render phase and automatically manages a "loading" state for the entire composition. useBufferState gives you manual, granular control over the buffering state at any point in the component lifecycle, making it ideal for user-triggered events, dynamic data fetching, or conditional async operations that occur after initial mount.
Is there a performance cost to using useBufferState?
The hook itself has negligible overhead—it simply registers a callback in a React context and returns a memoized function. However, the actual performance impact depends on how long you keep the buffer active. Extended buffering periods will delay video rendering and increase memory usage because Remotion keeps the current frame in memory waiting for your unblock() call. Always minimize the duration between delayPlayback() and unblock() to maintain smooth rendering performance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →