Skip to content
We've just launched - check out our docs! đź“—
  • Examples
  • React
  • Create & Watch an Asset

Create and Watch an 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

đź’ˇ

This example assumes you have chosen a component library with typical components like Button, TextField, Spinner, etc. The Player is the only visual component provided.

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

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, a library for easily creating HTML5-compliant drag and drop zones for files (you can use another solution for file uploads):

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 (
    <>
      <Box {...getRootProps()}>
        <Box as="input" {...getInputProps()} />
        <Box as="p">
          <Text>Drag and drop or browse files</Text>
        </Box>
      </Box>
 
      {createError?.message && <Text>{createError.message}</Text>}
 
      {video ? (
        <Badge>{video.name}</Badge>
      ) : (
        <Text>Select a video file to upload.</Text>
      )}
      {progressFormatted && <Text>{progressFormatted}</Text>}
 
      <Button
        onClick={() => {
          createAsset?.();
        }}
        size="2"
        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.

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 (
    <Box>
      {!asset && (
        <Box>
          <Box as="div" {...getRootProps()}>
            <Box as="input" {...getInputProps()} />
            <Box as="p">
              <Text>Drag and drop or browse files</Text>
            </Box>
          </Box>
 
          {error?.message && <Text variant="red">{error.message}</Text>}
        </Box>
      )}
 
      {asset?.[0]?.playbackId && (
        <Player title={asset[0].name} playbackId={asset[0].playbackId} />
      )}
 
      <Flex>
        <Flex>
          {metrics?.metrics?.[0] && (
            <Badge>Views: {metrics?.metrics?.[0]?.startViews}</Badge>
          )}
          {video ? (
            <Badge>{video.name}</Badge>
          ) : (
            <Text>Select a video file to upload.</Text>
          )}
          {progressFormatted && <Text>{progressFormatted}</Text>}
        </Flex>
        {!asset?.[0].id && (
          <Button
            onClick={() => {
              createAsset?.();
            }}
            disabled={isLoading || !createAsset}
          >
            Upload
          </Button>
        )}
      </Flex>
    </Box>
  );
};

Wrap Up

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