Music Player

A fully accessible HTML5 audio player component with transport controls, volume, optional shuffle and repeat modes, seek bar, playlist, glass surface, and a detachable floating mode (corner placement, pointer-drag reposition, minimizable). Zero external dependencies, powered by the native Audio API, and it automatically adapts to the active Vanduo theme.

Install the player separately from the core framework. All live demos below use a bundled sample playlist from Invent the Universe, with Pale Blue Dot as the default first track. Ships an optional Vue 3 binding (@vanduo-oss/vd3-cbun/music-player) used here.

Minimal Player

Default config — play/pause, prev/next, volume, track name.

<VdMusicPlayer :tracks="tracks" />
With Progress Bar

Enable showProgress for a seek bar + elapsed/duration times.

<VdMusicPlayer :tracks="tracks" :options="{ showProgress: true }" />
With Playlist + Progress

Enable showPlaylist and showProgress for a fuller player. Shuffle and repeat are always on the control bar.

<VdMusicPlayer
  :tracks="tracks"
  :options="{ showPlaylist: true, showProgress: true }"
/>
Compact

Minimal floating bar, no track name shown.

<VdMusicPlayer :tracks="tracks" class="vd-music-player-compact" />
Small (default size)

Standard layout with track name — use vd-music-player-sm or omit size classes.

<VdMusicPlayer :tracks="tracks" class="vd-music-player-sm" />
Large

Roomier padding and controls — vd-music-player-lg for featured placement.

<VdMusicPlayer :tracks="tracks" class="vd-music-player-lg" />
Inline Layout — All Features

Full-width bar with progress bar, repeat, shuffle, and playlist.

<VdMusicPlayer
  :tracks="tracks"
  :options="{ showProgress: true, showPlaylist: true, shuffle: false, repeat: 'off' }"
  class="vd-music-player-inline"
/>
Glass surface

Add glass: true or the vd-music-player-glass class to any player size or layout. Uses the same frosted tokens as vd-glass in the core framework.

<VdMusicPlayer
  :tracks="tracks"
  :options="{ showProgress: true, glass: true }"
/>
Detachable — fixed corners

detachable: true, minimizable: true, draggable: false. Snap to a corner via floatingPosition or detach(el, position).

Detachable — draggable

draggable: true adds a drag handle when floating. Still supports minimize/expand. Free positioning overrides corner presets.

<script setup lang="ts">
import { ref } from 'vue';
import { VdMusicPlayer } from '@vanduo-oss/vd3-cbun/music-player';

const playerRef = ref();

function detachAndMinimize() {
  const api = playerRef.value?.player;
  const el = playerRef.value?.container();
  if (!api || !el) return;
  api.detach(el, 'bottom-right');
  api.minimize(el);
}
</script>

<template>
  <VdMusicPlayer
    ref="playerRef"
    :tracks="tracks"
    :options="{ showProgress: true, showPlaylist: true, detachable: true, draggable: true, minimizable: true }"
  />
</template>
Programmatic API

Drive the player through the exposed player instance.

State:

<script setup lang="ts">
import { ref } from 'vue';
import { VdMusicPlayer } from '@vanduo-oss/vd3-cbun/music-player';

const playerRef = ref();

// The component exposes { player, container() } — drive it imperatively:
function control() {
  const api = playerRef.value?.player;
  const el = playerRef.value?.container();
  if (!api || !el) return;

  api.play(el);
  api.pause(el);
  api.next(el);
  api.previous(el);
  api.setTrack(el, 0);
  api.setVolume(el, 0.75);
  api.shuffle(el);
  api.repeat(el);
  api.setRepeat(el, 'one');
  api.detach(el, 'bottom-right');
  api.attach(el);
  api.minimize(el);
  api.expand(el);

  const state = api.getState(el);
}
</script>

<template>
  <VdMusicPlayer ref="playerRef" :tracks="tracks" />
</template>
Live event log

All 10 player emits are captured here in real time.

Most recent 0 event(s):
  • No events yet — interact with the player above.
API Reference

Install

pnpm add @vanduo-oss/vd3-cbun

Usage

<script setup lang="ts">
import { VdMusicPlayer } from '@vanduo-oss/vd3-cbun/music-player';

