WavesurferPlayer

A declarative, memoized React component that renders a wavesurfer.js waveform. The simplest way to drop a player into any component.

Overview

WavesurferPlayer (default export of wave-cn.tsx) is a thin wrapper around WaveSurfer.create(). It owns its container <div>, applies WAVESURFER_DEFAULTS, resolves CSS variables for the canvas, renders a skeleton while the audio decodes, and exposes every wavesurfer.js event as an on-prefixed prop.

Use it when you want a declarative player. Pair it with useWavePlayer for state and controls.

Usage

import WavesurferPlayer from "@/lib/wave-cn"

<WavesurferPlayer
  url="/coastline.mp3"
  waveColor="var(--primary)"
  height={80}
  onReady={(ws, duration) => console.log("ready", duration)}
  onFinish={() => console.log("finished")}
/>

What recreates the instance

Every WaveSurferOptions key is accepted as a prop. They fall into two groups:

GroupPropsBehaviour
Structuralurl, media, plugins, peaks, duration, sampleRate, backend, mediaControlsChanging one destroys the instance and creates a new one (re-fetch and re-decode).
Everything elseheight, barWidth, waveColor, dragToSeek, normalize, minPxPerSec, …Applied in place through wavesurfer.setOptions(). No re-decode.

plugins and peaks are compared by reference. Memoize them with useMemo, otherwise a new array on every render recreates the player on every render.

Getting the instance

onReady receives the live WaveSurfer instance. Store it in a ref for imperative calls, or let useWavePlayer do it for you:

import { useRef } from "react"
import WavesurferPlayer from "@/lib/wave-cn"
import type WaveSurfer from "wavesurfer.js"

export function MyPlayer() {
  const wsRef = useRef<WaveSurfer | null>(null)

  return (
    <WavesurferPlayer
      url="/coastline.mp3"
      onReady={(ws) => { wsRef.current = ws }}
      onDestroy={() => { wsRef.current = null }}
    />
  )
}

Injecting plugins

Pass a memoized plugins array. The instances you create act as templates: wavesurfer.js destroys plugins together with the instance, so the player clones each template every time it (re)creates the instance. Never keep a reference to the template expecting it to be live; look the live plugin up after ready:

import { useMemo } from "react"
import WavesurferPlayer from "@/lib/wave-cn"
import ZoomPlugin from "wavesurfer.js/dist/plugins/zoom.esm.js"
import TimelinePlugin from "wavesurfer.js/dist/plugins/timeline.esm.js"

const plugins = useMemo(() => [
  ZoomPlugin.create({ scale: 0.5, maxZoom: 1000 }),
  TimelinePlugin.create(),
], [])

<WavesurferPlayer
  url="/coastline.mp3"
  plugins={plugins}
  onReady={(ws) => {
    const zoom = ws.getActivePlugins().find((p) => p instanceof ZoomPlugin)
  }}
/>

Because plugins is structural, put plugin option props in the useMemo deps and the player recreates itself with the new options when they change.

For plugins that must be registered after the instance exists (e.g. RecordPlugin), register them in onReady:

<WavesurferPlayer
  onReady={(ws) => {
    ws.registerPlugin(RecordPlugin.create({ scrollingWaveform: true }))
  }}
/>

Instant render with precomputed peaks

Pass peaks and duration to draw the waveform immediately, without downloading and decoding the audio up front. Generate peaks at build time or on upload (for example with audiowaveform or ffmpeg) and serve them as JSON:

const peaks = useMemo(() => [track.peaks], [track.peaks])

<WavesurferPlayer url={track.url} peaks={peaks} duration={track.duration} />

The audio is still fetched lazily when playback starts.

Events

Every wavesurfer.js event is available as an on-prefixed prop. The handler receives the WaveSurfer instance first, then the event's own arguments. Handlers are read through a ref, so inline functions never cause re-subscription.

<WavesurferPlayer
  url="/coastline.mp3"
  onReady={(ws, duration) => {}}
  onPlay={(ws) => {}}
  onPause={(ws) => {}}
  onFinish={(ws) => {}}
  onTimeupdate={(ws, currentTime) => {}}
  onSeeking={(ws, currentTime) => {}}
  onDestroy={(ws) => {}}
/>

Only handlers present when the instance is created are subscribed. See the full event list at wavesurfer.js docs.

API Reference

Accepts all WaveSurferOptions (except container, which the player owns) plus every on{Event} handler and className.

PropTypeDefaultDescription
urlstring—Audio file URL. Structural.
mediaHTMLMediaElement—Use an existing <audio>/<video> as the source. Structural.
peaksArray<Float32Array | number[]>—Precomputed peaks. Structural, memoize.
durationnumber—Duration in seconds, used with peaks. Structural.
pluginsGenericPlugin[][]Memoized plugin templates. Structural.
waveColorstringvar(--muted-foreground)Accepts CSS vars, hex, hsl, oklch.
progressColorstringvar(--primary)Same formats as waveColor.
heightnumber64Canvas height in px.
barWidthnumber3Bar width in px.
barGapnumber2Gap between bars in px.
barRadiusnumber2Bar border radius.
minPxPerSecnumber1Minimum pixels per second (zoom level).
cursorWidthnumber0Playhead width in px.
classNamestring—Class on the wrapper <div>.
on{Event}(ws, ...args) => void—Any wavesurfer.js event handler.

On this page