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.

Select a video file to upload.

Step 1: 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>
  );
}

Step 2: 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>
    </>
  );
};

Step 3: 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.