Skip to content
Welcome to the new, unified Livepeer documentation! 👋
Reference
Player

Player

The Player component provides an easy way to display video or audio.

It automatically handles different source types from a Livepeer provider, such as WebRTC, MP4, and HLS. These are seamlessly integrated so that playback has low latency under all network conditions.

Usage

React
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.

React
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="f5eese9wwl88k4g8"
      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

BrowserVersion
Chrome102+
Chrome for Android105+
iOS Safari12.2+
Edge103+
Safari13.1+
Firefox103+
Opera89+
Samsung Internet17+
UC Browser13.4+
Firefox Android104+
Opera Miniall
🌐

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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
    />
  );
}

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.

React & React Native
// 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.

React & React Native
const src =
  'https://ipfs.livepeer.studio/ipfs/QmURv3J5BGsz23GaCUm7oXncm2M9SCj8RQDuFPGzAFSJw8';
 
function PlayerComponent() {
  return <Player src={src} />;
}

jwt or accessKey

💡

This section defines ways to play back a video which has a playback policy applied to it. Your application can prevent playback unless the user meets application-specific requirements (this can be done either with JWTs or webhooks, and solves use-cases like restricting viewership to users who own an NFT, have created an account on your platform, are part of a multisig, etc).

jwt

The JSON Web Token (JWT) used to access media gated by a jwt playback policy. Alternatively, accessKey can be used for a webhook playback policy. See the Access Control example for more details.

💡
Access control is now supported for both streams and assets!
React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      jwt={jwt}
    />
  );
}

accessKey

The access key used to access media behind a webhook playback policy. Alternatively, onAccessKeyRequest can be used to return this access key in a callback. See the Access Control example for more details.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      accessKey="access-key"
    />
  );
}

onAccessKeyRequest

A callback which returns the access key used to gate access to media. Similar to accessKey above, this is used to gate content based on a webhook playback policy. This is used instead of the accessKey prop, and can be asynchronous and accepts any Promise or simple callback which returns a string. See the Access Control example for more details.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      onAccessKeyRequest={async () => {
        return 'access-key';
      }}
    />
  );
}

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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      priority
    />
  );
}

lowLatency

When enabled, the Player will play back livestreams with low-latency WebRTC. If this does not succeed in playing back (commonly due to a slow network or connectivity issues), the Player will automatically fall back to HLS playback. Also, if the stream contains B-frames, or bidirectional frames, (which are common for users streaming with OBS or other streaming apps), the Player will automatically fall back, so that out-of-order frames are not displayed.

⚠️

OBS users should be instructed to use the Livepeer stream profile, or to manually turn off B-frames in their stream. See our Stream from OBS docs for more information.

This only applies to video elements which are playing back livestreams - VOD assets will play back with MP4 or HLS. Defaults to false.

You may also specify "force", which forces WebRTC and disables fallback to HLS. This is not a recommended default unless your application requires low latency (and should not play if low latency is not available), due to the possible connectivity issues which WebRTC can face, which HLS is not subject to.

💡

We are planning on adding similar functionality to the React Native Player - please reach out on Discord if you are interested.

React & React Native
function PlayerComponent() {
  return (
    <Player title="My Livestream" playbackId="wwl88k4g8f5eese9" lowLatency />
  );
}

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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
    />
  );
}

showTitle

Enables/disables the title component.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      aspectRatio="1to1"
    />
  );
}

loop

Sets whether the content will loop when finished. Defaults to false.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      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.

React
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      poster="/images/blender-poster.png"
    />
  );
}
Agent 327: Operation Barbershop

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.

React
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="f5eese9wwl88k4g8"
      poster={<PosterImage />}
    />
  );
}

showLoadingSpinner

Shows/hides the loading spinner for the media content. Defaults to true.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      showLoadingSpinner={false}
    />
  );
}

controls

Configures the timeout for autohiding controls, default volume, and (only on web) if keyboard hotkeys for controlling video are enabled.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      controls={{ autohide: 0, hotkeys: false, defaultVolume: 0.6 }}
    />
  );
}

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).

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      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).

React
import { Player } from '@livepeer/react';
 
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      showPipButton
    />
  );
}

viewerId

Allows a wallet identifier to be passed to the backend which allows for querying views by an identifier. This should be an wallet's public address.

We are planning on adding further features to the viewer ID, such as "verified views" with a provable signature claim attached to these view counts. Reach out to us on Discord (opens in a new tab) if you're interested!

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      viewerId="0xabcd000000000000000000000000000000001234"
    />
  );
}

autoUrlUpload

Enables automatic upload and playback from decentralized storage providers. Currently supports IPFS CIDs and IPFS/Arweave URLs. Defaults to true.

💡

For IPFS HTTP gateway URLs, the player currently only supports “path style” URLs and does not support “subdomain style” URLs. The player will support both styles of URLs in a future update.

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.

React & React Native
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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      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.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      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.

This can be used alongside renderChildrenOutsideContainer to render the children outside of the aspect ratio container. This is used for custom controls, so children of the Player can use useMediaController without any parent elements.

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).

React
import { useCallback } from 'react';
 
function PlayerComponent() {
  const mediaElementRef = useCallback((ref: HTMLMediaElement) => {
    console.log(ref.duration);
  }, []);
 
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      mediaElementRef={mediaElementRef}
    />
  );
}

