Player
The Player
component provides an easy way to display video or audio.
Usage
import { Player } from '@livepeer/react';
The following example assumes a stream or asset was created via useCreateAsset
or useCreateStream
, and the playbackId
was passed to the viewer.
import { Player } from '@livepeer/react';
import Image from 'next/image';
import blenderPoster from '../../../public/images/blender-poster.png';
const PosterImage = () => {
return (
<Image
src={blenderPoster}
layout="fill"
objectFit="cover"
priority
placeholder="blur"
/>
);
};
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
poster={<PosterImage />}
showPipButton
objectFit="cover"
priority
/>
);
};
Here we also introduce a custom PosterImage
React component, which is described in
more detail below in poster
configuration.

Compatibility
Browser | Version |
---|---|
Chrome | 102+ |
Chrome for Android | 105+ |
iOS Safari | 12.2+ |
Edge | 103+ |
Safari | 13.1+ |
Firefox | 103+ |
Opera | 89+ |
Samsung Internet | 17+ |
UC Browser | 13.4+ |
Firefox Android | 104+ |
Opera Mini | all |
We aim to support ~93% (opens in a new tab) of browsers tracked on caniuse (opens in a new tab). We use browserslist (opens in a new tab) to track compatibility and core-js (opens in a new tab) for polyfills.
Configuration
playbackId or src
A playbackId
for an Asset (opens in a new tab) or
Stream (opens in a new tab), or src
, a media source URL. One of these is
required.
playbackId
If a playback ID is provided, the playback URL corresponding to the playback ID will be automatically fetched.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
/>
);
}
The playbackId
passed to the Player
can be either a short playback ID
which is created on asset/stream creation, or, for an asset, an IPFS CID. If
the provided IPFS CID or IPFS/Arweave URL has not been uploaded yet, it can be auto-uploaded and
played back - see autoUrlUpload
for more details.
// only after the asset has been persisted to IPFS
// equivalent to the above example
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="bafybeida3w2w7fch2fy6rfvfttqamlcyxgd3ddbf4u25n7fxzvyvcaegxy"
/>
);
}
src
Using an arbitrary src
that is not from a provider will not be transcoded
(unless autoUrlUpload
is used), and will take up
significant network bandwidth. It's highly recommended to upload media to a
provider, and serve content to viewers with a playbackId
.
The Player also supports an arbitrary src
URL which can correspond to any common video or audio which most browsers support.
See caniuse video format (opens in a new tab) for more details on browser support.
Metrics reporting will not work with an arbitrary src
(e.g. not a Studio
playback URL).
If the src
is an IPFS/Arweave URL, it can be auto-uploaded and played back - see autoUrlUpload
for
more details.
const src =
'https://ipfs.livepeer.studio/ipfs/QmURv3J5BGsz23GaCUm7oXncm2M9SCj8RQDuFPGzAFSJw8';
function PlayerComponent() {
return <Player src={src} />;
}
jwt
The JSON Web Token (JWT) used to access the media. This is used to gate content based on a playback policy. See the Access Control example for more details.
Currently, access control is only supported with Streams. Access control for Asset playback is coming soon!
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
jwt={jwt}
/>
);
}
priority
When true, the media will be considered high priority and preload. Lazy loading is automatically disabled for media using priority
.
You should use the priority property on any media detected as the Largest Contentful Paint (LCP) (opens in a new tab) element.
It may be appropriate to have multiple, as different content may be the LCP element for different viewport sizes.
Should only be used when the media is visible above the fold. Defaults to false.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
priority
/>
);
}
title
The title
for the content. This is highly recommended, since it is used for accessibility labels (opens in a new tab)
in the Player. If you do not want to show the title visually, see showTitle
.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
/>
);
}
showTitle
Enables/disables the title component.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
showTitle={false}
/>
);
}
aspectRatio
Sets the aspect ratio for the content. Highly recommended for a great viewing experience (for more information, see
Cumulative Layout Shift (opens in a new tab)). Defaults to 16to9
.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
aspectRatio="1to1"
/>
);
}
loop
Sets whether the content will loop when finished. Defaults to false
.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
loop
/>
);
}
poster
Sets the poster image. This can be either a string for an image URL, or a React component.
The poster
can be a simple image URL, and it will be rendered with a regular img
HTML tag.
It is recommended to use an optimized React image component for this (see below), as opposed to passing a simple URL.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
poster="/images/blender-poster.png"
/>
);
}

