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