Player

The root that holds player state, the container that goes fullscreen, and the overlay that plays and pauses on click.

import { Player } from 'react-media-kit'
import { Player, Media, PlayButton } from 'react-media-kit'
import './video-player.css'

export function VideoPlayer({ src }: { src: string }) {
  return (
    <Player.Root onError={(e) => console.error(e.type, e.error)}>
      <Player.Container className="player" tabIndex={0}>
        <Media.Video src={src} className="player-video" playsInline />
        <Player.Overlay label="Play or pause" className="player-overlay" />
        <PlayButton.Root className="player-play">Play</PlayButton.Root>
      </Player.Container>
    </Player.Root>
  )
}

Give Player.Container a tabIndex (the example uses 0) so it can take focus. The keyboard shortcuts only run while focus is on the container or something inside it.

Anatomy

How the parts nest.

<Player.Root>
  <Player.Container>
    <Media.Video />
    <Player.Overlay />
  </Player.Container>
</Player.Root>

API reference

Parts accept the attributes of the element they render, plus a ref, except where noted. Every part that renders an element also sets the media state attributes.

Player.Root

renders no element

Holds the player state and shares it with the parts inside it and with the usePlayer and usePlayerControls hooks. Every other component must be rendered inside a Player.Root.

PropTypeDefaultDescription
childrenReactNoderequiredThe parts of the player.
langstring—Locale used (via Intl) to format the seekbar’s aria-valuetext. Other labels, including the skip button’s, are always in English.
onError(error: PlayerError) => void—Called when something fails, with { type: "media" | "fullscreen" | "play" | "pip", error }. For "media", error is the element’s MediaError. The handler is read once, when Player.Root mounts. Later changes are ignored.

Player.Root renders no element, so it takes no HTML attributes and no ref.

Player.Container

renders <div>

Wraps the media and its controls inside Player.Root. It is the element that enters fullscreen, and it handles the keyboard shortcuts below. It is always position: relative.

PropTypeDefaultDescription
skipIntervalnumber5Seconds to skip with ← and →. At least 1. Fractions are rounded down, and anything else throws a ReactMediaKitError.
volumeIntervalnumber0.05Volume change per ↑ or ↓ press. Above 0 and up to 1. Anything else throws a ReactMediaKitError.
Data attributeDescription
data-fullscreenThe player is fullscreen.
data-pipThe video is playing picture-in-picture.

Your onKeyDown runs before the built-in one; call event.preventDefault() in it to skip the built-in behavior.

Player.Overlay

renders <button>

A full-size button over the media (position: absolute; inset: 0; touch-action: manipulation). It has no visual styling of its own, so make it invisible with CSS, as in the example. It must be inside Player.Container; anywhere else, clicks on it are ignored.

Enter and Space play or pause immediately. A single click or tap does too, after waiting doubleClickInterval milliseconds to see whether a second click follows. A double click or tap toggles fullscreen, or calls your handler, and doesn’t play or pause.

PropTypeDefaultDescription
labelstringrequiredBecomes the button’s aria-label.
onDoubleClick"fullscreen" | PointerEventHandler"fullscreen"What a mouse double click does: toggle fullscreen, or call your handler instead.
onDoubleTouch"fullscreen" | PointerEventHandler"fullscreen"What a touch or pen double tap does: toggle fullscreen, or call your handler instead.
doubleClickIntervalnumber300Longest gap, in milliseconds, between two clicks that still counts as a double click. A single click also waits this long before playing or pausing.

Your onKeyDown, onPointerDown and onPointerUp handlers run before the built-in ones; call event.preventDefault() in one to skip its built-in behavior.

Keyboard interactions

While Seekbar.Root or Volume.Slider has focus, the arrow keys go to that slider instead; Space, Enter, M and F still work. Modifier keys aren’t checked, and only Space and Enter are ignored in form fields, so M, F and the arrows still fire while typing in an input inside the container.

KeyAction
SpaceEnterPlay or pause. Ignored while a button, link or form field has focus, so that element’s own action runs instead.
Left arrowRight arrowSkip back or forward by skipInterval seconds.
Up arrowDown arrowRaise or lower the volume by volumeInterval. Also unmutes.
MMute or unmute.
FEnter or exit fullscreen.

Media state attributes

Five of these describe loading, and exactly one of them is set at a time. The other three mark features the browser lacks. Use them to hide controls that can’t work, or to style the player while it loads.

Data attributeDescription
data-media-pendingLoading hasn’t started. This is the initial state.
data-media-loadingThe media has started loading.
data-media-metadata-loadedThe duration and dimensions are known, but there isn’t enough data to play yet.
data-media-playableThe media can play. It stays set if playback later stalls to buffer.
data-media-errorThe media element reported an error, such as a network or decoding failure. A blocked play() call doesn’t set it; use onError for that.
data-fullscreen-unsupportedThe browser can’t go fullscreen, or support hasn’t been detected yet.
data-pip-unsupportedPicture-in-picture isn’t available, such as for audio, or support hasn’t been detected yet.
data-volume-change-unsupportedThe browser doesn’t let scripts set the volume, such as on iOS, or support hasn’t been detected yet.