packages/docs/docs/videos/sequence.mdx
To play multiple videos one after another:
<Step>1</Step> Render each <Video> in a <Series.Sequence>.
<Step>2</Step> Use Mediabunny to determine the duration of each video.
<Step>3</Step> Return the durations and their sum from calculateMetadata().
Render each <Video> in a <Series.Sequence> and set its durationInFrames:
import {Video} from '@remotion/media';
import React from 'react';
import {Series, useVideoConfig} from 'remotion';
export type VideoToEmbed = {
src: string;
durationInFrames: number | null;
};
export type VideosInSequenceProps = {
videos: VideoToEmbed[];
};
export const VideosInSequence: React.FC<VideosInSequenceProps> = ({videos}) => {
const {fps} = useVideoConfig();
return (
<Series>
{videos.map((video) => {
if (video.durationInFrames === null) {
throw new Error('Could not get video duration');
}
return (
<Series.Sequence
key={video.src}
durationInFrames={video.durationInFrames}
premountFor={fps}
>
<Video src={video.src} />
</Series.Sequence>
);
})}
</Series>
);
};
premountFor mounts each video one second before it enters the timeline, giving it time to load in the Player.
Create a calculateMetadata() function that gets the duration of each video using Mediabunny's computeDuration(), converts the durations to frames, and returns their sum as the composition duration:
// @filename: VideosInSequence.tsx
export type VideosInSequenceProps = {
videos: {src: string; durationInFrames: number | null}[];
};
// @filename: calculate-metadata.ts
// ---cut---
import {ALL_FORMATS, Input, UrlSource} from 'mediabunny';
import type {CalculateMetadataFunction} from 'remotion';
import type {VideosInSequenceProps} from './VideosInSequence';
const getVideoDuration = async (src: string) => {
const input = new Input({
formats: ALL_FORMATS,
source: new UrlSource(src, {
getRetryDelay: () => null,
}),
});
return input.computeDuration();
};
export const calculateMetadata: CalculateMetadataFunction<
VideosInSequenceProps
> = async ({props}) => {
const fps = 30;
const videos = await Promise.all(
props.videos.map(async (video) => {
const durationInSeconds = await getVideoDuration(video.src);
return {
...video,
durationInFrames: Math.floor(durationInSeconds * fps),
};
}),
);
const durationInFrames = videos.reduce(
(sum, video) => sum + video.durationInFrames,
0,
);
return {
durationInFrames,
fps,
props: {
...props,
videos,
},
};
};
Mediabunny loads the assets using fetch(). Remote URLs must be CORS-enabled; files referenced with staticFile() are served from the same origin.
In the root file, register the component and pass the videos through defaultProps:
// @filename: VideosInSequence.tsx
import React from 'react';
export type VideosInSequenceProps = {
videos: {src: string; durationInFrames: number | null}[];
};
export const VideosInSequence: React.FC<VideosInSequenceProps> = () => null;
// @filename: calculate-metadata.ts
import type {CalculateMetadataFunction} from 'remotion';
import type {VideosInSequenceProps} from './VideosInSequence';
export const calculateMetadata: CalculateMetadataFunction<
VideosInSequenceProps
> = async ({props}) => ({durationInFrames: 1, props});
// @filename: Root.tsx
// ---cut---
import React from 'react';
import {Composition, staticFile} from 'remotion';
import {calculateMetadata} from './calculate-metadata';
import {VideosInSequence} from './VideosInSequence';
export const RemotionRoot: React.FC = () => {
return (
<Composition
id="VideosInSequence"
component={VideosInSequence}
width={1920}
height={1080}
defaultProps={{
videos: [
{
durationInFrames: null,
src: 'https://remotion.media/BigBuckBunny.mp4',
},
{
durationInFrames: null,
src: staticFile('localvideo.mp4'),
},
],
}}
calculateMetadata={calculateMetadata}
/>
);
};
Mobile browsers may block videos that start after the beginning of the composition. See the notes about browser autoplay behavior for the available options.