Interactive audio waveform rendering and playback library for web applications
—
Visual overlays and markers for audio segments with interactive drag, resize, content support, and comprehensive event handling for creating time-based annotations and selections.
Create and manage a regions plugin instance for visual audio segment marking.
/**
* Regions plugin for creating visual overlays on the waveform
*/
class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
/**
* Create a regions plugin instance
* @param options - Plugin configuration (currently undefined/no options)
* @returns New RegionsPlugin instance
*/
static create(options?: RegionsPluginOptions): RegionsPlugin;
/**
* Add a new region to the waveform
* @param params - Region configuration parameters
* @returns Created region instance
*/
addRegion(params: RegionParams): Region;
/**
* Remove all regions from the waveform
*/
clearRegions(): void;
/**
* Get all current regions
* @returns Array of all region instances
*/
getRegions(): Region[];
/**
* Enable drag selection to create regions by dragging on waveform
* @param options - Drag selection configuration
* @returns Function to disable drag selection
*/
enableDragSelection(options?: DragSelectionOptions): () => void;
}
type RegionsPluginOptions = undefined;
interface DragSelectionOptions {
color?: string;
[key: string]: any;
}Usage Examples:
import Regions from "wavesurfer.js/dist/plugins/regions.esm.js";
// Create and register regions plugin
const regions = Regions.create();
wavesurfer.registerPlugin(regions);
// Add basic region
const region1 = regions.addRegion({
start: 10,
end: 25,
color: "rgba(255, 0, 0, 0.3)",
content: "Verse 1",
});
// Add complex region with full options
const region2 = regions.addRegion({
id: "chorus-1",
start: 30,
end: 60,
drag: true,
resize: true,
color: "rgba(0, 255, 0, 0.3)",
content: "Chorus",
minLength: 5,
maxLength: 120,
});
// Enable drag to create regions
const disableDragSelect = regions.enableDragSelection({
color: "rgba(0, 0, 255, 0.2)",
});
// Get all regions
const allRegions = regions.getRegions();
console.log(`${allRegions.length} regions created`);
// Clear all regions
regions.clearRegions();Individual region objects with playback, modification, and content management capabilities.
interface Region {
/** Unique region identifier */
id: string;
/** Start time in seconds */
start: number;
/** End time in seconds */
end: number;
/** Whether region can be dragged */
drag: boolean;
/** Whether region can be resized */
resize: boolean;
/** Region background color */
color: string;
/** Region content element */
content?: HTMLElement;
/** Minimum region length in seconds */
minLength: number;
/** Maximum region length in seconds */
maxLength: number;
/** Audio channel index */
channelIdx: number;
/**
* Play the region audio, optionally stopping at a specific time
* @param end - Optional end time, defaults to region end
* @returns Promise that resolves when playback starts
*/
play(end?: number): Promise<void>;
/**
* Update region parameters
* @param params - Partial region parameters to update
*/
setOptions(params: Partial<RegionParams>): void;
/**
* Remove the region from the waveform
*/
remove(): void;
/**
* Set or update region content (text or HTML element)
* @param content - Text string or HTML element
*/
setContent(content: string | HTMLElement): void;
}Usage Examples:
// Create region with all properties
const region = regions.addRegion({
id: "intro",
start: 0,
end: 15,
drag: true,
resize: true,
resizeStart: true,
resizeEnd: true,
color: "rgba(255, 255, 0, 0.4)",
content: "Introduction",
minLength: 2,
maxLength: 30,
channelIdx: 0,
contentEditable: true,
});
// Play region
await region.play(); // Play from region start to end
await region.play(12); // Play from region start to 12 seconds
// Update region properties
region.setOptions({
color: "rgba(255, 0, 255, 0.4)",
content: "Updated Introduction",
end: 18,
});
// Modify region content
region.setContent("New Title");
region.setContent(document.createElement("div")); // HTML element
// Remove region
region.remove();
// Access region properties
console.log(`Region "${region.id}" from ${region.start}s to ${region.end}s`);
console.log(`Duration: ${region.end - region.start}s`);Configuration options for creating and updating regions.
interface RegionParams {
/** The id of the region, defaults to random string */
id?: string;
/** The start position of the region (in seconds) - required */
start: number;
/** The end position of the region (in seconds), defaults to start + small duration */
end?: number;
/** Allow/disallow dragging the region, defaults to true */
drag?: boolean;
/** Allow/disallow resizing the region, defaults to true */
resize?: boolean;
/** Allow/disallow resizing the start of the region, defaults to resize value */
resizeStart?: boolean;
/** Allow/disallow resizing the end of the region, defaults to resize value */
resizeEnd?: boolean;
/** The color of the region (CSS color), defaults to random color */
color?: string;
/** Content string or HTML element */
content?: string | HTMLElement;
/** Min length when resizing (in seconds), defaults to 0 */
minLength?: number;
/** Max length when resizing (in seconds), defaults to Infinity */
maxLength?: number;
/** The index of the channel, defaults to 0 */
channelIdx?: number;
/** Allow/Disallow contenteditable property for content, defaults to false */
contentEditable?: boolean;
}Usage Examples:
// Minimal region (only start required)
const quickRegion = regions.addRegion({
start: 45,
// end defaults to start + small duration
// other properties use defaults
});
// Comprehensive region configuration
const detailedRegion = regions.addRegion({
id: "bridge-section",
start: 120,
end: 150,
drag: true,
resize: true,
resizeStart: false, // Can't resize start
resizeEnd: true, // Can resize end
color: "#ff6b6b",
content: "Bridge",
minLength: 10,
maxLength: 60,
channelIdx: 0,
contentEditable: true,
});
// Read-only region
const staticRegion = regions.addRegion({
id: "marker",
start: 75,
end: 75.1, // Very short duration for marker
drag: false,
resize: false,
color: "red",
content: "Important Moment",
});
// Multi-channel region
const stereoRegion = regions.addRegion({
start: 30,
end: 40,
channelIdx: 1, // Second channel
color: "rgba(0, 255, 255, 0.3)",
});Events emitted by individual regions and the regions plugin for interaction tracking.
interface RegionEvents {
/** Before the region is removed */
remove: [];
/** When the region's parameters are being updated */
update: [side?: 'start' | 'end'];
/** When dragging or resizing is finished */
'update-end': [];
/** On region play */
play: [end?: number];
/** On mouse click */
click: [event: MouseEvent];
/** Double click */
dblclick: [event: MouseEvent];
/** Mouse over */
over: [event: MouseEvent];
/** Mouse leave */
leave: [event: MouseEvent];
/** Content changed */
'content-changed': [];
}
interface RegionsPluginEvents extends BasePluginEvents {
/** When a new region is initialized but not rendered yet */
'region-initialized': [region: Region];
/** When a region is created and rendered */
'region-created': [region: Region];
/** When a region is being updated */
'region-update': [region: Region, side?: 'start' | 'end'];
/** When a region is done updating */
'region-updated': [region: Region];
/** When a region is removed */
'region-removed': [region: Region];
/** When a region is clicked */
'region-clicked': [region: Region, e: MouseEvent];
/** When a region is double-clicked */
'region-double-clicked': [region: Region, e: MouseEvent];
/** When playback enters a region */
'region-in': [region: Region];
/** When playback leaves a region */
'region-out': [region: Region];
/** When region content is changed */
'region-content-changed': [region: Region];
}Usage Examples:
// Listen to plugin-level events
regions.on("region-created", (region) => {
console.log(`New region created: ${region.id}`);
setupRegionControls(region);
});
regions.on("region-clicked", (region, event) => {
console.log(`Clicked region: ${region.id}`);
showRegionDetails(region);
});
regions.on("region-updated", (region) => {
console.log(`Region ${region.id} updated: ${region.start}s - ${region.end}s`);
saveRegionData(region);
});
regions.on("region-in", (region) => {
console.log(`Entered region: ${region.id}`);
highlightRegion(region);
});
regions.on("region-out", (region) => {
console.log(`Left region: ${region.id}`);
unhighlightRegion(region);
});
// Listen to individual region events
const region = regions.addRegion({ start: 10, end: 20, content: "Test" });
region.on("click", (event) => {
event.stopPropagation();
toggleRegionSelection(region);
});
region.on("dblclick", (event) => {
region.play();
});
region.on("update", (side) => {
console.log(`Updating region ${side || 'position'}`);
showUpdateIndicator();
});
region.on("update-end", () => {
console.log("Region update complete");
hideUpdateIndicator();
validateRegion(region);
});
region.on("content-changed", () => {
console.log("Region content changed");
autosaveContent(region);
});Additional region functionality for complex use cases and applications.
Usage Examples:
// Region templates for consistent styling
const regionTemplates = {
verse: {
color: "rgba(255, 0, 0, 0.3)",
drag: true,
resize: true,
minLength: 15,
maxLength: 45,
},
chorus: {
color: "rgba(0, 255, 0, 0.3)",
drag: true,
resize: true,
minLength: 20,
maxLength: 40,
},
bridge: {
color: "rgba(0, 0, 255, 0.3)",
drag: true,
resize: true,
minLength: 10,
maxLength: 30,
},
};
// Create templated regions
function createTemplatedRegion(type, start, end, content) {
return regions.addRegion({
...regionTemplates[type],
start,
end,
content,
id: `${type}-${Date.now()}`,
});
}
// Batch region operations
const songStructure = [
{ type: "verse", start: 0, end: 30, content: "Verse 1" },
{ type: "chorus", start: 30, end: 60, content: "Chorus 1" },
{ type: "verse", start: 60, end: 90, content: "Verse 2" },
{ type: "chorus", start: 90, end: 120, content: "Chorus 2" },
];
songStructure.forEach(section => {
createTemplatedRegion(section.type, section.start, section.end, section.content);
});
// Region export/import for persistence
function exportRegions() {
return regions.getRegions().map(region => ({
id: region.id,
start: region.start,
end: region.end,
color: region.color,
content: region.content?.textContent || "",
drag: region.drag,
resize: region.resize,
}));
}
function importRegions(regionData) {
regions.clearRegions();
regionData.forEach(data => {
regions.addRegion(data);
});
}
// Save/load regions
localStorage.setItem("savedRegions", JSON.stringify(exportRegions()));
const savedRegions = JSON.parse(localStorage.getItem("savedRegions") || "[]");
importRegions(savedRegions);
// Region validation and constraints
function validateRegionOverlap(newRegion) {
const existingRegions = regions.getRegions();
for (const region of existingRegions) {
if (region === newRegion) continue;
const hasOverlap =
(newRegion.start < region.end && newRegion.end > region.start);
if (hasOverlap) {
console.warn(`Region ${newRegion.id} overlaps with ${region.id}`);
return false;
}
}
return true;
}
// Auto-adjust regions to prevent overlap
regions.on("region-update", (region) => {
if (!validateRegionOverlap(region)) {
// Auto-adjust to prevent overlap
const conflicts = regions.getRegions().filter(r =>
r !== region && r.start < region.end && r.end > region.start
);
if (conflicts.length > 0) {
const nearestEnd = Math.min(...conflicts.map(r => r.end));
region.setOptions({ start: nearestEnd });
}
}
});Install with Tessl CLI
npx tessl i tessl/npm-wavesurfer-js