> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Player

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

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

<Tabs>
  <Tab title="React">
    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    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.

    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    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`](#poster) configuration.

    <br />
  </Tab>

  <Tab title="React Native">
    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    import { Player } from '@livepeer/react-native';
    ```

    The following example assumes a stream or asset was created via `useCreateAsset`
    or `useCreateStream`, and the `playbackId` was passed to the viewer.

    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    import { Player } from '@livepeer/react-native';

    import blenderPoster from './assets/images/blender-poster.png';

    function PlayerComponent() {
      return (
        <Player
          title="Agent 327: Operation Barbershop"
          playbackId="f5eese9wwl88k4g8"
          poster={blenderPoster}
        />
      );
    };
    ```
  </Tab>
</Tabs>

<iframe src="https://lvpr.tv/?v=f5eese9wwl88k4g8" frameborder="0" width="100%" height="410" webkitallowFullScreen mozallowFullScreen allowFullScreen />

## Compatibility

<Tabs>
  <Tab title="React">
    | 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     |

    <Info>
      We aim to support
      [\~93%](https://browsersl.ist/#q=last+2+versions%2C%3E+0.2%25%2Cnot+dead) of
      browsers tracked on [caniuse](https://caniuse.com/). We use
      [browserslist](https://browsersl.ist/) to track compatibility and
      [core-js](https://github.com/zloirock/core-js) for polyfills.
    </Info>
  </Tab>

  <Tab title="React Native">
    | Framework    | Version |
    | ------------ | ------- |
    | React Native | 0.64.0+ |
    | Expo         | 43+     |
    | React        | 17+     |

    <Info>
      We aim to support the latest versions of React Native and Expo. If you are
      using an older version of Expo or React Native, we highly recommend upgrading
      to ensure compatibility.
    </Info>
  </Tab>
</Tabs>

## Configuration

### playbackId or src

A `playbackId` for an
[asset](https://github.com/livepeer/react/blob/main/packages/core/src/types/provider.ts)
or
[Stream](https://github.com/livepeer/react/blob/main/packages/core/src/types/provider.ts),
or `src`, a media source URL. One of these is required.

#### playbackId

<INfo>
  If a playback ID is provided, the playback URL corresponding to the playback
  ID will be automatically fetched.
</INfo>

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`](#autourlupload) for more
details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
// 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

<Warning>
  Using an arbitrary `src` that is not from a provider will not be transcoded
  (unless [`autoUrlUpload`](#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`.
</Warning>

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](https://caniuse.com/?search=video%20format) for more
details on browser support.

<Warning>
  Metrics reporting **will not work** with an arbitrary `src` (e.g. not a Studio
  playback URL).
</Warning>

If the `src` is an IPFS/Arweave URL, it can be auto-uploaded and played back -
see [`autoUrlUpload`](#autourlupload) for more details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const src =
  "https://ipfs.livepeer.studio/ipfs/QmURv3J5BGsz23GaCUm7oXncm2M9SCj8RQDuFPGzAFSJw8";

function PlayerComponent() {
  return <Player src={src} />;
}
```

### jwt or accessKey

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

#### 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](/developers/guides/access-control-jwt) for more
details.

<Tip>Access control is now supported for both streams and assets!</Tip>

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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](/developers/guides/access-control-webhooks) for more
details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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](/developers/guides/access-control-webhooks) for more
details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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)](https://web.dev/lcp/) 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.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      priority
    />
  );
}
```

### lowLatency

When enabled, the Player will play back **live streams** with low-latency
WebRTC. Defaults to `true`. 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 (bidirectional
frames, which are common for users streaming with OBS or other streaming apps),
the Player will automatically switch to HLS, so that out-of-order frames are not
displayed.

<Warning>
  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](/developers/guides/stream-via-obs) docs for more information.
</Warning>

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

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.

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

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="My Livestream"
      playbackId="wwl88k4g8f5eese9"
      lowLatency="force"
    />
  );
}
```

### clipLength

The clip length enables the "live clipping" feature - the value is in seconds,
and controls the length of the clip that is created when the clip button is
clicked by a viewer. This can be one of 90, 60, 45, 30, 15, or 10 seconds.

This allows for dynamic viewing experiences, along with the `onClipStarted`,
`onClipCreated`, and `onClipError` callbacks for clips.

<Tip>
  You can use the `onClipStarted`, `onClipCreated`, and `onClipError` callbacks
  to control UI outside of the Player, to show toasts or other loading
  indicators, as well as track the status of the Asset which is created and
  processed. See `useAsset` for how to poll the status of the asset.
</Tip>

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Live Stream"
      playbackId="terese9wwl88k4g8"
      clipLength={30}
      onClipStarted={() => {
        console.log("Clip API request is pending...");
      }}
      onClipCreated={(asset: Asset) => {
        // poll the status of the asset for the
        // download URL or playback ID
        console.log(asset.id);
      }}
      onClipError={(error: Error) => {
        console.error(error.message);
      }}
    />
  );
}
```

### playRecording

When allowed, the Player will include recording URLs in playback. This is the
HLS URL that allows users to watch the entire recording of a stream, without
requiring the user to play from the latest playhead (as is usually required in
stream playback). This can be used to create historical clipping experiences for
streams - users can watch a version of the stream that constantly appended with
the latest segments.

<Info>
  Keep in mind that latency for the latest segments will be higher when this
  option is enabled, and is not recommended for low latency applications.
</Info>

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player title="Live Stream" playbackId="terese9wwl88k4g8" playRecording />
  );
}
```

### title

The `title` for the content. This is highly recommended, since it is used for
[accessibility labels](https://web.dev/semantics-aria/) in the Player. If you do
not want to show the title visually, see [`showTitle`](#showtitle).

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
    />
  );
}
```

