React AV

Headless media toolkit for React

Media players,
built to your spec.

React AV handles playback state, captions, streaming and timeline editing. Compose it with hooks and unstyled components, and keep every pixel of the interface your own.

$npm i @react-av/core
Anatomy of a React AV player: a media root holding a container, video, viewport controls, caption track and hooks.<Media.Root><Media.Container><Media.Video />16:901:24<Track kind="subtitles" />useMediaPlaying()useMediaCurrentTime()AStateMedia.RootBLayoutMedia.ContainerCControlsMedia.ViewportDCaptions@react-av/vttEHooksuseMedia*()
Fig. 00 · Anatomy of a playerScale 1:1
01 · Player

One player.
Any skin.

Three skins on one <Media.Root>. Switch while it plays: position, volume and captions carry over because only the markup changes.

Build an HLS or DASH player →

Interactive React video player

02 · Editor

A timeline editor you can ship.

Subtitle editing as composable parts. Restyle it for a studio tool or a course builder without forking a line.

Select a cue to edit it, drag across the track to add one, or use the arrow keys to nudge timing.

Build a React subtitle editor →

Interactive React subtitle editor

editor.tsx

<Media.Root>
  <Track kind="subtitles" src="/lesson.vtt" id="captions" />
  <Editor mediaComponent={<Media.Video src="/lesson.mp4" />}>
    <TimelineEditor>
      <TimelineControlBar />
      <TimelineContainer>
        <TimelineHeader />
        <TimelineSubtitlesTrack id="captions">
          <TimelineSubtitleCueEditor />
        </TimelineSubtitlesTrack>
      </TimelineContainer>
    </TimelineEditor>
  </Editor>
</Media.Root>
03 · Hooks

Sync anything to playback.

Every piece of media state is a hook. Lyrics, transcripts, quizzes, chapter lists: read the clock, render what you like.

Add WebVTT captions to React →

Interactive lyrics and audio player

lyrics.tsx

function Lyrics() {
  const [cues, active] = useMediaTextTrack("lyrics");
  const [, seek] = useMediaCurrentTime();

  return cues.map(cue => (
    <Cue as="li" cue={cue} key={cue.startTime}
      data-active={active.includes(cue)}
      onClick={() => seek(cue.startTime)} />
  ));
}
04 · Packages

Take only what you need.

All documentation →