Skip to main content

<OffthreadVideo>

Available from Remotion 3.0.11

This component imports and displays a video, similar to <Video/>, but during rendering, extracts the exact frame from the video and displays it in a <Img> tag. This extraction process happens outside the browser using FFMPEG.

This component was designed to combat limitations of the default <Video> element. See: <Video> vs <OffthreadVideo>.

Example

tsx
import { AbsoluteFill, OffthreadVideo, staticFile } from "remotion";
 
export const MyVideo = () => {
return (
<AbsoluteFill>
<OffthreadVideo src={staticFile("video.webm")} />
</AbsoluteFill>
);
};
tsx
import { AbsoluteFill, OffthreadVideo, staticFile } from "remotion";
 
export const MyVideo = () => {
return (
<AbsoluteFill>
<OffthreadVideo src={staticFile("video.webm")} />
</AbsoluteFill>
);
};

You can load a video from an URL as well:

tsx
export const MyComposition = () => {
return (
<AbsoluteFill>
<OffthreadVideo src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" />
</AbsoluteFill>
);
};
tsx
export const MyComposition = () => {
return (
<AbsoluteFill>
<OffthreadVideo src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" />
</AbsoluteFill>
);
};

Props

The props volume, playbackRate, muted and acceptableTimeShiftInSeconds are supported and work the same as in <Video>.

The props onError, className and style are supported and get passed to the underlying HTML element. Remember that during render, this is a <img> element, and during preview, this is a <video> element.

imageFormat

Available since v3.0.22

Either jpeg or png. Default jpeg.
With png, transparent videos (VP8, VP9, ProRes) can be displayed, however it is around 40% slower, with VP8 videos being much slower.

Performance tips

Avoid embedding a video beyond it's end (for example: Rendering a 5 second video inside 10 second composition). To create parity with the <Video> element, the video still displays its last frame in that case. However, to fetch the last frame specifically is a significantly more expensive operation than a frame from a known timestamp.

Looping a video

Unlike <Video>, OffthreadVideo does not currently implement the loop property. You can use the following snippet that uses @remotion/media-utils to loop a video.

LoopedOffthreadVideo.tsx
tsx
import { getVideoMetadata } from "@remotion/media-utils";
import React, { useEffect, useState } from "react";
import {
continueRender,
delayRender,
Loop,
OffthreadVideo,
staticFile,
useVideoConfig,
} from "remotion";
 
const src = staticFile("myvideo.mp4");
 
export const LoopedOffthreadVideo: React.FC = () => {
const [duration, setDuration] = useState<null | number>(null);
const [handle] = useState(() => delayRender());
const { fps } = useVideoConfig();
 
useEffect(() => {
getVideoMetadata(src)
.then(({ durationInSeconds }) => {
setDuration(durationInSeconds);
continueRender(handle);
})
.catch((err) => {
console.log(err);
});
}, [handle]);
 
if (duration === null) {
return null;
}
 
return (
<Loop durationInFrames={Math.floor(fps * duration)}>
<OffthreadVideo src={src} />
</Loop>
);
};
LoopedOffthreadVideo.tsx
tsx
import { getVideoMetadata } from "@remotion/media-utils";
import React, { useEffect, useState } from "react";
import {
continueRender,
delayRender,
Loop,
OffthreadVideo,
staticFile,
useVideoConfig,
} from "remotion";
 
const src = staticFile("myvideo.mp4");
 
export const LoopedOffthreadVideo: React.FC = () => {
const [duration, setDuration] = useState<null | number>(null);
const [handle] = useState(() => delayRender());
const { fps } = useVideoConfig();
 
useEffect(() => {
getVideoMetadata(src)
.then(({ durationInSeconds }) => {
setDuration(durationInSeconds);
continueRender(handle);
})
.catch((err) => {
console.log(err);
});
}, [handle]);
 
if (duration === null) {
return null;
}
 
return (
<Loop durationInFrames={Math.floor(fps * duration)}>
<OffthreadVideo src={src} />
</Loop>
);
};

See also