# Video Player

> A React video player with controls that hide while you watch, a chapter scrubber, captions, speed, picture in picture and full screen.

- Type: React component (Media)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add video-player`
- Docs: https://wingo-ui.com/components/video-player

## Interaction

Click or tap the big play button (or anywhere on the picture) to start; with a mouse, a click on the picture plays and pauses and a double click goes full screen. While it plays, the controls and the title fade away after 2.5 s of stillness and come back when you move the mouse, tap or focus a control; the captions glide down with them. Hover the scrubber and it thickens, a thumb appears and a pill shows the time and the chapter under the pointer; press and drag to scrub (a mouse seeks as it moves, a finger seeks on release), chapters are small ticks on the bar. The row has play, skip back / forward 10 s, mute with a volume slider, the time, a captions button (a menu when there are several languages), a speed menu, picture in picture and full screen; on phones the menus open as bottom sheets. On a touch screen a tap shows or hides the controls, a tap on the middle of the picture plays and pauses while they show, the big button is flanked by skip buttons, and a double tap on the left or right third jumps 10 s with a ripple and a pill that counts up as you keep tapping. Keyboard (focus the player or any control): Space or k play and pause, Left / Right seek 5 s, j / l 10 s, Up / Down volume, m mute, 0 to 9 jump to 0% to 90%, Home / End, < and > change the speed, f full screen, c captions on or off, i picture in picture; on the focused scrubber Left / Right seek 5 s, PageUp / PageDown 30 s. While it plays in picture in picture the stage says so and the bar stays usable. A missing file shows a message with a retry button.

## Installation

```bash
npx wingo-ui@latest add video-player
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/video-player` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/video-player.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Slider](https://wingo-ui.com/components/slider) ([Markdown](https://wingo-ui.com/components/slider.md))
- [Spinner](https://wingo-ui.com/components/spinner) ([Markdown](https://wingo-ui.com/components/spinner.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [useMediaControls](https://wingo-ui.com/components/use-media-controls) ([Markdown](https://wingo-ui.com/components/use-media-controls.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { VideoPlayer } from "@/components/ui/video-player"

export function CityTour() {
  return (
    <VideoPlayer
      src="/demo/city-tour.mp4"
      poster="/demo/city-tour.jpg"
      title="City tour"
      tracks={[
        { src: "/demo/city-tour.en.vtt", srclang: "en", label: "English", default: true },
        { src: "/demo/city-tour.ro.vtt", srclang: "ro", label: "Romanian" },
      ]}
      chapters={[
        { start: 0, title: "The main boulevard" },
        { start: 7.8, title: "The mountains" },
        { start: 11, title: "Home" },
        { start: 16, title: "Final" },
      ]}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string \| VideoSource[]` |  | The video url, or several { src, type } sources (webm, mp4) for the browser to pick from. |
