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

# React Player

> The @livepeer/react Player component: composable video playback with automatic HLS/WebRTC transport negotiation, ABR, poster, and playback metrics.

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

<CenteredContainer preset="readable90">
  <Tip>The Player component is composable: Root, Container, Video, Controls, and indicator primitives. Compose only the parts you need. Unstyled by default; bring your own CSS or Tailwind.</Tip>
</CenteredContainer>

<CustomDivider />

`@livepeer/react` provides a composable Player that handles HLS and WebRTC playback with automatic transport negotiation. Pass a playback source and the component handles ABR selection, poster rendering, and playback state.

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
npm install @livepeer/react
```

<CustomDivider />

## Basic player

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

export function VideoPlayer({ playbackId }: { playbackId: string }) {
  return (
    <Player.Root
      src={[
        {
          type: 'hls',
          src: \`https://livepeercdn.studio/hls/\${playbackId}/index.m3u8\`,
        },
      ]}
    >
      <Player.Container>
        <Player.Video title="Video player" />
        <Player.Controls className="flex items-center gap-2">
          <Player.PlayPauseTrigger className="w-8 h-8">
            <Player.PlayingIndicator asChild matcher={false}>
              <span>Play</span>
            </Player.PlayingIndicator>
            <Player.PlayingIndicator asChild>
              <span>Pause</span>
            </Player.PlayingIndicator>
          </Player.PlayPauseTrigger>
          <Player.MuteTrigger className="w-8 h-8" />
          <Player.Volume className="w-24" />
          <Player.Time className="text-sm" />
        </Player.Controls>
      </Player.Container>
    </Player.Root>
  );
}
```

`Player.Root` accepts a `src` array of source objects. For Livepeer streams and assets, the HLS source is at `https://livepeercdn.studio/hls/{playbackId}/index.m3u8`. For WebRTC, use the `getSrc` utility from `@livepeer/react/external`.

<CustomDivider />

## Player components

**Root components:**

* `Player.Root` accepts `src` (source array), `autoPlay`, `volume`, and event callbacks
* `Player.Container` wraps the video element and controls
* `Player.Video` renders the `<video>` element with automatic transport selection

**Playback controls:**

* `Player.PlayPauseTrigger` toggles play/pause
* `Player.PlayingIndicator` renders conditionally based on play state (`matcher={true}` for playing, `matcher={false}` for paused)
* `Player.MuteTrigger` toggles mute
* `Player.Volume` renders a volume slider
* `Player.Seek` and `Player.SeekBuffer` for scrubbing and buffered range display
* `Player.Time` renders current time / duration
* `Player.FullscreenTrigger` and `Player.FullscreenIndicator`
* `Player.PictureInPictureTrigger`

**Quality and rate:**

* `Player.VideoQualitySelect` and `Player.VideoQualitySelectItem` for manual ABR override
* `Player.RateSelect` and `Player.RateSelectItem` for playback speed

**Status indicators:**

* `Player.LoadingIndicator` renders during buffering
* `Player.ErrorIndicator` renders on playback failure
* `Player.LiveIndicator` renders when the source is a live stream
* `Player.Poster` renders a poster image before playback starts

All components are unstyled. Apply CSS classes directly or use Tailwind utility classes. The `asChild` prop delegates rendering to the child element (Radix pattern).

<CustomDivider />

## WebRTC playback

For sub-second latency playback, add a WebRTC source alongside HLS:

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

const src = getSrc('https://livepeercdn.studio/hls/{playbackId}/index.m3u8');
// Returns [{ type: 'webrtc', src: '...' }, { type: 'hls', src: '...' }]

<Player.Root src={src}>
  <Player.Container>
    <Player.Video />
  </Player.Container>
</Player.Root>
```

The Player tries WebRTC first and falls back to HLS if the browser or network does not support it.

<CustomDivider />

Source: [`livepeer/ui-kit`](https://github.com/livepeer/ui-kit) `packages/react/src/player.tsx`.

The [video overview](/v2/developers/build/video/overview) covers the stream creation and ingest flow that produces the `playbackId` used here.
