Loading audio

From a URL

The simplest way to load audio is to pass a url option when creating the instance:

import WaveSurfer from 'wavesurfer.js'

const ws = WaveSurfer.create({
  container: '#waveform',
  url: '/audio/track.mp3',
})

You can also load (or reload) audio after creation by calling ws.load():

ws.load('/audio/track.mp3')

load returns a promise that resolves when the waveform is ready. The ready event fires at the same point:

ws.on('ready', (duration) => {
  console.log('Loaded, duration:', duration)
})

To swap tracks, call ws.load() again with a new URL. Any in-flight fetch is automatically cancelled before the new one starts.


From a Blob or File

Use ws.loadBlob(blob) to load audio from a Blob or File object. This is ideal for file pickers, drag-and-drop, or any scenario where you already have the audio data in memory.

Wiring up an <input type="file">:

<input type="file" id="audio-file" accept="audio/*" />
<div id="waveform"></div>
import WaveSurfer from 'wavesurfer.js'

const ws = WaveSurfer.create({ container: '#waveform' })

document.getElementById('audio-file').addEventListener('change', (e) => {
  const file = e.target.files[0]
  if (!file) return
  ws.loadBlob(file)
})

Empty blob guard: passing a zero-length blob will cause an error. Always check blob.size > 0 before calling loadBlob:

if (file && file.size > 0) {
  ws.loadBlob(file)
} else {
  console.warn('File is empty — nothing to load.')
}

From an existing media element

If you already have an <audio> or <video> element on the page, pass it via the media option. wavesurfer.js will attach to it rather than creating its own element:

<audio id="player" src="/audio/track.mp3" controls></audio>
<div id="waveform"></div>
const ws = WaveSurfer.create({
  container: '#waveform',
  media: document.querySelector('#player'),
})

This pattern is useful when you need the native browser controls alongside the waveform, or when the media element’s lifecycle is managed elsewhere.

Visualising video: pass a <video> element the same way. wavesurfer.js will draw the waveform for its audio track while the video plays in its own element. See the video example for a working demo.

You can also swap the media element after creation with ws.setMediaElement(element).


CORS

When loading audio from a different origin (a CDN, S3 bucket, or third-party host), the server must return appropriate CORS headers — at minimum:

Access-Control-Allow-Origin: *

wavesurfer.js fetches audio with the browser’s fetch API. You can pass any standard RequestInit options via the fetchParams option. For example, to include cookies with a credentialed cross-origin request:

const ws = WaveSurfer.create({
  container: '#waveform',
  url: 'https://cdn.example.com/audio.mp3',
  fetchParams: {
    credentials: 'include',
    headers: {
      Authorization: 'Bearer my-token',
    },
  },
})

S3 / CDN buckets: out-of-the-box, most object storage buckets block cross-origin requests. You must add a CORS rule to the bucket policy that allows GET requests from your domain. Consult your provider’s documentation (AWS S3 CORS, Cloudflare R2, GCS bucket CORS, etc.).


Large files

For most files the default MediaElement backend is the right choice: the browser’s <audio> element can begin playing before the entire file has downloaded, keeping time-to-first-sound low. The WebAudio backend decodes the entire file into an AudioBuffer in memory before playback can start, which for large files (hundreds of megabytes) can:

  • Hold the full file in RAM.
  • Take several seconds to decode.
  • Fail entirely if the file exceeds the browser’s memory limits or the tab’s allocation cap.

Recommendations for large files:

  • Stick with the default MediaElement backend (omit backend from your options or set backend: 'MediaElement').
  • Pre-compute the waveform peaks server-side and pass them via the peaks and duration options to skip the in-browser decode entirely. See Pre-computed peaks for how to generate and use them.
// Large file: use pre-computed peaks to avoid in-browser decoding
const ws = WaveSurfer.create({
  container: '#waveform',
  url: '/audio/very-large.flac',
  peaks: [[/* your pre-computed peak data */]],
  duration: 3600, // seconds
})

Handling errors

Subscribe to the error event to catch network, decoding, and media errors in one place:

ws.on('error', (err) => {
  console.error('WaveSurfer error:', err)
})

The error event fires for:

  • Fetch failures — 4xx/5xx HTTP responses, CORS blocks, or network timeouts.
  • Decoding failures — the Web Audio API throws an EncodingError: Unable to decode audio data when the browser cannot parse the file. This usually means the codec or container is not supported by that browser (for example, FLAC on older iOS, or OGG on some Safari versions). To fix this, re-encode the audio to a universally supported format such as MP3 or AAC, or switch to the MediaElement backend which delegates decoding to the native media engine and has broader codec support.
  • Media element errors — the underlying <audio> or <video> element encountered a MediaError (for example, a corrupt file or unsupported stream).
ws.on('error', (err) => {
  if (err.message.includes('Unable to decode audio data')) {
    // Codec not supported by the Web Audio API in this browser.
    // Try re-encoding to MP3/AAC, or use backend: 'MediaElement'.
  } else if (err.message.startsWith('Failed to fetch')) {
    // Network or CORS issue — check the URL and server headers.
  }
})

Errors thrown asynchronously during construction (when url is passed as an option) cannot be caught with a try/catch around WaveSurfer.create(). Always attach an error listener to handle them reliably.