wavesurfer.js 7.10.3 → 7.11.1

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 (64) hide show
  1. package/dist/__tests__/renderer-utils.test.d.ts +1 -0
  2. package/dist/__tests__/renderer-utils.test.js +320 -0
  3. package/dist/decoder.js +30 -6
  4. package/dist/draggable.js +16 -5
  5. package/dist/event-emitter.js +7 -6
  6. package/dist/fetcher.js +14 -16
  7. package/dist/player.js +2 -1
  8. package/dist/plugins/envelope.cjs +1 -1
  9. package/dist/plugins/envelope.esm.js +1 -1
  10. package/dist/plugins/envelope.js +1 -1
  11. package/dist/plugins/envelope.min.js +1 -1
  12. package/dist/plugins/hover.cjs +1 -1
  13. package/dist/plugins/hover.d.ts +1 -1
  14. package/dist/plugins/hover.esm.js +1 -1
  15. package/dist/plugins/hover.js +1 -1
  16. package/dist/plugins/hover.min.js +1 -1
  17. package/dist/plugins/minimap.cjs +1 -1
  18. package/dist/plugins/minimap.d.ts +1 -0
  19. package/dist/plugins/minimap.esm.js +1 -1
  20. package/dist/plugins/minimap.js +1 -1
  21. package/dist/plugins/minimap.min.js +1 -1
  22. package/dist/plugins/record.cjs +1 -1
  23. package/dist/plugins/record.d.ts +1 -0
  24. package/dist/plugins/record.esm.js +1 -1
  25. package/dist/plugins/record.js +1 -1
  26. package/dist/plugins/record.min.js +1 -1
  27. package/dist/plugins/regions.cjs +1 -1
  28. package/dist/plugins/regions.d.ts +4 -2
  29. package/dist/plugins/regions.esm.js +1 -1
  30. package/dist/plugins/regions.js +1 -1
  31. package/dist/plugins/regions.min.js +1 -1
  32. package/dist/plugins/spectrogram-windowed.cjs +1 -1
  33. package/dist/plugins/spectrogram-windowed.esm.js +1 -1
  34. package/dist/plugins/spectrogram-windowed.js +1 -1
  35. package/dist/plugins/spectrogram-windowed.min.js +1 -1
  36. package/dist/plugins/spectrogram.cjs +1 -1
  37. package/dist/plugins/spectrogram.esm.js +1 -1
  38. package/dist/plugins/spectrogram.js +1 -1
  39. package/dist/plugins/spectrogram.min.js +1 -1
  40. package/dist/plugins/timeline.cjs +1 -1
  41. package/dist/plugins/timeline.d.ts +2 -0
  42. package/dist/plugins/timeline.esm.js +1 -1
  43. package/dist/plugins/timeline.js +1 -1
  44. package/dist/plugins/timeline.min.js +1 -1
  45. package/dist/plugins/zoom.cjs +1 -1
  46. package/dist/plugins/zoom.d.ts +10 -1
  47. package/dist/plugins/zoom.esm.js +1 -1
  48. package/dist/plugins/zoom.js +1 -1
  49. package/dist/plugins/zoom.min.js +1 -1
  50. package/dist/renderer-utils.d.ts +117 -0
  51. package/dist/renderer-utils.js +232 -0
  52. package/dist/renderer.d.ts +3 -3
  53. package/dist/renderer.js +133 -182
  54. package/dist/timer.d.ts +2 -1
  55. package/dist/timer.js +23 -9
  56. package/dist/types.d.ts +2 -0
  57. package/dist/wavesurfer.cjs +1 -1
  58. package/dist/wavesurfer.d.ts +2 -0
  59. package/dist/wavesurfer.esm.js +1 -1
  60. package/dist/wavesurfer.js +27 -10
  61. package/dist/wavesurfer.min.js +1 -1
  62. package/dist/webaudio.d.ts +4 -0
  63. package/dist/webaudio.js +13 -2
  64. package/package.json +6 -5
package/dist/renderer.js CHANGED
@@ -20,6 +20,7 @@ var __rest = (this && this.__rest) || function (s, e) {
20
20
  };
21
21
  import { makeDraggable } from './draggable.js';
22
22
  import EventEmitter from './event-emitter.js';
