wavesurfer.js 7.0.0-beta.1 → 7.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +81 -36
  3. package/dist/decoder.d.ts +1 -1
  4. package/dist/draggable.d.ts +1 -0
  5. package/dist/draggable.js +59 -0
  6. package/dist/fetcher.d.ts +2 -0
  7. package/dist/fetcher.js +4 -0
  8. package/dist/player.d.ts +1 -1
  9. package/dist/player.js +2 -2
  10. package/dist/plugins/envelope.js +4 -25
  11. package/dist/plugins/envelope.min.cjs +1 -0
  12. package/dist/plugins/minimap.js +0 -3
  13. package/dist/plugins/minimap.min.cjs +1 -0
  14. package/dist/plugins/record.d.ts +3 -1
  15. package/dist/plugins/record.js +11 -5
  16. package/dist/plugins/record.min.cjs +1 -0
  17. package/dist/plugins/regions.d.ts +1 -0
  18. package/dist/plugins/regions.js +17 -52
  19. package/dist/plugins/regions.min.cjs +1 -0
  20. package/dist/plugins/spectrogram-fft.d.ts +9 -0
  21. package/dist/plugins/spectrogram-fft.js +150 -0
  22. package/dist/plugins/spectrogram.d.ts +3 -0
  23. package/dist/plugins/spectrogram.js +3 -2
  24. package/dist/plugins/{spectrogram.min.js → spectrogram.min.cjs} +1 -1
  25. package/dist/plugins/timeline.min.cjs +1 -0
  26. package/dist/renderer.d.ts +11 -21
  27. package/dist/renderer.js +213 -136
  28. package/dist/wavesurfer.d.ts +17 -14
  29. package/dist/wavesurfer.js +54 -65
  30. package/dist/wavesurfer.min.cjs +1 -0
  31. package/package.json +17 -11
  32. package/dist/plugins/envelope.min.js +0 -1
  33. package/dist/plugins/minimap.min.js +0 -1
  34. package/dist/plugins/multitrack.d.ts +0 -117
  35. package/dist/plugins/multitrack.js +0 -507
  36. package/dist/plugins/multitrack.min.js +0 -1
  37. package/dist/plugins/record.min.js +0 -1
  38. package/dist/plugins/regions.min.js +0 -1
  39. package/dist/plugins/timeline.min.js +0 -1
  40. package/dist/wavesurfer.min.js +0 -1
package/dist/renderer.js CHANGED
@@ -1,24 +1,27 @@
1
+ import { makeDraggable } from './draggable.js';
1
2
  import EventEmitter from './event-emitter.js';
