Skip to main content

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 <OffthreadVideo> tag that includes an URL as source.

import {AbsoluteFill, OffthreadVideo} from 'remotion';

type VideoProps = {
  videoURL: string;
};

export const MyComponent: React.FC<VideoProps> = ({videoURL}) => {
  return (
    <AbsoluteFill>
      <OffthreadVideo 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.

import {Player} from '@remotion/player';
import {useState} from 'react';

export const RemotionPlayer: React.FC = () => {
  const [videoUrl, setVideoUrl] = useState<string | null>(null);

  const handleChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files === null) {
      return;
    }

    const file = event.target.files[0];
    //upload is an example function  & returns a URL when a file is uploaded on the cloud.
    const cloudURL = await upload(file);
    // E.g., cloudURL = https://exampleBucketName.s3.ExampleAwsRegion.amazonaws.com
    setVideoUrl(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.

import {Player} from '@remotion/player';
import {useCallback, useState} from 'react';

type VideoState =
  | {
      type: 'empty';
    }
  | {
      type: 'blob' | 'cloud';
      url: string;
    };

export const RemotionPlayer: React.FC = () => {
  const [videoState, setVideoState] = useState<VideoState>({
    type: 'empty',
  });

  const handleChange = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files === null) {
      return;
    }

    const file = event.target.files[0];
    const blobUrl = URL.createObjectURL(file);
    setVideoState({type: 'blob', url: blobUrl});
    const cloudUrl = await upload(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.

note

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.

Validating video compatibility

Before uploading a video, you should validate whether the browser can decode it. This saves bandwidth and provides immediate feedback to the user. For a complete guide on validating user videos including handling incompatible formats, see Validating user videos.

See also