Interactive audio waveform rendering and playback library for web applications
—
Timeline display, minimap navigation, zoom controls, and hover interactions for enhanced waveform navigation and user experience with time-based visualization aids.
Display time labels, notches, and markers below or above the waveform for precise time navigation.
/**
* Timeline plugin for displaying time labels and notches
*/
class TimelinePlugin extends BasePlugin<TimelinePluginEvents, TimelinePluginOptions> {
/**
* Create a timeline plugin instance
* @param options - Timeline configuration options
* @returns New TimelinePlugin instance
*/
static create(options?: TimelinePluginOptions): TimelinePlugin;
}
interface TimelinePluginOptions {
/** Height of timeline in pixels, defaults to 20 */
height?: number;
/** Position relative to waveform, defaults to 'afterend' */
insertPosition?: 'beforebegin' | 'afterend';
/** Time interval between major labels in seconds */
timeInterval?: number;
/** Interval between primary labels (larger text) */
primaryLabelInterval?: number;
/** Interval between secondary labels (smaller text) */
secondaryLabelInterval?: number;
/** CSS styles for the timeline container */
style?: Partial<CSSStyleDeclaration>;
/** Format function for time labels */
formatTimeCallback?: (seconds: number) => string;
/** Whether to display labels, defaults to true */
displayLabels?: boolean;
}
interface TimelinePluginEvents extends BasePluginEvents {
/** When timeline is rendered */
'timeline-ready': [];
}Usage Examples:
import Timeline from "wavesurfer.js/dist/plugins/timeline.esm.js";
// Basic timeline
const timeline = Timeline.create({
height: 30,
timeInterval: 10, // Major marks every 10 seconds
primaryLabelInterval: 30, // Large labels every 30 seconds
secondaryLabelInterval: 10, // Small labels every 10 seconds
});
wavesurfer.registerPlugin(timeline);
// Customized timeline appearance
const styledTimeline = Timeline.create({
height: 40,
insertPosition: 'beforebegin', // Above waveform
style: {
backgroundColor: '#f0f0f0',
borderTop: '1px solid #ccc',
color: '#333',
fontSize: '12px',
fontFamily: 'monospace',
},
formatTimeCallback: (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
},
});
// Minimal timeline for mobile
const mobileTimeline = Timeline.create({
height: 20,
primaryLabelInterval: 60, // Less frequent labels
secondaryLabelInterval: 30,
displayLabels: true,
style: {
fontSize: '10px',
},
});
// Timeline event handling
timeline.on('timeline-ready', () => {
console.log('Timeline rendered');
});Provide a small overview waveform that serves as a scrollbar and navigation aid for the main waveform.
/**
* Minimap plugin for waveform overview and navigation
*/
class MinimapPlugin extends BasePlugin<MinimapPluginEvents, MinimapPluginOptions> {
/**
* Create a minimap plugin instance
* @param options - Minimap configuration options
* @returns New MinimapPlugin instance
*/
static create(options?: MinimapPluginOptions): MinimapPlugin;
}
interface MinimapPluginOptions {
/** Height of minimap in pixels, defaults to 50 */
height?: number;
/** Position relative to waveform container */
insertPosition?: string;
/** Wave color for minimap, defaults to main waveform color */
waveColor?: string;
/** Progress color for minimap, defaults to main progress color */
progressColor?: string;
/** Background color for minimap */
backgroundColor?: string;
/** Border styling for minimap */
borderColor?: string;
/** Opacity of the minimap, defaults to 1 */
opacity?: number;
/** Whether minimap is interactive, defaults to true */
interact?: boolean;
}
interface MinimapPluginEvents extends BasePluginEvents {
/** When minimap is ready */
'minimap-ready': [];
/** When minimap is clicked */
'minimap-click': [progress: number];
}Usage Examples:
import Minimap from "wavesurfer.js/dist/plugins/minimap.esm.js";
// Basic minimap
const minimap = Minimap.create({
height: 60,
waveColor: '#ddd',
progressColor: '#999',
});
wavesurfer.registerPlugin(minimap);
// Styled minimap
const styledMinimap = Minimap.create({
height: 80,
waveColor: 'rgba(255, 255, 255, 0.8)',
progressColor: 'rgba(255, 255, 255, 1)',
backgroundColor: '#1a1a1a',
borderColor: '#333',
opacity: 0.9,
});
// Interactive minimap with events
const interactiveMinimap = Minimap.create({
height: 50,
interact: true,
});
interactiveMinimap.on('minimap-click', (progress) => {
console.log(`Minimap clicked at ${(progress * 100).toFixed(1)}%`);
// Main waveform automatically seeks to clicked position
});
interactiveMinimap.on('minimap-ready', () => {
console.log('Minimap ready for interaction');
});Provide zoom controls and functionality for detailed waveform examination.
/**
* Zoom plugin for waveform zoom controls
*/
class ZoomPlugin extends BasePlugin<ZoomPluginEvents, ZoomPluginOptions> {
/**
* Create a zoom plugin instance
* @param options - Zoom configuration options
* @returns New ZoomPlugin instance
*/
static create(options?: ZoomPluginOptions): ZoomPlugin;
/**
* Zoom in by the configured scale factor
*/
zoomIn(): void;
/**
* Zoom out by the configured scale factor
*/
zoomOut(): void;
/**
* Get current zoom level
* @returns Current pixels per second
*/
getCurrentZoom(): number;
}
interface ZoomPluginOptions {
/** Zoom scale factor, defaults to 2 */
scale?: number;
/** Maximum zoom level in pixels per second, defaults to 1000 */
maxZoom?: number;
/** Minimum zoom level in pixels per second, defaults to 1 */
minZoom?: number;
/** Mouse wheel sensitivity, defaults to 1 */
deltaThreshold?: number;
/** Enable mouse wheel zooming, defaults to true */
wheelZoom?: boolean;
/** Enable keyboard zoom shortcuts, defaults to true */
keyboardZoom?: boolean;
}
interface ZoomPluginEvents extends BasePluginEvents {
/** When zoom level changes */
'zoom-changed': [zoomLevel: number];
/** When zoom reaches maximum level */
'zoom-max': [zoomLevel: number];
/** When zoom reaches minimum level */
'zoom-min': [zoomLevel: number];
}Usage Examples:
import Zoom from "wavesurfer.js/dist/plugins/zoom.esm.js";
// Basic zoom controls
const zoom = Zoom.create({
scale: 1.5, // 1.5x zoom factor
maxZoom: 500,
minZoom: 10,
});
wavesurfer.registerPlugin(zoom);
// Zoom control buttons
document.getElementById('zoom-in').addEventListener('click', () => {
zoom.zoomIn();
});
document.getElementById('zoom-out').addEventListener('click', () => {
zoom.zoomOut();
});
// Advanced zoom with custom controls
const advancedZoom = Zoom.create({
scale: 2,
maxZoom: 1000,
minZoom: 5,
wheelZoom: true,
keyboardZoom: true,
deltaThreshold: 5,
});
// Zoom event handling
advancedZoom.on('zoom-changed', (zoomLevel) => {
console.log(`Zoom level: ${zoomLevel} px/sec`);
document.getElementById('zoom-level').textContent = `${zoomLevel}px/s`;
updateZoomSlider(zoomLevel);
});
advancedZoom.on('zoom-max', (zoomLevel) => {
console.log('Maximum zoom reached');
document.getElementById('zoom-in').disabled = true;
});
advancedZoom.on('zoom-min', (zoomLevel) => {
console.log('Minimum zoom reached');
document.getElementById('zoom-out').disabled = true;
});
// Custom zoom slider
const zoomSlider = document.getElementById('zoom-slider');
zoomSlider.addEventListener('input', (event) => {
const zoomLevel = parseInt(event.target.value);
wavesurfer.zoom(zoomLevel);
});
function updateZoomSlider(zoomLevel) {
zoomSlider.value = zoomLevel;
}Display vertical line and timestamp on waveform hover for precise time identification.
/**
* Hover plugin for showing time information on waveform hover
*/
class HoverPlugin extends BasePlugin<HoverPluginEvents, HoverPluginOptions> {
/**
* Create a hover plugin instance
* @param options - Hover configuration options
* @returns New HoverPlugin instance
*/
static create(options?: HoverPluginOptions): HoverPlugin;
}
interface HoverPluginOptions {
/** Color of the hover line, defaults to '#333' */
lineColor?: string;
/** Width of the hover line, defaults to '1px' */
lineWidth?: string;
/** Background color of time label, defaults to '#fff' */
labelBackground?: string;
/** Text color of time label, defaults to '#000' */
labelColor?: string;
/** Font size of time label, defaults to '12px' */
labelSize?: string;
/** Custom time formatting function */
formatTimeCallback?: (seconds: number) => string;
/** Whether to show the time label, defaults to true */
showLabel?: boolean;
/** Whether to show the hover line, defaults to true */
showLine?: boolean;
}
interface HoverPluginEvents extends BasePluginEvents {
/** When hover starts */
'hover-start': [time: number];
/** When hover position changes */
'hover-move': [time: number];
/** When hover ends */
'hover-end': [];
}Usage Examples:
import Hover from "wavesurfer.js/dist/plugins/hover.esm.js";
// Basic hover functionality
const hover = Hover.create({
lineColor: '#ff0000',
lineWidth: '2px',
labelBackground: 'rgba(0, 0, 0, 0.8)',
labelColor: '#fff',
labelSize: '14px',
});
wavesurfer.registerPlugin(hover);
// Custom time formatting
const customHover = Hover.create({
formatTimeCallback: (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = (seconds % 60).toFixed(2);
return `${mins}:${secs.padStart(5, '0')}`;
},
showLabel: true,
showLine: true,
});
// Hover event handling for custom actions
hover.on('hover-start', (time) => {
console.log(`Hover started at ${time}s`);
showTimeTooltip(time);
});
hover.on('hover-move', (time) => {
updateTimeTooltip(time);
// Show additional information at hover position
showContextualInfo(time);
});
hover.on('hover-end', () => {
console.log('Hover ended');
hideTimeTooltip();
hideContextualInfo();
});
// Minimal hover for mobile (line only)
const mobileHover = Hover.create({
lineColor: 'rgba(255, 255, 255, 0.8)',
lineWidth: '1px',
showLabel: false, // Hide label on mobile
showLine: true,
});Combine navigation plugins for comprehensive waveform control and user experience.
Usage Examples:
// Complete navigation setup
function setupCompleteNavigation() {
// Timeline for time reference
const timeline = Timeline.create({
height: 25,
timeInterval: 5,
primaryLabelInterval: 15,
secondaryLabelInterval: 5,
formatTimeCallback: formatTimeMmSs,
});
// Minimap for overview
const minimap = Minimap.create({
height: 50,
waveColor: '#e0e0e0',
progressColor: '#888',
});
// Zoom controls
const zoom = Zoom.create({
scale: 1.8,
maxZoom: 800,
minZoom: 20,
});
// Hover feedback
const hover = Hover.create({
lineColor: '#4A90E2',
formatTimeCallback: formatTimeMmSs,
});
// Register all plugins
[timeline, minimap, zoom, hover].forEach(plugin => {
wavesurfer.registerPlugin(plugin);
});
// Synchronized zoom updates
zoom.on('zoom-changed', (level) => {
updateZoomIndicator(level);
saveZoomPreference(level);
});
// Minimap click feedback
minimap.on('minimap-click', (progress) => {
const time = progress * wavesurfer.getDuration();
showSeekFeedback(time);
});
}
// Navigation keyboard shortcuts
function setupKeyboardNavigation() {
document.addEventListener('keydown', (event) => {
if (!wavesurfer) return;
switch (event.key) {
case 'ArrowLeft':
wavesurfer.skip(-5); // Skip back 5 seconds
event.preventDefault();
break;
case 'ArrowRight':
wavesurfer.skip(5); // Skip forward 5 seconds
event.preventDefault();
break;
case '=':
case '+':
zoom?.zoomIn();
event.preventDefault();
break;
case '-':
zoom?.zoomOut();
event.preventDefault();
break;
case ' ':
wavesurfer.playPause();
event.preventDefault();
break;
case 'Home':
wavesurfer.setTime(0);
event.preventDefault();
break;
case 'End':
wavesurfer.setTime(wavesurfer.getDuration());
event.preventDefault();
break;
}
});
}
// Navigation state persistence
function setupNavigationPersistence() {
// Save navigation state
function saveNavigationState() {
const state = {
zoom: zoom?.getCurrentZoom() || wavesurfer.options.minPxPerSec,
position: wavesurfer.getCurrentTime(),
scroll: wavesurfer.getScroll(),
};
localStorage.setItem('waveform-navigation', JSON.stringify(state));
}
// Restore navigation state
function restoreNavigationState() {
const saved = localStorage.getItem('waveform-navigation');
if (saved) {
const state = JSON.parse(saved);
// Restore zoom
if (state.zoom) {
wavesurfer.zoom(state.zoom);
}
// Restore position and scroll
if (state.position) {
wavesurfer.setTime(state.position);
}
if (state.scroll) {
wavesurfer.setScroll(state.scroll);
}
}
}
// Save state on changes
wavesurfer.on('zoom', saveNavigationState);
wavesurfer.on('scroll', saveNavigationState);
wavesurfer.on('seeking', saveNavigationState);
// Restore on load
wavesurfer.on('ready', restoreNavigationState);
}
// Responsive navigation
function setupResponsiveNavigation() {
const isMobile = window.innerWidth < 768;
// Mobile-optimized plugins
if (isMobile) {
const mobileTimeline = Timeline.create({
height: 20,
primaryLabelInterval: 30,
style: { fontSize: '10px' },
});
const mobileHover = Hover.create({
showLabel: false, // Touch interfaces don't need hover labels
lineColor: 'rgba(255, 255, 255, 0.8)',
});
// No minimap on mobile to save space
wavesurfer.registerPlugin(mobileTimeline);
wavesurfer.registerPlugin(mobileHover);
} else {
// Full desktop navigation
setupCompleteNavigation();
}
// Handle orientation changes
window.addEventListener('orientationchange', () => {
setTimeout(() => {
// Refresh navigation layout
wavesurfer.getActivePlugins().forEach(plugin => {
if (plugin.onResize) plugin.onResize();
});
}, 100);
});
}
// Utility functions
function formatTimeMmSs(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${mins}:${secs}`;
}
function showSeekFeedback(time) {
const feedback = document.getElementById('seek-feedback');
feedback.textContent = `Seeking to ${formatTimeMmSs(time)}`;
feedback.style.display = 'block';
setTimeout(() => {
feedback.style.display = 'none';
}, 1000);
}Install with Tessl CLI
npx tessl i tessl/npm-wavesurfer-js