useMediaControls React hook in TypeScript
v1.0.0Added in 1.0.0
A React hook for custom audio and video players: play, seek, volume, speed, buffering and a smooth playhead from one media element.
Press play on the podcast: the bar glides every frame while the time text changes once a second. Tap or drag on the bar to seek, change the speed and mute. With the player focused, Space or k plays and pauses, Left / Right seek by the step, j / l by twice that, Up / Down change the volume, m mutes, 0 to 9 jump to 0% to 90%, < and > change the speed. Turn exclusive on and starting one voice message pauses the other and the podcast.
useMediaControls is a Pro React hook in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add use-media-controls, together with the npm package motion.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add use-media-controlsuseMediaControls is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import useMediaControls from @/hooks/use-media-controls.
import { motion } from "motion/react"
import { formatMediaTime, handleMediaKey, useMediaControls } from "@/hooks/use-media-controls"
export function VoiceNote({ src }: { src: string }) {
// destructure ref: reading media.ref for the ref prop makes the react compiler treat all of media as a ref
const { ref, ...media } = useMediaControls({ exclusive: true })
return (
<div tabIndex={0} onKeyDown={(event) => handleMediaKey(event, media)} className="flex items-center gap-3">
<audio ref={ref} src={src} preload="metadata" />
<button type="button" onClick={media.toggle}>{media.playing ? "Pause" : "Play"}</button>
<div className="h-1 flex-1 overflow-hidden rounded-full bg-foreground/10">
<motion.div className="h-full origin-left bg-foreground" style={{ scaleX: media.progress }} />
</div>
<span className="tabular-nums">{formatMediaTime(media.playing ? media.second : media.duration)}</span>
</div>
)
}API
exclusivedefaultfalsebooleanStarting this player pauses every other exclusive player on the page (voice messages in a chat, a list of episodes).
defaultRatedefault1numberThe playback speed applied when the element attaches (defaultVolume 0 to 1 and defaultMuted work the same way).
seekStepdefault5numberhandleMediaKey(event, controls, { seekStep }): seconds for Left / Right; j / l seek twice as far.
defaultVolume / defaultMuteddefault1number / booleanApplied when the element attaches.
onPlay / onPause / onEnded / onError / onTimeUpdate() => void / (error: MediaError | null) => void / (seconds: number) => voidLifecycle callbacks; onTimeUpdate fires with whole seconds (resume positions, analytics). The latest callbacks are always used.
useMediaControls(options?) => MediaControlsReturns { ref (put it on the <audio> / <video>; destructure it, const { ref, ...media } = useMediaControls(), because the React compiler lint treats an object whose field feeds a ref prop as a ref), element, playing, waiting (buffering), ended, ready, error, duration, second (whole seconds as state, re-renders at most once a second), currentTime / progress / buffered (motion values, progress and buffered 0 to 1, updated every frame while playing), volume, muted, rate, play() (resolves false when the browser blocks it), pause(), toggle(), seek(seconds), seekBy(delta), seekToFraction(0..1), setVolume, setMuted, toggleMute, setRate }.
Parts
handleMediaKey
functionThe shared keyboard map for onKeyDown on a player root. Leaves sliders, inputs, menus and listboxes alone; returns true (and prevents the default) when it handled the key. Signature: (event, controls, options?: { seekStep?: number = 5; volumeStep?: number = 0.1; rates?: number[]; volumeKeys?: boolean = true }) => boolean.
formatMediaTime
function"0:42", "12:05", "1:02:03"; unknown or live durations print "--:--". forceHours keeps the text width stable in long videos. Signature: (seconds: number, options?: { forceHours?: boolean }) => string.
MediaControls
typeThe return value: ref, element, playback flags, duration, second, motion values for the playhead and every action.
UseMediaControlsOptions
typeThe options object: exclusive, defaultRate, defaultVolume, defaultMuted and the event callbacks.
MediaKeyOptions
typeThe options of handleMediaKey: seekStep, volumeStep, rates and volumeKeys.
MEDIA_RATES
constant0.5, 0.75, 1, 1.25, 1.5, 1.75, 2: the speed menu of every player. Type: readonly number[].
Examples
Voice messages, one at a time
exclusive: starting one voice message pauses the other, like a chat app.
Podcast at 1.5x
defaultRate 1.5 and a 15 second seek step for the arrow keys.
Custom video controls
The hook on a <video> element, with a play button and a gliding progress bar over the picture.
Dependencies
Files
- hooks/use-media-controls.ts
FAQ
Is the useMediaControls hook free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install useMediaControls?
Run npx wingo-ui@latest add use-media-controls. It also adds the npm package motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/use-media-controls.
See also
Used in
More React hooks and utilities
Articles using this component
- React Audio Player with Waveform: Scrub, Seek, Peaks
Component guides9 min read