Media & time
Video player
A video player with its own controls — progressive files (MP4, WebM), no streaming.
Import
import { VideoPlayer } from '@the_viveksingh/vivek-ui'One bar, real controls
Every control is a real button or range with a name, and the shortcuts are the ones every player taught people: Space/K play, arrows seek and change volume, J/L jump ten seconds, M mutes, F goes fullscreen. Controls fade while the pointer rests and come back on any key or focus - faded, never removed.
Paused. Space or K plays, arrows seek and change volume, M mutes, F goes fullscreen. Controls fade while the pointer rests and return on any key or focus.
<VideoPlayer
label="Product tour"
src="/media/tour.mp4"
poster="/media/tour-poster.jpg"
onEnded={() => track('tour_watched')}
/>Sources, captions and speeds
Several sources let the browser pick the first it can decode. WebVTT tracks add a Captions toggle (C); the default track starts showing. rates sets the speeds offered - an empty array hides the control. Cross-origin captions need crossOrigin on the element, which videoProps passes through.
Paused. Space or K plays, arrows seek and change volume, M mutes, F goes fullscreen. Controls fade while the pointer rests and return on any key or focus.
<VideoPlayer
label="Keynote"
src={[
{ src: '/media/keynote.webm', type: 'video/webm' },
{ src: '/media/keynote.mp4', type: 'video/mp4' },
]}
tracks={[
{ src: '/media/keynote.en.vtt', srclang: 'en', label: 'English', default: true },
{ src: '/media/keynote.hi.vtt', srclang: 'hi', label: 'हिन्दी' },
]}
rates={[1, 1.5, 2]}
ratio={4 / 3}
videoProps={{ crossOrigin: 'anonymous' }}
/>Props
Generated from the package's own type declarations, so this table cannot drift from the code.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | VideoSource[] | — | One URL, or several sources the browser tries **in order**. List the most widely supported format first (MP4/H.264, then WebM): Safari holds the document's `load` event while it works through a format it cannot decode, so a WebM-first list makes the whole page appear to hang there. |
poster | string | — | Frame shown before playback. **A raster image (JPEG, PNG, WebP)** — Safari ignores an SVG poster, and the document's `load` event never fires while it waits for one. |
tracks | VideoTrack[] | — | WebVTT text tracks. The first `default` one (else the first) is the captions track. |
label | string | — | Accessible name of the player. Default `'Video player'`. |
ratio | number | — | Width / height. Default `16 / 9`. |
autoPlay | boolean | — | — |
muted | boolean | — | Start muted — required by every browser for `autoPlay` to actually play. |
loop | boolean | — | — |
playsInline | boolean | — | Default `true`: play in place on phones instead of taking over the screen. |
preload | 'none' | 'metadata' | 'auto' | — | — |
rates | number[] | — | Playback speeds offered. Default `[0.5, 1, 1.25, 1.5, 2]`; `[]` hides the control. |
seekStep | number | — | Seconds moved by ArrowLeft / ArrowRight. Default `5`. |
hideControlsAfter | number | — | Hide the controls this many ms after the pointer rests while playing. `0` never hides. Default `2500`. |
errorMessage | ReactNode | — | Shown when the media fails to load or decode. |
videoRef | Ref<HTMLVideoElement> | — | The `<video>` element, for anything the props do not cover. |
videoProps | Omit<VideoHTMLAttributes<HTMLVideoElement>, 'src' | 'poster' | 'controls' | 'autoPlay' | 'muted' | 'loop' | 'playsInline' | 'preload'> | — | Extra attributes for the `<video>` — `crossOrigin` for cross-origin captions, for one. |
onPlayingChange | (playing: boolean) => void | — | — |
onTimeChange | (currentTime: number, duration: number) => void | — | — |
onEnded | () => void | — | — |
onError | () => void | — | — |
Every remaining prop is spread onto the root element, so all standard HTML and ARIA attributes work. className and style are merged with the library's own, never replaced, and the ref forwards to the root DOM node.
Rendering
VideoPlayer declares 'use client' because it needs state, effects or event handlers. Importing it into a Server Component creates a client boundary at this component — everything above it stays on the server.