onPlaybackStatusUpdate

Callback called when the Player store status updates. This should be used with playbackStatusSelector to limit state updates. This allows developers to use the underlying state of the Player component in their UI.

React & React Native
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      onPlaybackStatusUpdate={(muted) => console.log(muted)}
      playbackStatusSelector={(state) => state.muted}
    />
  );
}

Hooks

usePlayerList

⚠️

usePlayerList is currently only available for React Native, since there are List primitives which provide a standard API to hook into. We are working on a React version.

The usePlayerList hook makes it easy to preload and display videos in a FlatList (opens in a new tab)-compatible list, by using viewabilityConfigCallbackPairs (opens in a new tab) to trigger preloading on upcoming videos, and automatically playing/pausing media when it is shown/hidden from the list.

See our Expo sample app (opens in a new tab) for a real-world example.

Usage

const videos = [
  {
    title: 'Fountain',
    playbackId: '6119i9hncfr7gopr',
  },
  {
    title: 'Happy Dog',
    playbackId: '8cf0gfaqtuajakyf',
  },
];
 
function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemVisibleMinimumViewTime: 100,
    itemVisiblePercentThreshold: 60,
    itemPreload: 2,
  });
 
  return (
    <FlatList
      data={listProps.data}
      viewabilityConfigCallbackPairs={listProps.viewabilityConfigCallbackPairs}
      renderItem={({ item, index }) => (
        <Player
          {...item.playerProps}
          playbackId={item.playbackId}
          title={item.title}
          autoPlay
          loop
        />
      )}
    />
  );
}

Return Value

The return value extends the data array which is passed into the hook, to add Player-specific props to it. The viewabilityConfigCallbackPairs prop is also returned, which must be passed into the FlatList-compatible list to maintain the correct preloading/playing states for the Player(s).

{
  data: (
    TInputArray & {
      playerProps: {
        _isCurrentlyShown: boolean;
        priority: boolean;
      };
    }
  )[];
 
  viewabilityConfigCallbackPairs: ViewabilityConfigCallbackPairs;
}

Configuration

data

The data used in the list - required. This is extended to add Player-specific props, which are passed into the Player using item.playerProps.

function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
  });
 
  return (
    <FlatList
      {...listProps}
      renderItem={({ item }) => (
        <Player {...item.playerProps} playbackId={item.playbackId} />
      )}
    />
  );
}
itemPreload

The number of items to preload ahead of the currently-viewable Player. For instance, if itemPreload is set to 8 and there are 30 items in the Player list, when the user is currently viewing the 5th item, the 6th-14th Players will actively preload in the background. Defaults to 3.

function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemPreload: 8,
  });
}
itemVisibleMinimumViewTime

The minimum amount of time in milliseconds that an item must be viewable before it is considered "shown". This will also be the delay between when the item becomes visible and when the autoPlay logic is triggered. Defaults to 100 ms, use null to unset. See FlatList's viewabilityConfig (opens in a new tab) for more details.

function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemVisibleMinimumViewTime: 300,
  });
}
itemVisiblePercentThreshold

The percent of the item that must be visible for the item to count as "viewable", from 0-100. Defaults to 60%, use null to unset. See FlatList's viewabilityConfig (opens in a new tab) for more details.

function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemVisiblePercentThreshold: 35,
  });
}
itemVisibleViewAreaCoveragePercentThreshold

The percent of the viewport that must be covered for a partially occluded item to count as "viewable", 0-100. No default value. See FlatList's viewabilityConfig (opens in a new tab) for more details.

function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemVisiblePercentThreshold: null, // the default item percent can be unset, if needed
    itemVisibleViewAreaCoveragePercentThreshold: 75,
  });
}
itemVisibleWaitForInteraction

Indicates that the item is not considered viewable until the user scrolls or recordInteraction is called after render. Defaults to false. See FlatList's viewabilityConfig (opens in a new tab) for more details.

function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemVisibleWaitForInteraction: true,
  });
}

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.

pages/demo.js
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.

App.js
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.

server.js
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>
  `);
};

Technical Details

The Player is opinionated about the ways it handles playback of media, specifically regarding HLS reconnect and playback URL choices.

HLS Reconnect

The Player will perform a linear backoff strategy when it encounters any network/response errors. The reconnect will initially wait one second, and add one second every retry. This ensures that if there are any intermittent network/API errors, they are resolved as quickly as possible. Most open source players do not do this automatically, which results in a poor playback experience for the user.

MP4 Playback URLs

When videos are uploaded which are shorter than two minutes, the Studio provider will automatically generate MP4 renditions alongside the usual HLS playback URL. This allows for rapid playback/caching at the CDN layer to enable your application to load video instantly.

The MP4 renditions will be prioritized over HLS and are chosen with the following algorithm: the device screen width is multiplied by a static multiplier (currently set to x2.5). This value is then compared to the rendition widths, and the renditions are prioritized based on the distance between these values. This results in a choice of a rendition which is close to the screen size without visual quality issues. For instance, a device with a 1280 pixel width would compute 1280px * 2.5 = 3200px, and then sort the MP4 renditions by which are closest to this value.