npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@glitchlab/vue-video-player

v1.6.1

Published

Vue 3 / Nuxt 3 video player with HLS support, device-mode toggle, hover-to-play, and zero global CSS side-effects.

Readme

@glitchlab/vue-video-player

A lightweight, HLS-capable Vue 3 video player with a polished overlay UI, device-mode toggle, hover-to-play, and zero global CSS side-effects. Ships a Nuxt 3 module for zero-config integration.

npm bundle size license: MIT types: TypeScript

Live demo → https://video-player-playgraound.vercel.app/ — drop in a video file or paste any URL (HLS .m3u8 supported). The deployed playground uses the React build, but the same UX, props, and styles ship with this Vue package.


Why this player

  • HLS streaming via hls.js with automatic native fallback (Safari)
  • YouTube support — pass a YouTube URL and it embeds automatically, no extra config
  • Custom control bar (default) — a clean YouTube-style layout: full-width seek bar, playback controls on the left, utilities on the right. Or use "native" controls, or none.
  • Settings menu — one gear button with a two-level menu for speed, quality, subtitles and audio track
  • Thumbnail seek preview — hover the seek bar to see a frame preview from a WebVTT storyboard sprite
  • Chapters — named timeline segments with tick marks on the seek bar, from a WebVTT file or an inline array
  • Playlist — play a list of videos in sequence with auto-advance and prev/next buttons
  • Quality selectorAuto + per-resolution switching for multi-rendition HLS streams
  • Audio-track switcher — pick between multiple audio tracks (e.g. languages) when the stream provides them
  • Captions<track> subtitles/captions, with optional cue styling (font size, colors, background)
  • Live stream UX — a LIVE badge for live HLS, with click-to-jump-to-edge
  • Mini-player — floats to a viewport corner when scrolled out of view while playing
  • Ambient mode — an optional soft, blurred color glow behind the player
  • Loading & error states — a buffering spinner and a styled error overlay with a retry button
  • Event emitsplay, pause, ended, time-update, seeked, volume-change, milestone, error
  • Persisted preferences — opt-in volume, speed and resume-position memory via localStorage
  • Theming — rebrand with a theme prop (accent color, radius) or --gvp-* CSS variables
  • Keyboard shortcuts + double-click to toggle fullscreen
  • Accessible — focus rings, ARIA on every control, screen-reader announcements, prefers-reduced-motion
  • Nuxt 3 modulemodules: ["@glitchlab/vue-video-player/nuxt"] and you're done
  • Scoped CSS, no preflight — all styles live under .gvp-root. No * resets, no theme tokens leaked into your design system
  • Device-mode toggle — flip between desktop (16:9) and mobile (9:16) presets
  • Hover-to-play with safe play/pause race handling
  • Lightweight: ~3.5 KB CSS + ~19 KB JS gzipped (hls.js is a peer dependency, loaded once)
  • Fully typed; SSR-safe

Installation

npm install @glitchlab/vue-video-player hls.js
# or
pnpm add @glitchlab/vue-video-player hls.js
# or
yarn add @glitchlab/vue-video-player hls.js

Peer dependencies: vue >= 3, hls.js >= 1

Import the stylesheet once at your app entry:

import "@glitchlab/vue-video-player/style.css";

Quick start

Vue 3

<script setup lang="ts">
import { VueVideoPlayer } from "@glitchlab/vue-video-player";
import "@glitchlab/vue-video-player/style.css";
</script>

<template>
  <VueVideoPlayer
    src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
    poster="/images/poster.jpg"
  />
</template>

Nuxt 3

Add the module to nuxt.config.ts:

export default defineNuxtConfig({
  modules: ["@glitchlab/vue-video-player/nuxt"],
  css: ["@glitchlab/vue-video-player/style.css"]
});

Then use the component anywhere — no manual import required:

<template>
  <VueVideoPlayer
    src="https://example.com/video/playlist.m3u8"
    poster="/images/poster.jpg"
  />
</template>

Props

