55 / Agent UI
Video Player
Video with captions that can be turned on, a real scrubber, and controls that survive full screen.
"use client"
import { SAMPLE_VIDEO, videoCaptions } from "@/components/demos/audio-fixtures"
import { VideoPlayer } from "mischief-ui/video-player"
export function VideoPlayerDemo() {
return (
<div className="w-full max-w-xl">
<VideoPlayer
src={SAMPLE_VIDEO}
label="a screen recording of the invoice"
tracks={videoCaptions}
/>
</div>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/video-playerimport { VideoPlayer } from "mischief-ui/video-player"Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.
"use client" import * as React from "react"import { Captions, CaptionsOff, Maximize, Minimize, Pause, Play, Volume2, VolumeX,} from "lucide-react" Usage
export function Recording({ src, captions }) {
return <VideoPlayer src={src} label="the walkthrough" tracks={captions} />
}Captions are a track, not a transcript
A caption belongs to a moment in the video, which is what WebVTT and the track element already express. Rendering your own list of lines beside the picture is a different thing, useful for reading and useless for watching.
<VideoPlayer
src="/walkthrough.mp4"
label="the walkthrough"
tracks={[{ src: "/walkthrough.vtt", srcLang: "en", label: "English", default: true }]}
/>Taking the browser's controls away takes its caption menu with them, so the track mode is set here by hand. The browser still draws the cues over the picture once a track is showing, which is why the toggle is three lines rather than a caption renderer.
Owning the controls means owning all of them
Play, mute, speed, captions and full screen are each a real button with a name that says which way it will go, and the pressed ones carry aria-pressed. Seeking is a range input, so arrow keys, Home and End work and the position is announced as a time rather than a count of seconds.
Full screen is requested on the whole component rather than on the video, so the controls come along instead of being replaced by the browser's own.
API
srcstringThe video.labelstringNames the video in every control's label.posterstringShown before playback starts.tracksreadonly VideoTrack[]Caption and subtitle tracks.ratesreadonly number[]Speeds the button cycles through. Defaults to 1, 1.5 and 2....rootPropsHTMLAttributes<HTMLDivElement>Native root attributes.VideoTrack
srcstringThe WebVTT file.srcLangstringA BCP 47 tag, such as "en".labelstringHow the track is named to a reader.kind"captions" | "subtitles"Captions carry sound, subtitles carry speech. Defaults to captions.defaultbooleanStart with this one showing.Accessibility
The video carries an accessible name, and every control is a button whose name says the action it will take rather than the state it is in. Toggles carry aria-pressed. Seeking is a native range input announced as a position in minutes and seconds, so it works from a keyboard. Captions are real tracks, which means the browser draws them, the reader can style them in their own settings, and a deaf viewer gets them without a transcript being bolted on beside the picture. Full screen is entered on the component so the controls remain.