0
# Plugin System
1
2
Extensible plugin architecture for adding custom functionality to WaveSurfer with plugin registration, lifecycle management, and built-in plugin utilities.
3
4
## Capabilities
5
6
### Plugin Registration and Management
7
8
Register, unregister, and manage plugins throughout the WaveSurfer lifecycle.
9
10
```typescript { .api }
11
interface WaveSurfer {
12
/**
13
* Register a wavesurfer.js plugin
14
* @param plugin - Plugin instance to register
15
* @returns The registered plugin instance
16
*/
17
registerPlugin<T extends GenericPlugin>(plugin: T): T;
18
19
/**
20
* Unregister a wavesurfer.js plugin
21
* @param plugin - Plugin instance to unregister
22
*/
23
unregisterPlugin(plugin: GenericPlugin): void;
24
25
/**
26
* Get all currently active plugins
27
* @returns Array of active plugin instances
28
*/
29
getActivePlugins(): GenericPlugin[];
30
}
31
32
type GenericPlugin = BasePlugin<BasePluginEvents, unknown>;
33
```
34
35
**Usage Examples:**
36
37
```typescript
38
import WaveSurfer from "wavesurfer.js";
39
import Regions from "wavesurfer.js/dist/plugins/regions.esm.js";
40
import Timeline from "wavesurfer.js/dist/plugins/timeline.esm.js";
41
42
// Register plugins during creation
43
const wavesurfer = WaveSurfer.create({
44
container: "#waveform",
45
plugins: [
46
Regions.create(),
47
Timeline.create({ height: 30 }),
48
],
49
});
50
51
// Register plugins after creation
52
const minimap = Minimap.create({ height: 60 });
53
wavesurfer.registerPlugin(minimap);
54
55
// Unregister specific plugin
56
wavesurfer.unregisterPlugin(minimap);
57
58
// Check active plugins
59
const activePlugins = wavesurfer.getActivePlugins();
60
console.log(`${activePlugins.length} plugins active`);
61
62
// Find specific plugin
63
const regionsPlugin = activePlugins.find(p => p.constructor.name === 'RegionsPlugin');
64
if (regionsPlugin) {
65
console.log("Regions plugin is active");
66
}
67
```
68
69
### BasePlugin Class
70
71
Base class for creating custom plugins with event handling and lifecycle management.
72
73
```typescript { .api }
74
/**
75
* Base class for wavesurfer plugins with event handling and lifecycle management
76
*/
77
class BasePlugin<EventTypes extends BasePluginEvents, Options> {
78
/**
79
* Create a plugin instance
80
* @param options - Plugin configuration options
81
*/
82
constructor(options: Options);
83
84
/**
85
* Destroy the plugin and unsubscribe from all events
86
* Automatically called when WaveSurfer is destroyed
87
*/
88
destroy(): void;
89
90
/** Reference to the WaveSurfer instance (available after registration) */
91
protected wavesurfer?: WaveSurfer;
92
93
/** Array of event unsubscribe functions for cleanup */
94
protected subscriptions: (() => void)[];
95
96
/** Plugin configuration options */
97
protected options: Options;
98
99
/** Called after plugin is registered and wavesurfer is available */
100
protected onInit(): void;
101
}
102
103
interface BasePluginEvents {
104
/** Fired when plugin is destroyed */
105
destroy: [];
106
}
107
```
108
109
**Usage Examples:**
110
111
```typescript
112
// Custom plugin example
113
class CustomAnalyzerPlugin extends BasePlugin {
114
constructor(options = {}) {
115
super(options);
116
}
117
118
// Called when plugin is registered
119
protected onInit() {
120
if (!this.wavesurfer) return;
121
122
// Subscribe to wavesurfer events
123
this.subscriptions.push(
124
this.wavesurfer.on("ready", () => {
125
this.analyzeAudio();
126
}),
127
128
this.wavesurfer.on("timeupdate", (time) => {
129
this.updateAnalysis(time);
130
})
131
);
132
}
133
134
private analyzeAudio() {
135
const audioBuffer = this.wavesurfer?.getDecodedData();
136
if (audioBuffer) {
137
console.log("Analyzing audio data...");
138
// Custom analysis logic
139
}
140
}
141
142
private updateAnalysis(time) {
143
// Real-time analysis updates
144
}
145
}
146
147
// Use custom plugin
148
const analyzer = new CustomAnalyzerPlugin({ threshold: 0.5 });
149
wavesurfer.registerPlugin(analyzer);
150
```
151
152
### Plugin Event System
153
154
Plugins can emit and listen to events for communication with the main application.
155
156
```typescript { .api }
157
interface BasePlugin<EventTypes, Options> {
158
/**
159
* Subscribe to plugin events
160
* @param event - Event name
161
* @param listener - Event callback
162
* @returns Unsubscribe function
163
*/
164
on<EventName extends keyof EventTypes>(
165
event: EventName,
166
listener: (...args: EventTypes[EventName]) => void
167
): () => void;
168
169
/**
170
* Subscribe to plugin event once
171
* @param event - Event name
172
* @param listener - Event callback
173
* @returns Unsubscribe function
174
*/
175
once<EventName extends keyof EventTypes>(
176
event: EventName,
177
listener: (...args: EventTypes[EventName]) => void
178
): () => void;
179
180
/**
181
* Emit plugin event (protected method for plugin use)
182
* @param eventName - Event name
183
* @param args - Event arguments
184
*/
185
protected emit<EventName extends keyof EventTypes>(
186
eventName: EventName,
187
...args: EventTypes[EventName]
188
): void;
189
}
190
```
191
192
**Usage Examples:**
193
194
```typescript
195
// Plugin with custom events
196
interface CustomPluginEvents extends BasePluginEvents {
197
"analysis-complete": [results: AnalysisResults];
198
"threshold-exceeded": [value: number, time: number];
199
}
200
201
class CustomAnalyzerPlugin extends BasePlugin<CustomPluginEvents, AnalyzerOptions> {
202
private analyzeAudio() {
203
// Perform analysis...
204
const results = { peak: 0.8, rms: 0.3 };
205
206
// Emit custom event
207
this.emit("analysis-complete", results);
208
209
if (results.peak > this.options.threshold) {
210
this.emit("threshold-exceeded", results.peak, this.wavesurfer.getCurrentTime());
211
}
212
}
213
}
214
215
// Listen to plugin events
216
const analyzer = new CustomAnalyzerPlugin({ threshold: 0.7 });
217
wavesurfer.registerPlugin(analyzer);
218
219
analyzer.on("analysis-complete", (results) => {
220
console.log("Analysis results:", results);
221
updateVisualization(results);
222
});
223
224
analyzer.on("threshold-exceeded", (value, time) => {
225
console.warn(`Threshold exceeded: ${value} at ${time}s`);
226
showWarning(`High volume detected at ${formatTime(time)}`);
227
});
228
```
229
230
### Plugin Options and Configuration
231
232
Configure plugins with type-safe options and runtime updates.
233
234
```typescript { .api }
235
// Plugin options are defined by each plugin implementation
236
interface PluginOptions {
237
[key: string]: any;
238
}
239
240
class BasePlugin<EventTypes, Options> {
241
/** Plugin configuration options */
242
protected options: Options;
243
}
244
```
245
246
**Usage Examples:**
247
248
```typescript
249
// Plugin with typed options
250
interface VisualizerOptions {
251
color?: string;
252
height?: number;
253
updateInterval?: number;
254
smoothing?: boolean;
255
}
256
257
class VisualizerPlugin extends BasePlugin<BasePluginEvents, VisualizerOptions> {
258
private canvas: HTMLCanvasElement;
259
private ctx: CanvasRenderingContext2D;
260
261
protected onInit() {
262
this.createCanvas();
263
this.startVisualization();
264
}
265
266
private createCanvas() {
267
this.canvas = document.createElement("canvas");
268
this.canvas.height = this.options.height || 100;
269
this.canvas.style.backgroundColor = this.options.color || "#000";
270
271
this.ctx = this.canvas.getContext("2d");
272
this.wavesurfer.getWrapper().appendChild(this.canvas);
273
}
274
275
// Method to update options after creation
276
updateOptions(newOptions: Partial<VisualizerOptions>) {
277
this.options = { ...this.options, ...newOptions };
278
279
// Apply updates
280
if (newOptions.color) {
281
this.canvas.style.backgroundColor = newOptions.color;
282
}
283
if (newOptions.height) {
284
this.canvas.height = newOptions.height;
285
}
286
}
287
}
288
289
// Create with options
290
const visualizer = new VisualizerPlugin({
291
color: "#1a1a1a",
292
height: 120,
293
updateInterval: 60,
294
smoothing: true,
295
});
296
297
// Update options later
298
visualizer.updateOptions({
299
color: "#2a2a2a",
300
smoothing: false,
301
});
302
```
303
304
### Plugin Utilities and Helper Methods
305
306
Access WaveSurfer utilities and DOM helpers for plugin development.
307
308
```typescript { .api }
309
interface WaveSurfer {
310
/** For plugins: get the waveform wrapper div */
311
getWrapper(): HTMLElement;
312
313
/** For plugins: get the scroll container client width */
314
getWidth(): number;
315
}
316
317
// Static utilities available to plugins
318
WaveSurfer.BasePlugin: typeof BasePlugin;
319
WaveSurfer.dom: {
320
createElement(tagName: string, content?: object, container?: Node): HTMLElement;
321
};
322
```
323
324
**Usage Examples:**
325
326
```typescript
327
class OverlayPlugin extends BasePlugin {
328
private overlay: HTMLElement;
329
330
protected onInit() {
331
this.createOverlay();
332
this.positionOverlay();
333
}
334
335
private createOverlay() {
336
// Use WaveSurfer DOM utilities
337
this.overlay = WaveSurfer.dom.createElement("div", {
338
style: {
339
position: "absolute",
340
top: "0",
341
left: "0",
342
pointerEvents: "none",
343
background: "rgba(255, 0, 0, 0.3)",
344
height: "100%",
345
width: "100px",
346
},
347
});
348
349
// Add to waveform wrapper
350
const wrapper = this.wavesurfer.getWrapper();
351
wrapper.appendChild(this.overlay);
352
}
353
354
private positionOverlay() {
355
const width = this.wavesurfer.getWidth();
356
357
// Position overlay relative to waveform
358
this.overlay.style.left = `${width * 0.25}px`;
359
this.overlay.style.width = `${width * 0.5}px`;
360
}
361
362
// Update on resize
363
private updateOverlay() {
364
this.positionOverlay();
365
}
366
}
367
368
// Plugin development utilities
369
class DeveloperPlugin extends BasePlugin {
370
protected onInit() {
371
// Access wrapper for DOM manipulation
372
const wrapper = this.wavesurfer.getWrapper();
373
console.log("Wrapper element:", wrapper);
374
375
// Monitor width changes
376
this.subscriptions.push(
377
this.wavesurfer.on("redraw", () => {
378
const width = this.wavesurfer.getWidth();
379
console.log(`Waveform width: ${width}px`);
380
})
381
);
382
}
383
}
384
```
385
386
### Plugin Communication and Integration
387
388
Enable plugins to communicate with each other and integrate with external systems.
389
390
```typescript { .api }
391
// Plugins can access other plugins through the WaveSurfer instance
392
interface WaveSurfer {
393
getActivePlugins(): GenericPlugin[];
394
}
395
```
396
397
**Usage Examples:**
398
399
```typescript
400
// Plugin that integrates with other plugins
401
class IntegratorPlugin extends BasePlugin {
402
private regionsPlugin: any;
403
private timelinePlugin: any;
404
405
protected onInit() {
406
// Find other plugins
407
const plugins = this.wavesurfer.getActivePlugins();
408
409
this.regionsPlugin = plugins.find(p =>
410
p.constructor.name === 'RegionsPlugin'
411
);
412
413
this.timelinePlugin = plugins.find(p =>
414
p.constructor.name === 'TimelinePlugin'
415
);
416
417
if (this.regionsPlugin) {
418
this.setupRegionIntegration();
419
}
420
}
421
422
private setupRegionIntegration() {
423
// Listen to region events
424
this.regionsPlugin.on("region-created", (region) => {
425
console.log(`New region: ${region.start}s - ${region.end}s`);
426
this.syncWithTimeline(region);
427
});
428
}
429
430
private syncWithTimeline(region) {
431
if (this.timelinePlugin) {
432
// Custom integration logic
433
console.log("Syncing region with timeline");
434
}
435
}
436
}
437
438
// Plugin factory pattern for configuration
439
class PluginFactory {
440
static createAnalyzerSuite(options = {}) {
441
return [
442
new SpectrumAnalyzer(options.spectrum),
443
new LevelMeter(options.levels),
444
new PeakDetector(options.peaks),
445
];
446
}
447
}
448
449
// Use plugin suite
450
const analyzerPlugins = PluginFactory.createAnalyzerSuite({
451
spectrum: { fftSize: 2048 },
452
levels: { updateRate: 30 },
453
peaks: { threshold: 0.8 },
454
});
455
456
analyzerPlugins.forEach(plugin => {
457
wavesurfer.registerPlugin(plugin);
458
});
459
```
460
461
### Plugin Lifecycle Management
462
463
Handle plugin initialization, cleanup, and state management.
464
465
```typescript { .api }
466
class BasePlugin<EventTypes, Options> {
467
/** Internal method called by WaveSurfer when plugin is registered */
468
_init(wavesurfer: WaveSurfer): void;
469
470
/** Override this method for plugin initialization logic */
471
protected onInit(): void;
472
473
/** Clean up plugin resources and event listeners */
474
destroy(): void;
475
}
476
```
477
478
**Usage Examples:**
479
480
```typescript
481
// Plugin with complex lifecycle
482
class ResourceIntensivePlugin extends BasePlugin {
483
private worker: Worker;
484
private animationId: number;
485
private canvas: HTMLCanvasElement;
486
487
protected onInit() {
488
this.initWorker();
489
this.initCanvas();
490
this.startAnimation();
491
492
// Clean up on wavesurfer destruction
493
this.subscriptions.push(
494
this.wavesurfer.on("destroy", () => {
495
this.cleanup();
496
})
497
);
498
}
499
500
private initWorker() {
501
this.worker = new Worker("/audio-processor-worker.js");
502
this.worker.onmessage = (event) => {
503
this.handleWorkerMessage(event.data);
504
};
505
}
506
507
private initCanvas() {
508
this.canvas = document.createElement("canvas");
509
this.wavesurfer.getWrapper().appendChild(this.canvas);
510
}
511
512
private startAnimation() {
513
const animate = () => {
514
this.render();
515
this.animationId = requestAnimationFrame(animate);
516
};
517
animate();
518
}
519
520
private cleanup() {
521
// Cancel animation
522
if (this.animationId) {
523
cancelAnimationFrame(this.animationId);
524
}
525
526
// Terminate worker
527
if (this.worker) {
528
this.worker.terminate();
529
}
530
531
// Remove canvas
532
if (this.canvas && this.canvas.parentNode) {
533
this.canvas.parentNode.removeChild(this.canvas);
534
}
535
}
536
537
// Override destroy to include custom cleanup
538
destroy() {
539
this.cleanup();
540
super.destroy(); // Call parent cleanup
541
}
542
}
543
544
// Plugin state management
545
class StatefulPlugin extends BasePlugin {
546
private state = {
547
initialized: false,
548
active: false,
549
data: null,
550
};
551
552
protected onInit() {
553
this.state.initialized = true;
554
555
this.subscriptions.push(
556
this.wavesurfer.on("play", () => {
557
this.state.active = true;
558
this.onActivate();
559
}),
560
561
this.wavesurfer.on("pause", () => {
562
this.state.active = false;
563
this.onDeactivate();
564
})
565
);
566
}
567
568
private onActivate() {
569
console.log("Plugin activated");
570
// Start processing
571
}
572
573
private onDeactivate() {
574
console.log("Plugin deactivated");
575
// Pause processing
576
}
577
578
// Public method to check state
579
isActive() {
580
return this.state.active;
581
}
582
}
583
```