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

plugin-system.mddocs/

Plugin System

Extensible plugin architecture for adding custom functionality to WaveSurfer with plugin registration, lifecycle management, and built-in plugin utilities.

Capabilities

Plugin Registration and Management

Register, unregister, and manage plugins throughout the WaveSurfer lifecycle.

interface WaveSurfer {
  /**
   * Register a wavesurfer.js plugin
   * @param plugin - Plugin instance to register
   * @returns The registered plugin instance
   */
  registerPlugin<T extends GenericPlugin>(plugin: T): T;
  
  /**
   * Unregister a wavesurfer.js plugin
   * @param plugin - Plugin instance to unregister
   */
  unregisterPlugin(plugin: GenericPlugin): void;
  
  /**
   * Get all currently active plugins
   * @returns Array of active plugin instances
   */
  getActivePlugins(): GenericPlugin[];
}

type GenericPlugin = BasePlugin<BasePluginEvents, unknown>;

Usage Examples:

import WaveSurfer from "wavesurfer.js";
import Regions from "wavesurfer.js/dist/plugins/regions.esm.js";
import Timeline from "wavesurfer.js/dist/plugins/timeline.esm.js";

// Register plugins during creation
const wavesurfer = WaveSurfer.create({
  container: "#waveform",
  plugins: [
    Regions.create(),
    Timeline.create({ height: 30 }),
  ],
});

// Register plugins after creation
const minimap = Minimap.create({ height: 60 });
wavesurfer.registerPlugin(minimap);

// Unregister specific plugin
wavesurfer.unregisterPlugin(minimap);

// Check active plugins
const activePlugins = wavesurfer.getActivePlugins();
console.log(`${activePlugins.length} plugins active`);

// Find specific plugin
const regionsPlugin = activePlugins.find(p => p.constructor.name === 'RegionsPlugin');
if (regionsPlugin) {
  console.log("Regions plugin is active");
}

BasePlugin Class

Base class for creating custom plugins with event handling and lifecycle management.

/**
 * Base class for wavesurfer plugins with event handling and lifecycle management
 */
class BasePlugin<EventTypes extends BasePluginEvents, Options> {
  /**
   * Create a plugin instance
   * @param options - Plugin configuration options
   */
  constructor(options: Options);
  
  /**
   * Destroy the plugin and unsubscribe from all events
   * Automatically called when WaveSurfer is destroyed
   */
  destroy(): void;
  
  /** Reference to the WaveSurfer instance (available after registration) */
  protected wavesurfer?: WaveSurfer;
  
  /** Array of event unsubscribe functions for cleanup */
  protected subscriptions: (() => void)[];
  
  /** Plugin configuration options */
  protected options: Options;
  
  /** Called after plugin is registered and wavesurfer is available */
  protected onInit(): void;
}

interface BasePluginEvents {
  /** Fired when plugin is destroyed */
  destroy: [];
}

Usage Examples:

// Custom plugin example
class CustomAnalyzerPlugin extends BasePlugin {
  constructor(options = {}) {
    super(options);
  }
  
  // Called when plugin is registered
  protected onInit() {
    if (!this.wavesurfer) return;
    
    // Subscribe to wavesurfer events
    this.subscriptions.push(
      this.wavesurfer.on("ready", () => {
        this.analyzeAudio();
      }),
      
      this.wavesurfer.on("timeupdate", (time) => {
        this.updateAnalysis(time);
      })
    );
  }
  
  private analyzeAudio() {
    const audioBuffer = this.wavesurfer?.getDecodedData();
    if (audioBuffer) {
      console.log("Analyzing audio data...");
      // Custom analysis logic
    }
  }
  
  private updateAnalysis(time) {
    // Real-time analysis updates
  }
}

// Use custom plugin
const analyzer = new CustomAnalyzerPlugin({ threshold: 0.5 });
wavesurfer.registerPlugin(analyzer);

Plugin Event System

Plugins can emit and listen to events for communication with the main application.

interface BasePlugin<EventTypes, Options> {
  /**
   * Subscribe to plugin events
   * @param event - Event name
   * @param listener - Event callback
   * @returns Unsubscribe function
   */
  on<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventTypes[EventName]) => void
  ): () => void;
  
  /**
   * Subscribe to plugin event once
   * @param event - Event name  
   * @param listener - Event callback
   * @returns Unsubscribe function
   */
  once<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventTypes[EventName]) => void
  ): () => void;
  
  /**
   * Emit plugin event (protected method for plugin use)
   * @param eventName - Event name
   * @param args - Event arguments
   */
  protected emit<EventName extends keyof EventTypes>(
    eventName: EventName,
    ...args: EventTypes[EventName]
  ): void;
}

