A media timeline for audio and video with its own play button and clock, so it plays on its own or follows a real media element: chapters drawn as separate segments with small gaps that open and swell under the pointer, a buffered range, the played fill and a thumb that appears on hover and grows while dragging. A bubble above the track shows the time and the chapter under the pointer, elapsed and remaining times can sit on either side, and click and key jumps land on a soft spring while dragging follows the pointer exactly. Arrow keys seek 5 seconds (1 with Shift), Page keys a tenth, Home and End the ends, all on a native range input for full accessibility.
The import and the props worth knowing about, in one place.
import { PlayerScrubber, formatTime } from "@/components/beste/component/player-scrubber";
// Uncontrolled, with chapters and a buffered range
<PlayerScrubber
duration={242}
defaultValue={71}
buffered={150} // seconds loaded, or [[start, end], ...]
chapters={[
{ start: 0, title: "Intro" },
{ start: 38, title: "Says" },
{ start: 121, title: "Hammers" },
]}
showTime // elapsed on the left, remaining on the right
/>
// Controlled by a media element: follow playback, seek on release
<PlayerScrubber
duration={audio.duration}
value={currentTime}
onValueCommit={(seconds) => console.log("seek to", seconds)}
tone="outline" // "muted" (default) | "outline" | "ghost"
size="lg" // "sm" | "default" | "lg"
/>
formatTime(3725); // "1:02:05"Every one of these is in the component already. They are listed because a props table cannot mention a gesture, so nothing else on this page can tell you they exist.
| Left / Right | Seek back or forward 5 seconds. |
|---|---|
| Shift + Arrow | Seek 1 second, for fine placement. |
| Page Up / Page Down | A tenth of the duration. |
| Home / End | Jump to the start or the end. |
| Space / K | Play or pause, with the play button shown. |
| Drag | The playhead follows the pointer exactly and seeks once on release. |
Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
duration* | number | — | Length of the media, in seconds. |
value | number | — | Playhead position in seconds, controlled. |
defaultValue | number | — | Starting playhead position in seconds, uncontrolled. |
onValueChange | (seconds: number) => void | — | Every drag frame and every key press, with the new time in seconds. |
onValueCommit | (seconds: number) => void | — | Once per gesture: on release, or after a key press. Seek the media here. |
buffered | number | [number, number][] | — | Loaded media: seconds from the start, or explicit `[start, end]` ranges. |
chapters | PlayerChapter[] | — | Chapters drawn as separate segments of the track. |
showTime | boolean | — | Elapsed time on the left and remaining time on the right. |
showChapter | boolean | — | The current chapter's title and its place in the list, above the track. |
showPlay | boolean | — | A play and pause button before the track. |
playing | boolean | — | Whether the playhead runs, controlled. With an uncontrolled value the scrubber keeps its own clock. |
defaultPlaying | boolean | — | Whether it starts running, uncontrolled. |
onPlayingChange | (playing: boolean) => void | — | |
loop | boolean | — | Start over at the end instead of stopping. |
rate | number | — | Playback speed of the built-in clock, 1 is real time. |
tone | "muted" | "outline" | "ghost" | — | |
size | "sm" | "default" | "lg" | — | |
disabled | boolean | — | |
name | string | — | Form name for the underlying range input. |
aria-label | string | — | Accessible name. Defaults to "Seek". |
className | string | — |