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.
Drag and drop or browse files
Configuring Providers
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>
);
}
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):
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
.
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.