Skip to content

Vue

The @arraypress waveform family ships three Vue 3 wrappers, each a thin, typed component over its vanilla core:

Package Component(s) Wraps
@arraypress/waveform-player-vue <WaveformPlayer> @arraypress/waveform-player
@arraypress/waveform-bar-vue <WaveformBar>, <WaveformBarTrigger> @arraypress/waveform-bar
@arraypress/waveform-playlist-vue <WaveformPlaylist> @arraypress/waveform-playlist

Every wrapper follows the same design:

  • Each is a defineComponent with a render function — no .vue compile step, dual ESM/CJS builds, and a hand-written .d.ts. Core options surface as props, lifecycle events as emits, and the instance as a template ref.
  • Prop types are derived from the core’s WaveformPlayerOptions, not re-declared — so they never drift as the core evolves.
  • The core JS is dynamically import()ed inside onMounted, so the audio + canvas + fetch surface never runs during SSR / Nuxt.
  • Every optional prop is declared { type: …, default: undefined }. An absent prop is never forwarded to the core, so the core’s own default applies — you only pass the props you want to override.
  • CSS is never auto-loaded — you import each core’s stylesheet once at your app entry.

vue and @arraypress/waveform-player are peer dependencies (Vue ^3.5.0; core player ^1.8.0).

Terminal window
npm install @arraypress/waveform-player-vue @arraypress/waveform-player vue

Import the core stylesheet once at your app entry:

// main.ts / app entry
import '@arraypress/waveform-player/dist/waveform-player.css';
<script setup lang="ts">
import { WaveformPlayer } from '@arraypress/waveform-player-vue';
</script>
<template>
<WaveformPlayer
url="/audio/track.mp3"
title="My Track"
waveform-style="mirror"
:waveform-color="['#fafafa', '#71717a']"
show-bpm
@timeupdate="(currentTime, duration) => { /* live progress */ }"
/>
</template>

src is the core’s shorthand alias for url (url wins if both are set). Provide one of the two.

Every core player option is accepted as a typed prop — url, src, audioMode, height, samples, barWidth, barSpacing, barRadius, colorPreset, gradient-array colours (waveformColor, progressColor), accessibleSeek, markers, showBPM, and so on. The prop type WaveformPlayerProps is Omit<WaveformPlayerOptions, …callbacks>, so it tracks the core automatically. See Player → Options for the full inherited surface.

Because every optional prop defaults to undefined, props you don’t pass are not forwarded and the core’s default wins.

The six core lifecycle callbacks surface as emits (not callback props) — each forwards the live WaveformPlayer instance:

<WaveformPlayer
url="/audio/track.mp3"
@load="(player) => console.log('loaded', player)"
@play="() => console.log('playing')"
@pause="() => console.log('paused')"
@timeupdate="(currentTime, duration) => console.log(`${currentTime} / ${duration}`)"
@end="() => console.log('finished')"
@error="(err) => console.error('audio failed:', err)"
/>
Emit Payload Notes
@load (instance) Fired after the waveform is drawn.
@play (instance) Playback started (both audio modes).
@pause (instance) Playback paused (both modes).
@end (instance) Track ended (external mode synthesizes it at progress ≥ 1).
@timeupdate (currentTime, duration, instance) Progress tick. Same argument order in both modes.
@error (error, instance) Load / decode / audio error.

emit is stable, so listeners always reach the latest handler without tearing the player down. Only construction-time props (url, audioMode, visualization options, …) trigger a re-mount.

Grab the instance with a template ref typed as WaveformPlayerExpose. Methods are reached through defineExpose:

<script setup lang="ts">
import { ref } from 'vue';
import {
WaveformPlayer,
type WaveformPlayerExpose,
} from '@arraypress/waveform-player-vue';
const player = ref<WaveformPlayerExpose>();
</script>
<template>
<WaveformPlayer ref="player" url="/audio/track.mp3" />
<button @click="player?.togglePlay()">Play / Pause</button>
<button @click="player?.seekTo(60)">Jump to 1:00</button>
<button @click="player?.loadTrack('/audio/next.mp3', 'Next Track', 'Artist')">
Load next
</button>
</template>
Method Signature Notes
play () => Promise<void> | undefined Returns the native play() promise in self mode; undefined in external mode.
pause () => void
togglePlay () => void
seekTo (seconds: number) => void Self mode only.
seekToPercent (percent: number) => void Fraction 0..1. Self mode only.
setVolume (volume: number) => void 0..1. Self mode only.
setPlaybackRate (rate: number) => void Clamped 0.5..2. Self mode only.
setPlayingState (playing: boolean) => void External mode — push play/pause visual state.
setProgress (currentTime, duration) => void External mode — push the progress overlay from your own clock.
loadTrack (url, title?, artist?, options?) => Promise<void> Swap track at runtime; auto-plays unless options.autoplay === false.
instance readonly WaveformPlayer | null Escape hatch — full core API (load, setWaveformData, refreshTheme, container, statics, …).

