CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-wavesurfer-js

Interactive audio waveform rendering and playback library for web applications

Pending
Overview
Eval results
Files

timeline-navigation.mddocs/

Timeline and Navigation

Timeline display, minimap navigation, zoom controls, and hover interactions for enhanced waveform navigation and user experience with time-based visualization aids.

Capabilities

Timeline Plugin

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');
});

Minimap Plugin

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');
});

Zoom Plugin

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;
}

Hover Plugin

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,
});

Navigation Integration

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

docs

audio-processing.md

audio-recording.md

core-waveform-control.md

event-system.md

index.md

plugin-system.md

regions-plugin.md

timeline-navigation.md

visual-customization.md

tile.json