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.
- package/dist/__tests__/renderer-utils.test.d.ts +1 -0
- package/dist/__tests__/renderer-utils.test.js +320 -0
- package/dist/decoder.js +30 -6
- package/dist/draggable.js +16 -5
- package/dist/event-emitter.js +7 -6
- package/dist/fetcher.js +14 -16
- package/dist/player.js +2 -1
- package/dist/plugins/envelope.cjs +1 -1
- package/dist/plugins/envelope.esm.js +1 -1
- package/dist/plugins/envelope.js +1 -1
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/hover.cjs +1 -1
- package/dist/plugins/hover.d.ts +1 -1
- package/dist/plugins/hover.esm.js +1 -1
- package/dist/plugins/hover.js +1 -1
- package/dist/plugins/hover.min.js +1 -1
- package/dist/plugins/minimap.cjs +1 -1
- package/dist/plugins/minimap.d.ts +1 -0
- package/dist/plugins/minimap.esm.js +1 -1
- package/dist/plugins/minimap.js +1 -1
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/record.cjs +1 -1
- package/dist/plugins/record.d.ts +1 -0
- package/dist/plugins/record.esm.js +1 -1
- package/dist/plugins/record.js +1 -1
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.cjs +1 -1
- package/dist/plugins/regions.d.ts +4 -2
- package/dist/plugins/regions.esm.js +1 -1
- package/dist/plugins/regions.js +1 -1
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/spectrogram-windowed.cjs +1 -1
- package/dist/plugins/spectrogram-windowed.esm.js +1 -1
- package/dist/plugins/spectrogram-windowed.js +1 -1
- package/dist/plugins/spectrogram-windowed.min.js +1 -1
- package/dist/plugins/spectrogram.cjs +1 -1
- package/dist/plugins/spectrogram.esm.js +1 -1
- package/dist/plugins/spectrogram.js +1 -1
- package/dist/plugins/spectrogram.min.js +1 -1
- package/dist/plugins/timeline.cjs +1 -1
- package/dist/plugins/timeline.d.ts +2 -0
- package/dist/plugins/timeline.esm.js +1 -1
- package/dist/plugins/timeline.js +1 -1
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/plugins/zoom.cjs +1 -1
- package/dist/plugins/zoom.d.ts +10 -1
- package/dist/plugins/zoom.esm.js +1 -1
- package/dist/plugins/zoom.js +1 -1
- package/dist/plugins/zoom.min.js +1 -1
- package/dist/renderer-utils.d.ts +117 -0
- package/dist/renderer-utils.js +232 -0
- package/dist/renderer.d.ts +3 -3
- package/dist/renderer.js +133 -182
- package/dist/timer.d.ts +2 -1
- package/dist/timer.js +23 -9
- package/dist/types.d.ts +2 -0
- package/dist/wavesurfer.cjs +1 -1
- package/dist/wavesurfer.d.ts +2 -0
- package/dist/wavesurfer.esm.js +1 -1
- package/dist/wavesurfer.js +27 -10
- package/dist/wavesurfer.min.js +1 -1
- package/dist/webaudio.d.ts +4 -0
- package/dist/webaudio.js +13 -2
- 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
|
|
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
|
|
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
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
248
|
+
var _a;
|
|
257
249
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
258
250
|
this.container.remove();
|
|
259
|
-
(
|
|
260
|
-
|
|
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
|
|
260
|
+
let rejectFn;
|
|
266
261
|
const onClear = () => {
|
|
267
|
-
if (timeout)
|
|
262
|
+
if (timeout) {
|
|
268
263
|
clearTimeout(timeout);
|
|
269
|
-
|
|
270
|
-
|
|
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((
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
// Clear any pending delay
|
|
275
275
|
onClear();
|
|
276
|
-
reject
|
|
276
|
+
// Store reject function for cleanup
|
|
277
|
+
rejectFn = reject;
|
|
278
|
+
// Set new timeout
|
|
277
279
|
timeout = setTimeout(() => {
|
|
278
280
|
timeout = undefined;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
rejectFn = undefined;
|
|
282
|
+
resolve();
|
|
281
283
|
}, delayMs);
|
|
282
284
|
});
|
|
283
285
|
};
|
|
284
286
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
297
|
+
}
|
|
298
|
+
convertColorValues(color) {
|
|
299
|
+
return utils.resolveColorValue(color, this.getPixelRatio());
|
|
301
300
|
}
|
|
302
301
|
getPixelRatio() {
|
|
303
|
-
return
|
|
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
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
352
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 +=
|
|
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
|
|
631
|
-
|
|
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
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.
|
|
5
|
+
this.animationFrameId = null;
|
|
6
|
+
this.isRunning = false;
|
|
6
7
|
}
|
|
7
8
|
start() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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.
|
|
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;
|