Calls before the async import resolves are safe no-ops.


Bar — <WaveformBar> + <WaveformBarTrigger>

Section titled “Bar — <WaveformBar> + <WaveformBarTrigger>”

The bar is a singleton: render <WaveformBar> once in your root layout, then scatter <WaveformBarTrigger> elements anywhere to play or queue tracks.

Peer deps: @arraypress/waveform-bar (^1.3.1), @arraypress/waveform-player (^1.7.2), Vue ^3.5.0.

Terminal window
npm install @arraypress/waveform-bar-vue @arraypress/waveform-bar @arraypress/waveform-player vue
<script setup lang="ts">
import { WaveformBar } from '@arraypress/waveform-bar-vue';
</script>
<template>
<RouterView />
<WaveformBar
:config="{
persist: true,
continuous: true,
showQueue: true,
actions: {
favorite: { endpoint: '/api/favorites' },
cart: { endpoint: '/api/cart' },
},
}"
/>
</template>

On mount the component dynamically imports the core and calls window.WaveformBar.init(config). When the config’s structural shape changes it re-runs init() (idempotent — the library destroys and recreates internally); on unmount it calls destroy() so route changes and HMR reloads don’t leak listeners.

<WaveformBar> prop Type Default Notes
config WaveformBarConfig undefined Passed verbatim to init(). See table below.
persist boolean true Relocate the .waveform-bar element under the host <div> so it survives route changes.
hostId string 'waveform-bar-host' DOM id of the persist host.

class and style fall through to the persist host <div> (base class wb-host).

Bar-vue hand-declares this type (the bar core ships no .d.ts), so this is the source of truth for the typed config surface. Every field is optional.

Group Fields
Behaviour persist, autoResume, continuous, repeat ('off' | 'all' | 'one'), shuffle
UI toggles showQueue, showPrevNext, showRepeat, showShuffle, showVolume, showMute, showTime, showTrackLink, showMeta, maxMeta (number)
Layout mode ('waveform' | 'classic'), wide, position ('bottom' | 'top'), collapsible
Theming defaultArtwork (string | null), theme ('dark' | 'light' | null)
Waveform waveform (boolean), waveformStyle, waveformHeight, barWidth, barSpacing, waveformColor, progressColor, markerColor
Sharing / errors share, shareParam, errorText
Volume / storage volume, storageKey
Server actions actions ({ favorite?, cart? }, each { endpoint, method?, headers? })

mode defaults to 'waveform' (the standard layout with a width-adjustable waveform). Set mode: 'classic' for the Spotify-style centre layout with a full-width seek bar. showShuffle adds a shuffle toggle to the transport cluster; shuffle starts with random queue advance on.

<WaveformBarTrigger> emits the data-wb-* attribute contract the bar scans for. It is polymorphic — a <button> by default — and any unlisted DOM attribute or native listener (@click, data-testid, role, …) falls through to the rendered element.

<script setup lang="ts">
import { WaveformBarTrigger } from '@arraypress/waveform-bar-vue';
</script>
<template>
<!-- Default: a <button> with auto play/pause icons -->
<WaveformBarTrigger
url="/audio/track.mp3"
id="track-42"
title="Midnight Dreams"
artist="The Wavelength"
artwork="/img/cover.jpg"
/>
<!-- Append to the queue with custom content -->
<WaveformBarTrigger mode="queue" url="/audio/track.mp3" title="Midnight Dreams">
+ Queue
</WaveformBarTrigger>
<!-- Wrap a whole card as the trigger -->
<WaveformBarTrigger as="div" :url="track.url" no-default-icons>
<article class="product-card"></article>
</WaveformBarTrigger>
</template>

Track data props (WaveformBarTrackData) — each maps 1:1 to a data-wb-* attribute; arrays are JSON-encoded and absent props emit no attribute:

Prop Type Notes
url string Required — play target + identity.
id string Defaults to url.
title, artist, album, artwork, link string Display metadata.
duration, bpm string | number
musicalKey string data-wb-key.
meta string[] Extra metadata chips.
waveform number[] | string Pre-computed peaks or .json URL.
markers WaveformBarMarker[] DJ-mode cue markers ({ time, label, title?, artist?, artwork?, bpm?, key?, color? }).
favorited, inCart boolean Initial action state.

Trigger-specific props:

Prop Type Default Notes
mode 'play' | 'queue' 'play' Immediate play vs. append to queue.
as 'button' | 'a' | 'div' | 'span' 'button' Rendered tag.
href string Used when as="a".
noDefaultIcons boolean false Suppress the injected play/pause SVGs.
aria-label string auto Falls through to the element; auto-generated from title when absent.
default slot Custom content; overrides the default icons.

class is merged with the base class wb-icon-swap.


A declarative tracks array with optional chapters, an embedded player, and imperative track / chapter navigation.