| Prop | Type | Default | Description | |---------------------|---------------------------------------------------|-----------------------------------------|------------------------------------------------------------------------------------------| | src | string | — | Video URL. .m3u8 is routed through HLS automatically. Optional when playlist is set. | | poster | string | — | Poster image shown before playback starts. | | playlist | PlaylistItem[] | — | A list of videos to play in sequence. Auto-advances on end; prev/next buttons appear. Takes precedence over src. See Playlist. | | defaultIndex | number | 0 | Index of the playlist item to start on. | | autoAdvance | boolean | true | Auto-advance to the next playlist item when one ends. | | thumbnails | string | — | URL of a WebVTT storyboard for seek-bar thumbnail previews. See Thumbnails. | | chapters | string \| Chapter[] | — | WebVTT chapters URL, or an inline array of { start, title }. See Chapters. | | showDeviceToggle | boolean | true | Show the desktop/mobile toggle pill in the top-left. | | defaultDevice | "desktop" \| "mobile" | "desktop" | Initial device mode. | | hoverPlay | boolean | false | Start playback on mouse-enter, pause on mouse-leave. | | tooltipText | string | — | Tooltip text shown above the play button on hover. | | closable | boolean | false | Show a close button in the top-right; emits close when clicked. | | class | string | "" | Extra class added to the outer container (alongside .gvp-root). | | muted | boolean | true | Mute the video. Required for autoplay in most browsers. | | loop | boolean | false | Loop playback. | | controls | boolean \| "custom" \| "native" | true | true (default) / "custom" — branded control bar (play/seek/time/speed/captions/volume/PiP/fullscreen) that auto-hides during playback. "native" — native browser controls. false — no controls (just the play overlay). See Custom controls. | | autoPlay | boolean | false | Start playback as soon as the source is ready. Works for HLS (after MANIFEST_PARSED), native MP4/WebM (after loadedmetadata), and YouTube embeds. Browsers block sound-on autoplay, so this only fires when muted is also true (the default). | | frameMaxWidth | { desktop?: string; mobile?: string } | { desktop: "960px", mobile: "420px" } | Max width of the player in each device mode. | | aspectRatio | { desktop?: AspectRatio; mobile?: AspectRatio } | { desktop: "16/9", mobile: "9/16" } | Aspect ratio per device mode. AspectRatio is `${number}/${number}`. | | hlsConfig | Hls.HlsConfig | — | Optional hls.js config. Use a stable reference (e.g. shallowRef) to avoid HLS rebuilds.| | isHls | boolean | false | Force HLS routing even when the URL doesn't end in .m3u8. | | theme | PlayerTheme | — | { accent?, radius? } — rebrand via CSS variables. See Theming. | | captionStyle | CaptionStyle | — | { fontSize?, textColor?, backgroundColor?, backgroundOpacity? } — style caption cues. | | miniPlayer | boolean | false | Float to a viewport corner when scrolled out of view while playing. See Mini-player. | | miniPlayerPosition| "bottom-right" \| "bottom-left" \| "top-right" \| "top-left" | "bottom-right" | Corner the mini-player docks to. | | ambientMode | boolean | false | Show a soft, blurred color glow behind the player. See Ambient mode. | | persistKey | string | — | When set, persists volume, speed and resume position to localStorage under this key. |

Events

| Event | Payload | Description | |-------------------|-------------------------------|----------------------------------------------------------------------| | close | — | Emitted when the close button is clicked. Requires closable=true. | | play | — | Emitted when playback starts or resumes. | | pause | — | Emitted when playback pauses. | | ended | — | Emitted when the current video reaches its end. | | time-update | (currentTime, duration) | Emitted on every timeupdate (~4×/sec); times in seconds. | | seeked | (currentTime) | Emitted after a seek completes, with the new time in seconds. | | volume-change | (volume, muted) | Emitted when volume or mute state changes. | | milestone | (percent) | Emitted once when watch progress crosses 25%, 50%, 75% and 100%. | | error | — | Emitted when the underlying media element reports an error. | | playlist-change | (index, item) | Emitted when the active playlist item changes. |

Slots

| Slot | Description | |-----------|------------------------------------------------------------------------------------------| | default | Rendered inside the underlying <video>. Use for <track> elements (captions/subs). |


Custom controls

The branded control bar is on by default — it looks and behaves the same across every browser and OS, no inconsistent native chrome:

<!-- Custom control bar — this is the default, :controls="true" -->
<VueVideoPlayer src="/videos/movie.m3u8" />

<!-- Same thing, explicit -->
<VueVideoPlayer src="/videos/movie.m3u8" controls="custom" />

<!-- Native browser controls instead -->
<VueVideoPlayer src="/videos/movie.m3u8" controls="native" />

<!-- No controls at all -->
<VueVideoPlayer src="/videos/movie.m3u8" :controls="false" />

The bar uses a YouTube-style layout — a full-width seek bar on top, then a button row:

  • Left group — play/pause, prev/next (when a playlist is active), volume (mute toggle + slider that expands on hover), and the time / LIVE badge
  • Right group — the settings gear, Picture-in-Picture, and fullscreen

