Video Player
Headless video player: the shared MediaPlayer controls under VideoPlayer* names plus the native <video> part, fullscreen, and picture-in-picture. Shared types and helpers are exported from @vizejs/ui/media-player.
|
|
| Package |
@vizejs/ui/video-player |
| Maturity |
stable |
| Own the source |
vize lib pull video-player |
| Requires |
context, media-player |
| Aliases |
video player, video, video controls, picture in picture, fullscreen video |
| Covers |
HTML video, Fullscreen API, Picture-in-Picture API, Vidstack Player, Media Chrome |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { VideoPlayerCaptionsButton, VideoPlayerLoadingIndicator, VideoPlayerMuteButton, VideoPlayerPlayButton, VideoPlayerPlaybackRateButton, VideoPlayer, VideoPlayerRoot, VideoPlayerSeekSlider, VideoPlayerTimeDisplay, VideoPlayerVolumeSlider, VideoPlayerFullscreenButton, VideoPlayerPictureInPictureButton, VideoPlayerVideo } from "@vizejs/ui/video-player";
Or copy the source into your project with vize lib pull video-player (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Video player wiring a native video with captions to play, seek, volume, captions, and fullscreen controls. -->
<script setup lang="ts">
import {
VideoPlayer,
VideoPlayerCaptionsButton,
VideoPlayerFullscreenButton,
VideoPlayerMuteButton,
VideoPlayerPictureInPictureButton,
VideoPlayerPlayButton,
VideoPlayerSeekSlider,
VideoPlayerTimeDisplay,
VideoPlayerVideo,
VideoPlayerVolumeSlider,
} from "@vizejs/ui/video-player";
</script>
<template>
<VideoPlayer aria-label="Product tour">
<VideoPlayerVideo
src="/media/product-tour.mp4"
poster="/media/product-tour.jpg"
preload="none"
:width="640"
:height="360"
>
<track kind="captions" src="/media/product-tour.en.vtt" srclang="en" label="English" />
</VideoPlayerVideo>
<VideoPlayerPlayButton v-slot="{ paused }">{{
paused ? "Play" : "Pause"
}}</VideoPlayerPlayButton>
<VideoPlayerTimeDisplay mode="current" />
<VideoPlayerSeekSlider />
<VideoPlayerTimeDisplay mode="duration" />
<VideoPlayerMuteButton v-slot="{ muted }">{{
muted ? "Unmute" : "Mute"
}}</VideoPlayerMuteButton>
<VideoPlayerVolumeSlider />
<VideoPlayerCaptionsButton>Captions</VideoPlayerCaptionsButton>
<VideoPlayerPictureInPictureButton unsupported="hide"
>Picture-in-picture</VideoPlayerPictureInPictureButton
>
<VideoPlayerFullscreenButton v-slot="{ fullscreen }">{{
fullscreen ? "Exit fullscreen" : "Fullscreen"
}}</VideoPlayerFullscreenButton>
</VideoPlayer>
</template>
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. |
VideoPlayerLoadingIndicator
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. |
VideoPlayer
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 |
— |
|
VideoPlayerRoot
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 |
— |
|
VideoPlayerSeekSlider
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 |
— |
|
VideoPlayerTimeDisplay
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. |
VideoPlayerVolumeSlider
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 |
— |
|
Source: src/families/media/video-player/video-player-fullscreen-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. |
unsupported |
VideoPlayerUnsupportedBehavior |
"disable" |
Behavior when the platform lacks support, detected after mount (the server always renders the unsupported state): disable the button or hide it. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the request. 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/video-player/video-player-picture-in-picture-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. |
unsupported |
VideoPlayerUnsupportedBehavior |
"disable" |
Behavior when the platform lacks support, detected after mount (the server always renders the unsupported state): disable the button or hide it. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the request. 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. |
VideoPlayerVideo
Source: src/families/media/video-player/video-player-video.vue
Props
| Prop |
Type |
Default |
Description |
src |
string |
undefined |
Video source, validated by the shared media-source policy. Unsafe sources are dropped (data-invalid-src); use <source> children for format fallbacks. |
poster |
string |
undefined |
Poster image, validated as an image source. |
preload |
MediaPlayerPreload |
"metadata" |
Native preload hint. |
playsinline |
boolean |
true |
Play inline on iOS instead of entering native fullscreen. |
autoplay |
boolean |
false |
Native autoplay. Browsers usually require the root defaultMuted as well. |
loop |
boolean |
false |
Restart at the end. |
controls |
boolean |
false |
Also render the browser's native controls. |
crossOrigin |
MediaPlayerCrossOrigin |
undefined |
Native CORS policy, required for cross-origin captions. |
disablePictureInPicture |
boolean |
false |
Hide the browser picture-in-picture affordance and disable the PiP button. |
width |
number | string |
undefined |
Intrinsic width used to reserve layout space. |
height |
number | string |
undefined |
Intrinsic height used to reserve layout space. |
allowInsecure |
boolean |
false |
Permit unencrypted http: sources for local development. |
Slots
| Slot |
Slot props |
Description |
default |
MediaPlayerSlotState |
<source> and <track> children, plus fallback content. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Behavior
Normative state x input -> outcome table for video-player-video.vue,
video-player-fullscreen-button.vue, and video-player-picture-in-picture-button.vue
(@vizejs/ui/video-player). The shared controls re-exported as VideoPlayer*
follow media-player.behavior.md. Every row is proven by the named test.
| ID |
State |
Input |
Outcome |
Evidence |
| VP1 |
render |
props |
native <video> with validated src/poster, playsinline, preload, CORS, size, slot <track>s; unsafe sources are dropped |
renders a native video with validated sources and inline-playback attributes |
| VP2 |
defaultMuted |
autoplay |
the element renders and stays muted so muted autoplay is allowed |
defaultMuted mutes the element for muted autoplay |
| VP3 |
fullscreen supported |
button / F key |
the root container enters and leaves fullscreen; state, data-fullscreen, and labels follow fullscreenchange |
fullscreen toggles the root through the standard API and reports state |
| VP4 |
prefixed platforms |
toggle |
falls back to webkitRequestFullscreen, then native iOS video fullscreen with webkitbeginfullscreen/webkitendfullscreen |
fullscreen falls back to WebKit prefixes and native video fullscreen |
| VP5 |
PiP supported |
button |
enters and leaves picture-in-picture, tracking enterpictureinpicture/leavepictureinpicture; disablePictureInPicture opts out |
picture-in-picture is detected after mount and toggles the video |
| VP6 |
unsupported |
render |
platform buttons are disabled, or hidden with unsupported="hide", and report data-supported="false" |
unsupported platform controls are disabled or hidden |
| VP7 |
any |
click with preventDefault() |
the request is skipped; ariaLabel overrides the default label |
platform buttons honor preventDefault and aria-label overrides |
| VP8 |
aliases |
import / setup |
VideoPlayer aliases the shared root; video parts require the provider |
VideoPlayer aliases the shared root and requires a provider for its parts |
| VP9 |
SSR |
isolated requests |
markup is byte-identical and platform buttons render the unsupported state until mount |
renders byte-identical video player markup with unsupported platform controls |
| VP10 |
SSR / hydration |
hydrate |
server markup hydrates without warnings or node replacement |
hydrates video player markup without warnings or node replacement |
| VP11 |
types |
compile |
the root is the shared MediaPlayer root and unsupported behavior is a closed union |
video-player.types.test-d.ts |
Platform capabilities are detected after mount. WebKit-prefixed APIs are reached
through in checks and Reflect, never through casts.