Skip to content
Welcome to the new, unified Livepeer documentation! 👋
Guides
Upload a Video Asset

Upload a video asset

Creating and viewing assets is easy with livepeer.js! The example below uses useCreateAsset to create and view a video asset.

⚠️

For rapid processing of assets that will also be archived on IPFS or Arweave, we strongly encourage either (1) uploading to Livepeer with the IPFS storage option enabled, or (2) uploading the raw file to Livepeer via useCreateAsset or the API prior to archiving on dStorage, rather than passing the IPFS / Arweave gateway URL. The gateway URL will work, but may incur longer-than-usual processing time.

Select a video file to upload.

Configuring Providers

First, we create a new livepeer.js client with the Studio provider and a CORS-protected API key:

App.tsx
import {
  LivepeerConfig,
  createReactClient,
  studioProvider,
} from '@livepeer/react';
import * as React from 'react';
 
const livepeerClient = createReactClient({
  provider: studioProvider({
    apiKey: process.env.NEXT_PUBLIC_STUDIO_API_KEY,
  }),
});
 
// Pass client to React Context Provider
function App() {
  return (
    <LivepeerConfig client={livepeerClient}>
      <CreateAndViewAsset />
    </LivepeerConfig>
  );
}

Video File Upload

Now that our providers are set up, we set up file uploads with React Dropzone (opens in a new tab), a library for easily creating HTML5-compliant drag and drop zones for files (you can use another solution for file uploads):

CreateAndViewAsset.tsx
import { useCreateAsset } from '@livepeer/react';
 
import { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
 
export const CreateAndViewAsset = () => {
  const [video, setVideo] = useState<File | undefined>();
  const {
    mutate: createAsset,
    data: asset,
    status,
    progress,
    error,
  } = useCreateAsset(
    video
      ? {
          sources: [{ name: video.name, file: video }] as const,
        }
      : null,
  );
 
  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    if (acceptedFiles && acceptedFiles.length > 0 && acceptedFiles?.[0]) {
      setVideo(acceptedFiles[0]);
    }
  }, []);
 
  const { getRootProps, getInputProps } = useDropzone({
    accept: {
      'video/*': ['*.mp4'],
    },
    maxFiles: 1,
    onDrop,
  });
 
  const progressFormatted = useMemo(
    () =>
      progress?.[0].phase === 'failed'
        ? 'Failed to process video.'
        : progress?.[0].phase === 'waiting'
        ? 'Waiting'
        : progress?.[0].phase === 'uploading'
        ? `Uploading: ${Math.round(progress?.[0]?.progress * 100)}%`
        : progress?.[0].phase === 'processing'
        ? `Processing: ${Math.round(progress?.[0].progress * 100)}%`
        : null,
    [progress],
  );
 
  return (
    <>
      <div {...getRootProps()}>
        <input {...getInputProps()} />
        <p>Drag and drop or browse files</p>
      </div>
 
      {createError?.message && <p>{createError.message}</p>}
 
      {video ? <p>{video.name}</p> : <p>Select a video file to upload.</p>}
      {progressFormatted && <p>{progressFormatted}</p>}
 
      <button
        onClick={() => {
          createAsset?.();
        }}
        disabled={!createAsset || createStatus === 'loading'}
      >
        Upload
      </button>
    </>
  );
};

Upload video to IPFS

If you want to upload the video directly to IPFS, you can simple update the sources (opens in a new tab) array in useCreateAsset hook.

  const {
    mutate: createAsset,
    data: asset,
    status,
    progress,
    error,
  } = useCreateAsset(
    video
      ? {
          sources: [
            {
              name: video.name,
              file: video,
              storage: {
                ipfs: true,
                metadata: {
                  name: 'interesting video',
                  description: 'a great description of the video',
                }
              }
            }
          ] as const,
        }
      : null,
  );

Stream Transcoded Video

Lastly, when the video is uploaded, we wait for useCreateAsset to poll the API until the asset has been processed and has a playbackId. Then, we show the video using the Player.

CreateAndViewAsset.tsx
import { Player, useAssetMetrics, useCreateAsset } from '@livepeer/react';
 
import { useCallback, useMemo, useState } from 'react';
import { useDropzone } from 'react-dropzone';
 
export const Asset = () => {
  const [video, setVideo] = useState<File | undefined>();
  const {
    mutate: createAsset,
    data: asset,
    status,
    progress,
    error,
  } = useCreateAsset(
    video
      ? {
          sources: [{ name: video.name, file: video }] as const,
        }
      : null,
  );
  const { data: metrics } = useAssetMetrics({
    assetId: asset?.[0].id,
    refetchInterval: 30000,
  });
 
  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    if (acceptedFiles && acceptedFiles.length > 0 && acceptedFiles?.[0]) {
      setVideo(acceptedFiles[0]);
    }
  }, []);
 
  const { getRootProps, getInputProps } = useDropzone({
    accept: {
      'video/*': ['*.mp4'],
    },
    maxFiles: 1,
    onDrop,
  });
 
  const isLoading = useMemo(
    () =>
      status === 'loading' ||
      (asset?.[0] && asset[0].status?.phase !== 'ready'),
    [status, asset],
  );
 
  const progressFormatted = useMemo(
    () =>
      progress?.[0].phase === 'failed'
        ? 'Failed to process video.'
        : progress?.[0].phase === 'waiting'
        ? 'Waiting...'
        : progress?.[0].phase === 'uploading'
        ? `Uploading: ${Math.round(progress?.[0]?.progress * 100)}%`
        : progress?.[0].phase === 'processing'
        ? `Processing: ${Math.round(progress?.[0].progress * 100)}%`
        : null,
    [progress],
  );
 
  return (
    <div>
      {!asset && (
        <div {...getRootProps()}>
          <input {...getInputProps()} />
          <p>Drag and drop or browse files</p>
 
          {error?.message && <p>{error.message}</p>}
        </div>
      )}
 
      {asset?.[0]?.playbackId && (
        <Player title={asset[0].name} playbackId={asset[0].playbackId} />
      )}
 
      <div>
        {metrics?.metrics?.[0] && (
          <p>Views: {metrics?.metrics?.[0]?.startViews}</p>
        )}
 
        {video ? <p>{video.name}</p> : <p>Select a video file to upload.</p>}
 
        {progressFormatted && <p>{progressFormatted}</p>}
 
        {!asset?.[0].id && (
          <button
            onClick={() => {
              createAsset?.();
            }}
            disabled={isLoading || !createAsset}
          >
            Upload
          </button>
        )}
      </div>
    </div>
  );
};

Wrap Up

That's it! You just set up a scalable, decentralized video asset streaming solution for an app.