or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

audio-processing.mdaudio-recording.mdcore-waveform-control.mdevent-system.mdindex.mdplugin-system.mdregions-plugin.mdtimeline-navigation.mdvisual-customization.md

plugin-system.mddocs/

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

```