Player options
Full inherited option surface — Player → Options.
The @arraypress waveform family ships four 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 |
@arraypress/waveform-sounds-vue |
<WaveformSounds> |
@arraypress/waveform-sounds |
Every wrapper follows the same design:
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.WaveformPlayerOptions; WaveformSoundsOptions for the sound list), not re-declared — so they never drift as the core evolves.import()ed inside onMounted, so the audio + canvas + fetch surface never runs during SSR / Nuxt.{ 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.<WaveformPlayer>vue and @arraypress/waveform-player are peer dependencies (Vue ^3.5.0; core player ^1.8.0).
npm install @arraypress/waveform-player-vue @arraypress/waveform-player vuepnpm add @arraypress/waveform-player-vue @arraypress/waveform-player vueyarn add @arraypress/waveform-player-vue @arraypress/waveform-player vuebun add @arraypress/waveform-player-vue @arraypress/waveform-player vueImport the core stylesheet once at your app entry:
// main.ts / app entryimport '@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.25..4. 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.
<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.
npm install @arraypress/waveform-bar-vue @arraypress/waveform-bar @arraypress/waveform-player vuepnpm add @arraypress/waveform-bar-vue @arraypress/waveform-bar @arraypress/waveform-player vueyarn add @arraypress/waveform-bar-vue @arraypress/waveform-bar @arraypress/waveform-player vuebun add @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).
WaveformBarConfigBar-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.
<WaveformPlaylist>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.
npm install @arraypress/waveform-playlist-vue @arraypress/waveform-playlist @arraypress/waveform-player vuepnpm add @arraypress/waveform-playlist-vue @arraypress/waveform-playlist @arraypress/waveform-player vueyarn add @arraypress/waveform-playlist-vue @arraypress/waveform-playlist @arraypress/waveform-player vuebun add @arraypress/waveform-playlist-vue @arraypress/waveform-playlist @arraypress/waveform-player vueImport 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.
tracks (required)WaveformPlaylistTrackInput[]:
| Field | Type | Notes |
|---|---|---|
url |
string |
Required → data-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:
WaveformPlaylistOptions: continuous, expandChapters, showDuration, showChapterMarkers, chapterMarkerColor, showPlayState.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.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.
<WaveformSounds>A searchable, filterable list of sound previews — a sample pack, a preset bank — with a mini waveform per row and one shared audio engine. With sounds, the list’s markup is rendered on the server too (Nuxt, vue/server-renderer) by the core’s DOM-free renderer, and the runtime adopts it instead of rebuilding. See WaveformSounds for what the list does.
Peer deps: @arraypress/waveform-sounds (^0.1.0), @arraypress/waveform-player (^1.24.5), Vue ^3.5.0.
npm install @arraypress/waveform-sounds-vue @arraypress/waveform-sounds @arraypress/waveform-player vuepnpm add @arraypress/waveform-sounds-vue @arraypress/waveform-sounds @arraypress/waveform-player vueyarn add @arraypress/waveform-sounds-vue @arraypress/waveform-sounds @arraypress/waveform-player vuebun add @arraypress/waveform-sounds-vue @arraypress/waveform-sounds @arraypress/waveform-player vueImport both stylesheets, and import the player for its global — the list plays through window.WaveformPlayer, and the wrapper doesn’t import it for you (or pass the class as playerClass):
import '@arraypress/waveform-player'; // registers window.WaveformPlayer, the engineimport '@arraypress/waveform-player/styles.css';import '@arraypress/waveform-sounds/styles.css';<script setup lang="ts">import { WaveformSounds } from '@arraypress/waveform-sounds-vue';</script>
<template> <WaveformSounds manifest="/sounds.json" @play="(sound) => console.log(sound.title)" /></template>Write sounds.json with waveform-gen --manifest — mind its --base-url, which has to match where the audio is served. With a manifest the list is fetched and built in the browser; pass a :sounds array instead to render it on the server. A sounds array — even an empty one — wins over manifest.
Every WaveformSoundsOptions key except the callbacks is a runtime-declared prop, typed from the core’s index.d.ts — sounds / manifest, player, search, filters, sorts, showCount, menuSearch, idPrefix, urlState, loopToggle, maxTypeChips, pageSize, columns, waveformStyle, waveformColor, progressColor, barWidth, barGap, loop, autoAdvance, arrowAudition, playerOptions, playerClass, strings. Use them camelCase or kebab-case in templates (:page-size="100"). See Options for each one. class, style and id fall through to the host, which always carries wfp-host.
idPrefix, else one from Vue’s useId() — unique per list, identical on the server and the client.urlState (boolean | string; a bare url-state attribute is true) is runtime-only: the server renders the unfiltered list and the runtime applies the address on load.loop is the exception — it’s applied live through setLoop().playerOptions callbacks always reach the latest prop value; swapping one doesn’t rebuild.Theme it through style — set --ws-surface (your page background) so the server-rendered first paint is right: <WaveformSounds :sounds="sounds" style="--ws-surface: #09090b" />.
The core’s callbacks surface as emits, with the same arguments. emit is stable, so a new listener never rebuilds the list.
| Emit | Arguments |
|---|---|
@ready |
(instance) |
@play / @pause / @end |
(sound, instance) |
@filter |
(visible: Sound[], instance) |
@error |
(error, instance) |
The core’s bubbling waveformsounds:* events fire from the host as well.
Grab the instance with a template ref typed as WaveformSoundsExpose:
<script setup lang="ts">import { ref } from 'vue';import { WaveformSounds, type WaveformSoundsExpose } from '@arraypress/waveform-sounds-vue';
const list = ref<WaveformSoundsExpose>();</script>
<template> <WaveformSounds ref="list" manifest="/sounds.json" /> <button @click="list?.setFilter({ type: 'Drum loops' })">Drum loops</button> <button @click="list?.setSort('bpm')">By BPM</button></template>| Method | Signature | Notes |
|---|---|---|
play |
(target?, opts?: { at?: number }) => void |
By index, id or Sound; no target resumes. |
pause |
() => void |
|
toggle |
(target?) => void |
|
next / previous |
() => void |
The next / previous visible sound. |
setFilter |
(patch: Partial<SoundsFilter>) => void |
Merged into the current filter. |
clearFilters |
() => void |
The sort stays. |
setSort |
(by: SoundsSort) => void |
|
setLoop |
(on: boolean) => void |
|
showMore |
() => void |
|
instance |
readonly WaveformSounds | null |
The core instance (visible, current, sounds, engine, ready, …). |
A call before the instance exists is a no-op; a call while it’s still building (a manifest fetch) runs once it’s ready, in order. After that, calls are synchronous — so play() from a click keeps the browser’s user activation.
All four components are SSR / Nuxt safe by construction: the host markup (for the playlist, the [data-track] children; for the sound list, the whole list when given sounds) 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>Player options
Full inherited option surface — Player → Options.
Events
The waveformplayer:* DOM events — Player → Events.
React
The same wrappers, typed for React — React.
Installation
Bundler, CDN, and CSS setup — Getting started.