Usage Examples:

// Plugin with custom events
interface CustomPluginEvents extends BasePluginEvents {
  "analysis-complete": [results: AnalysisResults];
  "threshold-exceeded": [value: number, time: number];
}

class CustomAnalyzerPlugin extends BasePlugin<CustomPluginEvents, AnalyzerOptions> {
  private analyzeAudio() {
    // Perform analysis...
    const results = { peak: 0.8, rms: 0.3 };
    
    // Emit custom event
    this.emit("analysis-complete", results);
    
    if (results.peak > this.options.threshold) {
      this.emit("threshold-exceeded", results.peak, this.wavesurfer.getCurrentTime());
    }
  }
}

// Listen to plugin events
const analyzer = new CustomAnalyzerPlugin({ threshold: 0.7 });
wavesurfer.registerPlugin(analyzer);

analyzer.on("analysis-complete", (results) => {
  console.log("Analysis results:", results);
  updateVisualization(results);
});

analyzer.on("threshold-exceeded", (value, time) => {
  console.warn(`Threshold exceeded: ${value} at ${time}s`);
  showWarning(`High volume detected at ${formatTime(time)}`);
});

Plugin Options and Configuration

Configure plugins with type-safe options and runtime updates.

// Plugin options are defined by each plugin implementation
interface PluginOptions {
  [key: string]: any;
}

class BasePlugin<EventTypes, Options> {
  /** Plugin configuration options */
  protected options: Options;
}

Usage Examples:

// Plugin with typed options
interface VisualizerOptions {
  color?: string;
  height?: number;
  updateInterval?: number;
  smoothing?: boolean;
}

class VisualizerPlugin extends BasePlugin<BasePluginEvents, VisualizerOptions> {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  
  protected onInit() {
    this.createCanvas();
    this.startVisualization();
  }
  
  private createCanvas() {
    this.canvas = document.createElement("canvas");
    this.canvas.height = this.options.height || 100;
    this.canvas.style.backgroundColor = this.options.color || "#000";
    
    this.ctx = this.canvas.getContext("2d");
    this.wavesurfer.getWrapper().appendChild(this.canvas);
  }
  
  // Method to update options after creation
  updateOptions(newOptions: Partial<VisualizerOptions>) {
    this.options = { ...this.options, ...newOptions };
    
    // Apply updates
    if (newOptions.color) {
      this.canvas.style.backgroundColor = newOptions.color;
    }
    if (newOptions.height) {
      this.canvas.height = newOptions.height;
    }
  }
}

// Create with options
const visualizer = new VisualizerPlugin({
  color: "#1a1a1a",
  height: 120,
  updateInterval: 60,
  smoothing: true,
});

// Update options later
visualizer.updateOptions({
  color: "#2a2a2a",
  smoothing: false,
});

Plugin Utilities and Helper Methods

Access WaveSurfer utilities and DOM helpers for plugin development.

interface WaveSurfer {
  /** For plugins: get the waveform wrapper div */
  getWrapper(): HTMLElement;
  
  /** For plugins: get the scroll container client width */
  getWidth(): number;
}

// Static utilities available to plugins
WaveSurfer.BasePlugin: typeof BasePlugin;
WaveSurfer.dom: {
  createElement(tagName: string, content?: object, container?: Node): HTMLElement;
};

Usage Examples:

class OverlayPlugin extends BasePlugin {
  private overlay: HTMLElement;
  
  protected onInit() {
    this.createOverlay();
    this.positionOverlay();
  }
  
  private createOverlay() {
    // Use WaveSurfer DOM utilities
    this.overlay = WaveSurfer.dom.createElement("div", {
      style: {
        position: "absolute",
        top: "0",
        left: "0",
        pointerEvents: "none",
        background: "rgba(255, 0, 0, 0.3)",
        height: "100%",
        width: "100px",
      },
    });
    
    // Add to waveform wrapper
    const wrapper = this.wavesurfer.getWrapper();
    wrapper.appendChild(this.overlay);
  }
  
  private positionOverlay() {
    const width = this.wavesurfer.getWidth();
    
    // Position overlay relative to waveform
    this.overlay.style.left = `${width * 0.25}px`;
    this.overlay.style.width = `${width * 0.5}px`;
  }
  
  // Update on resize
  private updateOverlay() {
    this.positionOverlay();
  }
}

// Plugin development utilities
class DeveloperPlugin extends BasePlugin {
  protected onInit() {
    // Access wrapper for DOM manipulation
    const wrapper = this.wavesurfer.getWrapper();
    console.log("Wrapper element:", wrapper);
    
    // Monitor width changes
    this.subscriptions.push(
      this.wavesurfer.on("redraw", () => {
        const width = this.wavesurfer.getWidth();
        console.log(`Waveform width: ${width}px`);
      })
    );
  }
}

