Handling user video uploads
In an app where users can upload videos and edit them, we can create a better user experience by loading the video into a player even before the upload is finished. Good news: This can be done pretty easily!
Allowing user uploads
We have a component which returns a <Video>
tag that includes an URL as source.
MyComposition.tsxtsx
import {AbsoluteFill ,Video } from 'remotion';typeVideoProps = {videoURL : string;};export constMyComponent :React .FC <VideoProps > = ({videoURL }) => {return (<AbsoluteFill ><Video src ={videoURL } /></AbsoluteFill >);};
MyComposition.tsxtsx
import {AbsoluteFill ,Video } from 'remotion';typeVideoProps = {videoURL : string;};export constMyComponent :React .FC <VideoProps > = ({videoURL }) => {return (<AbsoluteFill ><Video src ={videoURL } /></AbsoluteFill >);};
The video URL will be passed from the Remotion Player to our component.
Using a <input type="file">
element, we allow a user upload.
As soon as a file is fully uploaded to the cloud, the URL will be set and can be used by the component to display the video.
App.tsxtsx
import {Player } from '@remotion/player';import {useState } from 'react';export constRemotionPlayer :React .FC = () => {const [videoUrl ,setVideoUrl ] =useState <string | null>(null);consthandleChange =useCallback (async (event :React .ChangeEvent <HTMLInputElement >) => {if (event .target .files === null) {return;}constfile =event .target .files [0];//upload is an example function & returns a URL when a file is uploaded on the cloud.constcloudURL = awaitupload (file );// E.g., cloudURL = https://exampleBucketName.s3.ExampleAwsRegion.amazonaws.comsetVideoUrl (cloudURL );},[],);return (<div >{videoUrl === null ? null : (<Player component ={MyComposition }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}inputProps ={{videoUrl }}/>)}<input type ="file"onChange ={handleChange } /></div >);};
App.tsxtsx
import {Player } from '@remotion/player';import {useState } from 'react';export constRemotionPlayer :React .FC = () => {const [videoUrl ,setVideoUrl ] =useState <string | null>(null);consthandleChange =useCallback (async (event :React .ChangeEvent <HTMLInputElement >) => {if (event .target .files === null) {return;}constfile =event .target .files [0];//upload is an example function & returns a URL when a file is uploaded on the cloud.constcloudURL = awaitupload (file );// E.g., cloudURL = https://exampleBucketName.s3.ExampleAwsRegion.amazonaws.comsetVideoUrl (cloudURL );},[],);return (<div >{videoUrl === null ? null : (<Player component ={MyComposition }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}inputProps ={{videoUrl }}/>)}<input type ="file"onChange ={handleChange } /></div >);};
The implementation of the upload()
function is provider-specific and we do not show an implementation in this article. We assume it is a function that takes a file, uploads it and returns an URL.
Optimistic updates
When we start the upload of the file, we can create a blob URL using URL.createObjectURL()
which can be used to display the local file in a <Video>
tag. When the file is done uploading and we get the remote URL, the component shall use remote URL as source.
App.tsxtsx
import {Player } from '@remotion/player';import {useCallback ,useState } from 'react';typeVideoState =| {type : 'empty';}| {type : 'blob' | 'cloud';url : string;};export constRemotionPlayer :React .FC = () => {const [videoState ,setVideoState ] =useState <VideoState >({type : 'empty',});consthandleChange =useCallback (async (event :React .ChangeEvent <HTMLInputElement >) => {if (event .target .files === null) {return;}constfile =event .target .files [0];constblobUrl =URL .createObjectURL (file );setVideoState ({type : 'blob',url :blobUrl });constcloudUrl = awaitupload (file );setVideoState ({type : 'cloud',url :cloudUrl });URL .revokeObjectURL (blobUrl );},[],);return (<div >{videoState .type !== 'empty' ? (<Player component ={MyComposition }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}inputProps ={{videoUrl :videoState .url }}/>) : null}<input type ="file"onChange ={handleChange } /></div >);};
App.tsxtsx
import {Player } from '@remotion/player';import {useCallback ,useState } from 'react';typeVideoState =| {type : 'empty';}| {type : 'blob' | 'cloud';url : string;};export constRemotionPlayer :React .FC = () => {const [videoState ,setVideoState ] =useState <VideoState >({type : 'empty',});consthandleChange =useCallback (async (event :React .ChangeEvent <HTMLInputElement >) => {if (event .target .files === null) {return;}constfile =event .target .files [0];constblobUrl =URL .createObjectURL (file );setVideoState ({type : 'blob',url :blobUrl });constcloudUrl = awaitupload (file );setVideoState ({type : 'cloud',url :cloudUrl });URL .revokeObjectURL (blobUrl );},[],);return (<div >{videoState .type !== 'empty' ? (<Player component ={MyComposition }durationInFrames ={120}compositionWidth ={1920}compositionHeight ={1080}fps ={30}inputProps ={{videoUrl :videoState .url }}/>) : null}<input type ="file"onChange ={handleChange } /></div >);};
This will result in the user immediately seeing the video as they drag it into the input field. It is a good practice to call URL.revokeObjectURL()
after the local video is not used anymore to free up the used memory.
As soon as possible, replace the blob:
URL with the real URL.
Since blob:
URLs don't support the HTTP Range
header, video seeking performance is degraded when using blob:
URLs.