# VPlayer — Every video source, one React component
> VPlayer is a dependency-free React video player component. One API for MP4, HLS, YouTube, Vimeo and Bilibili, with captions, chapters, playlists, three control skins and analytics callbacks. 14 kB gzipped, React 18+, TypeScript types included.
- Package: `vplayer-react` v1.6.0
- Install: `npm i vplayer-react`
- Repository: https://github.com/ardaydilek/vplayer-react
- Licence: MIT
---
## Quick start
```tsx
import { VPlayer } from 'vplayer-react';
export default function Page() {
return (
);
}
```
That is the whole setup. There is no stylesheet to import, nothing to register
and no provider to wrap the tree in.
---
## What it does
### Sources
- **One API, five source types** — Local and remote media files, HLS manifests, YouTube, Vimeo and Bilibili — the component works out which it is and renders the right thing.
- **Lazy embeds** — Platform iframes stay unmounted behind the poster until someone actually presses play, so a page full of players costs one image each.
- **Playlists** — Pass an array for prev/next and auto-advance, or drive the index yourself with activeIndex and onIndexChange.
- **Native HLS** — Plays where the browser supports it, and surfaces a clear error overlay where it doesn’t — instead of failing silently.
### The viewing experience
- **Three control skins** — Classic, minimal and floating carry identical controls — the difference is layout and surface. Each adapts down to a 300px player without a control ever losing its hit area.
- **Captions that clear the controls** — Cues are drawn inside the picture and lift above the bar while it’s on screen, so they stay readable — even for a wide clip fullscreened on a portrait phone.
- **Chapters and scrub previews** — Tick marks with hover labels, Shift+Arrow chapter jumps, and sprite-sheet frame previews while scrubbing.
- **Keyboard and touch, properly** — Scoped shortcuts that never hijack the page, 44px hit areas, touch scrubbing, PiP, and a reduced-motion path for every transition.
- **Errors that recover** — A failed load names what the viewer can check — missing file, blocked by the server, unplayable codec — and offers a retry instead of a dead end.
### The developer experience
- **Zero runtime dependencies** — Pure React and inline styles. No CSS file to import, nothing to configure, nothing else pulled into your lockfile.
- **Analytics callbacks** — onPlay, onSeek, onBuffer, onMilestone, onChapterChange, onVolumeChange, onError and a dozen more — enough to instrument playback without a wrapper.
- **Ref API** — play, pause, seek, setVolume, toggleMute, toggleFullscreen, and an escape hatch to the underlying media element.
- **Controlled or uncontrolled** — playing, volume, muted, playbackRate and the playlist index all accept controlled props — or leave them off and the player keeps its own state.
- **Clip mode and resume** — initialTime resumes where a viewer left off, endTime plays an excerpt out of a longer file, and persistVolume remembers the level across reloads.
- **Typed and server-safe** — Full TypeScript types, matching ESM and CJS declarations, and a “use client” directive so importing from a Server Component just works.
---
## Recipes
### Any source
One component, five kinds of URL. Platform links become lazy embeds behind a poster; media files and HLS manifests get the full custom control bar.
```tsx
// Local or remote media file
// HLS — plays natively where the browser supports it,
// with a clear error overlay where it doesn’t
// Platform links — lazy-loaded iframes behind the poster
// Check before you render
VPlayer.canPlay("https://youtu.be/OZjOHE_Y8vY"); // true
```
### Control skins
Three layouts over one set of controls, each adapting down to a 300px player. Below ~580px the inline skins fall back to the stacked arrangement, and every control keeps a 40×44px hit area the whole way down.
```tsx
// Full-width scrim, scrubber on its own row (default)
// One row: scrubber inline between elapsed and remaining
// The same row inside a detached, blurred pill
```
### Captions
WebVTT tracks with a CC picker in the bar. Cues are drawn inside the picture and rise to clear the controls — so they never sit behind the bar, and never land in the black band when a wide clip is fullscreened on a portrait phone.
```tsx
```
### Playlists
Pass an array and prev/next appear with auto-advance. Or drive the index from your own sidebar and keep the player in sync.
```tsx
// Uncontrolled — the player owns the index
console.log("advanced")}
/>
// Controlled — your UI owns it
const [track, setTrack] = useState(0);
e.url)}
activeIndex={track}
onIndexChange={setTrack}
/>
```
### Chapters
Tick marks on the scrubber with labels on hover, Shift+Arrow to jump between them, and a callback when the viewer crosses a boundary.
```tsx
console.log("Now:", ch?.label)}
/>
```
### Controlled
Drive playback from your own state when you need to — a transcript that scrubs, a lesson that pauses on a quiz, a hero that mutes when it scrolls away. Leave the props off and the player owns it all.
```tsx
const [playing, setPlaying] = useState(false);
setPlaying(true)}
onPause={() => setPlaying(false)}
/>
```
### Clips & resume
Start where the viewer left off, stop at a timestamp to play an excerpt of a longer file, and remember the volume they picked across reloads.
```tsx
```
### Error states
A failed video gets an overlay naming what the viewer can check — not a blanket "format not supported" — and a Try again that reloads the media rather than ending the journey.
```tsx
// Check a URL before you commit to rendering it
VPlayer.canPlay("https://cdn.example.com/master.m3u8"); // true
{
// 2 network · 3 decode · 4 missing, blocked or unplayable
analytics.track("video_error", { code: err?.code });
}}
/>
```
### Ref API
An imperative handle for the cases props can't express — a transcript that seeks, a keyboard shortcut somewhere else on the page, a custom overlay.
```tsx
import { useRef } from 'react';
import { VPlayer, type VPlayerHandle } from 'vplayer-react';
function Player() {
const ref = useRef(null);
return (
<>
>
);
}
```
### Analytics
Every callback you need to instrument playback, including quartile milestones that fire once per source.
```tsx
analytics.track("video_play")}
onEnded={() => analytics.track("video_complete")}
onMilestone={(pct) => analytics.track("milestone", { pct })}
onSeek={(time) => analytics.track("seek", { time })}
onBuffer={(pct) => console.log("buffered", pct)}
onVolumeChange={(vol, muted) => console.log({ vol, muted })}
onError={(err) => Sentry.captureMessage(`video ${err?.code}`)}
/>
```
### Key bindings
Shortcuts are scoped to the player — they only fire when it has focus, so they never hijack the page. Remap or disable any of them.
```tsx
import type { VPlayerKeymap } from 'vplayer-react';
const keymap: VPlayerKeymap = {
play: "p", // p instead of Space / K
fullscreen: ["f", "F"], // accept both cases
shortcuts: false, // disable the ? overlay entirely
};
```
---
## Keyboard shortcuts
Scoped to the player: they fire only while it has focus, so a page with three
players — or a search box — never loses a keystroke to the video. Every binding
is remappable through the `keymap` prop, and any of them can be disabled.
| Keys | Action |
|------|--------|
| `Space / K` | Play or pause |
| `F` | Toggle fullscreen |
| `M` | Toggle mute |
| `← / →` | Rewind or forward 5s |
| `Shift + ← / →` | Previous or next chapter |
| `↑ / ↓` | Volume up or down |
| `0–9` | Seek to 0%–90% |
| `< / >` | Decrease or increase speed |
| `?` | Toggle the shortcuts overlay |
| `Esc` | Close menus and overlays |
---
## Questions
### Which React video player handles MP4 and YouTube in the same component?
vplayer-react does. Pass any URL to `src` and the component works out what it is: media files and HLS manifests render with the full custom control bar, while YouTube, Vimeo and Bilibili links become lazy-loaded embeds behind the poster. `VPlayer.canPlay(url)` tells you in advance whether a URL is recognised.
### How large is vplayer-react?
14.0 kB minified and gzipped, with zero runtime dependencies — nothing but React itself enters your lockfile, and there is no stylesheet to import. The component is styled with inline styles plus one small injected stylesheet for hover, focus and reduced-motion rules.
### Does it work with Next.js and React Server Components?
Yes. The published bundles carry the `"use client"` directive, so importing vplayer-react directly inside a Server Component works without a wrapper. It ships both ESM and CommonJS builds with matching TypeScript declarations, and requires React 18 or newer as a peer dependency.
### Does it support HLS streams?
Yes, where the browser plays HLS natively — Safari, iOS and some Chromium builds. Pass an `.m3u8` URL like any other source. In browsers without native support the player surfaces a clear error overlay rather than failing silently, so you can decide whether to ship an HLS polyfill alongside it.
### How do I add subtitles or closed captions?
Pass a `tracks` array of WebVTT files and a CC picker appears in the control bar. The player renders the cues itself rather than leaving them to the browser, so they sit inside the picture, rise to clear the control bar while it is on screen, and stay put when a widescreen clip is fullscreened on a portrait phone. `captionStyle` restyles them without any CSS file.
### Is it accessible?
Keyboard shortcuts are scoped to the player, so a page with three players — or a search box — never loses a keystroke to the video. Every control is a real button with an accessible name and a 40×44px hit area, the seek and volume sliders implement the ARIA slider pattern, hidden controls leave the tab order, and every transition has a `prefers-reduced-motion` path.
### Can I drive playback from my own state?
Yes. `playing`, `volume`, `muted`, `playbackRate` and the playlist index all accept controlled props, and there is an imperative handle with `play()`, `pause()`, `seek()`, `setVolume()`, `toggleMute()`, `toggleFullscreen()` and an escape hatch to the underlying media element. Leave the props off and the player keeps its own state.
### Can I change how the controls look?
`controlsVariant` picks between three skins carrying identical controls: `classic` (full-width scrim with the scrubber on its own row), `minimal` (one row with the scrubber inline between elapsed and remaining time) and `floating` (that row inside a detached, blurred pill). `accentColor` and `iconColor` handle brand colour, and each skin adapts down to a 300px-wide player.
---
## Full reference
The complete prop table, callback list and type shapes live in the README:
https://github.com/ardaydilek/vplayer-react#readme