The settings gear opens a two-level menu grouping playback speed, quality (resolution), subtitles and audio track. Rows for unavailable settings (e.g. no extra audio tracks) are hidden, so the menu never shows a dead option.

It auto-hides 3 seconds after the last interaction during playback and reappears on mouse move; it stays visible while paused. Single-click the video toggles play/pause; double-click toggles fullscreen.

Keyboard shortcuts

When the player has focus (controls="custom"):

| Key | Action | |----------------|-------------------| | Space / K | Play / pause | | / | Seek ∓ 5s | | / | Volume ± 10% | | M | Mute toggle | | F | Fullscreen toggle | | P | Picture-in-Picture |

Double-clicking the video surface also toggles fullscreen.

Platform notes

The bar degrades gracefully where a platform can't support a control:

  • iPhone Safari — the volume slider is hidden (HTMLVideoElement.volume is read-only on iOS); the mute toggle remains. Fullscreen uses Apple's native player.
  • Firefox — the Picture-in-Picture button is hidden (no JS API; use Firefox's own PiP affordance).
  • YouTube URLs — the custom bar does not render; YouTube's iframe can't be driven by it, so YouTube's own controls are shown instead.

Styling

Every part has a .gvp-* class hook — override what you need:

.gvp-controls       { /* the bar */ }
.gvp-ctrl-btn       { /* generic control button */ }
.gvp-seek-progress  { background: deeppink; }
.gvp-volume-fill    { /* volume slider fill */ }

Thumbnail seek preview

Hover the seek bar to see a frame preview. Pass thumbnails a URL to a WebVTT storyboard file — the format YouTube and Vimeo use. Each cue maps a time range to a region of a sprite image:

WEBVTT

00:00:00.000 --> 00:00:05.000
storyboard.jpg#xywh=0,0,160,90

00:00:05.000 --> 00:00:10.000
storyboard.jpg#xywh=160,0,160,90
<VueVideoPlayer src="/videos/movie.m3u8" thumbnails="/videos/storyboard.vtt" />

The #xywh=x,y,w,h fragment crops a tile out of the sprite; relative image paths resolve against the VTT's own URL. A cue with no #xywh uses the whole image. You can generate a storyboard from a video with ffmpeg:

ffmpeg -i video.mp4 -vf "fps=1/5,scale=160:90,tile=5x100" storyboard.jpg

Only used with the custom control bar.


Chapters

Segment the timeline into named chapters. They add tick marks to the seek bar and show the chapter title in the hover preview. Pass either a WebVTT chapters URL or an inline array:

<!-- Inline -->
<VueVideoPlayer
  src="/videos/tutorial.m3u8"
  :chapters="[
    { start: 0, title: 'Introduction' },
    { start: 150, title: 'Getting started' },
    { start: 600, title: 'Advanced topics' },
  ]"
/>

<!-- From a WebVTT chapters file -->
<VueVideoPlayer src="/videos/tutorial.m3u8" chapters="/videos/chapters.vtt" />

Inline items may omit end — it's filled from the next chapter's start (or the video duration). Only used with the custom control bar.


Playlist

Pass playlist an array of items to play videos in sequence. The player auto-advances when one ends and shows prev/next buttons in the control bar. Each item can carry its own poster, thumbnails and chapters:

<VueVideoPlayer
  :playlist="[
    { src: '/videos/ep1.m3u8', title: 'Episode 1', thumbnails: '/videos/ep1.vtt' },
    { src: '/videos/ep2.m3u8', title: 'Episode 2' },
    { src: '/videos/ep3.m3u8', title: 'Episode 3' },
  ]"
  :default-index="0"
  auto-advance
  @playlist-change="(index, item) => console.log('Now playing', item.title)"
/>

playlist takes precedence over src. Set :auto-advance="false" to require a manual next click. PlaylistItem is exported for typing.


Event emits

Wire the player's playback events into your own analytics or UI without a separate timer:

<VueVideoPlayer
  src="/videos/movie.m3u8"
  @play="track('video_play')"
  @pause="track('video_pause')"
  @ended="track('video_complete')"
  @time-update="(time, duration) => (progress = time / duration)"
  @seeked="(time) => track('video_seek', { time })"
  @volume-change="(volume, muted) => console.log(volume, muted)"
  @milestone="(percent) => track(`watched_${percent}`)"
  @error="showRetry()"
/>