2
3
  class Renderer extends EventEmitter {
3
- constructor(container, options) {
4
+ constructor(options) {
4
5
  super();
5
- this.options = {
6
- height: 0,
7
- };
8
- this.timeout = null;
6
+ this.timeouts = [];
9
7
  this.isScrolling = false;
10
8
  this.audioData = null;
11
9
  this.resizeObserver = null;
12
10
  this.isDragging = false;
13
- this.options = { ...options };
14
- if (typeof container === 'string') {
15
- container = document.querySelector(container);
11
+ this.options = options;
12
+ let parent;
13
+ if (typeof options.container === 'string') {
14
+ parent = document.querySelector(options.container);
15
+ }
16
+ else if (options.container instanceof HTMLElement) {
17
+ parent = options.container;
16
18
  }
17
- if (!container) {
19
+ if (!parent) {
18
20
  throw new Error('Container not found');
19
21
  }
22
+ this.parent = parent;
20
23
  const [div, shadow] = this.initHtml();
21
- container.appendChild(div);
24
+ parent.appendChild(div);
22
25
  this.container = div;
23
26
  this.scrollContainer = shadow.querySelector('.scroll');
24
27
  this.wrapper = shadow.querySelector('.wrapper');
@@ -45,31 +48,32 @@ class Renderer extends EventEmitter {
45
48
  this.emit('scroll', startX, endX);
46
49
  });
47
50
  // Re-render the waveform on container resize
51
+ const delay = this.createDelay(100);
48
52
  this.resizeObserver = new ResizeObserver(() => {
49
- this.delay(() => this.reRender(), 100);
53
+ delay(() => this.reRender());
50
54
  });
51
55
  this.resizeObserver.observe(this.scrollContainer);
52
56
  }
53
57
  initDrag() {
54
- this.wrapper.addEventListener('mousedown', (e) => {
55
- const minDx = 5;
56
- const x = e.clientX;
57
- const move = (e) => {
58
- const diff = Math.abs(e.clientX - x);
59
- if (diff >= minDx) {
60
- this.isDragging = true;
61
- const rect = this.wrapper.getBoundingClientRect();
62
- this.emit('drag', Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)));
63
- }
64
- };
65
- const up = () => {
66
- document.removeEventListener('mousemove', move);
67
- document.removeEventListener('mouseup', up);
68
- this.isDragging = false;
69
- };
70
- document.addEventListener('mousemove', move);
71
- document.addEventListener('mouseup', up);
72
- });
58
+ makeDraggable(this.wrapper,
59
+ // On drag
60
+ (_, __, x) => {
61
+ this.emit('drag', Math.max(0, Math.min(1, x / this.wrapper.clientWidth)));
62
+ },
63
+ // On start drag
64
+ () => (this.isDragging = true),
65
+ // On end drag
66
+ () => (this.isDragging = false));
67
+ }
68
+ getHeight() {
69
+ const defaultHeight = 128;
70
+ if (this.options.height == null)
71
+ return defaultHeight;
72
+ if (!isNaN(Number(this.options.height)))
73
+ return Number(this.options.height);
74
+ if (this.options.height === 'auto')
75
+ return this.parent.clientHeight || defaultHeight;
76
+ return defaultHeight;
73
77
  }
74
78
  initHtml() {
75
79
  const div = document.createElement('div');
@@ -99,15 +103,16 @@ class Renderer extends EventEmitter {
99
103
  z-index: 2;
100
104
  }
101
105
  :host .canvases {
106
+ min-height: ${this.getHeight()}px;
107
+ }
108
+ :host .canvases > div {
102
109
  position: relative;
103
- height: ${this.options.height}px;
104
110
  }
105
111
  :host canvas {
106
112
  display: block;
107
113
  position: absolute;
108
114
  top: 0;
109
115
  image-rendering: pixelated;
110
- height: ${this.options.height}px;
111
116
  }
112
117
  :host .progress {
113
118
  pointer-events: none;
@@ -119,23 +124,24 @@ class Renderer extends EventEmitter {
119
124
  height: 100%;
120
125
  overflow: hidden;
121
126
  }
127
+ :host .progress > div {
128
+ position: relative;
129
+ }
122
130
  :host .cursor {
123
131
  pointer-events: none;
124
132
  position: absolute;
125
- z-index: 2;
133
+ z-index: 5;
126
134
  top: 0;
127
135
  left: 0;
128
136
  height: 100%;
129
137
  border-radius: 2px;
130
- width: ${this.options.cursorWidth}px;
131
- background-color: ${this.options.cursorColor || this.options.progressColor};
132
138
  }
133
139
  </style>
134
140
 
135
141
  <div class="scroll" part="scroll">
136
142
  <div class="wrapper">
137
143
  <div class="canvases"></div>
138
- <div class="progress"></div>
144
+ <div class="progress" part="progress"></div>
139
145
  <div class="cursor" part="cursor"></div>
140
146
  </div>
141
147
  </div>
@@ -157,116 +163,173 @@ class Renderer extends EventEmitter {
157
163
  this.container.remove();
158
164
  this.resizeObserver?.disconnect();
159
165
  }
160
- delay(fn, delayMs = 10) {
161
- if (this.timeout) {
162
- clearTimeout(this.timeout);
163
- }
164
- return new Promise((resolve) => {
165
- this.timeout = setTimeout(() => {
166
- resolve(fn());
167
- }, delayMs);
166
+ createDelay(delayMs = 10) {
167
+ const context = {};
168
+ this.timeouts.push(context);
169
+ return (callback) => {
170
+ context.timeout && clearTimeout(context.timeout);
171
+ context.timeout = setTimeout(callback, delayMs);
172
+ };
173
+ }
174
+ // Convert array of color values to linear gradient
175
+ convertColorValues(color) {
176
+ if (!Array.isArray(color))
177
+ return color || '';
178
+ if (color.length < 2)
179
+ return color[0] || '';
180
+ const canvasElement = document.createElement('canvas');
181
+ const ctx = canvasElement.getContext('2d');
182
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvasElement.height);
183
+ const colorStopPercentage = 1 / (color.length - 1);
184
+ color.forEach((color, index) => {
185
+ const offset = index * colorStopPercentage;
186
+ gradient.addColorStop(offset, color);
168
187
  });
188
+ return gradient;
169
189
  }
170
- async renderPeaks(audioData, width, height, pixelRatio) {
171
- const barWidth = this.options.barWidth != null && !isNaN(this.options.barWidth) ? this.options.barWidth * pixelRatio : 1;
172
- const barGap = this.options.barGap != null && !isNaN(this.options.barGap)
173
- ? this.options.barGap * pixelRatio
174
- : this.options.barWidth
175
- ? barWidth / 2
176
- : 0;
177
- const barRadius = this.options.barRadius || 0;
178
- const scaleY = this.options.barHeight || 1;
179
- const leftChannel = audioData.getChannelData(0);
180
- const len = leftChannel.length;
181
- const barCount = Math.floor(width / (barWidth + barGap));
182
- const barIndexScale = barCount / len;
190
+ renderBars(channelData, options, ctx) {
191
+ ctx.fillStyle = this.convertColorValues(options.waveColor);
192
+ // Custom rendering function
193
+ if (options.renderFunction) {
194
+ options.renderFunction(channelData, ctx);
195
+ return;
196
+ }
197
+ const topChannel = channelData[0];
198
+ const bottomChannel = channelData[1] || channelData[0];
199
+ const length = topChannel.length;
200
+ const pixelRatio = window.devicePixelRatio || 1;
201
+ const { width, height } = ctx.canvas;
183
202
  const halfHeight = height / 2;
184
- const isMono = audioData.numberOfChannels === 1;
185
- const rightChannel = isMono ? leftChannel : audioData.getChannelData(1);
186
- const useNegative = isMono && rightChannel.some((v) => v < 0);
187
- const draw = (start, end) => {
188
- let prevX = 0;
189
- let prevLeft = 0;
190
- let prevRight = 0;
191
- const canvas = document.createElement('canvas');
192
- canvas.width = Math.round((width * (end - start)) / len);
193
- canvas.height = this.options.height;
194
- canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
195
- canvas.style.height = `${this.options.height}px`;
196
- canvas.style.left = `${Math.floor((start * width) / pixelRatio / len)}px`;
197
- this.canvasWrapper.appendChild(canvas);
198
- const ctx = canvas.getContext('2d', {
199
- desynchronized: true,
200
- });
201
- ctx.beginPath();
202
- ctx.fillStyle = this.options.waveColor ?? '';
203
- // Firefox shim until 2023.04.11
204
- if (!ctx.roundRect)
205
- ctx.roundRect = ctx.fillRect;
206
- for (let i = start; i < end; i++) {
207
- const barIndex = Math.round((i - start) * barIndexScale);
208
- if (barIndex > prevX) {
209
- const leftBarHeight = Math.round(prevLeft * halfHeight * scaleY);
210
- const rightBarHeight = Math.round(prevRight * halfHeight * scaleY);
211
- ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + (rightBarHeight || 1), barRadius);
212
- prevX = barIndex;
213
- prevLeft = 0;
214
- prevRight = 0;
215
- }
216
- const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
217
- const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
218
- if (leftValue > prevLeft) {
219
- prevLeft = leftValue;
220
- }
221
- // If stereo, both channels are drawn as max values
222
- // If mono with negative values, the bottom channel will be the min negative values
223
- if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
224
- prevRight = rightValue < 0 ? -rightValue : rightValue;
225
- }
203
+ const barHeight = options.barHeight || 1;
204
+ const barWidth = options.barWidth ? options.barWidth * pixelRatio : 1;
205
+ const barGap = options.barGap ? options.barGap * pixelRatio : options.barWidth ? barWidth / 2 : 0;
206
+ const barRadius = options.barRadius || 0;
207
+ const barIndexScale = width / (barWidth + barGap) / length;
208
+ let max = 1;
209
+ if (options.normalize) {
210
+ max = 0;
211
+ for (let i = 0; i < length; i++) {
212
+ const value = Math.abs(topChannel[i]);
213
+ if (value > max)
214
+ max = value;
226
215
  }
227
- ctx.fill();
228
- ctx.closePath();
229
- // Draw a progress canvas
230
- const progressCanvas = canvas.cloneNode();
231
- this.progressWrapper.appendChild(progressCanvas);
232
- const progressCtx = progressCanvas.getContext('2d', {
233
- desynchronized: true,
234
- });
235
- if (canvas.width > 0 && canvas.height > 0) {
236
- progressCtx.drawImage(canvas, 0, 0);
216
+ }
217
+ const vScale = (halfHeight / max) * barHeight;
218
+ ctx.beginPath();
219
+ let prevX = 0;
220
+ let maxTop = 0;
221
+ let maxBottom = 0;
222
+ for (let i = 0; i <= length; i++) {
223
+ const x = Math.round(i * barIndexScale);
224
+ if (x > prevX) {
225
+ const leftBarHeight = Math.round(maxTop * vScale);
226
+ const rightBarHeight = Math.round(maxBottom * vScale);
227
+ const barHeight = leftBarHeight + rightBarHeight || 1;
228
+ // Vertical alignment
229
+ let y = halfHeight - leftBarHeight;
230
+ if (options.barAlign === 'top')
231
+ y = 0;
232
+ else if (options.barAlign === 'bottom')
233
+ y = height - barHeight;
234
+ ctx.roundRect(prevX * (barWidth + barGap), y, barWidth, barHeight, barRadius);
235
+ prevX = x;
236
+ maxTop = 0;
237
+ maxBottom = 0;
237
238
  }
238
- // Set the composition method to draw only where the waveform is drawn
239
- progressCtx.globalCompositeOperation = 'source-in';
240
- progressCtx.fillStyle = this.options.progressColor ?? '';
241
- // This rectangle acts as a mask thanks to the composition method
242
- progressCtx.fillRect(0, 0, canvas.width, canvas.height);
243
- };
244
- // Clear the canvas
245
- this.canvasWrapper.innerHTML = '';
246
- this.progressWrapper.innerHTML = '';
239
+ const magnitudeTop = Math.abs(topChannel[i] || 0);
240
+ const magnitudeBottom = Math.abs(bottomChannel[i] || 0);
241
+ if (magnitudeTop > maxTop)
242
+ maxTop = magnitudeTop;
243
+ if (magnitudeBottom > maxBottom)
244
+ maxBottom = magnitudeBottom;
245
+ }
246
+ ctx.fill();
247
+ ctx.closePath();
248
+ }
249
+ renderSingleCanvas(channelData, options, width, height, start, end, canvasContainer, progressContainer) {
250
+ const pixelRatio = window.devicePixelRatio || 1;
251
+ const canvas = document.createElement('canvas');
252
+ const length = channelData[0].length;
253
+ canvas.width = Math.round((width * (end - start)) / length);
254
+ canvas.height = height * pixelRatio;
255
+ canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
256
+ canvas.style.height = `${height}px`;
257
+ canvas.style.left = `${Math.floor((start * width) / pixelRatio / length)}px`;
258
+ canvasContainer.appendChild(canvas);
259
+ const ctx = canvas.getContext('2d');
260
+ this.renderBars(channelData.map((channel) => channel.slice(start, end)), options, ctx);
261
+ // Draw a progress canvas
262
+ const progressCanvas = canvas.cloneNode();
263
+ progressContainer.appendChild(progressCanvas);
264
+ const progressCtx = progressCanvas.getContext('2d');
265
+ if (canvas.width > 0 && canvas.height > 0) {
266
+ progressCtx.drawImage(canvas, 0, 0);
267
+ }
268
+ // Set the composition method to draw only where the waveform is drawn
269
+ progressCtx.globalCompositeOperation = 'source-in';
270
+ progressCtx.fillStyle = this.convertColorValues(options.progressColor);
271
+ // This rectangle acts as a mask thanks to the composition method
272
+ progressCtx.fillRect(0, 0, canvas.width, canvas.height);
273
+ }
274
+ renderWaveform(channelData, options, width) {
275
+ // A container for canvases
276
+ const canvasContainer = document.createElement('div');
277
+ const height = this.getHeight();
278
+ canvasContainer.style.height = `${height}px`;
279
+ this.canvasWrapper.style.minHeight = `${height}px`;
280
+ this.canvasWrapper.appendChild(canvasContainer);
281
+ // A container for progress canvases
282
+ const progressContainer = canvasContainer.cloneNode();
283
+ this.progressWrapper.appendChild(progressContainer);
247
284
  // Determine the currently visible part of the waveform
248
285
  const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
286
+ const len = channelData[0].length;
249
287
  const scale = len / scrollWidth;
250
288
  let viewportWidth = Math.min(Renderer.MAX_CANVAS_WIDTH, clientWidth);
251
- viewportWidth -= viewportWidth % ((barWidth + barGap) / pixelRatio);
252
- const start = Math.floor(Math.abs(scrollLeft) * scale);
253
- const end = Math.ceil(start + viewportWidth * scale);
254
- // Draw the visible portion of the waveform
255
- draw(start, end);
256
- // Draw the rest of the waveform with a timeout for better performance
257
- const step = end - start;
258
- for (let i = end; i < len; i += step) {
259
- await this.delay(() => {
260
- draw(i, Math.min(len, i + step));
261
- });
289
+ // Adjust width to avoid gaps between canvases when using bars
290
+ if (options.barWidth || options.barGap) {
291
+ const barWidth = options.barWidth || 0.5;
292
+ const barGap = options.barGap || barWidth / 2;
293
+ const totalBarWidth = barWidth + barGap;
294
+ if (viewportWidth % totalBarWidth !== 0) {
295
+ viewportWidth = Math.floor(viewportWidth / totalBarWidth) * totalBarWidth;
296
+ }
262
297
  }
263
- for (let i = start - 1; i >= 0; i -= step) {
264
- await this.delay(() => {
265
- draw(Math.max(0, i - step), i);
266
- });
298
+ const start = Math.floor(Math.abs(scrollLeft) * scale);
299
+ const end = Math.floor(start + viewportWidth * scale);
300
+ const viewportLen = end - start;
301
+ // Draw a portion of the waveform from start peak to end peak
302
+ const draw = (start, end) => {
303
+ this.renderSingleCanvas(channelData, options, width, height, Math.max(0, start), Math.min(end, len), canvasContainer, progressContainer);
304
+ };
305
+ // Draw the waveform in viewport chunks, each with a delay
306
+ const headDelay = this.createDelay();
307
+ const tailDelay = this.createDelay();
308
+ const renderHead = (fromIndex, toIndex) => {
309
+ draw(fromIndex, toIndex);
310
+ if (fromIndex > 0) {
311
+ headDelay(() => {
312
+ renderHead(fromIndex - viewportLen, toIndex - viewportLen);
313
+ });
314
+ }
315
+ };
316
+ const renderTail = (fromIndex, toIndex) => {
317
+ draw(fromIndex, toIndex);
318
+ if (toIndex < len) {
319
+ tailDelay(() => {
320
+ renderTail(fromIndex + viewportLen, toIndex + viewportLen);
321
+ });
322
+ }
323
+ };
324
+ renderHead(start, end);
325
+ if (end < len) {
326
+ renderTail(end, end + viewportLen);
267
327
  }
268
328
  }
269
329
  render(audioData) {
330
+ // Clear previous timeouts
331
+ this.timeouts.forEach((context) => context.timeout && clearTimeout(context.timeout));
332
+ this.timeouts = [];
270
333
  // Determine the width of the waveform
271
334
  const pixelRatio = window.devicePixelRatio || 1;
272
335
  const parentWidth = this.scrollContainer.clientWidth;
@@ -276,7 +339,6 @@ class Renderer extends EventEmitter {
276
339
  const useParentWidth = this.options.fillParent && !this.isScrolling;
277
340
  // Width and height of the waveform in pixels
278
341
  const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
279
- const { height } = this.options;
280
342
  // Set the width of the wrapper
281
343
  this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
282
344
  // Set additional styles
@@ -284,9 +346,24 @@ class Renderer extends EventEmitter {
284
346
  this.scrollContainer.classList.toggle('noScrollbar', !!this.options.hideScrollbar);
285
347
  this.cursor.style.backgroundColor = `${this.options.cursorColor || this.options.progressColor}`;
286
348
  this.cursor.style.width = `${this.options.cursorWidth}px`;
287
- this.canvasWrapper.style.height = `${this.options.height}px`;
349
+ // Clear the canvases
350
+ this.canvasWrapper.innerHTML = '';
351
+ this.progressWrapper.innerHTML = '';
288
352
  // Render the waveform
289
- this.renderPeaks(audioData, width, height, pixelRatio);
353
+ if (this.options.splitChannels) {
354
+ // Render a waveform for each channel
355
+ for (let i = 0; i < audioData.numberOfChannels; i++) {
356
+ const options = { ...this.options, ...this.options.splitChannels[i] };
357
+ this.renderWaveform([audioData.getChannelData(i)], options, width);
358
+ }
359
+ }
360
+ else {
361
+ // Render a single waveform for the first two channels (left and right)
362
+ const channels = [audioData.getChannelData(0)];
363
+ if (audioData.numberOfChannels > 1)
364
+ channels.push(audioData.getChannelData(1));
365
+ this.renderWaveform(channels, this.options, width);
366
+ }
290
367
  this.audioData = audioData;
291
368
  this.emit('render');
292
369
  }
@@ -1,15 +1,14 @@
1
1
  import type { GenericPlugin } from './base-plugin.js';
2
2
  import Player from './player.js';
3
- import { type RendererStyleOptions } from './renderer.js';
4
3
  export type WaveSurferOptions = {
5
4
  /** HTML element or CSS selector */
6
5
  container: HTMLElement | string;
7
- /** The height of the waveform in pixels */
8
- height?: number;
6
+ /** The height of the waveform in pixels, or "auto" to fill the container height */
7
+ height?: number | 'auto';
9
8
  /** The color of the waveform */
10
- waveColor?: string;
9
+ waveColor?: string | string[] | CanvasGradient;
11
10
  /** The color of the progress mask */
12
- progressColor?: string;
11
+ progressColor?: string | string[] | CanvasGradient;
13
12
  /** The color of the playpack cursor */
14
13
  cursorColor?: string;
15
14
  /** The cursor width */
@@ -22,6 +21,8 @@ export type WaveSurferOptions = {
22
21
  barRadius?: number;
23
22
  /** A vertical scaling factor for the waveform */
24
23
  barHeight?: number;
24
+ /** Vertical bar alignment */
25
+ barAlign?: 'top' | 'bottom';
25
26
  /** Minimum pixels per second of audio (i.e. zoom level) */
26
27
  minPxPerSec?: number;
27
28
  /** Stretch the waveform to fill the container, true by default */
@@ -29,7 +30,7 @@ export type WaveSurferOptions = {
29
30
  /** Audio URL */
30
31
  url?: string;
31
32
  /** Pre-computed audio data */
32
- peaks?: Float32Array[] | Array<number[]>;
33
+ peaks?: Array<Float32Array | number[]>;
33
34
  /** Pre-computed duration */
34
35
  duration?: number;
35
36
  /** Use an existing media element instead of creating one */
@@ -48,11 +49,16 @@ export type WaveSurferOptions = {
48
49
  autoCenter?: boolean;
49
50
  /** Decoding sample rate. Doesn't affect the playback. Defaults to 8000 */
50
51
  sampleRate?: number;
52
+ /** Render each audio channel as a separate waveform */
53
+ splitChannels?: WaveSurferOptions[];
54
+ /** Stretch the waveform to the full height */
55
+ normalize?: boolean;
51
56
  /** The list of plugins to initialize on start */
52
57
  plugins?: GenericPlugin[];
58
+ /** Custom render function */
59
+ renderFunction?: (peaks: Array<Float32Array | number[]>, ctx: CanvasRenderingContext2D) => void;
53
60
  };
54
61
  declare const defaultOptions: {
55
- height: number;
56
62
  waveColor: string;
57
63
  progressColor: string;
58
64
  cursorWidth: number;
@@ -68,8 +74,6 @@ export type WaveSurferEvents = {
68
74
  load: [url: string];
69
75
  /** When the audio has been decoded */
70
76
  decode: [duration: number];
71
- /** When the media element has loaded enough to play */
72
- canplay: [duration: number];
73
77
  /** When the audio is both decoded and can play */
74
78
  ready: [duration: number];
75
79
  /** When a waveform is drawn */
@@ -87,7 +91,7 @@ export type WaveSurferEvents = {
87
91
  /** When the user seeks to a new position */
88
92
  seeking: [currentTime: number];
89
93
  /** When the user interacts with the waveform (i.g. clicks or drags on it) */
90
- interaction: [];
94
+ interaction: [newTime: number];
91
95
  /** When the user clicks on the waveform */
92
96
  click: [relativeX: number];
93
97
  /** When the user drags the cursor */
@@ -105,17 +109,16 @@ declare class WaveSurfer extends Player<WaveSurferEvents> {
105
109
  private timer;
106
110
  private plugins;
107
111
  private decodedData;
108
- private canPlay;
112
+ private duration;
109
113
  protected subscriptions: Array<() => void>;
110
114
  /** Create a new WaveSurfer instance */
111
115
  static create(options: WaveSurferOptions): WaveSurfer;
112
116
  /** Create a new WaveSurfer instance */
113
117
  constructor(options: WaveSurferOptions);
114
- setOptions(options: Partial<RendererStyleOptions> & Pick<WaveSurferOptions, 'interact' | 'audioRate'>): void;
118
+ setOptions(options: Partial<WaveSurferOptions>): void;
119
+ private initTimerEvents;
115
120
  private initPlayerEvents;
116
121
  private initRendererEvents;
117
- private initTimerEvents;
118
- private initReadyEvent;
119
122
  private initPlugins;
120
123
  /** Register a wavesurfer.js plugin */
121
124
  registerPlugin<T extends GenericPlugin>(plugin: T): T;