Peer deps: @arraypress/waveform-playlist (^1.3.0), @arraypress/waveform-player (^1.8.0), Vue ^3.5.0.

Terminal window
npm install @arraypress/waveform-playlist-vue @arraypress/waveform-playlist @arraypress/waveform-player vue

Import both stylesheets, and import the player core for its global side effect — the playlist constructs new window.WaveformPlayer(...) for the active track:

import '@arraypress/waveform-player/dist/waveform-player.css';
import '@arraypress/waveform-playlist/dist/waveform-playlist.css';
import '@arraypress/waveform-player'; // registers window.WaveformPlayer
<script setup lang="ts">
import { WaveformPlaylist } from '@arraypress/waveform-playlist-vue';
const tracks = [
{ url: '/audio/a.mp3', title: 'Track A', artist: 'Artist' },
{
url: '/audio/b.mp3',
title: 'Track B',
chapters: [
{ time: 0, label: 'Intro' },
{ time: '1:30', label: 'Verse' },
{ time: 180, label: 'Chorus', color: '#22d3ee' },
],
},
];
</script>
<template>
<WaveformPlaylist continuous waveform-style="bars" :tracks="tracks" />
</template>

Each track renders into the [data-track] / [data-chapter] markup the playlist constructor parses on mount. The host carries the base class wfp-host.

WaveformPlaylistTrackInput[]:

Field Type Notes
url string Requireddata-url.
title string Falls back to the filename if omitted.
artist string Artist row.
artwork string Artwork URL.
album string Media Session metadata.
duration string Display duration, e.g. '3:45'.
markers WaveformPlaylistMarker[] JSON-encoded into data-markers.
chapters WaveformPlaylistChapterInput[] { time: number | string, label, color? }time accepts seconds or 'M:SS'.

WaveformPlaylistProps combines three groups:

  1. Playlist options picked from WaveformPlaylistOptions: continuous, expandChapters, showDuration, showChapterMarkers, chapterMarkerColor, showPlayState.
  2. Pass-through player options — the full WaveformPlayerOptions visualization / colour / behaviour surface, minus per-track content (url, src, title, artist, artwork, album, markers, waveform), the style alias, the player’s own layout, and the lifecycle callbacks.
  3. Vue extras — the required tracks (plus class, style, id falling through to the host).
Prop Type Default Notes
tracks WaveformPlaylistTrackInput[] [] Required. An empty array renders an empty playlist (the core skips init).
layout 'list' | 'minimal' 'list' Full track list vs. compact button switcher. Overrides the player’s own layout.

Common pass-through player props include waveformStyle, height, colorPreset, waveformColor, progressColor, showBPM, accessibleSeek, and autoplay — see Player → Options for the complete list.

Grab the instance with a template ref typed as WaveformPlaylistExpose:

<script setup lang="ts">
import { ref } from 'vue';
import {
WaveformPlaylist,
type WaveformPlaylistExpose,
} from '@arraypress/waveform-playlist-vue';
const playlist = ref<WaveformPlaylistExpose>();
</script>
<template>
<WaveformPlaylist ref="playlist" :tracks="tracks" continuous />
<button @click="playlist?.previousTrack()">Prev</button>
<button @click="playlist?.nextTrack()">Next</button>
<button @click="playlist?.selectTrack(0)">First</button>
</template>
Method Signature Notes
selectTrack (index: number) => void Select + load a track by index.
seekToChapter (trackIndex, time) => void Loads the target track first if needed, then seeks.
nextTrack () => void
previousTrack () => void
getPlayer () => unknown | null The embedded WaveformPlayer instance (full player API).
getCurrentTrackIndex () => number
getTracks () => WaveformPlaylistTrack[] Parsed tracks (with resolved element / index).
instance readonly WaveformPlaylist | null Escape hatch for anything else.

Calls before the async import resolves are no-ops.


All three components are SSR / Nuxt safe by construction: the host markup (and, for the playlist, the [data-track] children) renders on the server, and the browser-only core is dynamically import()ed inside onMounted — which runs only on the client, never during server render. The interactive UI hydrates in once the library loads.

No extra guarding is needed for the imperative path: keep instantiation inside onMounted (the wrappers already do), never at module top level. Reserve layout space with style (e.g. :style="{ minHeight: '96px' }") to avoid a content shift before the waveform draws.

For Nuxt, you can render the components directly. To skip server rendering entirely and sidestep any hydration mismatch, wrap them in <ClientOnly>:

<template>
<ClientOnly>
<WaveformPlayer url="/audio/track.mp3" title="Midnight Drive" />
<template #fallback>
<div class="waveform-placeholder" style="min-height: 96px" />
</template>
</ClientOnly>
</template>
<script setup lang="ts">
import { WaveformPlayer } from '@arraypress/waveform-player-vue';
</script>

React

The same wrappers, typed for React — React.