Playback & interaction
Play, pause, stop
The three core playback methods correspond directly to their audio-player equivalents:
// Start playback from the current position
await ws.play()
// Pause at the current position
ws.pause()
// Toggle between playing and paused
await ws.playPause()
// Pause and rewind to the beginning
ws.stop()
play() and playPause() are async — they return a Promise that resolves once the browser has accepted the play request. Awaiting them lets you detect failures (for example, autoplay blocks) before proceeding.
try {
await ws.play()
} catch (err) {
console.error('Playback failed:', err)
}
The finish event fires when audio plays through to the end:
ws.on('finish', () => {
console.log('Playback finished')
})
Seeking
Two methods let you move the playhead programmatically:
// seekTo() takes a ratio between 0 (start) and 1 (end)
ws.seekTo(0.5) // jump to the midpoint
ws.seekTo(0) // jump to the beginning
// setTime() takes an absolute time in seconds
ws.setTime(30) // jump to 0:30
ws.setTime(ws.getCurrentTime() + 10) // skip forward 10 s
Read back the current position and total length with:
const current = ws.getCurrentTime() // seconds elapsed
const total = ws.getDuration() // total duration in seconds
console.log(`${current.toFixed(1)} / ${total.toFixed(1)} s`)
getDuration() returns 0 until audio has loaded. Wait for the ready event before reading it:
ws.on('ready', (duration) => {
console.log('Loaded, duration:', duration)
})
Speed and pitch
Use setPlaybackRate() to change speed at any time — before or during playback:
ws.setPlaybackRate(1.5) // 1.5× normal speed
ws.setPlaybackRate(0.75) // 75 % speed (slow down)
ws.setPlaybackRate(1) // back to normal
The method accepts an optional second argument preservePitch (a boolean). When true (the browser default) pitch correction is applied so the audio does not sound chipmunk-like at high speeds; set it to false to let pitch shift with speed:
ws.setPlaybackRate(2, true) // fast but keeps original pitch (default browser behaviour)
ws.setPlaybackRate(2, false) // fast and pitch rises
To set the initial playback rate before audio starts, use the audioRate constructor option:
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
audioRate: 1.5, // start at 1.5× speed
})
To give users a speed control, call ws.setPlaybackRate() from a <select> or range <input> change handler. The rate takes effect immediately, even mid-playback.
Volume and mute
// Volume accepts a float from 0 (silent) to 1 (full volume)
ws.setVolume(0.5) // 50 %
ws.setVolume(0) // silent (but not muted)
ws.setVolume(1) // full volume
const vol = ws.getVolume() // read back the current value
// Mute / unmute without changing the volume level
ws.setMuted(true) // mute
ws.setMuted(false) // unmute
const muted = ws.getMuted() // true or false
Setting volume to 0 is not the same as muting — setMuted(true) sets the underlying <audio> element’s muted attribute, which some browsers handle differently and which survives volume changes.
Playing a segment / looping
play(start, end)
In wavesurfer.js v7 the play() method still accepts optional start and end arguments (both in seconds):
// Play from second 10 to second 20, then stop
await ws.play(10, 20)
// Play from second 5 to the end
await ws.play(5)
Internally play(start, end) calls setTime(start) and then pauses automatically when currentTime reaches end. This is the built-in segment playback API — no plugin required.
Looping a region with the Regions plugin
For repeated looping of a segment, the Regions plugin gives you named, draggable regions that each have their own play() method:
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js'
const regions = ws.registerPlugin(RegionsPlugin.create())
const loop = regions.addRegion({
start: 10,
end: 20,
color: 'rgba(0, 100, 200, 0.2)',
})
// play() with no argument plays from region start but does NOT stop at region end
loop.play() // plays from start, does NOT stop at region end
loop.play(true) // plays from start and stops at region end (explicit)
loop.play(false) // same as loop.play() — does NOT stop at region end
The region.play() signature is play(stopAtEnd?: boolean). By default stopAtEnd is undefined (falsy) — which means it plays from the region’s start but does not automatically stop at the end. Pass true to stop playback at the region’s end.
To continuously loop a region, listen to the region-out event (fired by the Regions plugin whenever playback leaves a region) and restart:
regions.on('region-out', (region) => {
region.play() // restart from region start; region-out fires again each time it exits
})
The region-out event is the correct loop hook. Avoid polling timeupdate yourself — the plugin already does this internally and emits region-in / region-out at the right moments.
Click-to-seek and drag
Interaction with the waveform is on by default. Clicking anywhere on the waveform seeks to that position. You can disable this entirely:
// Disable all waveform interaction (clicks and drags will be ignored)
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
interact: false,
})
// Toggle interaction at runtime
ws.toggleInteraction(false) // disable
ws.toggleInteraction(true) // re-enable
Drag-to-seek (scrubbing) is off by default. Enable it so users can drag the cursor to any position:
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
dragToSeek: true, // default debounce of 200 ms while paused
})
// Or supply a custom debounce time (in milliseconds)
const ws2 = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
dragToSeek: { debounceTime: 100 },
})
dragToSeek on mobile can block page scroll. When dragToSeek is enabled, a vertical swipe starting on the waveform is consumed as a drag gesture and will not scroll the page. If your layout requires the waveform to be scrollable on touch devices, either disable dragToSeek or wrap the waveform in a container that is not part of the main scroll flow.
Do not set media.currentTime directly. Bypassing ws.setTime() or ws.seekTo() and writing to the underlying <audio> element’s currentTime directly can cause the progress overlay to reset to 0 because wavesurfer’s renderer is not notified. Always use the wavesurfer API methods.
Autoplay
Browsers require a user gesture before audio can play. Calling ws.play() programmatically on page load will result in a rejected Promise — most browsers do not throw a visible error, they just silently prevent playback.
iOS Safari has stricter rules on top of this: any page that has never received a tap or click will reject play(), and audio is paused automatically when the screen locks.
The safest pattern is always to trigger playback from a user event:
document.querySelector('#play-btn').addEventListener('click', () => {
ws.play()
})
If you do need autoplay (for example in kiosk scenarios), use the autoplay option and accept that it will silently fail on most mobile browsers until the user interacts with the page:
const ws = WaveSurfer.create({
container: '#waveform',
url: '/audio.mp3',
autoplay: true, // ignored silently on most mobile browsers
})
Avoid calling play() then pause() back-to-back
A common mistake is to call play() and then immediately pause() before the browser has accepted the play request:
// WRONG — triggers "AbortError: play() request was interrupted by a call to pause()"
ws.play()
ws.pause() // too soon
// RIGHT — wait for the play Promise to settle first
await ws.play()
ws.pause()
The AbortError: play() request was interrupted by a call to pause() error appears in the console when you call pause() (or stop()) synchronously after play() before the browser has fulfilled the play request. Always await ws.play() before calling pause().
See Troubleshooting for more detail on autoplay policies and workarounds.