If the poster is a React component, it will be rendered with similar CSS attributes to the img
above. In the below example,
we show the use of Next.js Image (opens in a new tab) to render an optimized
image, which will automatically handle slow network conditions/different device sizes.
import { Player } from '@livepeer/react';
import Image from 'next/image';
import blenderPoster from './images/blender-poster.png';
const PosterImage = () => {
return (
<Image
src={blenderPoster}
layout="fill"
objectFit="cover"
placeholder="blur"
/>
);
};
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
poster={<PosterImage />}
/>
);
}

showLoadingSpinner
Shows/hides the loading spinner for the media content. Defaults to true
.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
showLoadingSpinner={false}
/>
);
}
controls
Configures the timeout for autohiding controls, and (only on web) if keyboard hotkeys for controlling video are enabled.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
controls={{ autohide: 0, hotkeys: false }}
/>
);
}
autoPlay and muted
Sets the video to autoplay when the content comes into focus on the webpage. If autoPlay
is specified, muted
will
be forced to be true
. This is enforced in many modern browsers (opens in a new tab).
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
muted
autoPlay
/>
);
}
objectFit
Sets the video's object fit (opens in a new tab) property. Defaults to contain
.
cover
is usually used when there is a guarantee that the aspectRatio
matches the content displayed in the Player.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
aspectRatio="1to1"
objectFit="cover"
/>
);
}
showPipButton
Shows the Picture-in-Picture button to the left of the fullscreen button. Defaults to false
.
See children for an example on how to use the underlying <PictureInPictureButton />
.
We support both the w3c (opens in a new tab) standard (which most modern browsers support), as well as the older Safari/iOS spec (opens in a new tab). See the browsers which support Picture-in-Picture on caniuse (opens in a new tab).
import { Player } from '@livepeer/react';
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
showPipButton
/>
);
}
autoUrlUpload
Enables automatic upload and playback from decentralized storage providers. Currently supports IPFS CIDs and IPFS/Arweave URLs. Defaults to true
.
If fallback
is specified, while the URL upload is being processed in the background, the video will start non-transcoded playback immediately
(defaulting to w3s.link
for IPFS and arweave.net
for Arweave). Once this finishes, the Player will switch to playing from the transcoded version
from the Livepeer provider. To show/hide the indicator of current upload progress, see showUploadingIndicator
.
It is highly recommended for the best playback experience to upload from an Arweave/IPFS URL using useCreateAsset
(with the format ipfs://<CID>
or ar://<HASH>
)
before presenting the content to the user - the first view with automatic URL upload can take a few minutes, then it will be permanently
cached and play back quickly.
An IPFS v0 or v1 CID (opens in a new tab) or
IPFS/Arweave URL (including directories) can be passed as the src
or
playbackID
to the Player, and it will automatically detect if it is a
dStorage identifier and attempt to play from a cached version. If the API does
not have a cached version with the corresponding ID, the Player will upload
the content using IPFS/Arweave, and then start playing the transcoded content
back. This may take a few minutes. If fallback
is specified, it will attempt
to play back instantly from the provided gateway or default gateway.
For best performance, use a custom gateway URL, and ideally, use the same gateway and pinning service, to avoid timeout errors.
function PlayerComponent() {
return (
<Player
title="Workout NFT"
src="ipfs://QmNmNBQE3RqdkN3KpeBVxpGDHUe8c9Bh5YNerERNoo98rB/4038.mp4"
autoUrlUpload={{ fallback: true, ipfsGateway: 'https://w3s.link' }}
/>
);
}
showUploadingIndicator
Shows/hides the uploading indication for autoUrlUpload
. Defaults to true
- only used with autoUrlUpload
.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
autoUrlUpload={{ fallback: true }}
showUploadingIndicator={false}
/>
);
}
theme
Sets the Player-specific theme overrides. It is recommended to use LivepeerConfig
for
any global app styles, and the theme
prop to override those styles on a per-Player basis.
function PlayerComponent() {
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
theme={{
borderStyles: {
containerBorderStyle: 'hidden',
},
colors: {
accent: '#00a55f',
},
space: {
controlsBottomMarginX: '10px',
controlsBottomMarginY: '5px',
controlsTopMarginX: '15px',
controlsTopMarginY: '10px',
},
radii: {
containerBorderRadius: '0px',
},
}}
/>
);
}
children
Overrides the custom controls for the Player. See the
Player
default controls (opens in a new tab)
for more details on how the ControlsContainer
component is used.
mediaElementRef
Sets the React callback ref
(opens in a new tab) passed to the underlying media element.
Useful when integrating with third party tools, or when access to the underlying video element is needed (usually it isn't!).
Simple refs are not supported - only callback refs (which will be called when the underlying media element is set/updated on initial render).
import { useCallback } from 'react';
function PlayerComponent() {
const mediaElementRef = useCallback((ref: HTMLMediaElement) => {
console.log(ref.duration);
}, []);
return (
<Player
title="Agent 327: Operation Barbershop"
playbackId="6d7el73r1y12chxr"
mediaElementRef={mediaElementRef}
/>
);
}
SSR
The following section only applies to web-based use-cases - React Native has no concept of SSR.
Next.js
The Player also comes with a React Query (opens in a new tab) prefetch query, prefetchPlayer
,
which makes it easy to prefetch the data used internally for the Player during server-side rendering.
First, you add a getStaticProps
(opens in a new tab) function to the page which
you want to prefetch data on. The props should match the Player props to ensure that the correct data is prefetched.
import { prefetchPlayer, studioProvider } from '@livepeer/react';
export const getStaticProps = async () => {
const dehydratedState = await prefetchPlayer(
{ playbackId },
{ provider: studioProvider({ apiKey: 'yourStudioApiKey' }) },
);
return {
props: {
dehydratedState,
},
revalidate: 600,
};
};
We need to update the _app.tsx
to pass the dehydratedState
in pageProps
to the LivepeerConfig. We also move the
livepeerClient
into a useMemo hook so that a new client is created on each request.
import {
LivepeerConfig,
createReactClient,
studioProvider,
} from '@livepeer/react';
import type { AppProps } from 'next/app';
import { useMemo } from 'react';
function App({ Component, pageProps }: AppProps<{ dehydratedState: string }>) {
// we create a new livepeer client on each request so data is
// not shared between users
const livepeerClient = useMemo(
() =>
createReactClient({
provider: studioProvider({
apiKey: process.env.NEXT_PUBLIC_STUDIO_API_KEY,
}),
}),
[],
);
return (
<LivepeerConfig
dehydratedState={pageProps?.dehydratedState}
client={livepeerClient}
>
<Component {...pageProps} />
</LivepeerConfig>
);
}
That's it! You now have data prefetching on the server, which is passed to the browser and used to hydrate the initial query client.
Other Frameworks
The process is very similar for other frameworks, with the exception that there is a clearClient
boolean which should be used
to ensure that the client cache is not reused across users.
import { prefetchPlayer, studioProvider } from '@livepeer/react';
export const handleRequest = async (req, res) => {
const dehydratedState = await prefetchPlayer(
{
playbackId,
clearClient: true,
},
{ provider: studioProvider({ apiKey: 'yourStudioApiKey' }) },
);
// sanitize the custom SSR generated data
// https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0
res.send(`
<html>
<body>
<div id="root">${html}</div>
<script>
window.__REACT_QUERY_STATE__ = ${yourSanitizedDehydratedState};
</script>
</body>
</html>
`);
};