wavesurfer.js 7.0.0-alpha.3 → 7.0.0-alpha.31
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/README.md +58 -26
- package/dist/base-plugin.d.ts +9 -5
- package/dist/base-plugin.js +11 -3
- package/dist/decoder.d.ts +2 -4
- package/dist/decoder.js +20 -31
- package/dist/event-emitter.d.ts +1 -1
- package/dist/event-emitter.js +4 -7
- package/dist/fetcher.js +2 -13
- package/dist/legacy-adapter.d.ts +7 -0
- package/dist/legacy-adapter.js +15 -0
- package/dist/player.d.ts +39 -9
- package/dist/player.js +90 -15
- package/dist/plugin/wavesurfer.envelope.min.js +1 -0
- package/dist/plugin/wavesurfer.minimap.min.js +1 -0
- package/dist/plugin/wavesurfer.multitrack.min.js +1 -0
- package/dist/plugin/wavesurfer.regions.min.js +1 -0
- package/dist/plugin/wavesurfer.timeline.min.js +1 -0
- package/dist/plugins/envelope.d.ts +60 -0
- package/dist/plugins/envelope.js +302 -0
- package/dist/plugins/envelope.min.js +1 -0
- package/dist/plugins/minimap.d.ts +27 -0
- package/dist/plugins/minimap.js +83 -0
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/multitrack.d.ts +113 -0
- package/dist/plugins/multitrack.js +504 -0
- package/dist/plugins/multitrack.min.js +1 -0
- package/dist/plugins/record.d.ts +23 -0
- package/dist/plugins/record.js +119 -0
- package/dist/plugins/record.min.js +1 -0
- package/dist/plugins/regions.d.ts +68 -32
- package/dist/plugins/regions.js +291 -158
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/spectrogram.d.ts +24 -0
- package/dist/plugins/spectrogram.js +165 -0
- package/dist/plugins/timeline.d.ts +38 -0
- package/dist/plugins/timeline.js +134 -0
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/renderer.d.ts +27 -12
- package/dist/renderer.js +223 -137
- package/dist/timer.d.ts +2 -1
- package/dist/timer.js +5 -3
- package/dist/wavesurfer.d.ts +132 -0
- package/dist/wavesurfer.js +220 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +51 -20
- package/dist/index.d.ts +0 -92
- package/dist/index.js +0 -166
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -30
- package/dist/wavesurfer.Regions.min.js +0 -1
package/dist/renderer.js
CHANGED
|
@@ -1,202 +1,288 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
class Renderer extends EventEmitter {
|
|
3
|
-
|
|
3
|
+
options = {
|
|
4
|
+
height: 0,
|
|
5
|
+
};
|
|
6
|
+
container;
|
|
7
|
+
scrollContainer;
|
|
8
|
+
wrapper;
|
|
9
|
+
canvasWrapper;
|
|
10
|
+
progressWrapper;
|
|
11
|
+
timeout = null;
|
|
12
|
+
isScrolling = false;
|
|
13
|
+
channelData = null;
|
|
14
|
+
duration = null;
|
|
15
|
+
resizeObserver = null;
|
|
16
|
+
constructor(params, options) {
|
|
4
17
|
super();
|
|
5
|
-
this.options = options;
|
|
18
|
+
this.options = { ...options };
|
|
6
19
|
let container = null;
|
|
7
|
-
if (typeof
|
|
8
|
-
container = document.querySelector(
|
|
20
|
+
if (typeof params.container === 'string') {
|
|
21
|
+
container = document.querySelector(params.container);
|
|
9
22
|
}
|
|
10
|
-
else if (
|
|
11
|
-
container =
|
|
23
|
+
else if (params.container instanceof HTMLElement) {
|
|
24
|
+
container = params.container;
|
|
12
25
|
}
|
|
13
26
|
if (!container) {
|
|
14
27
|
throw new Error('Container not found');
|
|
15
28
|
}
|
|
29
|
+
const [div, shadow] = this.initHtml();
|
|
30
|
+
container.appendChild(div);
|
|
31
|
+
this.container = div;
|
|
32
|
+
this.scrollContainer = shadow.querySelector('.scroll');
|
|
33
|
+
this.wrapper = shadow.querySelector('.wrapper');
|
|
34
|
+
this.canvasWrapper = shadow.querySelector('.canvases');
|
|
35
|
+
this.progressWrapper = shadow.querySelector('.progress');
|
|
36
|
+
// Set a click handler
|
|
37
|
+
this.wrapper.addEventListener('click', (e) => {
|
|
38
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
39
|
+
const x = e.clientX - rect.left;
|
|
40
|
+
const relativeX = x / rect.width;
|
|
41
|
+
this.emit('click', { relativeX });
|
|
42
|
+
});
|
|
43
|
+
// Re-render the waveform on container resize
|
|
44
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
45
|
+
this.delay(() => this.reRender(), 100);
|
|
46
|
+
});
|
|
47
|
+
this.resizeObserver.observe(this.scrollContainer);
|
|
48
|
+
}
|
|
49
|
+
initHtml() {
|
|
16
50
|
const div = document.createElement('div');
|
|
17
51
|
const shadow = div.attachShadow({ mode: 'open' });
|
|
18
52
|
shadow.innerHTML = `
|
|
19
53
|
<style>
|
|
54
|
+
:host {
|
|
55
|
+
user-select: none;
|
|
56
|
+
}
|
|
20
57
|
:host .scroll {
|
|
21
58
|
overflow-x: auto;
|
|
22
|
-
overflow-y:
|
|
23
|
-
user-select: none;
|
|
59
|
+
overflow-y: hidden;
|
|
24
60
|
width: 100%;
|
|
25
|
-
height: ${this.options.height}px;
|
|
26
61
|
position: relative;
|
|
27
62
|
}
|
|
63
|
+
:host .noScrollbar {
|
|
64
|
+
scrollbar-color: transparent;
|
|
65
|
+
scrollbar-width: none;
|
|
66
|
+
}
|
|
67
|
+
:host .noScrollbar::-webkit-scrollbar {
|
|
68
|
+
display: none;
|
|
69
|
+
-webkit-appearance: none;
|
|
70
|
+
}
|
|
28
71
|
:host .wrapper {
|
|
29
72
|
position: relative;
|
|
30
|
-
|
|
31
|
-
min-width: 100%;
|
|
32
|
-
height: 100%;
|
|
73
|
+
overflow: visible;
|
|
33
74
|
z-index: 2;
|
|
34
75
|
}
|
|
76
|
+
:host .canvases {
|
|
77
|
+
position: relative;
|
|
78
|
+
height: ${this.options.height}px;
|
|
79
|
+
}
|
|
35
80
|
:host canvas {
|
|
36
81
|
display: block;
|
|
37
|
-
|
|
38
|
-
|
|
82
|
+
position: absolute;
|
|
83
|
+
top: 0;
|
|
39
84
|
image-rendering: pixelated;
|
|
85
|
+
height: ${this.options.height}px;
|
|
40
86
|
}
|
|
41
87
|
:host .progress {
|
|
42
|
-
position: absolute;
|
|
43
|
-
z-index: 2;
|
|
44
|
-
top: 0;
|
|
45
|
-
left: 0;
|
|
46
|
-
height: 100%;
|
|
47
88
|
pointer-events: none;
|
|
48
|
-
clip-path: inset(100%);
|
|
49
|
-
}
|
|
50
|
-
:host .cursor {
|
|
51
89
|
position: absolute;
|
|
52
|
-
z-index:
|
|
90
|
+
z-index: 2;
|
|
53
91
|
top: 0;
|
|
54
92
|
left: 0;
|
|
93
|
+
width: 0;
|
|
55
94
|
height: 100%;
|
|
56
|
-
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
box-sizing: border-box;
|
|
57
97
|
}
|
|
58
98
|
</style>
|
|
59
99
|
|
|
60
100
|
<div class="scroll">
|
|
61
101
|
<div class="wrapper">
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
<div class="cursor"></div>
|
|
102
|
+
<div class="canvases"></div>
|
|
103
|
+
<div class="progress"></div>
|
|
65
104
|
</div>
|
|
66
105
|
</div>
|
|
67
106
|
`;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
107
|
+
return [div, shadow];
|
|
108
|
+
}
|
|
109
|
+
setOptions(options) {
|
|
110
|
+
this.options = options;
|
|
111
|
+
// Re-render the waveform
|
|
112
|
+
this.reRender();
|
|
113
|
+
}
|
|
114
|
+
getContainer() {
|
|
115
|
+
return this.scrollContainer;
|
|
116
|
+
}
|
|
117
|
+
getWrapper() {
|
|
118
|
+
return this.wrapper;
|
|
81
119
|
}
|
|
82
120
|
destroy() {
|
|
83
121
|
this.container.remove();
|
|
122
|
+
this.resizeObserver?.disconnect();
|
|
84
123
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
ctx.beginPath();
|
|
89
|
-
ctx.moveTo(0, height / 2);
|
|
90
|
-
// Channel 0 is left, 1 is right
|
|
91
|
-
const leftChannel = channelData[0];
|
|
92
|
-
const isMono = channelData.length === 1;
|
|
93
|
-
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
94
|
-
// Draw left channel in the top half of the canvas
|
|
95
|
-
let prevX = -1;
|
|
96
|
-
let prevY = 0;
|
|
97
|
-
for (let i = 0, len = leftChannel.length; i < len; i++) {
|
|
98
|
-
const x = Math.round((i / leftChannel.length) * width);
|
|
99
|
-
const y = Math.round(((1 - leftChannel[i]) * height) / 2);
|
|
100
|
-
if (x !== prevX || y > prevY) {
|
|
101
|
-
ctx.lineTo(x, y);
|
|
102
|
-
prevX = x;
|
|
103
|
-
prevY = y;
|
|
104
|
-
}
|
|
124
|
+
delay(fn, delayMs = 10) {
|
|
125
|
+
if (this.timeout) {
|
|
126
|
+
clearTimeout(this.timeout);
|
|
105
127
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const y = Math.round(((1 + rightChannel[i]) * height) / 2);
|
|
112
|
-
if (x !== prevX || (isMono ? y < -prevY : y > prevY)) {
|
|
113
|
-
ctx.lineTo(x, y);
|
|
114
|
-
prevX = x;
|
|
115
|
-
prevY = y;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
ctx.strokeStyle = ctx.fillStyle = this.options.waveColor;
|
|
119
|
-
ctx.stroke();
|
|
120
|
-
ctx.fill();
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
this.timeout = setTimeout(() => {
|
|
130
|
+
resolve(fn());
|
|
131
|
+
}, delayMs);
|
|
132
|
+
});
|
|
121
133
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
ctx.clearRect(0, 0, width, height);
|
|
127
|
-
const barWidthOption = this.options.barWidth || 1;
|
|
128
|
-
const barWidth = barWidthOption * devicePixelRatio;
|
|
129
|
-
const barGap = ((_a = this.options.barGap) !== null && _a !== void 0 ? _a : barWidthOption / 2) * devicePixelRatio;
|
|
130
|
-
const barRadius = (_b = this.options.barRadius) !== null && _b !== void 0 ? _b : 0;
|
|
134
|
+
async renderPeaks(channelData, width, height, pixelRatio) {
|
|
135
|
+
const barWidth = this.options.barWidth != null ? this.options.barWidth * pixelRatio : 1;
|
|
136
|
+
const barGap = this.options.barGap != null ? this.options.barGap * pixelRatio : this.options.barWidth ? barWidth / 2 : 0;
|
|
137
|
+
const barRadius = this.options.barRadius ?? 0;
|
|
131
138
|
const leftChannel = channelData[0];
|
|
139
|
+
const len = leftChannel.length;
|
|
140
|
+
const barCount = Math.floor(width / (barWidth + barGap));
|
|
141
|
+
const barIndexScale = barCount / len;
|
|
142
|
+
const halfHeight = height / 2;
|
|
132
143
|
const isMono = channelData.length === 1;
|
|
133
144
|
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
const useNegative = isMono && rightChannel.some((v) => v < 0);
|
|
146
|
+
const draw = (start, end) => {
|
|
147
|
+
let prevX = 0;
|
|
148
|
+
let prevLeft = 0;
|
|
149
|
+
let prevRight = 0;
|
|
150
|
+
const canvas = document.createElement('canvas');
|
|
151
|
+
canvas.width = Math.round((width * (end - start)) / len);
|
|
152
|
+
canvas.height = this.options.height;
|
|
153
|
+
canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
|
|
154
|
+
canvas.style.height = `${this.options.height}px`;
|
|
155
|
+
canvas.style.left = `${Math.floor((start * width) / pixelRatio / len)}px`;
|
|
156
|
+
this.canvasWrapper.appendChild(canvas);
|
|
157
|
+
const ctx = canvas.getContext('2d', {
|
|
158
|
+
desynchronized: true,
|
|
159
|
+
});
|
|
160
|
+
ctx.beginPath();
|
|
161
|
+
ctx.fillStyle = this.options.waveColor ?? '';
|
|
162
|
+
// Firefox shim until 2023.04.11
|
|
163
|
+
if (!ctx.roundRect)
|
|
164
|
+
ctx.roundRect = ctx.fillRect;
|
|
165
|
+
for (let i = start; i < end; i++) {
|
|
166
|
+
const barIndex = Math.round((i - start) * barIndexScale);
|
|
167
|
+
if (barIndex > prevX) {
|
|
168
|
+
const leftBarHeight = Math.round(prevLeft * halfHeight);
|
|
169
|
+
const rightBarHeight = Math.round(prevRight * halfHeight);
|
|
170
|
+
ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + rightBarHeight || 1, barRadius);
|
|
171
|
+
prevX = barIndex;
|
|
172
|
+
prevLeft = 0;
|
|
173
|
+
prevRight = 0;
|
|
174
|
+
}
|
|
175
|
+
const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
|
|
176
|
+
const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
|
|
177
|
+
if (leftValue > prevLeft) {
|
|
178
|
+
prevLeft = leftValue;
|
|
179
|
+
}
|
|
180
|
+
// If stereo, both channels are drawn as max values
|
|
181
|
+
// If mono with negative values, the bottom channel will be the min negative values
|
|
182
|
+
if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
|
|
183
|
+
prevRight = rightValue < 0 ? -rightValue : rightValue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
ctx.fill();
|
|
187
|
+
ctx.closePath();
|
|
188
|
+
// Draw a progress canvas
|
|
189
|
+
const progressCanvas = canvas.cloneNode();
|
|
190
|
+
this.progressWrapper.appendChild(progressCanvas);
|
|
191
|
+
const progressCtx = progressCanvas.getContext('2d', {
|
|
192
|
+
desynchronized: true,
|
|
193
|
+
});
|
|
194
|
+
if (canvas.width > 0 && canvas.height > 0) {
|
|
195
|
+
progressCtx.drawImage(canvas, 0, 0);
|
|
196
|
+
}
|
|
197
|
+
// Set the composition method to draw only where the waveform is drawn
|
|
198
|
+
progressCtx.globalCompositeOperation = 'source-in';
|
|
199
|
+
progressCtx.fillStyle = this.options.progressColor ?? '';
|
|
200
|
+
// This rectangle acts as a mask thanks to the composition method
|
|
201
|
+
progressCtx.fillRect(0, 0, canvas.width, canvas.height);
|
|
202
|
+
};
|
|
203
|
+
// Clear the canvas
|
|
204
|
+
this.canvasWrapper.innerHTML = '';
|
|
205
|
+
this.progressWrapper.innerHTML = '';
|
|
206
|
+
// Determine the currently visible part of the waveform
|
|
207
|
+
const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
|
|
208
|
+
const scale = len / scrollWidth;
|
|
209
|
+
const start = Math.floor(scrollLeft * scale);
|
|
210
|
+
const end = Math.ceil(Math.min(scrollWidth, scrollLeft + clientWidth) * scale);
|
|
211
|
+
// Draw the visible portion of the waveform
|
|
212
|
+
draw(start, end);
|
|
213
|
+
// Draw the rest of the waveform with a timeout for better performance
|
|
214
|
+
const step = end - start;
|
|
215
|
+
for (let i = end; i < len; i += step) {
|
|
216
|
+
await this.delay(() => {
|
|
217
|
+
draw(i, Math.min(len, i + step));
|
|
218
|
+
});
|
|
143
219
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
ctx.roundRect(i * (barWidth + barGap), height / 2 - leftBarHeight, barWidth, leftBarHeight + rightBarHeight, barRadius);
|
|
220
|
+
for (let i = start - 1; i >= 0; i -= step) {
|
|
221
|
+
await this.delay(() => {
|
|
222
|
+
draw(Math.max(0, i - step), i);
|
|
223
|
+
});
|
|
149
224
|
}
|
|
150
|
-
ctx.fillStyle = this.options.waveColor;
|
|
151
|
-
ctx.fill();
|
|
152
|
-
}
|
|
153
|
-
createProgressMask() {
|
|
154
|
-
const progressCtx = this.progressCanvas.getContext('2d', { desynchronized: true });
|
|
155
|
-
if (!progressCtx) {
|
|
156
|
-
throw new Error('Failed to get canvas context');
|
|
157
|
-
}
|
|
158
|
-
this.progressCanvas.width = this.mainCanvas.width;
|
|
159
|
-
this.progressCanvas.height = this.mainCanvas.height;
|
|
160
|
-
this.progressCanvas.style.width = this.mainCanvas.style.width;
|
|
161
|
-
this.progressCanvas.style.height = this.mainCanvas.style.height;
|
|
162
|
-
progressCtx.drawImage(this.mainCanvas, 0, 0);
|
|
163
|
-
progressCtx.globalCompositeOperation = 'source-in';
|
|
164
|
-
progressCtx.fillStyle = this.options.progressColor;
|
|
165
|
-
progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
|
|
166
225
|
}
|
|
167
226
|
render(channelData, duration) {
|
|
168
|
-
|
|
169
|
-
const
|
|
227
|
+
// Determine the width of the waveform
|
|
228
|
+
const pixelRatio = window.devicePixelRatio || 1;
|
|
229
|
+
const parentWidth = this.scrollContainer.clientWidth;
|
|
230
|
+
const scrollWidth = Math.ceil(duration * (this.options.minPxPerSec || 0));
|
|
231
|
+
// Whether the container should scroll
|
|
232
|
+
this.isScrolling = scrollWidth > parentWidth;
|
|
233
|
+
const useParentWidth = this.options.fillParent && !this.isScrolling;
|
|
234
|
+
// Width and height of the waveform in pixels
|
|
235
|
+
const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
|
|
170
236
|
const { height } = this.options;
|
|
171
|
-
|
|
172
|
-
this.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.
|
|
237
|
+
// Set the width of the wrapper
|
|
238
|
+
this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
|
|
239
|
+
// Set additional styles
|
|
240
|
+
this.scrollContainer.style.overflowX = this.isScrolling ? 'auto' : 'hidden';
|
|
241
|
+
this.scrollContainer.classList.toggle('noScrollbar', !!this.options.hideScrollbar);
|
|
242
|
+
this.progressWrapper.style.borderRightStyle = 'solid';
|
|
243
|
+
this.progressWrapper.style.borderRightColor = `${this.options.cursorColor || this.options.progressColor}`;
|
|
244
|
+
this.progressWrapper.style.borderRightWidth = `${this.options.cursorWidth}px`;
|
|
245
|
+
this.canvasWrapper.style.height = `${this.options.height}px`;
|
|
246
|
+
// Render the waveform
|
|
247
|
+
this.renderPeaks(channelData, width, height, pixelRatio);
|
|
248
|
+
this.channelData = channelData;
|
|
249
|
+
this.duration = duration;
|
|
177
250
|
}
|
|
178
|
-
|
|
251
|
+
reRender() {
|
|
252
|
+
// Return if the waveform has not been rendered yet
|
|
253
|
+
if (!this.channelData || !this.duration)
|
|
254
|
+
return;
|
|
179
255
|
// Remember the current cursor position
|
|
180
|
-
const oldCursorPosition = this.
|
|
181
|
-
|
|
182
|
-
this.render(channelData, duration);
|
|
256
|
+
const oldCursorPosition = this.progressWrapper.clientWidth;
|
|
257
|
+
// Set the new zoom level and re-render the waveform
|
|
258
|
+
this.render(this.channelData, this.duration);
|
|
183
259
|
// Adjust the scroll position so that the cursor stays in the same place
|
|
184
|
-
const newCursortPosition = this.
|
|
260
|
+
const newCursortPosition = this.progressWrapper.clientWidth;
|
|
185
261
|
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
186
262
|
}
|
|
263
|
+
zoom(minPxPerSec) {
|
|
264
|
+
this.options.minPxPerSec = minPxPerSec;
|
|
265
|
+
this.reRender();
|
|
266
|
+
}
|
|
187
267
|
renderProgress(progress, autoCenter = false) {
|
|
188
|
-
this.
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
268
|
+
this.progressWrapper.style.width = `${progress * 100}%`;
|
|
269
|
+
if (this.isScrolling && this.options.autoCenter) {
|
|
270
|
+
const { clientWidth, scrollLeft, scrollWidth } = this.scrollContainer;
|
|
271
|
+
const progressWidth = scrollWidth * progress;
|
|
272
|
+
const center = clientWidth / 2;
|
|
273
|
+
const minScroll = autoCenter ? center : clientWidth;
|
|
274
|
+
const minDiff = center / 20;
|
|
275
|
+
if (progressWidth > scrollLeft + minScroll || progressWidth < scrollLeft) {
|
|
276
|
+
// If the cursor is in viewport but not centered, scroll to the center slowly
|
|
277
|
+
if (progressWidth - (scrollLeft + center) >= minDiff && progressWidth < scrollLeft + clientWidth) {
|
|
278
|
+
this.scrollContainer.scrollLeft += minDiff;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// Otherwise, scroll to the center immediately
|
|
282
|
+
this.scrollContainer.scrollLeft = progressWidth - center;
|
|
283
|
+
}
|
|
195
284
|
}
|
|
196
285
|
}
|
|
197
286
|
}
|
|
198
|
-
getContainer() {
|
|
199
|
-
return this.scrollContainer;
|
|
200
|
-
}
|
|
201
287
|
}
|
|
202
288
|
export default Renderer;
|
package/dist/timer.d.ts
CHANGED
package/dist/timer.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
class Timer extends EventEmitter {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this.unsubscribe = () => undefined;
|
|
3
|
+
unsubscribe = () => undefined;
|
|
4
|
+
start() {
|
|
6
5
|
this.unsubscribe = this.on('tick', () => {
|
|
7
6
|
requestAnimationFrame(() => {
|
|
8
7
|
this.emit('tick');
|
|
@@ -10,6 +9,9 @@ class Timer extends EventEmitter {
|
|
|
10
9
|
});
|
|
11
10
|
this.emit('tick');
|
|
12
11
|
}
|
|
12
|
+
stop() {
|
|
13
|
+
this.unsubscribe();
|
|
14
|
+
}
|
|
13
15
|
destroy() {
|
|
14
16
|
this.unsubscribe();
|
|
15
17
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type RendererStyleOptions } from './renderer.js';
|
|
2
|
+
import Player from './player.js';
|
|
3
|
+
import type { GenericPlugin } from './base-plugin.js';
|
|
4
|
+
export type WaveSurferOptions = {
|
|
5
|
+
/** HTML element or CSS selector */
|
|
6
|
+
container: HTMLElement | string | null;
|
|
7
|
+
/** Height of the waveform in pixels */
|
|
8
|
+
height?: number;
|
|
9
|
+
/** The color of the waveform */
|
|
10
|
+
waveColor?: string;
|
|
11
|
+
/** The color of the progress mask */
|
|
12
|
+
progressColor?: string;
|
|
13
|
+
/** The color of the playpack cursor */
|
|
14
|
+
cursorColor?: string;
|
|
15
|
+
/** The cursor with */
|
|
16
|
+
cursorWidth?: number;
|
|
17
|
+
/** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
18
|
+
barWidth?: number;
|
|
19
|
+
/** Spacing between bars in pixels */
|
|
20
|
+
barGap?: number;
|
|
21
|
+
/** Rounded borders for bars */
|
|
22
|
+
barRadius?: number;
|
|
23
|
+
/** Minimum pixels per second of audio (zoom) */
|
|
24
|
+
minPxPerSec?: number;
|
|
25
|
+
/** Stretch the waveform to fill the container, true by default */
|
|
26
|
+
fillParent?: boolean;
|
|
27
|
+
/** Audio URL */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** Pre-computed audio data */
|
|
30
|
+
peaks?: Float32Array[] | Array<number[]>;
|
|
31
|
+
/** Pre-computed duration */
|
|
32
|
+
duration?: number;
|
|
33
|
+
/** Use an existing media element instead of creating one */
|
|
34
|
+
media?: HTMLMediaElement;
|
|
35
|
+
/** Play the audio on load */
|
|
36
|
+
autoplay?: boolean;
|
|
37
|
+
/** Is the waveform clickable? */
|
|
38
|
+
interact?: boolean;
|
|
39
|
+
/** Hide scrollbar **/
|
|
40
|
+
hideScrollbar?: boolean;
|
|
41
|
+
/** Audio rate */
|
|
42
|
+
audioRate?: number;
|
|
43
|
+
/** Keep scroll to the center of the waveform during playback */
|
|
44
|
+
autoCenter?: boolean;
|
|
45
|
+
/** Initialize plugins */
|
|
46
|
+
plugins?: GenericPlugin[];
|
|
47
|
+
};
|
|
48
|
+
declare const defaultOptions: {
|
|
49
|
+
height: number;
|
|
50
|
+
waveColor: string;
|
|
51
|
+
progressColor: string;
|
|
52
|
+
cursorWidth: number;
|
|
53
|
+
minPxPerSec: number;
|
|
54
|
+
fillParent: boolean;
|
|
55
|
+
interact: boolean;
|
|
56
|
+
autoCenter: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type WaveSurferEvents = {
|
|
59
|
+
decode: {
|
|
60
|
+
duration: number;
|
|
61
|
+
};
|
|
62
|
+
canplay: {
|
|
63
|
+
duration: number;
|
|
64
|
+
};
|
|
65
|
+
ready: {
|
|
66
|
+
duration: number;
|
|
67
|
+
};
|
|
68
|
+
play: void;
|
|
69
|
+
pause: void;
|
|
70
|
+
finish: void;
|
|
71
|
+
timeupdate: {
|
|
72
|
+
currentTime: number;
|
|
73
|
+
};
|
|
74
|
+
seeking: {
|
|
75
|
+
currentTime: number;
|
|
76
|
+
};
|
|
77
|
+
interaction: void;
|
|
78
|
+
zoom: {
|
|
79
|
+
minPxPerSec: number;
|
|
80
|
+
};
|
|
81
|
+
destroy: void;
|
|
82
|
+
};
|
|
83
|
+
export type WaveSurferPluginParams = {
|
|
84
|
+
wavesurfer: WaveSurfer;
|
|
85
|
+
container: HTMLElement;
|
|
86
|
+
wrapper: HTMLElement;
|
|
87
|
+
};
|
|
88
|
+
declare class WaveSurfer extends Player<WaveSurferEvents> {
|
|
89
|
+
options: WaveSurferOptions & typeof defaultOptions;
|
|
90
|
+
private fetcher;
|
|
91
|
+
private decoder;
|
|
92
|
+
private renderer;
|
|
93
|
+
private timer;
|
|
94
|
+
private plugins;
|
|
95
|
+
private decodedData;
|
|
96
|
+
private canPlay;
|
|
97
|
+
/** Create a new WaveSurfer instance */
|
|
98
|
+
static create(options: WaveSurferOptions): WaveSurfer;
|
|
99
|
+
/** Create a new WaveSurfer instance */
|
|
100
|
+
constructor(options: WaveSurferOptions);
|
|
101
|
+
setOptions(options: Partial<RendererStyleOptions>): void;
|
|
102
|
+
private initPlayerEvents;
|
|
103
|
+
private initRendererEvents;
|
|
104
|
+
private initTimerEvents;
|
|
105
|
+
private initReadyEvent;
|
|
106
|
+
private initPlugins;
|
|
107
|
+
/** Register a wavesurfer.js plugin */
|
|
108
|
+
registerPlugin<T extends GenericPlugin>(plugin: T): T;
|
|
109
|
+
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
110
|
+
load(url: string, channelData?: WaveSurferOptions['peaks'], duration?: number): Promise<void>;
|
|
111
|
+
private renderAudio;
|
|
112
|
+
/** Zoom in or out */
|
|
113
|
+
zoom(minPxPerSec: number): void;
|
|
114
|
+
/** Get the decoded audio data */
|
|
115
|
+
getDecodedData(): AudioBuffer | null;
|
|
116
|
+
getDuration(): number;
|
|
117
|
+
/** Toggle if the waveform should react to clicks */
|
|
118
|
+
toggleInteraction(isInteractive: boolean): void;
|
|
119
|
+
/** Seeks to a percentage of audio as [0..1] (0 = beginning, 1 = end) */
|
|
120
|
+
seekTo(progress: number): void;
|
|
121
|
+
/** Play or pause the audio */
|
|
122
|
+
playPause(): Promise<void>;
|
|
123
|
+
/** Stop the audio and go to the beginning */
|
|
124
|
+
stop(): void;
|
|
125
|
+
/** Skip N or -N seconds from the current positions */
|
|
126
|
+
skip(seconds: number): void;
|
|
127
|
+
/** Empty the waveform by loading a tiny silent audio */
|
|
128
|
+
empty(): void;
|
|
129
|
+
/** Unmount wavesurfer */
|
|
130
|
+
destroy(): void;
|
|
131
|
+
}
|
|
132
|
+
export default WaveSurfer;
|