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.