| `poster?` | `string` | `""` | The still shown before the first play (it fades out on play). Also the lock-screen artwork. |
| `title?` | `ReactNode` | `""` | A title in a top scrim while the controls show; when it is a string, also the region name and the lock-screen title. |
| `tracks?` | `VideoTrack[]` | `"[]"` | WebVTT captions { src, srclang, label, kind?, default? }. The native tracks stay hidden and the player draws the active cue itself, above the controls. Tracks on another origin need CORS (the video gets crossOrigin anonymous). |
| `chapters?` | `VideoChapter[]` |  | { start, title } in seconds: ticks on the scrubber, the chapter in the time pill and next to the time on wide players. |
| `aspectRatio?` | `string` | `"16 / 9"` | CSS aspect ratio of the stage, e.g. 1 / 1 for a listing card or 9 / 16 for a story. |
| `fit?` | `"contain" \| "cover"` | `"contain"` | contain letterboxes the whole picture on black, cover crops it to fill the stage. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Corner radius of the stage (0 / 12 / 16 / 24px). Full screen drops the corners. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Colors the played part of the scrubber and the captions-on underline. neutral is white on the black stage in both themes. |
| `color?` | `string` | `""` | Any CSS color for the played bar; replaces the tone. |
| `captionSize?` | `"sm" \| "md" \| "lg"` | `"md"` | Caption text size. Each step also grows with the player's own width, so a card and a full-screen player both read well. |
| `controls?` | `"auto" \| "always" \| "none"` | `"auto"` | auto hides the controls while playing and idle for hideDelay (paused, a menu open, keyboard focus or scrubbing keep them up); always keeps them; none shows only the picture, for background clips. |
| `hideDelay?` | `number` | `2500` | Milliseconds of stillness before the controls hide while playing. The mouse cursor hides with them. |
| `skipSeconds?` | `number` | `10` | Seconds for the skip buttons, a double tap and j / l. Arrow keys seek half of it. |
| `doubleTapSeek?` | `boolean` | `true` | Touch: a double tap on the left or right third seeks by skipSeconds; more taps keep adding and the pill counts. |
| `pip?` | `boolean` | `true` | The picture in picture button (and the i key) where the browser supports it. While it is out, the stage says so. |
| `fullscreen?` | `boolean` | `true` | The full screen button, the f key and double click. The whole player goes full screen, so the controls and captions stay ours; phones try to turn to landscape. iOS Safari falls back to its native player. |
| `autoPlay?` | `boolean` | `false` | Starts muted (browsers require it), pauses while scrolled out of view and resumes when back, unless the viewer paused it. |
| `muted?` | `boolean` | `false` | Starts without sound; the mute button stays usable. |
| `loop?` | `boolean` | `false` | Starts again from the beginning when it ends. |
| `startTime?` | `number` | `0` | Seconds to start from once the metadata loads, for continue watching. 0 starts at the beginning. |
| `exclusive?` | `boolean` | `true` | Starting this player pauses every other exclusive player and audio player on the page. |
| `mediaSession?` | `boolean` | `true` | While playing, sets the lock-screen title and artwork and handles play, pause and seek from the lock screen and headphones. |
| `preload?` | `"none" \| "metadata" \| "auto"` | `"metadata"` | How much the browser loads before play. metadata knows the duration without downloading the film. |
| `captionsDefault?` | `string \| "off"` | `""` | The caption language on at start (a track srclang), or "off". Empty uses the track marked default, else off. |
| `rates?` | `readonly number[]` | `"MEDIA_RATES"` | Speeds in the speed menu (0.5 to 2 by default). One rate hides the menu. |
| `onPlay?` | `() => void` |  | Called when playback starts. |
| `onPause?` | `() => void` |  | Called when playback pauses, including the automatic pause of autoPlay off screen. |
| `onEnded?` | `() => void` |  | Called at the end (never while looping). |
| `onError?` | `(error: MediaError \| null) => void` |  | Called when the video cannot be loaded or played. |
| `onTimeUpdate?` | `(seconds: number) => void` |  | Whole seconds while playing and after seeks, for saving progress. |
| `setApi?` | `(api: VideoPlayerApi) => void` |  | The media controls (play, pause, seek, second, duration, progress…) plus fullscreen, pip, captions, enterFullscreen(), exitFullscreen(), togglePip() and setCaptions(lang \| "off"). Called after mount and whenever the state changes. |
| `labels?` | `Partial<VideoPlayerLabels>` |  | Every visible and spoken string, English by default: play, pause, replay, seek, skipBack(s), skipForward(s), skippedBack(s), skippedForward(s), volume, mute, unmute, captions, captionsOff, speed, normal, rate(r), pip, exitPip, pipActive, fullscreen, exitFullscreen, error, retry, cancel (the phone sheet button), live, loading, video, position(current, total). |
| `classNames?` | `Partial<Record<"video" \| "poster" \| "bigPlay" \| "spinner" \| "captions" \| "cue" \| "ripple" \| "center" \| "skip" \| "pip" \| "title" \| "controls" \| "scrubber" \| "track" \| "thumb" \| "bubble" \| "row" \| "button" \| "time" \| "error" \| "live", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the stage, merged last. |

## Parts

### DEFAULT_VIDEO_PLAYER_LABELS (constant)

The English strings for every button, the scrubber, captions, speed, picture in picture and the screen reader position. Spread it and override a few keys through labels.

### VideoPlayerApi (type)

What setApi hands you: the media controls plus fullscreen, pip, captions and the methods to toggle them.

### VideoTrack (type)

A captions or subtitles file for tracks: { src, srclang, label, kind?: "subtitles" | "captions", default? }.

### VideoChapter (type)

One chapter for the scrubber: { start (seconds), title }.

### VideoPlayerProps (type)

The props of VideoPlayer, for wrappers that pass them through.

## Examples

- **Hero background**: autoPlay, muted, loop and no controls behind a headline; it pauses when scrolled out of view.
- **Large captions**: English captions on from the start at captionSize lg, drawn above the controls.
- **Listing video card**: A square cover crop in a listing card, three speeds and no picture in picture.
- **On a phone**: Tap for controls, double tap left or right to skip; speed and captions open as bottom sheets.
- **Chapters with setApi**: An outside chapter list that seeks the player and follows the playhead.
- **Brand tone, resume position**: tone brand colors the played bar; startTime resumes at 0:08 like continue watching.
- **Error**: A missing file shows the message and a retry button.

## See also

- [useMediaControls](https://wingo-ui.com/components/use-media-controls) ([Markdown](https://wingo-ui.com/components/use-media-controls.md))
- [Audio Player](https://wingo-ui.com/components/audio-player) ([Markdown](https://wingo-ui.com/components/audio-player.md))
- [Image Lightbox](https://wingo-ui.com/components/image-lightbox) ([Markdown](https://wingo-ui.com/components/image-lightbox.md))
- [Carousel](https://wingo-ui.com/components/carousel) ([Markdown](https://wingo-ui.com/components/carousel.md))

---

Canonical: https://wingo-ui.com/components/video-player
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
