Headless media controls shared by the video and audio players.
|
|
| Package |
@vizejs/ui/media-player |
| Maturity |
stable |
| Own the source |
vize lib pull media-player |
| Requires |
context, controllable-state, id |
| Aliases |
media player, media controls, player controls, seek bar, scrubber |
| Covers |
HTML media elements, WAI-ARIA Slider, Vidstack Player, Media Chrome, Radix-style compound controls |
Usage
import { MediaPlayerCaptionsButton, MediaPlayerLoadingIndicator, MediaPlayerMuteButton, MediaPlayerPlayButton, MediaPlayerPlaybackRateButton, MediaPlayer, MediaPlayerRoot, MediaPlayerSeekSlider, MediaPlayerTimeDisplay, MediaPlayerVolumeSlider } from "@vizejs/ui/media-player";
Or copy the source into your project with vize lib pull media-player (see Source Distribution).
API
Source: src/families/media/media-player/media-player-captions-button.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string | null |
undefined |
Accessible name override. undefined uses the state-dependent label from the root messages; null omits aria-label so visible slot text names the button. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to skip it. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Button content, e.g. an icon. Receives the playback state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Source: src/families/media/media-player/media-player-loading-indicator.vue
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Indicator content, rendered only while loading. Defaults to messages.loading. |
Source: src/families/media/media-player/media-player-mute-button.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string | null |
undefined |
Accessible name override. undefined uses the state-dependent label from the root messages; null omits aria-label so visible slot text names the button. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to skip it. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Button content, e.g. an icon. Receives the playback state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Source: src/families/media/media-player/media-player-play-button.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string | null |
undefined |
Accessible name override. undefined uses the state-dependent label from the root messages; null omits aria-label so visible slot text names the button. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to skip it. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Button content, e.g. an icon. Receives the playback state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Source: src/families/media/media-player/media-player-playback-rate-button.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string | null |
undefined |
Accessible name override. undefined uses the state-dependent label from the root messages; null omits aria-label so visible slot text names the button. |
rates |
readonly number[] |
[0.5, 1, 1.25, 1.5, 2] |
Rates cycled on click, in order. The next rate after the current one is chosen; an unknown current rate selects the first. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to skip it. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Button content, e.g. an icon. Receives the playback state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Source: src/families/media/media-player/media-player-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
volume |
number |
undefined |
Controlled volume from 0 to 1 (v-model:volume). |
defaultVolume |
number |
1 |
Initial volume for uncontrolled use. |
muted |
boolean |
undefined |
Controlled muted state (v-model:muted). |
defaultMuted |
boolean |
false |
Initial muted state for uncontrolled use. Rendered as the native muted attribute, so muted autoplay works before hydration. |
playbackRate |
number |
undefined |
Controlled playback rate (v-model:playbackRate). |
defaultPlaybackRate |
number |
1 |
Initial playback rate for uncontrolled use. |
currentTime |
number |
undefined |
Synchronized playback position (v-model:currentTime). The media element stays the source of truth: timeupdate emits the position, and a parent value more than half a second away from it seeks. |
keyboardShortcuts |
boolean |
true |
Handle media keyboard shortcuts while focus is inside the player. |
seekStep |
number |
5 |
Seconds moved by arrow keys on the seek slider and root shortcuts. |
skipStep |
number |
10 |
Seconds skipped by the J and L shortcuts. |
volumeStep |
number |
0.05 |
Volume change for arrow keys, from 0 to 1. |
messages |
MediaPlayerMessages |
undefined |
Localized accessible labels for every control part. Missing keys use English defaults. |
dir |
"ltr" | "rtl" |
"ltr" |
Reading direction used by horizontal sliders and arrow keys. |
ariaLabel |
string |
undefined |
Accessible player name; adds role="group". |
ariaLabelledby |
string |
undefined |
Ids that name the player; adds role="group". |
Events
| Event |
Payload |
Description |
update:volume |
[volume: number] |
Fired when the volume requests a new controlled value. |
update:muted |
[muted: boolean] |
Fired when the muted state requests a new controlled value. |
update:playbackRate |
[rate: number] |
Fired when the playback rate requests a new controlled value. |
update:currentTime |
[time: number] |
Fired on every native timeupdate and seek with the current position. |
play |
[] |
Fired after playback starts. |
pause |
[] |
Fired after playback pauses. |
ended |
[] |
Fired after playback reaches the end. |
playRejected |
[reason: unknown] |
Fired when play() is rejected, e.g. by an autoplay policy. |
error |
[code: number | null] |
Fired when the media element reports an error (MediaError.code). |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Media element and controls. Receives the complete playback state. |
Exposed
| Member |
Type |
Description |
buffered |
— |
|
captionTrack |
— |
|
currentTime |
— |
|
duration |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
error |
— |
|
fullscreen |
— |
|
loading |
— |
|
media |
— |
|
muted |
— |
|
pause |
— |
|
paused |
— |
|
pictureInPicture |
— |
|
play |
— |
|
playbackRate |
— |
|
seek |
— |
|
seekBy |
— |
|
seeking |
— |
|
setCaptionTrack |
— |
|
setMuted |
— |
|
setPlaybackRate |
— |
|
setVolume |
— |
|
state |
— |
|
textTracks |
— |
|
toggleCaptions |
— |
|
toggleFullscreen |
— |
|
togglePictureInPicture |
— |
|
togglePlay |
— |
|
volume |
— |
|
Source: src/families/media/media-player/media-player-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
volume |
number |
undefined |
Controlled volume from 0 to 1 (v-model:volume). |
defaultVolume |
number |
1 |
Initial volume for uncontrolled use. |
muted |
boolean |
undefined |
Controlled muted state (v-model:muted). |
defaultMuted |
boolean |
false |
Initial muted state for uncontrolled use. Rendered as the native muted attribute, so muted autoplay works before hydration. |
playbackRate |
number |
undefined |
Controlled playback rate (v-model:playbackRate). |
defaultPlaybackRate |
number |
1 |
Initial playback rate for uncontrolled use. |
currentTime |
number |
undefined |
Synchronized playback position (v-model:currentTime). The media element stays the source of truth: timeupdate emits the position, and a parent value more than half a second away from it seeks. |
keyboardShortcuts |
boolean |
true |
Handle media keyboard shortcuts while focus is inside the player. |
seekStep |
number |
5 |
Seconds moved by arrow keys on the seek slider and root shortcuts. |
skipStep |
number |
10 |
Seconds skipped by the J and L shortcuts. |
volumeStep |
number |
0.05 |
Volume change for arrow keys, from 0 to 1. |
messages |
MediaPlayerMessages |
undefined |
Localized accessible labels for every control part. Missing keys use English defaults. |
dir |
"ltr" | "rtl" |
"ltr" |
Reading direction used by horizontal sliders and arrow keys. |
ariaLabel |
string |
undefined |
Accessible player name; adds role="group". |
ariaLabelledby |
string |
undefined |
Ids that name the player; adds role="group". |
Events
| Event |
Payload |
Description |
update:volume |
[volume: number] |
Fired when the volume requests a new controlled value. |
update:muted |
[muted: boolean] |
Fired when the muted state requests a new controlled value. |
update:playbackRate |
[rate: number] |
Fired when the playback rate requests a new controlled value. |
update:currentTime |
[time: number] |
Fired on every native timeupdate and seek with the current position. |
play |
[] |
Fired after playback starts. |
pause |
[] |
Fired after playback pauses. |
ended |
[] |
Fired after playback reaches the end. |
playRejected |
[reason: unknown] |
Fired when play() is rejected, e.g. by an autoplay policy. |
error |
[code: number | null] |
Fired when the media element reports an error (MediaError.code). |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Media element and controls. Receives the complete playback state. |
Exposed
| Member |
Type |
Description |
state |
MediaPlayerPlaybackState |
Playback state. |
paused |
boolean |
Whether playback is paused (also true after the end). |
loading |
boolean |
Whether playback stalled while buffering or seeking. |
seeking |
boolean |
Whether a seek is in progress. |
currentTime |
number |
Current position in seconds. |
duration |
number |
Duration in seconds; 0 while unknown, Infinity for live streams. |
buffered |
readonly MediaPlayerTimeRange[] |
Buffered ranges in seconds. |
volume |
number |
Volume from 0 to 1. |
muted |
boolean |
Whether audio is muted. |
playbackRate |
number |
Playback rate multiplier. |
textTracks |
readonly MediaPlayerTextTrack[] |
Text tracks exposed by the media element. |
captionTrack |
number |
Index of the showing caption or subtitle track, or -1. |
fullscreen |
boolean |
Whether the root is the fullscreen element. |
pictureInPicture |
boolean |
Whether the video floats in picture-in-picture. |
error |
number | null |
Last media error code (MediaError.code), or null. |
element |
HTMLDivElement | null |
Rendered root element. |
media |
HTMLMediaElement | null |
Registered native media element. |
play |
() => Promise<boolean> |
Start playback. Resolves false when the browser rejects it (autoplay policy). |
pause |
() => void |
Pause playback. |
togglePlay |
() => Promise<boolean> |
Toggle playback; replays from the start after the end. |
seek |
(time: number) => void |
Seek to an absolute position in seconds (clamped to the duration). |
seekBy |
(delta: number) => void |
Seek relative to the current position in seconds. |
setVolume |
(volume: number) => boolean |
Request a volume from 0 to 1. |
setMuted |
(muted: boolean) => boolean |
Request a muted state. |
setPlaybackRate |
(rate: number) => boolean |
Request a playback rate. |
setCaptionTrack |
(index: number) => boolean |
Show one caption/subtitle track by index, or hide all with -1. |
toggleCaptions |
() => boolean |
Toggle between hidden captions and the last (or first) caption track. |
toggleFullscreen |
() => Promise<boolean> |
Enter or leave fullscreen on the root. Resolves whether the request succeeded. |
togglePictureInPicture |
() => Promise<boolean> |
Enter or leave picture-in-picture for a video. Resolves whether the request succeeded. |
Source: src/families/media/media-player/media-player-seek-slider.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name. Defaults to the root messages.seek. |
ariaLabelledby |
string |
undefined |
Ids that name the slider instead of aria-label. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Thumb content. Receives the playback state. |
Exposed
| Member |
Type |
Description |
dragging |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
thumb |
— |
|
Source: src/families/media/media-player/media-player-time-display.vue
Props
| Prop |
Type |
Default |
Description |
mode |
MediaPlayerTimeDisplayMode |
"current" |
Which time to show: the position, the duration, or the time remaining (rendered with a leading minus sign). |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState & { readonly text: string; readonly seconds: number } |
Custom rendering. Receives the formatted text, raw seconds, and playback state. |
Source: src/families/media/media-player/media-player-volume-slider.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name. Defaults to the root messages.volume. |
ariaLabelledby |
string |
undefined |
Ids that name the slider instead of aria-label. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
Thumb content. Receives the playback state. |
Exposed
| Member |
Type |
Description |
dragging |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
focus |
— |
|
thumb |
— |
|
Behavior
Normative state x input -> outcome table for the shared media controls
(@vizejs/ui/media-player): media-player-root.vue, media-player-play-button.vue,
media-player-mute-button.vue, media-player-seek-slider.vue,
media-player-volume-slider.vue, media-player-time-display.vue,
media-player-playback-rate-button.vue, media-player-captions-button.vue, and
media-player-loading-indicator.vue. The same parts ship as VideoPlayer* and
AudioPlayer*; the native element parts live in those families. Every row is
proven by the named test.
The native media element is the source of truth for playback. The root mirrors
its events (play, pause, ended, waiting, seeking, timeupdate,
durationchange, progress, volumechange, ratechange, error, text-track
changes, fullscreen, and picture-in-picture) into reactive state after mount.
Volume, muted state, and playback rate are controllable (v-model:volume,
v-model:muted, v-model:playbackRate); the element receives the resolved
values. Server rendering uses only props, so markup is deterministic.
| ID |
State |
Input |
Outcome |
Evidence |
| MP1 |
media registered |
render |
labelled group root, media id wiring, English labels, unseekable slider without a duration, disabled captions without caption tracks |
renders labelled controls wired to the registered media element |
| MP2 |
paused / playing / ended |
play button, native events |
toggles playback, labels follow Play/Pause/Replay, replay restarts, rejected play() emits playRejected and keeps paused |
play, pause, and replay follow native events and report rejected playback |
| MP3 |
known duration |
render / arrows / Page / Home / End |
aria-valuetext "1:23 of 5:00", progress and buffered CSS variables, ±seekStep, ±10 %, edges, clamped seeking, update:currentTime |
the seek slider exposes time value text, keyboard seeking, and buffered progress |
| MP4 |
slider focused |
Arrow keys |
sliders consume their keys, so root shortcuts do not seek twice |
the seek slider exposes time value text, keyboard seeking, and buffered progress |
| MP5 |
playing |
pointer scrubbing |
seeks with pointer capture, pauses while scrubbing, ignores other pointers, resumes afterwards |
pointer scrubbing seeks with capture and resumes playback afterwards |
| MP6 |
any |
volume keys / mute / native changes |
±volumeStep, ±10 %, edges, rounding; mute toggles; control changes unmute; native changes sync without unmuting; controlled values wait |
volume and mute controls stay in sync with the element and controlled props |
| MP7 |
RTL |
volume pointer |
the track maps from the reading-direction start |
volume pointer input maps the track width to the volume |
| MP8 |
any |
rate button / native rate change |
cycles rates, restarts the cycle from unknown rates, mirrors native changes, rejects non-positive rates |
playback rate cycles through rates and mirrors native rate changes |
| MP9 |
caption tracks |
captions button / setCaptionTrack |
shows one caption/subtitle track, hides the rest, leaves other kinds untouched, and restores the last caption track |
captions toggle between hidden and the last or first caption track |
| MP10 |
focus inside root |
Space/K, J/L, ←/→, ↑/↓, M, C, 0–9 |
toggles play, skips ±skipStep, seeks ±seekStep, changes volume, mutes, toggles captions, seeks to percent; modified keys pass through |
root keyboard shortcuts control playback, time, volume, and captions |
| MP11 |
editable / button focus |
shortcut keys |
text fields keep their keys, Space activates the focused button once, and keyboardShortcuts=false disables shortcuts |
shortcuts ignore editable targets, focused buttons, and opt-out roots |
| MP12 |
buffering / error |
waiting, canplay, error |
data-loading and the polite status report loading; errors emit MediaError.code |
loading and error states surface through data attributes, status, and emits |
| MP13 |
localized |
messages, ariaLabel |
every label and value text is localizable; ariaLabel overrides and null defers to slot text |
messages localize every label and aria-label overrides are honored |
| MP14 |
any |
click with preventDefault() / no media |
controls leave state unchanged; controls without media are disabled |
controls honor preventDefault and stay disabled without media |
| MP15 |
v-model:currentTime |
parent value changes |
the initial position applies on registration; parent values more than 0.5 s away seek |
synchronized currentTime seeks when the parent moves it |
| MP16 |
exposed instance |
imperative calls |
exposes state and play, pause, togglePlay, seek, seekBy, volume, mute, captions, fullscreen, and picture-in-picture controls |
exposes typed state and imperative controls |
| MP17 |
missing provider |
setup |
every part fails closed with the shared context diagnostic |
compound parts require a matching root provider |
| MP18 |
helpers |
pure functions |
time formatting, durations, ranges, shortcuts, slider keys, editable targets, and message merging are deterministic |
media-player-format.test.ts |
| MP19 |
SSR |
isolated requests |
markup is byte-identical and reflects defaultMuted/defaultVolume without client state |
renders byte-identical media player markup across isolated SSR requests |
| MP20 |
SSR / hydration |
hydrate |
server markup hydrates without warnings or node replacement, then registers the media element |
hydrates media player markup without warnings or node replacement |
| MP21 |
types |
compile |
states, kinds, shortcuts, slot state, messages, and exposes are closed and read-only |
media-player.types.test-d.ts |
Shortcut keys and listeners attach only on the client. Live streams report an
infinite duration and disable the seek slider.