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.
Drag and drop or browse files
Step 1: 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>
);
}
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):
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: 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,
);
Step 4: 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.