Quickstart
A working player in no time.
Install
Add raavl to your application:
npm install raavlUse a bundler that supports ESM and CSS imports, such as Vite or Next.js.
Import raavl/styles.css once in your application for the default waveform
styles, or supply your own CSS tokens.
For React integration, use the raavl/react adapter with react and react-dom.
The JavaScript API works independently of React.
JavaScript
Create your own container and controls:
<div id="waveform"></div>
<button id="play" disabled>Play</button>
<p id="error" role="alert"></p>import { createPlayer, mountWaveform } from "raavl";
import "raavl/styles.css";
const container = document.querySelector<HTMLElement>("#waveform")!;
const button = document.querySelector<HTMLButtonElement>("#play")!;
const errorText = document.querySelector<HTMLElement>("#error")!;
const report = (error: unknown) => {
errorText.textContent =
error instanceof Error ? error.message : String(error);
};
const player = createPlayer();
const view = mountWaveform(container, {
player,
appearance: { style: "bars", height: 160 },
interaction: { drag: "seek" },
label: "Episode waveform",
});
const off = player.subscribe(() => {
const state = player.getSnapshot();
button.disabled = state.source !== "ready";
button.textContent = state.playback === "playing" ? "Pause" : "Play";
if (state.error) report(state.error);
if (state.waveformError) report(state.waveformError);
});
button.onclick = () => {
if (player.getSnapshot().playback === "playing") player.pause();
else void player.play().catch(report);
};
try {
await player.setSource({ kind: "url", id: "episode-1", url: "/episode.mp3" });
} catch (error) {
report(error);
}
// Call when leaving the page or destroying this component.
function cleanup() {
button.onclick = null;
off();
view.dispose();
player.dispose();
}React
Use the complete player for a default UI with connected controls. Mark this as a client component in a Next.js App Router application.
"use client";
import { AudioPlayer } from "raavl/react";
import "raavl/styles.css";
export function EpisodePlayer() {
return (
<AudioPlayer
source={{ kind: "url", id: "episode-1", url: "/episode.mp3" }}
waveform={{ appearance: { style: "bars", height: 160 } }}
controls={{ playbackRate: true, loop: true }}
aria-label="Episode player"
/>
);
}The player owns source loading, controls, status feedback, and cleanup. Pass an
existing player instead of source to borrow playback. For a custom layout,
compose PlayerRoot, Waveform, and the individual controls; for a fully custom
interface, use the headless hooks. See the React API.
Supply your audio
Replace /episode.mp3 with an actual asset served by your app. Demo audio URLs on the documentation site are not assets installed with the library. For cross-origin waveform decoding, the audio host must permit CORS fetches.
setSource() resolves when metadata is available. Waveform preparation and playback buffering have separate states. Handle the play() promise because browsers may reject playback without a user gesture.
Next: sources and peaks, styling, or the player API.