### showTitle

Enables/disables the title component.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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](https://web.dev/cls/)). Defaults to `16to9`.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      loop
    />
  );
}
```

### poster

<Tabs>
  <Tab title="React">
    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.

    <Wraning>
      It is recommended to use an optimized React image component for this (see
      below), as opposed to passing a simple URL.
    </Wraning>

    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    function PlayerComponent() {
      return (
        <Player
          title="Agent 327: Operation Barbershop"
          playbackId="f5eese9wwl88k4g8"
          poster="/v1/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](https://nextjs.org/docs/basic-features/image-optimization) to
    render an optimized image, which will automatically handle slow network
    conditions/different device sizes.

    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    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 />}
        />
      );
    }
    ```
  </Tab>

  <Tab title="React Native">
    Sets the poster image. This can be either a remote URL or a local file resource.

    <Warning>
      It is recommended to use a local file resource, as opposed to passing a remote
      URL. Since this image is used during a loading state, it will ensure a smooth
      experience for users.
    </Warning>

    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    import posterImage from "./images/blender-poster.png";

    function PlayerComponent() {
      return (
        <Player
          title="Agent 327: Operation Barbershop"
          playbackId="f5eese9wwl88k4g8"
          poster={posterImage}
        />
      );
    }
    ```
  </Tab>
</Tabs>

### showLoadingSpinner

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

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      showLoadingSpinner={false}
    />
  );
}
```

### hlsConfig

Sets the underlying HLS.js config used for playback. See the
[HLS.js docs](https://github.com/video-dev/hls.js/blob/8d75e45992a7139e7cf9b11a056c8474e9ef4546/docs/API.md)
for more details on the possible values.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      hlsConfig={{ liveSyncDurationCount: Infinity }}
    />
  );
}
```

### webrtcConfig

Sets the underlying WebRTC config used for
[WHEP SDP negotiation](https://datatracker.ietf.org/doc/draft-murillo-whep/).
The possible config options are `constant` and `sdpTimeout`.

A `constant=true` boolean indicates that audio will not be distorted if the
playhead falls behind the livestream. This is usually used for music
applications, where audio quality and consistency is more important than
latency.

The `sdpTimeout` config sets the amount of time in milliseconds that the Player
will wait for a response from SDP negotiation before falling back to HLS.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      webrtcConfig={{ constant: true, sdpTimeout: 5000 }}
    />
  );
}
```

### controls

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

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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](https://developer.chrome.com/blog/autoplay/).

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      muted
      autoPlay
    />
  );
}
```

### objectFit

Sets the video's
[object fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
property. Defaults to `contain`. `cover` is usually used when there is a
guarantee that the `aspectRatio` matches the content displayed in the Player.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      aspectRatio="1to1"
      objectFit="cover"
    />
  );
}
```

### showPipButton

