Skip to content
We've just launched - check out our docs! 📗
  • React
  • Player

Player

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

import { Player } from '@livepeer/react';

Usage

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"
    />
  );
};
 
export const SimplePlayer = () => {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      poster={<PosterImage />}
      showPipButton
    />
  );
};

Here we also introduce a custom PosterImage React component, which is described in more detail below in poster configuration.

Compatibility

🌐

We aim to support ~93% of browsers tracked on caniuse. We use browserslist to track compatibility and core-js for polyfills.

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

Configuration

playbackId or src

A playbackId for an Asset or Stream, or src, a media source URL. One of these is required. If a playback ID is provided, it will automatically retrieve the playback URL corresponding to the playback ID.

playbackId

import { Player } from '@livepeer/react';
 
function App() {
  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.

import { Player } from '@livepeer/react';
 
function App() {
  // only after the asset has been persisted to IPFS
  // equivalent to the above example
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="bafybeida3w2w7fch2fy6rfvfttqamlcyxgd3ddbf4u25n7fxzvyvcaegxy"
    />
  );
}

src

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 for more details. Metrics reporting will not work with an arbitrary src that is not a recognized provider (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.

import { Player } from '@livepeer/react';
 
const src =
  'https://ipfs.livepeer.studio/ipfs/QmURv3J5BGsz23GaCUm7oXncm2M9SCj8RQDuFPGzAFSJw8';
 
function App() {
  return <Player src={src} />;
}
⚠️

Using an arbitrary src that is not from a provider will not be transcoded, 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 (which will serve transcoded content).

For example, try viewing/seeking the video below while simulating a slower network connection in your browser - it will be painfully slow. Then, check out the transcoded version above with the exact same network speed.

jwt

The JSON Web Token (JWT) used to access the content. 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!

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      jwt={jwt}
    />
  );
}

title

The title for the content. This is highly recommended, since it is used for accessibility labels in the Player. If you do not want to show the title visually, see showTitle.

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
    />
  );
}

showTitle

Enables/disables the title component.

import { Player } from '@livepeer/react';
 
function App() {
  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 (see why we want to prevent Cumulative Layout Shift). Defaults to 16to9.

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      aspectRatio="1to1"
    />
  );
}

loop

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

import { Player } from '@livepeer/react';
 
function App() {
  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.

💡

It is recommended to use an optimized React image component for this, as opposed to passing a simple URL.

Simple

The poster can be a simple image URL, and it will be rendered with a regular img HTML tag.

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      poster="/images/blender-poster.png"
    />
  );
}
Agent 327: Operation Barbershop

Advanced

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 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 '../../../public/images/blender-poster.png';
 
const PosterImage = () => {
  return (
    <Image
      src={blenderPoster}
      layout="fill"
      objectFit="cover"
      placeholder="blur"
    />
  );
};
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      poster={<PosterImage />}
    />
  );
}

showLoadingSpinner

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

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      showLoadingSpinner={false}
    />
  );
}

controls

Configures the timeout for autohiding controls, and if keyboard hotkeys for controlling video are enabled.

import { Player } from '@livepeer/react';
 
function App() {
  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 must also be present. This is enforced in many modern browsers and is a requirement for the Player.

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      muted
      autoPlay
    />
  );
}

objectFit

Sets the video's object fit property. Defaults to cover - contain is usually used in full-screen applications or when the aspectRatio does not match the content (or there is no guarantee the aspectRatio matches).

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="6d7el73r1y12chxr"
      aspectRatio="1to1"
      objectFit="contain"
    />
  );
}

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 standard (which most modern browsers support), as well as the older Safari/iOS spec. See the browsers which support Picture-in-Picture on caniuse.

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

autoUrlUpload

export type PlayerProps = {
  ...
  autoUrlUpload?:
    | boolean
    | { fallback: true; ipfsGateway?: string; arweaveGateway?: string };
  ...
};

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.

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 or IPFS/Arweave URL (such as ipfs://<CID>, ipfs://<CID>/asset.mp4, https://<CID>.ipfs.dweb.link/ or https://cloudflare-ipfs.com/ipfs/<CID>, ar://<HASH>, ar://<HASH>/asset.mp4, https://<SUBDOMAIN>.arweave.dev/<HASH> or https://arweave.net/<HASH>, including directories) can be passed as the src or playbackID to the Player, and it will automatically detect if it is a decentralized storage identifier and attempt to fetch the playback info. If the API does not have an Asset with the corresponding ID, the Player will upload the content from IPFS/Arweave, and then play the transcoded content back. This may take a few minutes. If fallback is specified, it will attempt playback instantly from the provided gateway or default gateway.

import { Player } from '@livepeer/react';
 
function App() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="Qmc5rn4Hi8a5JJQdrVAKyeEpCNJnM8t5fhJnoobstVcSFB"
      autoUrlUpload={{ fallback: true, ipfsGateway: 'https://ipfs.fleek.co' }}
    />
  );
}

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.

import { Player } from '@livepeer/react';
 
function App() {
  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. The below example removes the fullscreen button, adds a PiP button, and the volume slider (so volume is only 0% or 100%). See the Player default controls for more details on how the ControlsContainer component is used.

import {
  ControlsContainer,
  PictureInPictureButton,
  PlayButton,
  Player,
  Poster,
  Progress,
  TimeDisplay,
  Title,
  Volume,
} from '@livepeer/react';
 
import Image from 'next/image';
 
import blenderPoster from '../../../public/images/blender-poster-2.png';
 
const PosterImage = () => {
  return <Image src={blenderPoster} layout="fill" objectFit="cover" />;
};
 
const title = 'Agent 327: Operation Barbershop';
 
export const CustomControlsPlayer = () => {
  return (
    <Player
      title={title}
      playbackId="bafybeida3w2w7fch2fy6rfvfttqamlcyxgd3ddbf4u25n7fxzvyvcaegxy"
    >
      <ControlsContainer
        poster={<Poster content={<PosterImage />} title={title} />}
        top={<Title content={title} />}
        middle={<Progress />}
        left={
          <>
            <PlayButton />
            <Volume showSlider={false} />
            <TimeDisplay />
          </>
        }
        right={<PictureInPictureButton />}
      />
    </Player>
  );
};

SSR

Next.js

The Player also comes with a React Query 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 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.tsx
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.

// pages/_app.tsx
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>
  `);
};
Last updated on 11/28/2022