Build a timeline-based video editor
This document describes on a high-level how the Remotion Player can be synchronized with a timeline.
Read this document for guidance on building a video editor with the following characteristics:
- Multiple tracks that overlay each other
- Items can be arbitrarily placed on a track
- Items can be of different types (e.g. video, audio, text, etc.)
Item
defining the different
item types. Create another one for defining the shape of a Track
:
types.tstsx
typeBaseItem = {from : number;durationInFrames : number;id : string;};export typeSolidItem =BaseItem & {type : 'solid';color : string;};export typeTextItem =BaseItem & {type : 'text';text : string;color : string;};export typeVideoItem =BaseItem & {type : 'video';src : string;};export typeItem =SolidItem |TextItem |VideoItem ;export typeTrack = {name : string;items :Item [];};
types.tstsx
typeBaseItem = {from : number;durationInFrames : number;id : string;};export typeSolidItem =BaseItem & {type : 'solid';color : string;};export typeTextItem =BaseItem & {type : 'text';text : string;color : string;};export typeVideoItem =BaseItem & {type : 'video';src : string;};export typeItem =SolidItem |TextItem |VideoItem ;export typeTrack = {name : string;items :Item [];};
remotion/Main.tsxtsx
import type {Track ,Item } from './types';importReact from 'react';import {AbsoluteFill ,Sequence ,OffthreadVideo } from 'remotion';constItemComp :React .FC <{item :Item ;}> = ({item }) => {if (item .type === 'solid') {return <AbsoluteFill style ={{backgroundColor :item .color }} />;}if (item .type === 'text') {return <h1 >{item .text }</h1 >;}if (item .type === 'video') {return <OffthreadVideo src ={item .src } />;}throw newError (`Unknown item type: ${JSON .stringify (item )}`);};constTrack :React .FC <{track :Track ;}> = ({track }) => {return (<AbsoluteFill >{track .items .map ((item ) => {return (<Sequence key ={item .id }from ={item .from }durationInFrames ={item .durationInFrames }><ItemComp item ={item } /></Sequence >);})}</AbsoluteFill >);};export constMain :React .FC <{tracks :Track [];}> = ({tracks }) => {return (<AbsoluteFill >{tracks .map ((track ) => {return <Track track ={track }key ={track .name } />;})}</AbsoluteFill >);};
remotion/Main.tsxtsx
import type {Track ,Item } from './types';importReact from 'react';import {AbsoluteFill ,Sequence ,OffthreadVideo } from 'remotion';constItemComp :React .FC <{item :Item ;}> = ({item }) => {if (item .type === 'solid') {return <AbsoluteFill style ={{backgroundColor :item .color }} />;}if (item .type === 'text') {return <h1 >{item .text }</h1 >;}if (item .type === 'video') {return <OffthreadVideo src ={item .src } />;}throw newError (`Unknown item type: ${JSON .stringify (item )}`);};constTrack :React .FC <{track :Track ;}> = ({track }) => {return (<AbsoluteFill >{track .items .map ((item ) => {return (<Sequence key ={item .id }from ={item .from }durationInFrames ={item .durationInFrames }><ItemComp item ={item } /></Sequence >);})}</AbsoluteFill >);};export constMain :React .FC <{tracks :Track [];}> = ({tracks }) => {return (<AbsoluteFill >{tracks .map ((track ) => {return <Track track ={track }key ={track .name } />;})}</AbsoluteFill >);};
In CSS, the elements that are rendered at the bottom appear at the top. See: Layers
Render
a <Player />
component and pass the tracks
as inputProps
.
Editor.tsxtsx
importReact , {useMemo ,useState } from 'react';import {Player } from '@remotion/player';import type {Item } from './types';import {Main } from './remotion/Main';typeTrack = {name : string;items :Item [];};export constEditor = () => {const [tracks ,setTracks ] =useState <Track []>([{name : 'Track 1',items : []},{name : 'Track 2',items : []},]);constinputProps =useMemo (() => {return {tracks ,};}, [tracks ]);return (<><Player component ={Main }fps ={30}inputProps ={inputProps }durationInFrames ={600}compositionWidth ={1280}compositionHeight ={720}/></>);};
Editor.tsxtsx
importReact , {useMemo ,useState } from 'react';import {Player } from '@remotion/player';import type {Item } from './types';import {Main } from './remotion/Main';typeTrack = {name : string;items :Item [];};export constEditor = () => {const [tracks ,setTracks ] =useState <Track []>([{name : 'Track 1',items : []},{name : 'Track 2',items : []},]);constinputProps =useMemo (() => {return {tracks ,};}, [tracks ]);return (<><Player component ={Main }fps ={30}inputProps ={inputProps }durationInFrames ={600}compositionWidth ={1280}compositionHeight ={720}/></>);};
tracks
state and can update it using the setTracks
function.
We do not currently provide samples how to build a timeline component, since everybody has different needs and styling preferences.
An opinionated sample implementation is available for purchase in the Remotion Store.
remotion/Timeline.tsxtsx
constEditor :React .FC = () => {const [tracks ,setTracks ] =useState <Track []>([{name : 'Track 1',items : []},{name : 'Track 2',items : []},]);constinputProps =useMemo (() => {return {tracks ,};}, [tracks ]);return (<><Player component ={Main }fps ={30}inputProps ={inputProps }durationInFrames ={600}compositionWidth ={1280}compositionHeight ={720}/><Timeline tracks ={tracks }setTracks ={setTracks } /></>);};
remotion/Timeline.tsxtsx
constEditor :React .FC = () => {const [tracks ,setTracks ] =useState <Track []>([{name : 'Track 1',items : []},{name : 'Track 2',items : []},]);constinputProps =useMemo (() => {return {tracks ,};}, [tracks ]);return (<><Player component ={Main }fps ={30}inputProps ={inputProps }durationInFrames ={600}compositionWidth ={1280}compositionHeight ={720}/><Timeline tracks ={tracks }setTracks ={setTracks } /></>);};