const tracks = [
  { name: 'Pale Blue Dot', url: '/music/pale-blue-dot.mp3' },
  { name: 'Maia Nebula', url: '/music/maia-nebula.mp3' ];
</script>

<template>
  <VdMusicPlayer
    :tracks="tracks"
    :options="{ showPlaylist: true, showProgress: true }"
    @trackchange="(d) => console.log(d.name)" />
</template>

Component API

Prop / eventDescription
:tracksPlaylist — [{ name, url }].
:optionsPlayer options object (see Options below).
@play / @pause / @endedPlayback lifecycle.
@trackchangeActive track changed; detail { index, name, url }.
@volumechange / @repeatchangeVolume or repeat-mode changed.
@detach / @attach / @minimize / @expandFloating-mode transitions.
@readyEmitted once with the container element.
expose: player / container()Imperative API + container accessor.

Options

OptionTypeDefaultDescription
tracksArray[]Array of { name, url } objects. name is displayed; url is the audio file path.
volumenumber0.5Initial playback volume between 0 and 1.
shufflebooleanfalseShuffle the track list on init. Also shows the shuffle toggle button.
repeatstring'off''off', 'one' (restart current track), or 'all' (wrap playlist). Button cycles modes; repeat-one shows a 1 badge.
showProgressbooleanfalseShow a seek bar with elapsed/total time display.
showPlaylistbooleanfalseShow a collapsible track list panel. Also shows the shuffle button.
autoAdvancebooleantrueAutomatically play the next track when the current one ends. Ignored when repeat is 'one' or 'all'.
glassbooleanfalseFrosted-glass surface (vd-music-player-glass); pairs with theme --vd-glass-* tokens.
detachablebooleanfalseShow detach/attach controls and allow detach() to float the player.
floatingPositionstring'bottom-right'After detaching: 'bottom-left', 'bottom-right', 'top-left', or 'top-right'.
draggablebooleanfalseWhen detachable is true, show a drag handle and allow pointer-dragging while floating.
minimizablebooleanfalseShow a control to collapse the player to essential transport + volume.
startMinimizedbooleanfalseOn the first detach(), start in the minimized layout.
persistPositionbooleanfalseSave floating {x, y} to localStorage (via safeStorage when available).
persistKeystring'' (from element id)Storage key suffix for persistPosition; defaults from the element's id attribute.

Events

EventDescription
musicplayer:playPlayback started.
musicplayer:pausePlayback paused.
musicplayer:trackchangeActive track changed; detail { index, name, url }.
musicplayer:volumechangeVolume changed; detail { volume }.
musicplayer:repeatchangeRepeat mode changed; detail { repeat }.
musicplayer:endedFires when repeat is off and autoAdvance is false at track end.
musicplayer:detachPlayer floated above the page.
musicplayer:attachPlayer docked back into place.
musicplayer:minimizePlayer collapsed to essential transport + volume.
musicplayer:expandPlayer restored from minimized.
el.addEventListener('musicplayer:play', () => console.log('Playback started'));
el.addEventListener('musicplayer:trackchange', (e) => {
  const { index, name, url } = e.detail;
  console.log('Track changed to:', index, name, url);
});
el.addEventListener('musicplayer:volumechange', (e) => console.log('Volume:', e.detail.volume));
el.addEventListener('musicplayer:repeatchange', (e) => console.log('Repeat mode:', e.detail.repeat));
el.addEventListener('musicplayer:detach', () => console.log('Player is floating'));
el.addEventListener('musicplayer:attach', () => console.log('Player was docked'));
el.addEventListener('musicplayer:minimize', () => console.log('Minimized'));
el.addEventListener('musicplayer:expand', () => console.log('Expanded'));

CSS Variables

:root {
  --vd-music-player-accent: var(--vd-color-primary);
  --vd-music-player-bg: var(--vd-bg-primary);
  --vd-music-player-bg-secondary: var(--vd-bg-secondary);
  --vd-music-player-border: var(--vd-border-color);
  --vd-music-player-text: var(--vd-text-primary);
  --vd-music-player-text-muted: var(--vd-text-muted);
  --vd-music-player-track-bg: var(--vd-border-color);
  --vd-music-player-track-fill: var(--vd-color-primary);
  --vd-music-player-btn-size: 2.125rem;
  --vd-music-player-padding-x: 1.3125rem;
  --vd-music-player-padding-y: 0.8125rem;
  --vd-music-player-glass-bg: var(--vd-glass-bg-light);
  --vd-music-player-glass-border: var(--vd-glass-border-light);
  --vd-music-player-glass-shadow: var(--vd-glass-shadow);
  --vd-music-player-floating-z: 10050;
  --vd-music-player-floating-offset: 1rem;
}

Accessibility

  • The container is a role="region" labeled Music Player; transport controls sit in a role="group" labeled Playback controls.
  • Buttons expose aria-label text (Play/Pause, Previous/Next, Volume, Seek, playlist toggle with aria-expanded, shuffle and repeat with aria-pressed). Repeat uses labels Repeat, Repeat one, and Repeat all; repeat-one shows a visible 1 badge.
  • The current track name uses aria-live="polite" and aria-atomic="true" so screen readers announce track changes.
  • Playlist items set aria-current on the active row; decorative icons use aria-hidden="true".
  • Play/Pause supports Space and Enter on the play button; range inputs are keyboard-focusable with visible :focus-visible outlines.
  • Floating toolbar controls include descriptive aria-label values; minimize toggles aria-expanded.
  • Glass and floating transitions respect prefers-reduced-transparency and prefers-reduced-motion.
Attribution — bundled demo audio

A curated track set from Stellardrone, from the album Invent the Universe. Source: Bandcamp. Licensed under CC BY 4.0. No changes were made to the bundled audio files.