@munsonlabs/video-player
v0.0.5
Published
A Vue 3 video player supporting YouTube, Vimeo, Dailymotion, Brightcove, JW Player, and plain HTML5. Includes a sticky stage player, lazy loading, custom actions, IMA ad support, and web component exports.
Downloads
515
Readme
@munsonlabs/video-player
A Vue 3 video player supporting YouTube, Vimeo, Dailymotion, Brightcove, JW Player, and plain HTML5. Includes a sticky stage player, lazy loading, custom actions, IMA ad support, and web component exports.
Installation
npm install @munsonlabs/video-player video.jsVue Usage
Basic player
<script setup>
import { VideoItem } from '@munsonlabs/video-player'
import '@munsonlabs/video-player/style'
</script>
<template>
<VideoItem
title="Big Buck Bunny"
video-url="https://cdn.jwplayer.com/videos/O5chtspP-4VHSaSK0.mp4"
platform="html5"
poster="https://storage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"
/>
</template>Stage player
VideoStage is a sticky full-width player that receives videos from VideoItem components anywhere on the page via window events. When the stage scrolls out of view it minifies to a pip in the bottom-right corner.
<script setup>
import { VideoStage, VideoItem } from '@munsonlabs/video-player'
import '@munsonlabs/video-player/style'
</script>
<template>
<VideoStage @state-change="onStateChange" />
<VideoItem v-for="video in videos" :key="video.videoUrl" v-bind="video" />
</template>VideoItem / VideoPlayer Props
| Prop | Type | Default | Description |
| -------------- | -------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| videoUrl | string | — | Required. The video URL or platform-specific URI |
| platform | string | — | youtube · vimeo · dailymotion · brightcove · jwplayer · html5 |
| title | string | '' | Video title |
| poster | string | '' | Poster image URL |
| aspectRatio | string | '16:9' | e.g. '16:9', '9:16', '4:3' |
| autoplay | boolean | false | Autoplay on mount (mutes automatically) |
| muted | boolean | false | Start muted |
| lazy | boolean | true* | Show placeholder until clicked |
| nativeUi | boolean | false | Use the platform's native controls (YouTube, Vimeo, Dailymotion only) |
| autoStage | boolean | false | Immediately send this video to the stage on mount |
| playbackRate | number | 1 | Initial playback rate |
| adTagUrl | string | '' | VAST or VMAP ad tag URL |
| imaOptions | object | {} | Extra options passed to the IMA plugin (e.g. { customParameters: { section: 'news' } }) |
| techOptions | object | {} | Extra options passed directly to Video.js |
| payload | object | {} | Arbitrary data attached to every state-change event |
| action | PlayerAction | null | Button shown in the player HUD — see Actions |
| focusMode | boolean | false | The "show controls" button centers the video and renders controls below it — see Focus mode |
* lazy only has an effect on VideoItem (default true), which is what actually implements the placeholder-until-clicked behavior. VideoPlayer accepts the prop for type compatibility but defaults to false and never reads it — using <VideoPlayer> directly always mounts the real player immediately, regardless of lazy.
Platform URLs
| Platform | URL format |
| ------------- | -------------------------------------------------------------------------------------------------------- |
| youtube | https://www.youtube.com/watch?v=VIDEO_ID |
| vimeo | https://vimeo.com/VIDEO_ID |
| dailymotion | https://www.dailymotion.com/video/VIDEO_ID |
| brightcove | brightcove://ACCOUNT_ID/PLAYER_ID/embed/VIDEO_ID |
| jwplayer | https://cdn.jwplayer.com/videos/... · https://cdn.jwplayer.com/manifests/... · jwplayer://MEDIA_ID |
| html5 | Any direct MP4 / HLS URL |
Events
state-change
Emitted by VideoItem, VideoPlayer, and VideoStage.
interface StateChangeEvent {
type:
| 'play'
| 'pause'
| 'ended'
| 'seeked'
| 'error'
| 'adstart'
| 'adend'
| 'volumechange'
| 'ratechange'
| 'firstQuartile'
| 'midpoint'
| 'thirdQuartile'
| 'focusstart'
| 'focusend'
currentTime: number
duration: number
src: string
error?: { code: number; message: string } | null
isMuted?: boolean // volumechange only
playbackRate?: number // ratechange only
payload?: Record<string, unknown>
}firstQuartile / midpoint / thirdQuartile fire once each as playback crosses 25%, 50%, and 75% of the video's duration — useful for analytics integrations that expect IAB-style progress milestones. They reset and can fire again on loop restart.
focusstart / focusend fire when focus mode is entered/exited (only relevant if focusMode is set).
On error, the player shows a "Retry" button that re-attempts loading the current source. You can also trigger this yourself via a template ref on VideoPlayer, which exposes a retry() method along with togglePlay(), seek(), toggleMute(), setVolume(), toggleFullscreen(), toggleLoop(), and setPlaybackRate(). Note this is on VideoPlayer specifically — VideoItem doesn't forward a ref to it, since it may be showing a lazy placeholder instead:
<script setup>
import { ref } from 'vue'
const player = ref(null)
</script>
<template>
<VideoPlayer ref="player" video-url="..." platform="html5" />
<button @click="player.retry()">Retry</button>
</template><VideoItem video-url="..." platform="html5" @state-change="onStateChange" />Focus mode
Set focusMode to change what the "show controls" button (in the HUD row) does: instead of overlaying the expanded controls on top of the video, the video centers itself on the page and the controls bar renders below it, dimming the rest of the page behind a backdrop.
<VideoItem video-url="..." platform="youtube" focus-mode />- The video's original spot in the page keeps its size (an empty placeholder box) so surrounding layout doesn't jump while it's centered.
- Clicking the backdrop, pressing
Escape, or the close button in the controls bar all return it to its original position. - The move is animated via the View Transitions API where supported, falling back to an instant toggle otherwise.
- The video element itself is never moved in the DOM (no
Teleport) — it's purely a CSSposition: fixedtoggle — so YouTube/Vimeo/Dailymotion iframes are never reloaded by entering or exiting focus mode.
Actions
The action prop adds a button to the player HUD. Pass a built-in string or a custom action object.
Built-in actions
<VideoItem action="mute" ... />
<VideoItem action="loop" ... />Custom action
interface CustomAction {
icon: string // SVG string
label: string
onClick: () => void
}<script setup>
const saveAction = {
icon: `<svg .../>`,
label: 'Save',
onClick: () => console.log('saved'),
}
</script>
<template>
<VideoItem :action="saveAction" ... />
</template>Ads (IMA / VAST / VMAP)
Pass an adTagUrl to enable pre-roll, mid-roll, or VMAP ad schedules via the Google IMA SDK. The SDK is loaded automatically on demand.
<VideoItem video-url="https://..." platform="html5" ad-tag-url="https://pubads.g.doubleclick.net/..." />Custom IMA parameters
Use imaOptions to pass any option supported by videojs-ima, including customParameters for cust_params:
<VideoItem
video-url="https://..."
ad-tag-url="https://pubads.g.doubleclick.net/..."
:ima-options="{ customParameters: { section: 'news', topic: 'sport' } }"
/>Ads are not supported on YouTube, Vimeo, or Dailymotion.
Theming the HUD buttons
The three buttons in the HUD row — the action button (mute/loop/custom/save), the central play/pause button, and the "show controls" button — read their background and icon color from two CSS custom properties, falling back to the default frosted-glass look when unset. The expanded controls popup (opened via the "show controls" button) is unaffected and keeps its own fixed styling.
Set the variables on any ancestor of the player — e.g. a wrapper <div> or :root — to restyle all three buttons at once:
.my-video-wrapper {
--mvp-btn-bg: #e11d48;
--mvp-btn-color: #fff;
}Icons use fill="currentColor", so --mvp-btn-color recolors the icon along with --mvp-btn-bg for the background. Hover state is a filter: brightness() lift on top of --mvp-btn-bg, so it works for both the default translucent look and a solid theme color.
Window Events
VideoItem and VideoStage communicate via window custom events. You can dispatch these yourself to control the stage from outside Vue.
| Event | Detail | Description |
| -------------- | --------------------------------------------------------- | ---------------------------------------------- |
| video-select | VideoSelectDetail | Load a video into the stage |
| video-toggle | { videoUrl: string } | Play/pause a specific video in the stage |
| video-state | { currentVideoUrl: string \| null, isPlaying: boolean } | Dispatched by the stage when its state changes |
// Load a video into the stage
window.dispatchEvent(
new CustomEvent('video-select', {
detail: {
videoUrl: 'https://...',
platform: 'html5',
title: 'My Video',
poster: 'https://...',
autoplay: true,
},
}),
)
// Listen for stage state changes
window.addEventListener('video-state', (e) => {
console.log(e.detail.currentVideoUrl, e.detail.isPlaying)
})Web Component Usage
Import the element bundle to register all components as native custom elements. Vue is an external dependency — use an import map to provide it:
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module" src="@munsonlabs/video-player/element"></script>
<video-item
video-url="https://cdn.jwplayer.com/videos/O5chtspP-4VHSaSK0.mp4"
platform="html5"
title="Big Buck Bunny"
poster="https://storage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"
></video-item>Available elements
| Element | Vue equivalent |
| --------------------- | -------------------- |
| <video-item> | <VideoItem> |
| <video-stage> | <VideoStage> |
| <video-player> | <VideoPlayer> |
| <video-placeholder> | <VideoPlaceholder> |
Passing objects as web component attributes
Boolean and string props map directly to HTML attributes. Object props (imaOptions, techOptions, payload) can be passed as inline JSON strings — Vue's custom element runtime parses them automatically:
<video-item
video-url="https://..."
platform="html5"
ad-tag-url="https://..."
ima-options='{"customParameters":{"section":"news","topic":"sport"}}'
payload='{"articleId":"123"}'
></video-item>For action (which contains a function), set it via JavaScript:
const player = document.querySelector('video-item')
player.action = { icon: '<svg.../>', label: 'Save', onClick: () => {} }Listening to events
const player = document.querySelector('video-item')
player.addEventListener('state-change', (e) => {
console.log(e.detail)
})Peer Dependencies
| Package | Version |
| ---------- | ---------------------- |
| video.js | >=8 |
| vue | >=3 (Vue usage only) |