milestone fires exactly once each as watch progress crosses 25%, 50%, 75% and 100% — handy for completion analytics. Milestone tracking resets per source (including playlist advances).


Theming

Rebrand the player without overriding component classes. Pass a theme prop — it maps to CSS custom properties on the player root:

<VueVideoPlayer
  src="/videos/movie.m3u8"
  :theme="{ accent: '#e11d48', radius: '0.5rem' }"
/>

| Field | Maps to | Description | |----------|--------------------|------------------------------------------------------| | accent | --gvp-accent | Seek progress, play button, active states. Any CSS color. | | radius | --gvp-radius | Corner radius of the player frame. |

For deeper control, set the --gvp-* variables (or override .gvp-* classes) directly in your own CSS:

.gvp-root { --gvp-accent-rgb: 225 29 72; --gvp-radius: 0.5rem; }

Mini-player

Set miniPlayer and the player floats to a corner of the viewport when it scrolls out of view while playing — so viewers keep watching as they read the rest of the page. It returns when scrolled back; a close button dismisses it.

<VueVideoPlayer
  src="/videos/movie.m3u8"
  miniPlayer
  miniPlayerPosition="bottom-right"
/>

Ambient mode

ambientMode adds a soft, blurred color glow behind the player that samples the current video frame (YouTube's "ambient mode"). Give the player some surrounding padding so the glow is visible.

<VueVideoPlayer src="/videos/movie.m3u8" ambientMode />

Live streams

Live HLS streams are detected automatically (via hls.js's live flag). The control bar shows a LIVE badge instead of a duration — red and pulsing at the live edge, dimmed when behind. Clicking the badge jumps back to the live edge. No prop needed.


Persisted preferences

Pass a persistKey and the player remembers volume, mute, playback speed and the resume position across reloads and revisits, via localStorage:

<VueVideoPlayer src="/videos/course-1.m3u8" persistKey="my-course" />

Resume position is tracked per source URL, so one key can resume many videos independently. Omit persistKey to disable persistence entirely.


Caption styling

Style subtitle/caption cue text with captionStyle — applied via the ::cue pseudo-element:

<VueVideoPlayer
  src="/videos/talk.m3u8"
  :caption-style="{
    fontSize: '1.4em',
    textColor: '#fde047',
    backgroundColor: '#000000',
    backgroundOpacity: 0.75,
  }"
>
  <track kind="subtitles" src="/subs/en.vtt" srclang="en" label="English" default />
</VueVideoPlayer>

YouTube URLs

Pass any common YouTube URL as src and the player swaps the <video> element for a privacy-enhanced (youtube-nocookie.com) embed inside the same styled frame — no extra prop needed:

<VueVideoPlayer src="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
<VueVideoPlayer src="https://youtu.be/dQw4w9WgXcQ?t=90" :auto-play="true" />
<VueVideoPlayer src="https://www.youtube.com/shorts/dQw4w9WgXcQ" />

Recognised forms: youtube.com/watch?v=ID, youtu.be/ID, youtube.com/embed/ID, youtube.com/shorts/ID, youtube.com/live/ID, music.youtube.com/watch?v=ID, and bare 11-character IDs. A ?t= / ?start= timestamp in the URL is honored.

Which props work over YouTube

| Prop | YouTube behavior | |---------------------------------------|---------------------------------------------------------------------------------------------------| | muted | ✅ Mutes the embed (mute=1). | | loop | ✅ Loops the single video (loop=1 + playlist=<id>, YouTube's required workaround). | | controls | ✅ Shows/hides YouTube's controls (controls=1 / controls=0). | | autoPlay | ✅ Autoplays (autoplay=1). YouTube + browsers force muted autoplay, so mute=1 is set too — even if :muted="false". | | showDeviceToggle / defaultDevice | ✅ The desktop/mobile aspect-ratio toggle still works. | | closable / @close | ✅ The close button still renders and emits. | | class / frameMaxWidth / aspectRatio | ✅ Frame styling, sizing, and aspect ratio all apply. | | hoverPlay | ❌ No effect. Hover-to-play needs programmatic pause, which requires the YouTube IFrame Player API (not loaded). YouTube's own controls handle starting playback. | | tooltipText | ❌ No effect. The tooltip is attached to the centered play-button overlay, which isn't rendered for YouTube. | | poster | ❌ No effect. YouTube shows its own video thumbnail; a custom poster would require an overlay layer. | | default slot (<track> captions) | ❌ No effect. There's no <video> element to attach <track> to — use YouTube's own caption settings. | | hlsConfig / isHls | ❌ No effect. Not an HLS stream. |

If you need hoverPlay, a custom poster, or a play-button overlay over a YouTube video, you'd need the YouTube IFrame Player API integrated — that's not in this build (it'd add a ~30 KB external script). Open an issue if it matters for your use case.


Examples

Looping background video, no UI chrome

<VueVideoPlayer
  src="/videos/hero.m3u8"
  :muted="true"
  :loop="true"
  :auto-play="true"
  :show-device-toggle="false"
/>

Autoplay an HLS stream

<VueVideoPlayer
  src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
  :auto-play="true"
/>

autoPlay waits for the manifest to parse (HLS) or for loadedmetadata (native), then calls .play() for you. Browsers reject sound-on autoplay without a prior user gesture — muted (the default) is the reliable path.

Hover-to-play with a tooltip

<VueVideoPlayer
  src="/videos/demo.mp4"
  poster="/images/thumb.jpg"
  :hover-play="true"
  tooltip-text="Watch the demo"
/>

Dismissible player in a modal

<script setup lang="ts">
import { ref } from "vue";
import { VueVideoPlayer } from "@glitchlab/vue-video-player";

const open = ref(true);
</script>

<template>
  <VueVideoPlayer
    v-if="open"
    src="/videos/walkthrough.m3u8"
    :closable="true"
    :show-device-toggle="false"
    @close="open = false"
  />
</template>

Custom aspect ratio and frame width

<VueVideoPlayer
  src="/videos/portrait.mp4"
  default-device="mobile"
  :aspect-ratio="{ desktop: '4/3', mobile: '9/16' }"
  :frame-max-width="{ desktop: '720px', mobile: '360px' }"
/>

Captions and subtitles

<VueVideoPlayer src="/videos/talk.m3u8" :controls="true">
  <track kind="captions" src="/captions/talk.en.vtt" srclang="en" label="English" default />
  <track kind="subtitles" src="/captions/talk.es.vtt" srclang="es" label="Spanish" />
</VueVideoPlayer>

Custom hls.js configuration

<script setup lang="ts">
import { shallowRef } from "vue";
import { VueVideoPlayer } from "@glitchlab/vue-video-player";

const hlsConfig = shallowRef({
  enableWorker: true,
  lowLatencyMode: true,
  maxBufferLength: 30
});
</script>

<template>
  <VueVideoPlayer src="/videos/live.m3u8" :hls-config="hlsConfig" />
</template>

Use shallowRef (or any stable reference). A new object identity each render tears down and rebuilds the entire HLS instance.


TypeScript

Full type definitions ship with the package. Re-exported types:

import type {
  VideoPlayerProps,
  HLSPlayerProps,
  DeviceMode,
  AspectRatio
} from "@glitchlab/vue-video-player";

Styling and customization

All styles are scoped under .gvp-root. The CSS file (~3 KB minified) contains no global resets and no design-token bleed. Override what you need:

.gvp-root {
  border-radius: 8px;
}

.gvp-play {
  background-color: rebeccapurple;
}

.gvp-toggle-btn.is-active {
  color: deeppink;
}

Available class hooks:

| Class | Element | |-----------------------|-------------------------------------------------| | .gvp-root | Outer container | | .gvp-video | Underlying <video> element | | .gvp-vignette | Top vignette overlay | | .gvp-bottom-fade | Bottom gradient | | .gvp-toggle | Device-toggle wrapper | | .gvp-toggle-pill | The pill background | | .gvp-toggle-btn | Individual toggle button (.is-active modifier)| | .gvp-toggle-divider | Vertical divider between toggle buttons | | .gvp-close | Top-right close button | | .gvp-play-wrap | Center play-button container | | .gvp-play | Play button | | .gvp-tooltip | Tooltip above play button | | .gvp-icon | All inline SVG icons |


SSR

The component is SSR-safe. Server output renders the static frame; HLS attaches client-side once the video element mounts. Works with Nuxt 3, Vite-SSR, and any vanilla SSR setup.


Browser support

  • Chromium ≥ 88, Firefox ≥ 78, Safari ≥ 14, Edge ≥ 88
  • Native HLS playback on Safari (no hls.js cost)
  • Worker-based HLS on browsers with MSE

Contributing

git clone https://github.com/im-fahad/vue-video-player.git
cd vue-video-player
pnpm install
pnpm test
pnpm build

Issues and PRs welcome at https://github.com/im-fahad/vue-video-player/issues.


License

MIT © im-fahad