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

event-system.mddocs/

Event System

Comprehensive event handling system for lifecycle, playback, user interaction, and waveform state changes with subscription management and error handling.

Capabilities

Event Subscription and Management

Subscribe to events and manage event listeners with unsubscribe functionality.

interface WaveSurfer {
  /**
   * Subscribe to an event with callback function
   * @param event - Event name to listen for
   * @param listener - Callback function to execute
   * @param options - Optional subscription options
   * @returns Unsubscribe function
   */
  on<EventName extends keyof WaveSurferEvents>(
    event: EventName,
    listener: (...args: WaveSurferEvents[EventName]) => void,
    options?: { once?: boolean }
  ): () => void;

  /**
   * Subscribe to an event only once
   * @param event - Event name to listen for
   * @param listener - Callback function to execute
   * @returns Unsubscribe function
   */
  once<EventName extends keyof WaveSurferEvents>(
    event: EventName,
    listener: (...args: WaveSurferEvents[EventName]) => void
  ): () => void;

  /**
   * Unsubscribe from an event
   * @param event - Event name to unsubscribe from
   * @param listener - Specific callback function to remove
   */
  un<EventName extends keyof WaveSurferEvents>(
    event: EventName,
    listener: (...args: WaveSurferEvents[EventName]) => void
  ): void;

  /**
   * Clear all event listeners
   */
  unAll(): void;
}

Usage Examples:

// Basic event subscription
const unsubscribe = wavesurfer.on("ready", (duration) => {
  console.log(`Audio loaded, duration: ${duration} seconds`);
});

// One-time event subscription
wavesurfer.once("play", () => {
  console.log("First time playing!");
});

// Unsubscribe from specific event
const playHandler = () => console.log("Playing");
wavesurfer.on("play", playHandler);
wavesurfer.un("play", playHandler); // Remove specific handler

// Unsubscribe using returned function
const unsub = wavesurfer.on("pause", () => console.log("Paused"));
unsub(); // Remove this subscription

// Clear all event listeners
wavesurfer.unAll();

Lifecycle Events

Events related to WaveSurfer initialization, loading, and destruction.

interface WaveSurferEvents {
  /** Fired after WaveSurfer instance is created and initialized */
  init: [];
  
  /** Fired when the audio is fully loaded and ready to play */
  ready: [duration: number];
  
  /** Fired just before the WaveSurfer instance is destroyed */
  destroy: [];
}

Usage Examples:

// Initialize event - setup UI
wavesurfer.on("init", () => {
  console.log("WaveSurfer initialized");
  document.getElementById("loading").style.display = "none";
});

// Ready event - enable playback controls
wavesurfer.on("ready", (duration) => {
  console.log(`Audio ready, duration: ${duration.toFixed(2)}s`);
  document.getElementById("play-button").disabled = false;
  document.getElementById("duration").textContent = formatTime(duration);
});

// Destroy event - cleanup
wavesurfer.on("destroy", () => {
  console.log("WaveSurfer destroyed, cleaning up");
  document.getElementById("controls").innerHTML = "";
});

function formatTime(seconds) {
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60).toString().padStart(2, "0");
  return `${mins}:${secs}`;
}

Audio Loading Events

Events that track the audio loading and decoding process.

interface WaveSurferEvents {
  /** Fired when audio loading starts */
  load: [url: string];
  
  /** Fired during audio loading with progress percentage */
  loading: [percent: number];
  
  /** Fired when the audio has been decoded into waveform data */
  decode: [duration: number];
}

Usage Examples:

// Track loading process
wavesurfer.on("load", (url) => {
  console.log(`Loading audio from: ${url}`);
  showLoadingSpinner();
});

wavesurfer.on("loading", (percent) => {
  console.log(`Loading progress: ${percent}%`);
  updateProgressBar(percent);
});

wavesurfer.on("decode", (duration) => {
  console.log(`Audio decoded, duration: ${duration}s`);
  hideLoadingSpinner();
});

function showLoadingSpinner() {
  document.getElementById("spinner").style.display = "block";
}

function updateProgressBar(percent) {
  document.getElementById("progress-bar").style.width = `${percent}%`;
}

function hideLoadingSpinner() {
  document.getElementById("spinner").style.display = "none";
  document.getElementById("progress-bar").style.width = "0%";
}

Playback Events

Events related to audio playback state changes and position updates.

interface WaveSurferEvents {
  /** Fired when audio playback starts */
  play: [];
  