Plugin Communication and Integration

Enable plugins to communicate with each other and integrate with external systems.

// Plugins can access other plugins through the WaveSurfer instance
interface WaveSurfer {
  getActivePlugins(): GenericPlugin[];
}

Usage Examples:

// Plugin that integrates with other plugins
class IntegratorPlugin extends BasePlugin {
  private regionsPlugin: any;
  private timelinePlugin: any;
  
  protected onInit() {
    // Find other plugins
    const plugins = this.wavesurfer.getActivePlugins();
    
    this.regionsPlugin = plugins.find(p => 
      p.constructor.name === 'RegionsPlugin'
    );
    
    this.timelinePlugin = plugins.find(p => 
      p.constructor.name === 'TimelinePlugin'
    );
    
    if (this.regionsPlugin) {
      this.setupRegionIntegration();
    }
  }
  
  private setupRegionIntegration() {
    // Listen to region events
    this.regionsPlugin.on("region-created", (region) => {
      console.log(`New region: ${region.start}s - ${region.end}s`);
      this.syncWithTimeline(region);
    });
  }
  
  private syncWithTimeline(region) {
    if (this.timelinePlugin) {
      // Custom integration logic
      console.log("Syncing region with timeline");
    }
  }
}

// Plugin factory pattern for configuration
class PluginFactory {
  static createAnalyzerSuite(options = {}) {
    return [
      new SpectrumAnalyzer(options.spectrum),
      new LevelMeter(options.levels),
      new PeakDetector(options.peaks),
    ];
  }
}

// Use plugin suite
const analyzerPlugins = PluginFactory.createAnalyzerSuite({
  spectrum: { fftSize: 2048 },
  levels: { updateRate: 30 },
  peaks: { threshold: 0.8 },
});

analyzerPlugins.forEach(plugin => {
  wavesurfer.registerPlugin(plugin);
});

Plugin Lifecycle Management

Handle plugin initialization, cleanup, and state management.

class BasePlugin<EventTypes, Options> {
  /** Internal method called by WaveSurfer when plugin is registered */
  _init(wavesurfer: WaveSurfer): void;
  
  /** Override this method for plugin initialization logic */
  protected onInit(): void;
  
  /** Clean up plugin resources and event listeners */
  destroy(): void;
}

Usage Examples:

// Plugin with complex lifecycle
class ResourceIntensivePlugin extends BasePlugin {
  private worker: Worker;
  private animationId: number;
  private canvas: HTMLCanvasElement;
  
  protected onInit() {
    this.initWorker();
    this.initCanvas();
    this.startAnimation();
    
    // Clean up on wavesurfer destruction
    this.subscriptions.push(
      this.wavesurfer.on("destroy", () => {
        this.cleanup();
      })
    );
  }
  
  private initWorker() {
    this.worker = new Worker("/audio-processor-worker.js");
    this.worker.onmessage = (event) => {
      this.handleWorkerMessage(event.data);
    };
  }
  
  private initCanvas() {
    this.canvas = document.createElement("canvas");
    this.wavesurfer.getWrapper().appendChild(this.canvas);
  }
  
  private startAnimation() {
    const animate = () => {
      this.render();
      this.animationId = requestAnimationFrame(animate);
    };
    animate();
  }
  
  private cleanup() {
    // Cancel animation
    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
    }
    
    // Terminate worker
    if (this.worker) {
      this.worker.terminate();
    }
    
    // Remove canvas
    if (this.canvas && this.canvas.parentNode) {
      this.canvas.parentNode.removeChild(this.canvas);
    }
  }
  
  // Override destroy to include custom cleanup
  destroy() {
    this.cleanup();
    super.destroy(); // Call parent cleanup
  }
}

// Plugin state management
class StatefulPlugin extends BasePlugin {
  private state = {
    initialized: false,
    active: false,
    data: null,
  };
  
  protected onInit() {
    this.state.initialized = true;
    
    this.subscriptions.push(
      this.wavesurfer.on("play", () => {
        this.state.active = true;
        this.onActivate();
      }),
      
      this.wavesurfer.on("pause", () => {
        this.state.active = false;
        this.onDeactivate();
      })
    );
  }
  
  private onActivate() {
    console.log("Plugin activated");
    // Start processing
  }
  
  private onDeactivate() {
    console.log("Plugin deactivated");
    // Pause processing
  }
  
  // Public method to check state
  isActive() {
    return this.state.active;
  }
}

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