Vize

Media Player

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

MediaPlayerCaptionsButton

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.

MediaPlayerLoadingIndicator

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.

MediaPlayerMuteButton

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.

MediaPlayerPlayButton

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.

MediaPlayerPlaybackRateButton

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.

MediaPlayer

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 —

MediaPlayerRoot

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.

MediaPlayerSeekSlider

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 —

MediaPlayerTimeDisplay

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.

MediaPlayerVolumeSlider

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.