  /** Fired when audio playback is paused */
  pause: [];
  
  /** Fired when audio playback finishes naturally */
  finish: [];
  
  /** Fired continuously during playback with current time */
  timeupdate: [currentTime: number];
  
  /** Alias for timeupdate, but only fired when audio is playing */
  audioprocess: [currentTime: number];
  
  /** Fired when user seeks to a new position */
  seeking: [currentTime: number];
}

Usage Examples:

// Playback state management
wavesurfer.on("play", () => {
  document.getElementById("play-button").textContent = "Pause";
  document.body.classList.add("playing");
});

wavesurfer.on("pause", () => {
  document.getElementById("play-button").textContent = "Play";
  document.body.classList.remove("playing");
});

wavesurfer.on("finish", () => {
  document.getElementById("play-button").textContent = "Play";
  document.body.classList.remove("playing");
  console.log("Playback finished");
});

// Time tracking and display
wavesurfer.on("timeupdate", (currentTime) => {
  const duration = wavesurfer.getDuration();
  const progress = (currentTime / duration) * 100;
  
  document.getElementById("current-time").textContent = formatTime(currentTime);
  document.getElementById("progress").style.width = `${progress}%`;
});

// Seeking feedback
wavesurfer.on("seeking", (currentTime) => {
  console.log(`Seeking to: ${formatTime(currentTime)}`);
  showSeekingIndicator();
  setTimeout(hideSeekingIndicator, 500);
});

User Interaction Events

Events triggered by user interactions with the waveform.

interface WaveSurferEvents {
  /** Fired when user interacts with the waveform (clicks or drags) */
  interaction: [newTime: number];
  
  /** Fired when user clicks on the waveform */
  click: [relativeX: number, relativeY: number];
  
  /** Fired when user double-clicks on the waveform */
  dblclick: [relativeX: number, relativeY: number];
  
  /** Fired when user drags the cursor */
  drag: [relativeX: number];
  
  /** Fired when user starts dragging the cursor */
  dragstart: [relativeX: number];
  
  /** Fired when user ends dragging the cursor */
  dragend: [relativeX: number];
}

Usage Examples:

// General interaction tracking
wavesurfer.on("interaction", (newTime) => {
  console.log(`User interacted, jumped to: ${formatTime(newTime)}`);
  showInteractionFeedback();
});

// Click handling
wavesurfer.on("click", (relativeX, relativeY) => {
  const duration = wavesurfer.getDuration();
  const clickTime = relativeX * duration;
  console.log(`Clicked at ${formatTime(clickTime)} (${(relativeX * 100).toFixed(1)}%)`);
});

// Double-click for special actions
wavesurfer.on("dblclick", (relativeX, relativeY) => {
  const duration = wavesurfer.getDuration();
  const clickTime = relativeX * duration;
  console.log(`Double-clicked at ${formatTime(clickTime)}`);
  
  // Example: Toggle playback or create marker
  if (wavesurfer.isPlaying()) {
    wavesurfer.pause();
  } else {
    wavesurfer.play();
  }
});

// Drag interaction feedback
wavesurfer.on("dragstart", (relativeX) => {
  document.body.classList.add("dragging");
  console.log("Started dragging");
});

wavesurfer.on("drag", (relativeX) => {
  const duration = wavesurfer.getDuration();
  const dragTime = relativeX * duration;
  document.getElementById("drag-time").textContent = formatTime(dragTime);
});

wavesurfer.on("dragend", (relativeX) => {
  document.body.classList.remove("dragging");
  document.getElementById("drag-time").textContent = "";
  console.log("Finished dragging");
});

Display and Rendering Events

Events related to waveform visualization and rendering updates.

interface WaveSurferEvents {
  /** Fired when the visible waveform is redrawn */
  redraw: [];
  
  /** Fired when all audio channel chunks have finished drawing */
  redrawcomplete: [];
  
  /** Fired when the waveform is scrolled/panned */
  scroll: [visibleStartTime: number, visibleEndTime: number, scrollLeft: number, scrollRight: number];
  
  /** Fired when the zoom level changes */
  zoom: [minPxPerSec: number];
}

Usage Examples:

// Rendering progress tracking
wavesurfer.on("redraw", () => {
  console.log("Waveform redrawing...");
  showRenderingIndicator();
});

wavesurfer.on("redrawcomplete", () => {
  console.log("Waveform rendering complete");
  hideRenderingIndicator();
});