23
+ import * as utils from './renderer-utils.js';
23
24
  class Renderer extends EventEmitter {
24
25
  constructor(options, audioElement) {
25
26
  super();
@@ -31,6 +32,7 @@ class Renderer extends EventEmitter {
31
32
  this.isDragging = false;
32
33
  this.subscriptions = [];
33
34
  this.unsubscribeOnScroll = [];
35
+ this.dragUnsubscribe = null;
34
36
  this.subscriptions = [];
35
37
  this.options = options;
36
38
  const parent = this.parentFromOptionsContainer(options.container);
@@ -62,22 +64,16 @@ class Renderer extends EventEmitter {
62
64
  return parent;
63
65
  }
64
66
  initEvents() {
65
- const getClickPosition = (e) => {
66
- const rect = this.wrapper.getBoundingClientRect();
67
- const x = e.clientX - rect.left;
68
- const y = e.clientY - rect.top;
69
- const relativeX = x / rect.width;
70
- const relativeY = y / rect.height;
71
- return [relativeX, relativeY];
72
- };
73
67
  // Add a click listener
74
68
  this.wrapper.addEventListener('click', (e) => {
75
- const [x, y] = getClickPosition(e);
69
+ const rect = this.wrapper.getBoundingClientRect();
70
+ const [x, y] = utils.getRelativePointerPosition(rect, e.clientX, e.clientY);
76
71
  this.emit('click', x, y);
77
72
  });
78
73
  // Add a double click listener
79
74
  this.wrapper.addEventListener('dblclick', (e) => {
80
- const [x, y] = getClickPosition(e);
75
+ const rect = this.wrapper.getBoundingClientRect();
76
+ const [x, y] = utils.getRelativePointerPosition(rect, e.clientX, e.clientY);
81
77
  this.emit('dblclick', x, y);
82
78
  });
83
79
  // Drag
@@ -87,8 +83,11 @@ class Renderer extends EventEmitter {
87
83
  // Add a scroll listener
88
84
  this.scrollContainer.addEventListener('scroll', () => {
89
85
  const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
90
- const startX = scrollLeft / scrollWidth;
91
- const endX = (scrollLeft + clientWidth) / scrollWidth;
86
+ const { startX, endX } = utils.calculateScrollPercentages({
87
+ scrollLeft,
88
+ scrollWidth,
89
+ clientWidth,
90
+ });
92
91
  this.emit('scroll', startX, endX, scrollLeft, scrollLeft + clientWidth);
93
92
  });
94
93
  // Re-render the waveform on container resize
@@ -108,39 +107,31 @@ class Renderer extends EventEmitter {
108
107
  return;
109
108
  this.lastContainerWidth = width;
110
109
  this.reRender();
110
+ this.emit('resize');
111
111
  }
112
112
  initDrag() {
113
- this.subscriptions.push(makeDraggable(this.wrapper,
113
+ // Don't initialize drag if it's already set up
114
+ if (this.dragUnsubscribe)
115
+ return;
116
+ this.dragUnsubscribe = makeDraggable(this.wrapper,
114
117
  // On drag
115
118
  (_, __, x) => {
116
- this.emit('drag', Math.max(0, Math.min(1, x / this.wrapper.getBoundingClientRect().width)));
119
+ const width = this.wrapper.getBoundingClientRect().width;
120
+ this.emit('drag', utils.clampToUnit(x / width));
117
121
  },
118
122
  // On start drag
119
123
  (x) => {
120
124
  this.isDragging = true;
121
- this.emit('dragstart', Math.max(0, Math.min(1, x / this.wrapper.getBoundingClientRect().width)));
125
+ const width = this.wrapper.getBoundingClientRect().width;
126
+ this.emit('dragstart', utils.clampToUnit(x / width));
122
127
  },
123
128
  // On end drag
124
129
  (x) => {
125
130
  this.isDragging = false;
126
- this.emit('dragend', Math.max(0, Math.min(1, x / this.wrapper.getBoundingClientRect().width)));
127
- }));
128
- }
129
- getHeight(optionsHeight, optionsSplitChannel) {
130
- var _a;
131
- const defaultHeight = 128;
132
- const numberOfChannels = ((_a = this.audioData) === null || _a === void 0 ? void 0 : _a.numberOfChannels) || 1;
133
- if (optionsHeight == null)
134
- return defaultHeight;
135
- if (!isNaN(Number(optionsHeight)))
136
- return Number(optionsHeight);
137
- if (optionsHeight === 'auto') {
138
- const height = this.parent.clientHeight || defaultHeight;
139
- if (optionsSplitChannel === null || optionsSplitChannel === void 0 ? void 0 : optionsSplitChannel.every((channel) => !channel.overlay))
140
- return height / numberOfChannels;
141
- return height;
142
- }
143
- return defaultHeight;
131
+ const width = this.wrapper.getBoundingClientRect().width;
132
+ this.emit('dragend', utils.clampToUnit(x / width));
133
+ });
134
+ this.subscriptions.push(this.dragUnsubscribe);
144
135
  }
145
136
  initHtml() {
146
137
  const div = document.createElement('div');
@@ -177,6 +168,7 @@ class Renderer extends EventEmitter {
177
168
  }
178
169
  :host .canvases {
179
170
  min-height: ${this.getHeight(this.options.height, this.options.splitChannels)}px;
171
+ pointer-events: none;
180
172
  }
181
173
  :host .canvases > div {
182
174
  position: relative;
@@ -253,150 +245,125 @@ class Renderer extends EventEmitter {
253
245
  this.setScroll(scrollStart);
254
246
  }
255
247
  destroy() {
256
- var _a, _b;
248
+ var _a;
257
249
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
258
250
  this.container.remove();
259
- (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
260
- (_b = this.unsubscribeOnScroll) === null || _b === void 0 ? void 0 : _b.forEach((unsubscribe) => unsubscribe());
251
+ if (this.resizeObserver) {
252
+ this.resizeObserver.disconnect();
253
+ this.resizeObserver = null;
254
+ }
255
+ (_a = this.unsubscribeOnScroll) === null || _a === void 0 ? void 0 : _a.forEach((unsubscribe) => unsubscribe());
261
256
  this.unsubscribeOnScroll = [];
262
257
  }
263
258
  createDelay(delayMs = 10) {
264
259
  let timeout;
265
- let reject;
260
+ let rejectFn;
266
261
  const onClear = () => {
267
- if (timeout)
262
+ if (timeout) {
268
263
  clearTimeout(timeout);
269
- if (reject)
270
- reject();
264
+ timeout = undefined;
265
+ }
266
+ if (rejectFn) {
267
+ rejectFn();
268
+ rejectFn = undefined;
269
+ }
271
270
  };
272
271
  this.timeouts.push(onClear);
273
272
  return () => {
274
- return new Promise((resolveFn, rejectFn) => {
273
+ return new Promise((resolve, reject) => {
274
+ // Clear any pending delay
275
275
  onClear();
276
- reject = rejectFn;
276
+ // Store reject function for cleanup
277
+ rejectFn = reject;
278
+ // Set new timeout
277
279
  timeout = setTimeout(() => {
278
280
  timeout = undefined;
279
- reject = undefined;
280
- resolveFn();
281
+ rejectFn = undefined;
282
+ resolve();
281
283
  }, delayMs);
282
284
  });
283
285
  };
284
286
  }
285
- // Convert array of color values to linear gradient
286
- convertColorValues(color) {
287
- if (!Array.isArray(color))
288
- return color || '';
289
- if (color.length < 2)
290
- return color[0] || '';
291
- const canvasElement = document.createElement('canvas');
292
- const ctx = canvasElement.getContext('2d');
293
- const gradientHeight = canvasElement.height * (window.devicePixelRatio || 1);
294
- const gradient = ctx.createLinearGradient(0, 0, 0, gradientHeight);
295
- const colorStopPercentage = 1 / (color.length - 1);
296
- color.forEach((color, index) => {
297
- const offset = index * colorStopPercentage;
298
- gradient.addColorStop(offset, color);
287
+ getHeight(optionsHeight, optionsSplitChannel) {
288
+ var _a;
289
+ const numberOfChannels = ((_a = this.audioData) === null || _a === void 0 ? void 0 : _a.numberOfChannels) || 1;
290
+ return utils.resolveChannelHeight({
291
+ optionsHeight,
292
+ optionsSplitChannels: optionsSplitChannel,
293
+ parentHeight: this.parent.clientHeight,
294
+ numberOfChannels,
295
+ defaultHeight: utils.DEFAULT_HEIGHT,
299
296
  });
300
- return gradient;
297
+ }
298
+ convertColorValues(color) {
299
+ return utils.resolveColorValue(color, this.getPixelRatio());
301
300
  }
302
301
  getPixelRatio() {
303
- return Math.max(1, window.devicePixelRatio || 1);
302
+ return utils.getPixelRatio(window.devicePixelRatio);
304
303
  }
305
304
  renderBarWaveform(channelData, options, ctx, vScale) {
306
- const topChannel = channelData[0];
307
- const bottomChannel = channelData[1] || channelData[0];
308
- const length = topChannel.length;
309
305
  const { width, height } = ctx.canvas;
310
- const halfHeight = height / 2;
311
- const pixelRatio = this.getPixelRatio();
312
- const barWidth = options.barWidth ? options.barWidth * pixelRatio : 1;
313
- const barGap = options.barGap ? options.barGap * pixelRatio : options.barWidth ? barWidth / 2 : 0;
314
- const barRadius = options.barRadius || 0;
315
- const barIndexScale = width / (barWidth + barGap) / length;
316
- const rectFn = barRadius && 'roundRect' in ctx ? 'roundRect' : 'rect';
306
+ const { halfHeight, barWidth, barRadius, barIndexScale, barSpacing } = utils.calculateBarRenderConfig({
307
+ width,
308
+ height,
309
+ length: (channelData[0] || []).length,
310
+ options,
311
+ pixelRatio: this.getPixelRatio(),
312
+ });
313
+ const segments = utils.calculateBarSegments({
314
+ channelData,
315
+ barIndexScale,
316
+ barSpacing,
317
+ barWidth,
318
+ halfHeight,
319
+ vScale,
320
+ canvasHeight: height,
321
+ barAlign: options.barAlign,
322
+ });
317
323
  ctx.beginPath();
318
- let prevX = 0;
319
- let maxTop = 0;
320
- let maxBottom = 0;
321
- for (let i = 0; i <= length; i++) {
322
- const x = Math.round(i * barIndexScale);
323
- if (x > prevX) {
324
- const topBarHeight = Math.round(maxTop * halfHeight * vScale);
325
- const bottomBarHeight = Math.round(maxBottom * halfHeight * vScale);
326
- const barHeight = topBarHeight + bottomBarHeight || 1;
327
- // Vertical alignment
328
- let y = halfHeight - topBarHeight;
329
- if (options.barAlign === 'top') {
330
- y = 0;
331
- }
332
- else if (options.barAlign === 'bottom') {
333
- y = height - barHeight;
334
- }
335
- ctx[rectFn](prevX * (barWidth + barGap), y, barWidth, barHeight, barRadius);
336
- prevX = x;
337
- maxTop = 0;
338
- maxBottom = 0;
324
+ for (const segment of segments) {
325
+ if (barRadius && 'roundRect' in ctx) {
326
+ ;
327
+ ctx.roundRect(segment.x, segment.y, segment.width, segment.height, barRadius);
328
+ }
329
+ else {
330
+ ctx.rect(segment.x, segment.y, segment.width, segment.height);
339
331
  }
340
- const magnitudeTop = Math.abs(topChannel[i] || 0);
341
- const magnitudeBottom = Math.abs(bottomChannel[i] || 0);
342
- if (magnitudeTop > maxTop)
343
- maxTop = magnitudeTop;
344
- if (magnitudeBottom > maxBottom)
345
- maxBottom = magnitudeBottom;
346
332
  }
347
333
  ctx.fill();
348
334
  ctx.closePath();
349
335
  }
350
336
  renderLineWaveform(channelData, _options, ctx, vScale) {
351
- const drawChannel = (index) => {
352
- const channel = channelData[index] || channelData[0];
353
- const length = channel.length;
354
- const { height } = ctx.canvas;
355
- const halfHeight = height / 2;
356
- const hScale = ctx.canvas.width / length;
357
- ctx.moveTo(0, halfHeight);
358
- let prevX = 0;
359
- let max = 0;
360
- for (let i = 0; i <= length; i++) {
361
- const x = Math.round(i * hScale);
362
- if (x > prevX) {
363
- const h = Math.round(max * halfHeight * vScale) || 1;
364
- const y = halfHeight + h * (index === 0 ? -1 : 1);
365
- ctx.lineTo(prevX, y);
366
- prevX = x;
367
- max = 0;
368
- }
369
- const value = Math.abs(channel[i] || 0);
370
- if (value > max)
371
- max = value;
372
- }
373
- ctx.lineTo(prevX, halfHeight);
374
- };
337
+ const { width, height } = ctx.canvas;
338
+ const paths = utils.calculateLinePaths({ channelData, width, height, vScale });
375
339
  ctx.beginPath();
376
- drawChannel(0);
377
- drawChannel(1);
340
+ for (const path of paths) {
341
+ if (!path.length)
342
+ continue;
343
+ ctx.moveTo(path[0].x, path[0].y);
344
+ for (let i = 1; i < path.length; i++) {
345
+ const point = path[i];
346
+ ctx.lineTo(point.x, point.y);
347
+ }
348
+ }
378
349
  ctx.fill();
379
350
  ctx.closePath();
380
351
  }
381
352
  renderWaveform(channelData, options, ctx) {
382
353
  ctx.fillStyle = this.convertColorValues(options.waveColor);
383
- // Custom rendering function
384
354
  if (options.renderFunction) {
385
355
  options.renderFunction(channelData, ctx);
386
356
  return;
387
357
  }
388
- // Vertical scaling
389
- let vScale = options.barHeight || 1;
390
- if (options.normalize) {
391
- const max = Array.from(channelData[0]).reduce((max, value) => Math.max(max, Math.abs(value)), 0);
392
- vScale = max ? 1 / max : 1;
393
- }
394
- // Render waveform as bars
395
- if (options.barWidth || options.barGap || options.barAlign) {
358
+ const vScale = utils.calculateVerticalScale({
359
+ channelData,
360
+ barHeight: options.barHeight,
361
+ normalize: options.normalize,
362
+ });
363
+ if (utils.shouldRenderBars(options)) {
396
364
  this.renderBarWaveform(channelData, options, ctx, vScale);
397
365
  return;
398
366
  }
399
- // Render waveform as a polyline
400
367
  this.renderLineWaveform(channelData, options, ctx, vScale);
401
368
  }
402
369
  renderSingleCanvas(data, options, width, height, offset, canvasContainer, progressContainer) {
@@ -409,7 +376,13 @@ class Renderer extends EventEmitter {
409
376
  canvas.style.left = `${Math.round(offset)}px`;
410
377
  canvasContainer.appendChild(canvas);
411
378
  const ctx = canvas.getContext('2d');
412
- this.renderWaveform(data, options, ctx);
379
+ if (options.renderFunction) {
380
+ ctx.fillStyle = this.convertColorValues(options.waveColor);
381
+ options.renderFunction(data, ctx);
382
+ }
383
+ else {
384
+ this.renderWaveform(data, options, ctx);
385
+ }
413
386
  // Draw a progress canvas
414
387
  if (canvas.width > 0 && canvas.height > 0) {
415
388
  const progressCanvas = canvas.cloneNode();
@@ -427,17 +400,8 @@ class Renderer extends EventEmitter {
427
400
  const pixelRatio = this.getPixelRatio();
428
401
  const { clientWidth } = this.scrollContainer;
429
402
  const totalWidth = width / pixelRatio;
430
- let singleCanvasWidth = Math.min(Renderer.MAX_CANVAS_WIDTH, clientWidth, totalWidth);
403
+ const singleCanvasWidth = utils.calculateSingleCanvasWidth({ clientWidth, totalWidth, options });
431
404
  let drawnIndexes = {};
432
- // Adjust width to avoid gaps between canvases when using bars
433
- if (options.barWidth || options.barGap) {
434
- const barWidth = options.barWidth || 0.5;
435
- const barGap = options.barGap || barWidth / 2;
436
- const totalBarWidth = barWidth + barGap;
437
- if (singleCanvasWidth % totalBarWidth !== 0) {
438
- singleCanvasWidth = Math.floor(singleCanvasWidth / totalBarWidth) * totalBarWidth;
439
- }
440
- }
441
405
  // Nothing to render
442
406
  if (singleCanvasWidth === 0)
443
407
  return;
@@ -451,24 +415,15 @@ class Renderer extends EventEmitter {
451
415
  const offset = index * singleCanvasWidth;
452
416
  let clampedWidth = Math.min(totalWidth - offset, singleCanvasWidth);
453
417
  // Clamp the width to the bar grid to avoid empty canvases at the end
454
- if (options.barWidth || options.barGap) {
455
- const barWidth = options.barWidth || 0.5;
456
- const barGap = options.barGap || barWidth / 2;
457
- const totalBarWidth = barWidth + barGap;
458
- clampedWidth = Math.floor(clampedWidth / totalBarWidth) * totalBarWidth;
459
- }
418
+ clampedWidth = utils.clampWidthToBarGrid(clampedWidth, options);
460
419
  if (clampedWidth <= 0)
461
420
  return;
462
- const data = channelData.map((channel) => {
463
- const start = Math.floor((offset / totalWidth) * channel.length);
464
- const end = Math.floor(((offset + clampedWidth) / totalWidth) * channel.length);
465
- return channel.slice(start, end);
466
- });
421
+ const data = utils.sliceChannelData({ channelData, offset, clampedWidth, totalWidth });
467
422
  this.renderSingleCanvas(data, options, clampedWidth, height, offset, canvasContainer, progressContainer);
468
423
  };
469
424
  // Clear canvases to avoid too many DOM nodes
470
425
  const clearCanvases = () => {
471
- if (Object.keys(drawnIndexes).length > Renderer.MAX_NODES) {
426
+ if (utils.shouldClearCanvases(Object.keys(drawnIndexes).length)) {
472
427
  canvasContainer.innerHTML = '';
473
428
  progressContainer.innerHTML = '';
474
429
  drawnIndexes = {};
@@ -484,21 +439,18 @@ class Renderer extends EventEmitter {
484
439
  return;
485
440
  }
486
441
  // Lazy rendering
487
- const viewPosition = this.scrollContainer.scrollLeft / totalWidth;
488
- const startCanvas = Math.floor(viewPosition * numCanvases);
489
- // Draw the canvases in the viewport first
490
- draw(startCanvas - 1);
491
- draw(startCanvas);
492
- draw(startCanvas + 1);
442
+ const initialRange = utils.getLazyRenderRange({
443
+ scrollLeft: this.scrollContainer.scrollLeft,
444
+ totalWidth,
445
+ numCanvases,
446
+ });
447
+ initialRange.forEach((index) => draw(index));
493
448
  // Subscribe to the scroll event to draw additional canvases
494
449
  if (numCanvases > 1) {
495
450
  const unsubscribe = this.on('scroll', () => {
496
451
  const { scrollLeft } = this.scrollContainer;
497
- const canvasIndex = Math.floor((scrollLeft / totalWidth) * numCanvases);
498
452
  clearCanvases();
499
- draw(canvasIndex - 1);
500
- draw(canvasIndex);
501
- draw(canvasIndex + 1);
453
+ utils.getLazyRenderRange({ scrollLeft, totalWidth, numCanvases }).forEach((index) => draw(index));
502
454
  });
503
455
  this.unsubscribeOnScroll.push(unsubscribe);
504
456
  }
@@ -537,12 +489,15 @@ class Renderer extends EventEmitter {
537
489
  // Determine the width of the waveform
538
490
  const pixelRatio = this.getPixelRatio();
539
491
  const parentWidth = this.scrollContainer.clientWidth;
540
- const scrollWidth = Math.ceil(audioData.duration * (this.options.minPxPerSec || 0));
492
+ const { scrollWidth, isScrollable, useParentWidth, width } = utils.calculateWaveformLayout({
493
+ duration: audioData.duration,
494
+ minPxPerSec: this.options.minPxPerSec || 0,
495
+ parentWidth,
496
+ fillParent: this.options.fillParent,
497
+ pixelRatio,
498
+ });
541
499
  // Whether the container should scroll
542
- this.isScrollable = scrollWidth > parentWidth;
543
- const useParentWidth = this.options.fillParent && !this.isScrollable;
544
- // Width of the waveform in pixels
545
- const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
500
+ this.isScrollable = isScrollable;
546
501
  // Set the width of the wrapper
547
502
  this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
548
503
  // Set additional styles
@@ -585,12 +540,7 @@ class Renderer extends EventEmitter {
585
540
  // Adjust the scroll position so that the cursor stays in the same place
586
541
  if (this.isScrollable && scrollWidth !== this.scrollContainer.scrollWidth) {
587
542
  const { right: after } = this.progressWrapper.getBoundingClientRect();
588
- let delta = after - before;
589
- // to limit compounding floating-point drift
590
- // we need to round to the half px furthest from 0
591
- delta *= 2;
592
- delta = delta < 0 ? Math.floor(delta) : Math.ceil(delta);
593
- delta /= 2;
543
+ const delta = utils.roundToHalfAwayFromZero(after - before);
594
544
  this.scrollContainer.scrollLeft += delta;
595
545
  }
596
546
  }
@@ -621,14 +571,17 @@ class Renderer extends EventEmitter {
621
571
  // Keep the cursor centered when playing
622
572
  const center = progressWidth - scrollLeft - middle;
623
573
  if (isPlaying && this.options.autoCenter && center > 0) {
624
- this.scrollContainer.scrollLeft += Math.min(center, 10);
574
+ this.scrollContainer.scrollLeft += center;
625
575
  }
626
576
  }
627
577
  // Emit the scroll event
628
578
  {
629
579
  const newScroll = this.scrollContainer.scrollLeft;
630
- const startX = newScroll / scrollWidth;
631
- const endX = (newScroll + clientWidth) / scrollWidth;
580
+ const { startX, endX } = utils.calculateScrollPercentages({
581
+ scrollLeft: newScroll,
582
+ scrollWidth,
583
+ clientWidth,
584
+ });
632
585
  this.emit('scroll', startX, endX, newScroll, newScroll + clientWidth);
633
586
  }
634
587
  }
@@ -673,6 +626,4 @@ class Renderer extends EventEmitter {
673
626
  });
674
627
  }
675
628
  }
676
- Renderer.MAX_CANVAS_WIDTH = 8000;
677
- Renderer.MAX_NODES = 10;
678
629
  export default Renderer;
package/dist/timer.d.ts CHANGED
@@ -3,7 +3,8 @@ type TimerEvents = {
3
3
  tick: [];
4
4
  };
5
5
  declare class Timer extends EventEmitter<TimerEvents> {
6
- private unsubscribe;
6
+ private animationFrameId;
7
+ private isRunning;
7
8
  start(): void;
8
9
  stop(): void;
9
10
  destroy(): void;
package/dist/timer.js CHANGED
@@ -2,21 +2,35 @@ import EventEmitter from './event-emitter.js';
2
2
  class Timer extends EventEmitter {
3
3
  constructor() {
4
4
  super(...arguments);
5
- this.unsubscribe = () => undefined;
5
+ this.animationFrameId = null;
6
+ this.isRunning = false;
6
7
  }
7
8
  start() {
8
- this.unsubscribe = this.on('tick', () => {
9
- requestAnimationFrame(() => {
10
- this.emit('tick');
11
- });
12
- });
13
- this.emit('tick');
9
+ // Prevent multiple simultaneous loops
10
+ if (this.isRunning)
11
+ return;
12
+ this.isRunning = true;
13
+ const tick = () => {
14
+ // Only continue if timer is still running
15
+ if (!this.isRunning)
16
+ return;
17
+ this.emit('tick');
18
+ // Schedule next frame
19
+ this.animationFrameId = requestAnimationFrame(tick);
20
+ };
21
+ // Start the loop
22
+ tick();
14
23
  }
15
24
  stop() {
16
- this.unsubscribe();
25
+ this.isRunning = false;
26
+ // Cancel any pending animation frame
27
+ if (this.animationFrameId !== null) {
28
+ cancelAnimationFrame(this.animationFrameId);
29
+ this.animationFrameId = null;
30
+ }
17
31
  }
18
32
  destroy() {
19
- this.unsubscribe();
33
+ this.stop();
20
34
  }
21
35
  }
22
36
  export default Timer;
package/dist/types.d.ts CHANGED
@@ -243,6 +243,8 @@ type WaveSurferEvents = {
243
243
  destroy: [];
244
244
  /** When source file is unable to be fetched, decoded, or an error is thrown by media element */
245
245
  error: [error: Error];
246
+ /** When audio container resizing */
247
+ resize: [];
246
248
  };
247
249
  declare class WaveSurfer extends Player<WaveSurferEvents> {
248
250
  options: WaveSurferOptions & typeof defaultOptions;