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.
- package/LICENSE +29 -0
- package/README.md +81 -36
- package/dist/decoder.d.ts +1 -1
- package/dist/draggable.d.ts +1 -0
- package/dist/draggable.js +59 -0
- package/dist/fetcher.d.ts +2 -0
- package/dist/fetcher.js +4 -0
- package/dist/player.d.ts +1 -1
- package/dist/player.js +2 -2
- package/dist/plugins/envelope.js +4 -25
- package/dist/plugins/envelope.min.cjs +1 -0
- package/dist/plugins/minimap.js +0 -3
- package/dist/plugins/minimap.min.cjs +1 -0
- package/dist/plugins/record.d.ts +3 -1
- package/dist/plugins/record.js +11 -5
- package/dist/plugins/record.min.cjs +1 -0
- package/dist/plugins/regions.d.ts +1 -0
- package/dist/plugins/regions.js +17 -52
- package/dist/plugins/regions.min.cjs +1 -0
- package/dist/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/plugins/spectrogram-fft.js +150 -0
- package/dist/plugins/spectrogram.d.ts +3 -0
- package/dist/plugins/spectrogram.js +3 -2
- package/dist/plugins/{spectrogram.min.js → spectrogram.min.cjs} +1 -1
- package/dist/plugins/timeline.min.cjs +1 -0
- package/dist/renderer.d.ts +11 -21
- package/dist/renderer.js +213 -136
- package/dist/wavesurfer.d.ts +17 -14
- package/dist/wavesurfer.js +54 -65
- package/dist/wavesurfer.min.cjs +1 -0
- package/package.json +17 -11
- package/dist/plugins/envelope.min.js +0 -1
- package/dist/plugins/minimap.min.js +0 -1
- package/dist/plugins/multitrack.d.ts +0 -117
- package/dist/plugins/multitrack.js +0 -507
- package/dist/plugins/multitrack.min.js +0 -1
- package/dist/plugins/record.min.js +0 -1
- package/dist/plugins/regions.min.js +0 -1
- package/dist/plugins/timeline.min.js +0 -1
- 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(
|
|
4
|
+
constructor(options) {
|
|
4
5
|
super();
|
|
5
|
-
this.
|
|
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 =
|
|
14
|
-
|
|
15
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
53
|
+
delay(() => this.reRender());
|
|
50
54
|
});
|
|
51
55
|
this.resizeObserver.observe(this.scrollContainer);
|
|
52
56
|
}
|
|
53
57
|
initDrag() {
|
|
54
|
-
this.wrapper
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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:
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const
|
|
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
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
349
|
+
// Clear the canvases
|
|
350
|
+
this.canvasWrapper.innerHTML = '';
|
|
351
|
+
this.progressWrapper.innerHTML = '';
|
|
288
352
|
// Render the waveform
|
|
289
|
-
this.
|
|
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
|
}
|
package/dist/wavesurfer.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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<
|
|
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;
|