// Scroll tracking for custom controls
wavesurfer.on("scroll", (startTime, endTime, scrollLeft, scrollRight) => {
  console.log(`Viewing: ${formatTime(startTime)} - ${formatTime(endTime)}`);
  updateScrollIndicator(scrollLeft, scrollRight);
});

// Zoom level updates
wavesurfer.on("zoom", (minPxPerSec) => {
  console.log(`Zoom level: ${minPxPerSec} pixels/second`);
  document.getElementById("zoom-level").textContent = `${minPxPerSec}px/s`;
  updateZoomControls(minPxPerSec);
});

function updateScrollIndicator(scrollLeft, scrollRight) {
  const totalWidth = wavesurfer.getWidth();
  const viewportStart = (scrollLeft / totalWidth) * 100;
  const viewportEnd = (scrollRight / totalWidth) * 100;
  
  document.getElementById("scroll-indicator").style.left = `${viewportStart}%`;
  document.getElementById("scroll-indicator").style.width = `${viewportEnd - viewportStart}%`;
}

Error Handling

Handle errors that occur during audio loading, decoding, or playback.

interface WaveSurferEvents {
  /** Fired when an error occurs during loading, decoding, or playback */
  error: [error: Error];
}

Usage Examples:

// Comprehensive error handling
wavesurfer.on("error", (error) => {
  console.error("WaveSurfer error:", error);
  
  // Handle different error types
  if (error.message.includes("fetch")) {
    showError("Failed to load audio file. Please check the URL and try again.");
  } else if (error.message.includes("decode")) {
    showError("Unable to decode audio file. The file may be corrupted or in an unsupported format.");
  } else if (error.message.includes("Media")) {
    showError("Media playback error. Please try refreshing the page.");
  } else {
    showError(`An error occurred: ${error.message}`);
  }
  
  // Reset UI state
  hideLoadingSpinner();
  document.getElementById("play-button").disabled = true;
});

function showError(message) {
  const errorDiv = document.getElementById("error-message");
  errorDiv.textContent = message;
  errorDiv.style.display = "block";
  
  // Auto-hide after 5 seconds
  setTimeout(() => {
    errorDiv.style.display = "none";
  }, 5000);
}

// Retry mechanism
let retryCount = 0;
const maxRetries = 3;

wavesurfer.on("error", async (error) => {
  if (retryCount < maxRetries) {
    retryCount++;
    console.log(`Retrying... (${retryCount}/${maxRetries})`);
    
    // Wait before retry
    await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
    
    try {
      await wavesurfer.load(originalUrl);
      retryCount = 0; // Reset on success
    } catch (retryError) {
      console.error(`Retry ${retryCount} failed:`, retryError);
    }
  } else {
    console.error("Max retries exceeded, giving up");
    showError("Failed to load audio after multiple attempts.");
  }
});

Event Chaining and Complex Workflows

Combine multiple events for complex application workflows.

// Events can be chained and combined for complex behaviors

Usage Examples:

// Audio loading workflow
let loadingStartTime;

wavesurfer.on("load", (url) => {
  loadingStartTime = Date.now();
  console.log(`Starting to load: ${url}`);
});

wavesurfer.on("decode", () => {
  const loadingTime = Date.now() - loadingStartTime;
  console.log(`Audio decoded in ${loadingTime}ms`);
});

wavesurfer.on("ready", (duration) => {
  const totalTime = Date.now() - loadingStartTime;
  console.log(`Audio ready in ${totalTime}ms, duration: ${duration}s`);
  
  // Auto-play if requested
  if (shouldAutoPlay) {
    wavesurfer.play();
  }
});

// Playback session tracking
let playbackSessions = [];
let sessionStart;

wavesurfer.on("play", () => {
  sessionStart = {
    startTime: wavesurfer.getCurrentTime(),
    timestamp: Date.now(),
  };
});

wavesurfer.on("pause", () => {
  if (sessionStart) {
    playbackSessions.push({
      ...sessionStart,
      endTime: wavesurfer.getCurrentTime(),
      duration: Date.now() - sessionStart.timestamp,
    });
  }
});

wavesurfer.on("finish", () => {
  console.log(`Total playback sessions: ${playbackSessions.length}`);
  const totalListeningTime = playbackSessions.reduce(
    (sum, session) => sum + (session.endTime - session.startTime), 0
  );
  console.log(`Total listening time: ${formatTime(totalListeningTime)}`);
});

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