<Tabs>
  <Tab title="React">
    Shows the Picture-in-Picture button to the left of the fullscreen button.
    Defaults to `false`. See [children](#children) for an example on how to use the
    underlying `<PictureInPictureButton />`.

    <Info>
      We support both the [w3c](https://w3c.github.io/picture-in-picture/) standard
      (which most modern browsers support), as well as the [older Safari/iOS
      spec](https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls).
      See the browsers which support Picture-in-Picture on
      [caniuse](https://caniuse.com/picture-in-picture).
    </Info>

    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    import { Player } from "@livepeer/react";

    function PlayerComponent() {
      return (
        <Player
          title="Agent 327: Operation Barbershop"
          playbackId="f5eese9wwl88k4g8"
          showPipButton
        />
      );
    }
    ```
  </Tab>

  <Tab title="React Native">
    Picture-in-Picture is not supported in React Native, due to lack of support in
    `expo-av`.
  </Tab>
</Tabs>

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

<Info>
  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](https://discord.gg/livepeer) if you're interested!
</Info>

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`.

<Info>
  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.
</Info>

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`](#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.

<Info>
  An IPFS [v0 or v1 CID](https://docs.ipfs.tech/concepts/content-addressing/) 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.
</Info>

<Warning>
  For best performance, use a custom gateway URL, and ideally, use the same
  gateway and pinning service, to avoid timeout errors.
</Warning>

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`](/sdks/react/migration/3.x/LivepeerConfig) for any global app
styles, and the `theme` prop to override those styles on a per-Player basis.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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

<Tabs>
  <Tab title="React">
    Overrides the custom controls for the Player. See the
    [`Player` default controls](https://github.com/livepeer/react/blob/main/packages/react/src/components/media/Player.tsx)
    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.
  </Tab>

  <Tab title="React Native">
    Overrides the custom controls for the Player. See the
    [React Native `Player` default controls](https://github.com/livepeer/react/blob/main/packages/react-native/src/components/media/Player.tsx)
    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.
  </Tab>
</Tabs>

### streamOfflineErrorComponent

Sets the error component that is shown when playback fails due to the stream
being offline. When this component is shown, the Player will retry playback in
the background. This will override the
[`OfflineStreamError` component](https://github.com/livepeer/react/blob/main/packages/react/src/components/media/controls/PlaybackDisplayError.tsx).

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      streamOfflineErrorComponent={
        <div>
          Stream is offline. Playback will start automatically once the stream
          has started.
        </div>
      }
    />
  );
}
```

### accessControlErrorComponent

Sets the error component that is shown when playback fails due to an access
control error. This will override the
[`PrivateStreamError` component](https://github.com/livepeer/react/blob/main/packages/react/src/components/media/controls/PlaybackDisplayError.tsx).

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      accessControlErrorComponent={
        <div>It looks like you don't have permission to view this content.</div>
      }
    />
  );
}
```

### playbackFailedErrorComponent

Sets the error component that is shown when playback fails for a generic reason
(usually this is an intermittent network issue). When this component is shown,
the Player will be retrying playback in the background, with linear backoff.
This will override the
[`GenericError` component](https://github.com/livepeer/react/blob/main/packages/react/src/components/media/controls/PlaybackDisplayError.tsx).

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      playbackFailedErrorComponent={
        <div>
          Playback failed! Please wait while we retry in the background.
        </div>
      }
    />
  );
}
```

### mediaElementRef

Sets the React
[callback `ref`](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs)
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).

<Tabs>
  <Tab title="React">
    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    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}
        />
      );
    }
    ```
  </Tab>

  <Tab title="React Native">
    ```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    import { useCallback } from "react";

    function PlayerComponent() {
      return (
        <Player
          title="Agent 327: Operation Barbershop"
          playbackId="f5eese9wwl88k4g8"
          mediaElementRef={async (ref) => {
            await ref?.setVolumeAsync(0.5);
          }}
        />
      );
    }
    ```
  </Tab>
</Tabs>

### onPlaybackStatusUpdate

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

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function PlayerComponent() {
  return (
    <Player
      title="Agent 327: Operation Barbershop"
      playbackId="f5eese9wwl88k4g8"
      onPlaybackStatusUpdate={(muted) => console.log(muted)}
      playbackStatusSelector={(state) => state.muted}
    />
  );
}
```

## Hooks

### usePlayerList

<Warning>
  `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.
</Warning>

The `usePlayerList` hook makes it easy to preload and display videos in a
[`FlatList`](https://reactnative.dev/docs/flatlist)-compatible list, by using
[`viewabilityConfigCallbackPairs`](https://reactnative.dev/docs/flatlist#viewabilityconfigcallbackpairs)
to trigger preloading on upcoming videos, and automatically playing/pausing
media when it is shown/hidden from the list.

See our
[Expo sample app](https://github.com/livepeer/react/tree/main/examples/expo) for
a real-world example.

#### Usage

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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).

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
{
  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`.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`](https://reactnative.dev/docs/flatlist#viewabilityconfig)
for more details.

```tsx {4} theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`](https://reactnative.dev/docs/flatlist#viewabilityconfig)
for more details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`](https://reactnative.dev/docs/flatlist#viewabilityconfig)
for more details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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`](https://reactnative.dev/docs/flatlist#viewabilityconfig)
for more details.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
function SomeComponent() {
  const { listProps } = usePlayerList({
    data: videos,
    itemVisibleWaitForInteraction: true,
  });
}
```

## SSR

<Warning>
  The following section only applies to web-based use-cases - React Native has
  no concept of SSR.
</Warning>

### Next.js

The Player also comes with a
[Tanstack Query](https://tanstack.com/query/v4/docs/guides/ssr) 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`](https://nextjs.org/docs/basic-features/data-fetching/get-static-props)
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.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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.

```tsx theme={"theme":{"light":"github-light","dark":"dark-plus"}}
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.
