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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type WaveSurferPluginParams } from '../base-plugin.js';
|
|
5
|
+
export type EnvelopePluginOptions = {
|
|
6
|
+
fadeInStart?: number;
|
|
7
|
+
fadeInEnd?: number;
|
|
8
|
+
fadeOutStart?: number;
|
|
9
|
+
fadeOutEnd?: number;
|
|
10
|
+
volume?: number;
|
|
11
|
+
lineWidth?: string;
|
|
12
|
+
lineColor?: string;
|
|
13
|
+
dragPointSize?: number;
|
|
14
|
+
dragPointFill?: string;
|
|
15
|
+
dragPointStroke?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const defaultOptions: {
|
|
18
|
+
fadeInStart: number;
|
|
19
|
+
fadeOutEnd: number;
|
|
20
|
+
fadeInEnd: number;
|
|
21
|
+
fadeOutStart: number;
|
|
22
|
+
lineWidth: number;
|
|
23
|
+
lineColor: string;
|
|
24
|
+
dragPointSize: number;
|
|
25
|
+
dragPointFill: string;
|
|
26
|
+
dragPointStroke: string;
|
|
27
|
+
};
|
|
28
|
+
export type EnvelopePluginEvents = {
|
|
29
|
+
'fade-in-change': [time: number];
|
|
30
|
+
'fade-out-change': [time: number];
|
|
31
|
+
'volume-change': [volume: number];
|
|
32
|
+
};
|
|
33
|
+
declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePluginOptions> {
|
|
34
|
+
protected options: EnvelopePluginOptions & typeof defaultOptions;
|
|
35
|
+
private svg;
|
|
36
|
+
private audioContext;
|
|
37
|
+
private gainNode;
|
|
38
|
+
private volume;
|
|
39
|
+
private isFadingIn;
|
|
40
|
+
private isFadingOut;
|
|
41
|
+
constructor(options: EnvelopePluginOptions);
|
|
42
|
+
static create(options: EnvelopePluginOptions): EnvelopePlugin;
|
|
43
|
+
/** Called by wavesurfer, don't call manually */
|
|
44
|
+
init(params: WaveSurferPluginParams): void;
|
|
45
|
+
private makeDraggable;
|
|
46
|
+
private renderPolyline;
|
|
47
|
+
private initSvg;
|
|
48
|
+
destroy(): void;
|
|
49
|
+
private initWebAudio;
|
|
50
|
+
private naturalVolume;
|
|
51
|
+
private onVolumeChange;
|
|
52
|
+
private initFadeEffects;
|
|
53
|
+
getCurrentVolume(): number;
|
|
54
|
+
setStartTime(time: number): void;
|
|
55
|
+
setEndTime(time: number): void;
|
|
56
|
+
}
|
|
57
|
+
export default EnvelopePlugin;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin from '../base-plugin.js';
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
fadeInStart: 0,
|
|
7
|
+
fadeOutEnd: 0,
|
|
8
|
+
fadeInEnd: 0,
|
|
9
|
+
fadeOutStart: 0,
|
|
10
|
+
lineWidth: 4,
|
|
11
|
+
lineColor: 'rgba(0, 0, 255, 0.5)',
|
|
12
|
+
dragPointSize: 10,
|
|
13
|
+
dragPointFill: 'rgba(255, 255, 255, 0.8)',
|
|
14
|
+
dragPointStroke: 'rgba(255, 255, 255, 0.8)',
|
|
15
|
+
};
|
|
16
|
+
class EnvelopePlugin extends BasePlugin {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
super(options);
|
|
19
|
+
this.svg = null;
|
|
20
|
+
this.audioContext = null;
|
|
21
|
+
this.gainNode = null;
|
|
22
|
+
this.volume = 1;
|
|
23
|
+
this.isFadingIn = false;
|
|
24
|
+
this.isFadingOut = false;
|
|
25
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
26
|
+
this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
|
|
27
|
+
this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
|
|
28
|
+
this.options.dragPointStroke = this.options.dragPointStroke || defaultOptions.dragPointStroke;
|
|
29
|
+
this.volume = this.options.volume ?? 1;
|
|
30
|
+
}
|
|
31
|
+
static create(options) {
|
|
32
|
+
return new EnvelopePlugin(options);
|
|
33
|
+
}
|
|
34
|
+
/** Called by wavesurfer, don't call manually */
|
|
35
|
+
init(params) {
|
|
36
|
+
super.init(params);
|
|
37
|
+
if (!this.wavesurfer) {
|
|
38
|
+
throw Error('WaveSurfer is not initialized');
|
|
39
|
+
}
|
|
40
|
+
this.subscriptions.push(this.wavesurfer.once('decode', (duration) => {
|
|
41
|
+
this.options.fadeInStart = this.options.fadeInStart || 0;
|
|
42
|
+
this.options.fadeOutEnd = this.options.fadeOutEnd || duration;
|
|
43
|
+
this.options.fadeInEnd = this.options.fadeInEnd || this.options.fadeInStart;
|
|
44
|
+
this.options.fadeOutStart = this.options.fadeOutStart || this.options.fadeOutEnd;
|
|
45
|
+
this.initWebAudio();
|
|
46
|
+
this.initSvg();
|
|
47
|
+
this.initFadeEffects();
|
|
48
|
+
}));
|
|
49
|
+
let delay;
|
|
50
|
+
this.subscriptions.push(this.wavesurfer.on('zoom', () => {
|
|
51
|
+
if (delay)
|
|
52
|
+
clearTimeout(delay);
|
|
53
|
+
delay = setTimeout(() => {
|
|
54
|
+
this.svg?.remove();
|
|
55
|
+
this.initSvg();
|
|
56
|
+
}, 100);
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
makeDraggable(draggable, onDrag) {
|
|
60
|
+
draggable.addEventListener('mousedown', (e) => {
|
|
61
|
+
let x = e.clientX;
|
|
62
|
+
let y = e.clientY;
|
|
63
|
+
const wasInteractive = this.wavesurfer?.options.interact || true;
|
|
64
|
+
let delay;
|
|
65
|
+
// Make the wavesurfer ignore clicks when we're dragging
|
|
66
|
+
this.wavesurfer?.toggleInteraction(false);
|
|
67
|
+
const move = (e) => {
|
|
68
|
+
const dx = e.clientX - x;
|
|
69
|
+
const dy = e.clientY - y;
|
|
70
|
+
x = e.clientX;
|
|
71
|
+
y = e.clientY;
|
|
72
|
+
onDrag(dx, dy);
|
|
73
|
+
};
|
|
74
|
+
const up = () => {
|
|
75
|
+
document.removeEventListener('mousemove', move);
|
|
76
|
+
document.removeEventListener('mouseup', up);
|
|
77
|
+
// Restore interactive state
|
|
78
|
+
if (delay)
|
|
79
|
+
clearTimeout(delay);
|
|
80
|
+
delay = setTimeout(() => {
|
|
81
|
+
this.wavesurfer?.toggleInteraction(wasInteractive);
|
|
82
|
+
}, 100);
|
|
83
|
+
};
|
|
84
|
+
document.addEventListener('mousemove', move);
|
|
85
|
+
document.addEventListener('mouseup', up);
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
renderPolyline() {
|
|
91
|
+
if (!this.svg || !this.wrapper || !this.wavesurfer)
|
|
92
|
+
return;
|
|
93
|
+
const polyline = this.svg.querySelector('polyline');
|
|
94
|
+
const points = polyline.points;
|
|
95
|
+
const top = points.getItem(1).y;
|
|
96
|
+
const width = this.wrapper.clientWidth;
|
|
97
|
+
const duration = this.wavesurfer.getDuration();
|
|
98
|
+
points.getItem(0).x = (this.options.fadeInStart / duration) * width;
|
|
99
|
+
points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
|
|
100
|
+
const line = this.svg.querySelector('line');
|
|
101
|
+
line.setAttribute('x1', points.getItem(1).x.toString());
|
|
102
|
+
line.setAttribute('x2', points.getItem(2).x.toString());
|
|
103
|
+
line.setAttribute('y1', top.toString());
|
|
104
|
+
line.setAttribute('y2', top.toString());
|
|
105
|
+
const circles = this.svg.querySelectorAll('circle');
|
|
106
|
+
for (let i = 0; i < circles.length; i++) {
|
|
107
|
+
const circle = circles[i];
|
|
108
|
+
const point = polyline.points.getItem(i + 1);
|
|
109
|
+
circle.setAttribute('cx', point.x.toString());
|
|
110
|
+
circle.setAttribute('cy', top.toString());
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
initSvg() {
|
|
114
|
+
if (!this.wrapper || !this.wavesurfer)
|
|
115
|
+
return;
|
|
116
|
+
const width = this.wrapper.clientWidth;
|
|
117
|
+
const height = this.wrapper.clientHeight;
|
|
118
|
+
const duration = this.wavesurfer.getDuration();
|
|
119
|
+
// SVG element
|
|
120
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
121
|
+
svg.setAttribute('width', '100%');
|
|
122
|
+
svg.setAttribute('height', '100%');
|
|
123
|
+
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
124
|
+
svg.setAttribute('preserveAspectRatio', 'none');
|
|
125
|
+
svg.setAttribute('style', 'position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;');
|
|
126
|
+
this.svg = svg;
|
|
127
|
+
// A polyline representing the envelope
|
|
128
|
+
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
129
|
+
polyline.setAttribute('points', '0,0 0,0 0,0 0,0');
|
|
130
|
+
polyline.setAttribute('stroke', this.options.lineColor);
|
|
131
|
+
polyline.setAttribute('stroke-width', this.options.lineWidth);
|
|
132
|
+
polyline.setAttribute('fill', 'none');
|
|
133
|
+
polyline.setAttribute('style', 'pointer-events: none');
|
|
134
|
+
svg.appendChild(polyline);
|
|
135
|
+
// Draggable top line
|
|
136
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
137
|
+
line.setAttribute('stroke', 'none');
|
|
138
|
+
line.setAttribute('stroke-width', (this.options.lineWidth * 3).toString());
|
|
139
|
+
line.setAttribute('style', 'cursor: ns-resize; pointer-events: all;');
|
|
140
|
+
svg.appendChild(line);
|
|
141
|
+
const points = polyline.points;
|
|
142
|
+
const offset = this.options.dragPointSize / 2;
|
|
143
|
+
const top = height - this.volume * height + offset;
|
|
144
|
+
points.getItem(0).x = (this.options.fadeInStart / duration) * width;
|
|
145
|
+
points.getItem(0).y = height;
|
|
146
|
+
points.getItem(1).x = (this.options.fadeInEnd / duration) * width;
|
|
147
|
+
points.getItem(1).y = top;
|
|
148
|
+
points.getItem(2).x = (this.options.fadeOutStart / duration) * width;
|
|
149
|
+
points.getItem(2).y = top;
|
|
150
|
+
points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
|
|
151
|
+
points.getItem(3).y = height;
|
|
152
|
+
// Drag points
|
|
153
|
+
const dragPoints = [1, 2];
|
|
154
|
+
dragPoints.forEach(() => {
|
|
155
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
156
|
+
circle.setAttribute('r', (this.options.dragPointSize / 2).toString());
|
|
157
|
+
circle.setAttribute('fill', this.options.dragPointFill);
|
|
158
|
+
circle.setAttribute('stroke', this.options.dragPointStroke || this.options.dragPointFill);
|
|
159
|
+
circle.setAttribute('stroke-width', '2');
|
|
160
|
+
circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
|
|
161
|
+
svg.appendChild(circle);
|
|
162
|
+
});
|
|
163
|
+
this.wrapper.appendChild(svg);
|
|
164
|
+
// Initial polyline
|
|
165
|
+
this.renderPolyline();
|
|
166
|
+
// On top line drag
|
|
167
|
+
const onDragY = (dy) => {
|
|
168
|
+
const newTop = points.getItem(1).y + dy - offset;
|
|
169
|
+
if (newTop < -0.5 || newTop > height)
|
|
170
|
+
return;
|
|
171
|
+
points.getItem(1).y = newTop + offset;
|
|
172
|
+
points.getItem(2).y = newTop + offset;
|
|
173
|
+
this.renderPolyline();
|
|
174
|
+
const newVolume = Math.min(1, Math.max(0, (height - newTop) / height));
|
|
175
|
+
this.onVolumeChange(newVolume);
|
|
176
|
+
this.renderPolyline();
|
|
177
|
+
};
|
|
178
|
+
// On points drag
|
|
179
|
+
const onDragX = (dx, dy, index) => {
|
|
180
|
+
const point = polyline.points.getItem(index);
|
|
181
|
+
const newX = point.x + dx;
|
|
182
|
+
const newTime = (newX / width) * duration;
|
|
183
|
+
if ((index === 1 && newTime > this.options.fadeOutStart) || newTime < this.options.fadeInStart)
|
|
184
|
+
return;
|
|
185
|
+
if ((index === 2 && newTime < this.options.fadeInEnd) || newTime > this.options.fadeOutEnd)
|
|
186
|
+
return;
|
|
187
|
+
point.x = newX;
|
|
188
|
+
if (index === 1) {
|
|
189
|
+
this.options.fadeInEnd = newTime;
|
|
190
|
+
this.emit('fade-in-change', newTime);
|
|
191
|
+
}
|
|
192
|
+
else if (index === 2) {
|
|
193
|
+
this.options.fadeOutStart = newTime;
|
|
194
|
+
this.emit('fade-out-change', newTime);
|
|
195
|
+
}
|
|
196
|
+
// Also allow dragging points vertically
|
|
197
|
+
if (dy > 1 || dy < -1) {
|
|
198
|
+
onDragY(dy);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this.renderPolyline();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
// Draggable top line of the polyline
|
|
205
|
+
this.makeDraggable(line, (_, dy) => onDragY(dy));
|
|
206
|
+
// Make each point draggable
|
|
207
|
+
const draggables = svg.querySelectorAll('circle');
|
|
208
|
+
for (let i = 0; i < draggables.length; i++) {
|
|
209
|
+
const index = i + 1;
|
|
210
|
+
this.makeDraggable(draggables[i], (dx, dy) => onDragX(dx, dy, index));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
destroy() {
|
|
214
|
+
this.svg?.remove();
|
|
215
|
+
super.destroy();
|
|
216
|
+
}
|
|
217
|
+
initWebAudio() {
|
|
218
|
+
const audio = this.wavesurfer?.getMediaElement();
|
|
219
|
+
if (!audio)
|
|
220
|
+
return null;
|
|
221
|
+
this.volume = this.options.volume ?? audio.volume;
|
|
222
|
+
// Create an AudioContext
|
|
223
|
+
const audioContext = new window.AudioContext();
|
|
224
|
+
// Create a GainNode for controlling the volume
|
|
225
|
+
this.gainNode = audioContext.createGain();
|
|
226
|
+
this.gainNode.gain.value = this.volume;
|
|
227
|
+
// Create a MediaElementAudioSourceNode using the audio element
|
|
228
|
+
const source = audioContext.createMediaElementSource(audio);
|
|
229
|
+
// Connect the source to the GainNode, and the GainNode to the destination (speakers)
|
|
230
|
+
source.connect(this.gainNode);
|
|
231
|
+
this.gainNode.connect(audioContext.destination);
|
|
232
|
+
this.audioContext = audioContext;
|
|
233
|
+
}
|
|
234
|
+
naturalVolume(value) {
|
|
235
|
+
const minValue = 0.0001;
|
|
236
|
+
const maxValue = 1;
|
|
237
|
+
const exponent = 3; // Adjust the exponent to change the curve of the volume control
|
|
238
|
+
const interpolatedValue = minValue + (maxValue - minValue) * Math.pow(value, exponent);
|
|
239
|
+
return interpolatedValue;
|
|
240
|
+
}
|
|
241
|
+
onVolumeChange(volume) {
|
|
242
|
+
volume = this.naturalVolume(volume);
|
|
243
|
+
this.volume = volume;
|
|
244
|
+
this.emit('volume-change', volume);
|
|
245
|
+
if (!this.gainNode)
|
|
246
|
+
return;
|
|
247
|
+
this.gainNode.gain.value = volume;
|
|
248
|
+
}
|
|
249
|
+
initFadeEffects() {
|
|
250
|
+
if (!this.audioContext || !this.wavesurfer)
|
|
251
|
+
return;
|
|
252
|
+
const unsub = this.wavesurfer.on('timeupdate', (currentTime) => {
|
|
253
|
+
if (!this.audioContext || !this.gainNode)
|
|
254
|
+
return;
|
|
255
|
+
if (!this.wavesurfer?.isPlaying())
|
|
256
|
+
return;
|
|
257
|
+
if (this.audioContext.state === 'suspended') {
|
|
258
|
+
this.audioContext.resume();
|
|
259
|
+
}
|
|
260
|
+
// Fade in
|
|
261
|
+
if (!this.isFadingIn && currentTime >= this.options.fadeInStart && currentTime <= this.options.fadeInEnd) {
|
|
262
|
+
this.isFadingIn = true;
|
|
263
|
+
// Set the initial gain (volume) to 0 (silent)
|
|
264
|
+
this.gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
|
|
265
|
+
// Set the target gain (volume) to 1 (full volume) over N seconds
|
|
266
|
+
this.gainNode.gain.linearRampToValueAtTime(this.volume, this.audioContext.currentTime + (this.options.fadeInEnd - currentTime));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// Fade out
|
|
270
|
+
if (!this.isFadingOut && currentTime >= this.options.fadeOutStart && currentTime <= this.options.fadeOutEnd) {
|
|
271
|
+
this.isFadingOut = true;
|
|
272
|
+
// Set the target gain (volume) to 0 (silent) over N seconds
|
|
273
|
+
this.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + (this.options.fadeOutEnd - currentTime));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Reset fade in/out
|
|
277
|
+
let cancelRamp = false;
|
|
278
|
+
if (this.isFadingIn && (currentTime < this.options.fadeInStart || currentTime > this.options.fadeInEnd)) {
|
|
279
|
+
this.isFadingIn = false;
|
|
280
|
+
cancelRamp = true;
|
|
281
|
+
}
|
|
282
|
+
if (this.isFadingOut && (currentTime < this.options.fadeOutStart || currentTime >= this.options.fadeOutEnd)) {
|
|
283
|
+
this.isFadingOut = false;
|
|
284
|
+
cancelRamp = true;
|
|
285
|
+
}
|
|
286
|
+
if (cancelRamp) {
|
|
287
|
+
this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);
|
|
288
|
+
this.gainNode.gain.value = this.volume;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
this.subscriptions.push(unsub);
|
|
292
|
+
}
|
|
293
|
+
getCurrentVolume() {
|
|
294
|
+
return this.gainNode ? this.gainNode.gain.value : this.volume;
|
|
295
|
+
}
|
|
296
|
+
setStartTime(time) {
|
|
297
|
+
this.options.fadeInStart = time;
|
|
298
|
+
this.renderPolyline();
|
|
299
|
+
}
|
|
300
|
+
setEndTime(time) {
|
|
301
|
+
this.options.fadeOutEnd = time;
|
|
302
|
+
this.renderPolyline();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
export default EnvelopePlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Envelope=e():t.Envelope=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={284:(t,e,i)=>{i.d(e,{Z:()=>o});var s=i(139);class n extends s.Z{constructor(t){super(),this.subscriptions=[],this.options=t}init(t){this.wavesurfer=t.wavesurfer,this.container=t.container,this.wrapper=t.wrapper}destroy(){this.subscriptions.forEach((t=>t()))}}const o=n},139:(t,e,i)=>{i.d(e,{Z:()=>s});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}}},e={};function i(s){var n=e[s];if(void 0!==n)return n.exports;var o=e[s]={exports:{}};return t[s](o,o.exports,i),o.exports}i.d=(t,e)=>{for(var s in e)i.o(e,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var s={};return(()=>{i.d(s,{default:()=>o});var t=i(284);const e={fadeInStart:0,fadeOutEnd:0,fadeInEnd:0,fadeOutStart:0,lineWidth:4,lineColor:"rgba(0, 0, 255, 0.5)",dragPointSize:10,dragPointFill:"rgba(255, 255, 255, 0.8)",dragPointStroke:"rgba(255, 255, 255, 0.8)"};class n extends t.Z{constructor(t){super(t),this.svg=null,this.audioContext=null,this.gainNode=null,this.volume=1,this.isFadingIn=!1,this.isFadingOut=!1,this.options=Object.assign({},e,t),this.options.lineColor=this.options.lineColor||e.lineColor,this.options.dragPointFill=this.options.dragPointFill||e.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||e.dragPointStroke,this.volume=this.options.volume??1}static create(t){return new n(t)}init(t){if(super.init(t),!this.wavesurfer)throw Error("WaveSurfer is not initialized");let e;this.subscriptions.push(this.wavesurfer.once("decode",(t=>{this.options.fadeInStart=this.options.fadeInStart||0,this.options.fadeOutEnd=this.options.fadeOutEnd||t,this.options.fadeInEnd=this.options.fadeInEnd||this.options.fadeInStart,this.options.fadeOutStart=this.options.fadeOutStart||this.options.fadeOutEnd,this.initWebAudio(),this.initSvg(),this.initFadeEffects()}))),this.subscriptions.push(this.wavesurfer.on("zoom",(()=>{e&&clearTimeout(e),e=setTimeout((()=>{this.svg?.remove(),this.initSvg()}),100)})))}makeDraggable(t,e){t.addEventListener("mousedown",(t=>{let i=t.clientX,s=t.clientY;const n=this.wavesurfer?.options.interact||!0;let o;this.wavesurfer?.toggleInteraction(!1);const r=t=>{const n=t.clientX-i,o=t.clientY-s;i=t.clientX,s=t.clientY,e(n,o)},a=()=>{document.removeEventListener("mousemove",r),document.removeEventListener("mouseup",a),o&&clearTimeout(o),o=setTimeout((()=>{this.wavesurfer?.toggleInteraction(n)}),100)};document.addEventListener("mousemove",r),document.addEventListener("mouseup",a),t.preventDefault(),t.stopPropagation()}))}renderPolyline(){if(!this.svg||!this.wrapper||!this.wavesurfer)return;const t=this.svg.querySelector("polyline"),e=t.points,i=e.getItem(1).y,s=this.wrapper.clientWidth,n=this.wavesurfer.getDuration();e.getItem(0).x=this.options.fadeInStart/n*s,e.getItem(3).x=this.options.fadeOutEnd/n*s;const o=this.svg.querySelector("line");o.setAttribute("x1",e.getItem(1).x.toString()),o.setAttribute("x2",e.getItem(2).x.toString()),o.setAttribute("y1",i.toString()),o.setAttribute("y2",i.toString());const r=this.svg.querySelectorAll("circle");for(let e=0;e<r.length;e++){const s=r[e],n=t.points.getItem(e+1);s.setAttribute("cx",n.x.toString()),s.setAttribute("cy",i.toString())}}initSvg(){if(!this.wrapper||!this.wavesurfer)return;const t=this.wrapper.clientWidth,e=this.wrapper.clientHeight,i=this.wavesurfer.getDuration(),s=document.createElementNS("http://www.w3.org/2000/svg","svg");s.setAttribute("width","100%"),s.setAttribute("height","100%"),s.setAttribute("viewBox",`0 0 ${t} ${e}`),s.setAttribute("preserveAspectRatio","none"),s.setAttribute("style","position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;"),this.svg=s;const n=document.createElementNS("http://www.w3.org/2000/svg","polyline");n.setAttribute("points","0,0 0,0 0,0 0,0"),n.setAttribute("stroke",this.options.lineColor),n.setAttribute("stroke-width",this.options.lineWidth),n.setAttribute("fill","none"),n.setAttribute("style","pointer-events: none"),s.appendChild(n);const o=document.createElementNS("http://www.w3.org/2000/svg","line");o.setAttribute("stroke","none"),o.setAttribute("stroke-width",(3*this.options.lineWidth).toString()),o.setAttribute("style","cursor: ns-resize; pointer-events: all;"),s.appendChild(o);const r=n.points,a=this.options.dragPointSize/2,h=e-this.volume*e+a;r.getItem(0).x=this.options.fadeInStart/i*t,r.getItem(0).y=e,r.getItem(1).x=this.options.fadeInEnd/i*t,r.getItem(1).y=h,r.getItem(2).x=this.options.fadeOutStart/i*t,r.getItem(2).y=h,r.getItem(3).x=this.options.fadeOutEnd/i*t,r.getItem(3).y=e,[1,2].forEach((()=>{const t=document.createElementNS("http://www.w3.org/2000/svg","circle");t.setAttribute("r",(this.options.dragPointSize/2).toString()),t.setAttribute("fill",this.options.dragPointFill),t.setAttribute("stroke",this.options.dragPointStroke||this.options.dragPointFill),t.setAttribute("stroke-width","2"),t.setAttribute("style","cursor: ew-resize; pointer-events: all;"),s.appendChild(t)})),this.wrapper.appendChild(s),this.renderPolyline();const u=t=>{const i=r.getItem(1).y+t-a;if(i<-.5||i>e)return;r.getItem(1).y=i+a,r.getItem(2).y=i+a,this.renderPolyline();const s=Math.min(1,Math.max(0,(e-i)/e));this.onVolumeChange(s),this.renderPolyline()},d=(e,s,o)=>{const r=n.points.getItem(o),a=r.x+e,h=a/t*i;1===o&&h>this.options.fadeOutStart||h<this.options.fadeInStart||2===o&&h<this.options.fadeInEnd||h>this.options.fadeOutEnd||(r.x=a,1===o?(this.options.fadeInEnd=h,this.emit("fade-in-change",h)):2===o&&(this.options.fadeOutStart=h,this.emit("fade-out-change",h)),s>1||s<-1?u(s):this.renderPolyline())};this.makeDraggable(o,((t,e)=>u(e)));const l=s.querySelectorAll("circle");for(let t=0;t<l.length;t++){const e=t+1;this.makeDraggable(l[t],((t,i)=>d(t,i,e)))}}destroy(){this.svg?.remove(),super.destroy()}initWebAudio(){const t=this.wavesurfer?.getMediaElement();if(!t)return null;this.volume=this.options.volume??t.volume;const e=new window.AudioContext;this.gainNode=e.createGain(),this.gainNode.gain.value=this.volume,e.createMediaElementSource(t).connect(this.gainNode),this.gainNode.connect(e.destination),this.audioContext=e}naturalVolume(t){return 1e-4+.9999*Math.pow(t,3)}onVolumeChange(t){t=this.naturalVolume(t),this.volume=t,this.emit("volume-change",t),this.gainNode&&(this.gainNode.gain.value=t)}initFadeEffects(){if(!this.audioContext||!this.wavesurfer)return;const t=this.wavesurfer.on("timeupdate",(t=>{if(!this.audioContext||!this.gainNode)return;if(!this.wavesurfer?.isPlaying())return;if("suspended"===this.audioContext.state&&this.audioContext.resume(),!this.isFadingIn&&t>=this.options.fadeInStart&&t<=this.options.fadeInEnd)return this.isFadingIn=!0,this.gainNode.gain.setValueAtTime(0,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(this.volume,this.audioContext.currentTime+(this.options.fadeInEnd-t));if(!this.isFadingOut&&t>=this.options.fadeOutStart&&t<=this.options.fadeOutEnd)return this.isFadingOut=!0,void this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+(this.options.fadeOutEnd-t));let e=!1;this.isFadingIn&&(t<this.options.fadeInStart||t>this.options.fadeInEnd)&&(this.isFadingIn=!1,e=!0),this.isFadingOut&&(t<this.options.fadeOutStart||t>=this.options.fadeOutEnd)&&(this.isFadingOut=!1,e=!0),e&&(this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime),this.gainNode.gain.value=this.volume)}));this.subscriptions.push(t)}getCurrentVolume(){return this.gainNode?this.gainNode.gain.value:this.volume}setStartTime(t){this.options.fadeInStart=t,this.renderPolyline()}setEndTime(t){this.options.fadeOutEnd=t,this.renderPolyline()}}const o=n})(),s.default})()));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimap is a tiny copy of the main waveform serving as a navigation tool.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type WaveSurferPluginParams } from '../base-plugin.js';
|
|
5
|
+
import { type WaveSurferOptions } from '../wavesurfer.js';
|
|
6
|
+
export type MinimapPluginOptions = {
|
|
7
|
+
overlayColor?: string;
|
|
8
|
+
insertPosition?: InsertPosition;
|
|
9
|
+
} & WaveSurferOptions;
|
|
10
|
+
declare const defaultOptions: {
|
|
11
|
+
height: number;
|
|
12
|
+
overlayColor: string;
|
|
13
|
+
insertPosition: string;
|
|
14
|
+
};
|
|
15
|
+
export type MinimapPluginEvents = {
|
|
16
|
+
ready: [];
|
|
17
|
+
interaction: [];
|
|
18
|
+
};
|
|
19
|
+
declare class MinimapPlugin extends BasePlugin<MinimapPluginEvents, MinimapPluginOptions> {
|
|
20
|
+
protected options: MinimapPluginOptions & typeof defaultOptions;
|
|
21
|
+
private minimapWrapper;
|
|
22
|
+
private miniWavesurfer;
|
|
23
|
+
private overlay;
|
|
24
|
+
constructor(options: MinimapPluginOptions);
|
|
25
|
+
static create(options: MinimapPluginOptions): MinimapPlugin;
|
|
26
|
+
/** Called by wavesurfer, don't call manually */
|
|
27
|
+
init(params: WaveSurferPluginParams): void;
|
|
28
|
+
private initMinimapWrapper;
|
|
29
|
+
private initOverlay;
|
|
30
|
+
private initMinimap;
|
|
31
|
+
/** Unmount */
|
|
32
|
+
destroy(): void;
|
|
33
|
+
}
|
|
34
|
+
export default MinimapPlugin;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimap is a tiny copy of the main waveform serving as a navigation tool.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin from '../base-plugin.js';
|
|
5
|
+
import WaveSurfer from '../wavesurfer.js';
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
height: 50,
|
|
8
|
+
overlayColor: 'rgba(100, 100, 100, 0.1)',
|
|
9
|
+
insertPosition: 'afterend',
|
|
10
|
+
};
|
|
11
|
+
class MinimapPlugin extends BasePlugin {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options);
|
|
14
|
+
this.miniWavesurfer = null;
|
|
15
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
16
|
+
this.minimapWrapper = this.initMinimapWrapper();
|
|
17
|
+
this.overlay = this.initOverlay();
|
|
18
|
+
}
|
|
19
|
+
static create(options) {
|
|
20
|
+
return new MinimapPlugin(options);
|
|
21
|
+
}
|
|
22
|
+
/** Called by wavesurfer, don't call manually */
|
|
23
|
+
init(params) {
|
|
24
|
+
super.init(params);
|
|
25
|
+
if (!this.wavesurfer) {
|
|
26
|
+
throw Error('WaveSurfer is not initialized');
|
|
27
|
+
}
|
|
28
|
+
if (this.options.container) {
|
|
29
|
+
let container = null;
|
|
30
|
+
if (typeof this.options.container === 'string') {
|
|
31
|
+
container = document.querySelector(this.options.container);
|
|
32
|
+
}
|
|
33
|
+
else if (this.options.container instanceof HTMLElement) {
|
|
34
|
+
container = this.options.container;
|
|
35
|
+
}
|
|
36
|
+
container?.appendChild(this.minimapWrapper);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.container?.insertAdjacentElement(this.options.insertPosition, this.minimapWrapper);
|
|
40
|
+
}
|
|
41
|
+
this.subscriptions.push(this.wavesurfer.on('decode', () => {
|
|
42
|
+
this.initMinimap();
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
initMinimapWrapper() {
|
|
46
|
+
const div = document.createElement('div');
|
|
47
|
+
div.style.position = 'relative';
|
|
48
|
+
return div;
|
|
49
|
+
}
|
|
50
|
+
initOverlay() {
|
|
51
|
+
const div = document.createElement('div');
|
|
52
|
+
div.setAttribute('style', 'position: absolute; z-index: 2; left: 0; top: 0; bottom: 0;');
|
|
53
|
+
div.style.backgroundColor = this.options.overlayColor;
|
|
54
|
+
this.minimapWrapper.appendChild(div);
|
|
55
|
+
return div;
|
|
56
|
+
}
|
|
57
|
+
initMinimap() {
|
|
58
|
+
if (!this.wavesurfer || !this.wrapper)
|
|
59
|
+
return;
|
|
60
|
+
const data = this.wavesurfer.getDecodedData();
|
|
61
|
+
const media = this.wavesurfer.getMediaElement();
|
|
62
|
+
if (!data || !media)
|
|
63
|
+
return;
|
|
64
|
+
this.miniWavesurfer = WaveSurfer.create({
|
|
65
|
+
...this.options,
|
|
66
|
+
container: this.minimapWrapper,
|
|
67
|
+
minPxPerSec: 0,
|
|
68
|
+
fillParent: true,
|
|
69
|
+
media,
|
|
70
|
+
peaks: [data.getChannelData(0)],
|
|
71
|
+
duration: data.duration,
|
|
72
|
+
});
|
|
73
|
+
const overlayWidth = Math.round((this.minimapWrapper.clientWidth / this.wrapper.clientWidth) * 100);
|
|
74
|
+
this.overlay.style.width = `${overlayWidth}%`;
|
|
75
|
+
this.subscriptions.push(this.wavesurfer.on('timeupdate', (currentTime) => {
|
|
76
|
+
const offset = Math.max(0, Math.min((currentTime / data.duration) * 100 - overlayWidth / 2, 100 - overlayWidth)).toFixed(2);
|
|
77
|
+
this.overlay.style.left = `${offset}%`;
|
|
78
|
+
}));
|
|
79
|
+
this.subscriptions.push(this.miniWavesurfer.on('interaction', () => {
|
|
80
|
+
if (this.wavesurfer && this.miniWavesurfer) {
|
|
81
|
+
this.wavesurfer.setTime(this.miniWavesurfer.getCurrentTime());
|
|
82
|
+
}
|
|
83
|
+
if (this.container) {
|
|
84
|
+
this.container.scrollLeft =
|
|
85
|
+
(this.minimapWrapper.scrollLeft / this.minimapWrapper.scrollWidth) * this.container.scrollWidth;
|
|
86
|
+
}
|
|
87
|
+
this.emit('interaction');
|
|
88
|
+
}), this.miniWavesurfer.on('ready', () => {
|
|
89
|
+
this.emit('ready');
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
/** Unmount */
|
|
93
|
+
destroy() {
|
|
94
|
+
this.miniWavesurfer?.destroy();
|
|
95
|
+
this.minimapWrapper.remove();
|
|
96
|
+
super.destroy();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export default MinimapPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Minimap=e():t.Minimap=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>m});const i=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}},s=class extends i{constructor(t){super(),this.subscriptions=[],this.options=t}init(t){this.wavesurfer=t.wavesurfer,this.container=t.container,this.wrapper=t.wrapper}destroy(){this.subscriptions.forEach((t=>t()))}},n={decode:async function(t){let e;try{e=new AudioContext({sampleRate:3e3})}catch(t){e=new AudioContext({sampleRate:8e3})}const i=e.decodeAudioData(t);return i.finally((()=>e.close())),i},createBuffer:function(t,e){if("number"==typeof t[0]&&(t=[t]),t[0].some((t=>t>1||t<-1))){const e=Math.max(...t[0]);t=t.map((t=>t.map((t=>t/e))))}return{length:t[0].length,duration:e,numberOfChannels:t.length,sampleRate:t[0].length/e,getChannelData:e=>t?.[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}};class r extends i{constructor(t,e){super(),this.options={height:0},this.timeout=null,this.isScrolling=!1,this.channelData=null,this.duration=null,this.resizeObserver=null,this.options={...e};let i=null;if("string"==typeof t.container?i=document.querySelector(t.container):t.container instanceof HTMLElement&&(i=t.container),!i)throw new Error("Container not found");const[s,n]=this.initHtml();i.appendChild(s),this.container=s,this.scrollContainer=n.querySelector(".scroll"),this.wrapper=n.querySelector(".wrapper"),this.canvasWrapper=n.querySelector(".canvases"),this.progressWrapper=n.querySelector(".progress"),this.wrapper.addEventListener("click",(t=>{const e=this.wrapper.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",i)})),this.resizeObserver=new ResizeObserver((()=>{this.delay((()=>this.reRender()),100)})),this.resizeObserver.observe(this.scrollContainer)}initHtml(){const t=document.createElement("div"),e=t.attachShadow({mode:"open"});return e.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: hidden;\n width: 100%;\n position: relative;\n }\n :host .noScrollbar {\n scrollbar-color: transparent;\n scrollbar-width: none;\n }\n :host .noScrollbar::-webkit-scrollbar {\n display: none;\n -webkit-appearance: none;\n }\n :host .wrapper {\n position: relative;\n overflow: visible;\n z-index: 2;\n }\n :host .canvases {\n position: relative;\n height: ${this.options.height}px;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n height: ${this.options.height}px;\n }\n :host .progress {\n pointer-events: none;\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n width: 0;\n height: 100%;\n overflow: hidden;\n box-sizing: border-box;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getContainer(){return this.scrollContainer}getWrapper(){return this.wrapper}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}delay(t,e=10){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}async renderPeaks(t,e,i,s){const n=null!=this.options.barWidth?this.options.barWidth*s:1,o=null!=this.options.barGap?this.options.barGap*s:this.options.barWidth?n/2:0,a=this.options.barRadius??0,h=this.options.barHeight??1,l=t[0],d=l.length,c=Math.floor(e/(n+o))/d,p=i/2,u=1===t.length,m=u?l:t[1],g=u&&m.some((t=>t<0)),y=(t,i)=>{let r=0,u=0,y=0;const f=document.createElement("canvas");f.width=Math.round(e*(i-t)/d),f.height=this.options.height,f.style.width=`${Math.floor(f.width/s)}px`,f.style.height=`${this.options.height}px`,f.style.left=`${Math.floor(t*e/s/d)}px`,this.canvasWrapper.appendChild(f);const v=f.getContext("2d",{desynchronized:!0});v.beginPath(),v.fillStyle=this.options.waveColor??"",v.roundRect||(v.roundRect=v.fillRect);for(let e=t;e<i;e++){const i=Math.round((e-t)*c);if(i>r){const t=Math.round(u*p*h),e=Math.round(y*p*h);v.roundRect(r*(n+o),p-t,n,t+(e||1),a),r=i,u=0,y=0}const s=g?l[e]:Math.abs(l[e]),d=g?m[e]:Math.abs(m[e]);s>u&&(u=s),(g?d<-y:d>y)&&(y=d<0?-d:d)}v.fill(),v.closePath();const b=f.cloneNode();this.progressWrapper.appendChild(b);const w=b.getContext("2d",{desynchronized:!0});f.width>0&&f.height>0&&w.drawImage(f,0,0),w.globalCompositeOperation="source-in",w.fillStyle=this.options.progressColor??"",w.fillRect(0,0,f.width,f.height)};this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="";const{scrollLeft:f,scrollWidth:v,clientWidth:b}=this.scrollContainer,w=d/v;let C=Math.min(r.MAX_CANVAS_WIDTH,b);C-=C%((n+o)/s);const W=Math.floor(Math.abs(f)*w),M=Math.ceil(W+C*w);y(W,M);const P=M-W;for(let t=M;t<d;t+=P)await this.delay((()=>{y(t,Math.min(d,t+P))}));for(let t=W-1;t>=0;t-=P)await this.delay((()=>{y(Math.max(0,t-P),t)}))}render(t,e){const i=window.devicePixelRatio||1,s=this.scrollContainer.clientWidth,n=Math.ceil(e*(this.options.minPxPerSec||0));this.isScrolling=n>s;const r=this.options.fillParent&&!this.isScrolling,o=(r?s:n)*i,{height:a}=this.options;this.wrapper.style.width=r?"100%":`${n}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.progressWrapper.style.borderRightStyle="solid",this.progressWrapper.style.borderRightColor=`${this.options.cursorColor||this.options.progressColor}`,this.progressWrapper.style.borderRightWidth=`${this.options.cursorWidth}px`,this.canvasWrapper.style.height=`${this.options.height}px`,this.renderPeaks(t,o,a,i),this.channelData=t,this.duration=e}reRender(){if(!this.channelData||!this.duration)return;const t=this.progressWrapper.clientWidth;this.render(this.channelData,this.duration);const e=this.progressWrapper.clientWidth;this.scrollContainer.scrollLeft+=e-t}zoom(t){this.options.minPxPerSec=t,this.reRender()}renderProgress(t,e=!1){if(!isNaN(t)&&(this.progressWrapper.style.width=100*t+"%",this.isScrolling&&this.options.autoCenter)){const{clientWidth:i,scrollLeft:s,scrollWidth:n}=this.scrollContainer,r=n*t,o=i/2,a=o/20;(r>s+(e?o:i)||r<s)&&(r-(s+o)>=a&&r<s+i?this.scrollContainer.scrollLeft+=a:this.scrollContainer.scrollLeft=r-o)}}}r.MAX_CANVAS_WIDTH=4e3;const o=r,a=class extends i{constructor(t){super(),this.subscriptions=[],this.isExternalMedia=!1,t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}onceMediaEvent(t,e){return this.onMediaEvent(t,e,{once:!0})}loadUrl(t){this.media.src=t}destroy(){this.media.pause(),this.subscriptions.forEach((t=>t())),this.isExternalMedia||this.media.remove()}play(){return this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}setTime(t){this.media.currentTime=t}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}},h=class extends i{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},l={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoCenter:!0};class d extends a{static create(t){return new d(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.canPlay=!1,this.options=Object.assign({},l,t),this.fetcher=new class{async load(t){return fetch(t).then((t=>t.arrayBuffer()))}},this.timer=new h,this.renderer=new o({container:this.options.container},this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent(),this.initPlugins();const e=this.options.url||this.options.media?.src;e&&this.load(e,this.options.peaks,this.options.duration)}setOptions(t){this.options={...this.options,...t},this.renderer.setOptions(this.options)}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",t)})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop(),this.getCurrentTime()>=this.getDuration()&&this.emit("finish")})),this.onMediaEvent("canplay",(()=>{this.canPlay=!0,this.emit("canplay",this.getDuration())})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(t=>{this.options.interact&&(this.canPlay&&this.seekTo(t),this.emit("interaction"))})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",t),this.emit("audioprocess",t)})))}initReadyEvent(){const t=()=>{this.decodedData&&this.canPlay&&this.emit("ready",this.getDuration())};this.subscriptions.push(this.on("decode",t),this.on("canplay",t))}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init({wavesurfer:this,container:this.renderer.getContainer(),wrapper:this.renderer.getWrapper()}),this.plugins.push(t),t}getActivePlugins(){return this.plugins}async load(t,e,i){if(this.decodedData=null,this.canPlay=!1,this.loadUrl(t),this.emit("load",t),e)i||(i=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getMediaElement().duration)))}))||0),this.decodedData=n.createBuffer(e,i);else{const e=await this.fetcher.load(t);this.decodedData=await n.decode(e)}this.renderAudio(),this.emit("decode",this.getDuration()),this.emit("redraw")}renderAudio(){if(!this.decodedData)return;const t=[this.decodedData.getChannelData(0)];this.decodedData.numberOfChannels>1&&t.push(this.decodedData.getChannelData(1)),this.renderer.render(t,this.decodedData.duration)}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}getDuration(){const t=super.getDuration();return t>0&&t<1/0?t:this.decodedData?.duration||0}toggleInteraction(t){this.options.interact=t}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}playPause(){return this.isPlaying()?(this.pause(),Promise.resolve()):this.play()}stop(){this.pause(),this.setTime(0)}skip(t){this.setTime(this.getCurrentTime()+t)}empty(){this.load("",[[0]],.001)}destroy(){this.emit("destroy"),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const c=d,p={height:50,overlayColor:"rgba(100, 100, 100, 0.1)",insertPosition:"afterend"};class u extends s{constructor(t){super(t),this.miniWavesurfer=null,this.options=Object.assign({},p,t),this.minimapWrapper=this.initMinimapWrapper(),this.overlay=this.initOverlay()}static create(t){return new u(t)}init(t){if(super.init(t),!this.wavesurfer)throw Error("WaveSurfer is not initialized");if(this.options.container){let t=null;"string"==typeof this.options.container?t=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(t=this.options.container),t?.appendChild(this.minimapWrapper)}else this.container?.insertAdjacentElement(this.options.insertPosition,this.minimapWrapper);this.subscriptions.push(this.wavesurfer.on("decode",(()=>{this.initMinimap()})))}initMinimapWrapper(){const t=document.createElement("div");return t.style.position="relative",t}initOverlay(){const t=document.createElement("div");return t.setAttribute("style","position: absolute; z-index: 2; left: 0; top: 0; bottom: 0;"),t.style.backgroundColor=this.options.overlayColor,this.minimapWrapper.appendChild(t),t}initMinimap(){if(!this.wavesurfer||!this.wrapper)return;const t=this.wavesurfer.getDecodedData(),e=this.wavesurfer.getMediaElement();if(!t||!e)return;this.miniWavesurfer=c.create({...this.options,container:this.minimapWrapper,minPxPerSec:0,fillParent:!0,media:e,peaks:[t.getChannelData(0)],duration:t.duration});const i=Math.round(this.minimapWrapper.clientWidth/this.wrapper.clientWidth*100);this.overlay.style.width=`${i}%`,this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{const s=Math.max(0,Math.min(e/t.duration*100-i/2,100-i)).toFixed(2);this.overlay.style.left=`${s}%`}))),this.subscriptions.push(this.miniWavesurfer.on("interaction",(()=>{this.wavesurfer&&this.miniWavesurfer&&this.wavesurfer.setTime(this.miniWavesurfer.getCurrentTime()),this.container&&(this.container.scrollLeft=this.minimapWrapper.scrollLeft/this.minimapWrapper.scrollWidth*this.container.scrollWidth),this.emit("interaction")})),this.miniWavesurfer.on("ready",(()=>{this.emit("ready")})))}destroy(){this.miniWavesurfer?.destroy(),this.minimapWrapper.remove(),super.destroy()}}const m=u;return e.default})()));
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multitrack isn't a plugin, but rather a helper class for creating a multitrack audio player.
|
|
3
|
+
* Individual tracks are synced and played together. They can be dragged to set their start position.
|
|
4
|
+
*/
|
|
5
|
+
import { type WaveSurferOptions } from '../wavesurfer.js';
|
|
6
|
+
import { type EnvelopePluginOptions } from './envelope.js';
|
|
7
|
+
import EventEmitter from '../event-emitter.js';
|
|
8
|
+
export type TrackId = string | number;
|
|
9
|
+
export type TrackOptions = {
|
|
10
|
+
id: TrackId;
|
|
11
|
+
url?: string;
|
|
12
|
+
peaks?: WaveSurferOptions['peaks'];
|
|
13
|
+
draggable?: boolean;
|
|
14
|
+
startPosition: number;
|
|
15
|
+
startCue?: number;
|
|
16
|
+
endCue?: number;
|
|
17
|
+
fadeInEnd?: number;
|
|
18
|
+
fadeOutStart?: number;
|
|
19
|
+
volume?: number;
|
|
20
|
+
markers?: Array<{
|
|
21
|
+
time: number;
|
|
22
|
+
label?: string;
|
|
23
|
+
color?: string;
|
|
24
|
+
}>;
|
|
25
|
+
intro?: {
|
|
26
|
+
endTime: number;
|
|
27
|
+
label?: string;
|
|
28
|
+
color?: string;
|
|
29
|
+
};
|
|
30
|
+
options?: WaveSurferOptions;
|
|
31
|
+
};
|
|
32
|
+
export type MultitrackOptions = {
|
|
33
|
+
container: HTMLElement;
|
|
34
|
+
minPxPerSec?: number;
|
|
35
|
+
cursorColor?: string;
|
|
36
|
+
cursorWidth?: number;
|
|
37
|
+
trackBackground?: string;
|
|
38
|
+
trackBorderColor?: string;
|
|
39
|
+
rightButtonDrag?: boolean;
|
|
40
|
+
envelopeOptions?: EnvelopePluginOptions;
|
|
41
|
+
};
|
|
42
|
+
export type MultitrackEvents = {
|
|
43
|
+
canplay: [];
|
|
44
|
+
'start-position-change': [{
|
|
45
|
+
id: TrackId;
|
|
46
|
+
startPosition: number;
|
|
47
|
+
}];
|
|
48
|
+
'start-cue-change': [{
|
|
49
|
+
id: TrackId;
|
|
50
|
+
startCue: number;
|
|
51
|
+
}];
|
|
52
|
+
'end-cue-change': [{
|
|
53
|
+
id: TrackId;
|
|
54
|
+
endCue: number;
|
|
55
|
+
}];
|
|
56
|
+
'fade-in-change': [{
|
|
57
|
+
id: TrackId;
|
|
58
|
+
fadeInEnd: number;
|
|
59
|
+
}];
|
|
60
|
+
'fade-out-change': [{
|
|
61
|
+
id: TrackId;
|
|
62
|
+
fadeOutStart: number;
|
|
63
|
+
}];
|
|
64
|
+
'volume-change': [{
|
|
65
|
+
id: TrackId;
|
|
66
|
+
volume: number;
|
|
67
|
+
}];
|
|
68
|
+
'intro-end-change': [{
|
|
69
|
+
id: TrackId;
|
|
70
|
+
endTime: number;
|
|
71
|
+
}];
|
|
72
|
+
drop: [{
|
|
73
|
+
id: TrackId;
|
|
74
|
+
}];
|
|
75
|
+
};
|
|
76
|
+
export type MultitrackTracks = Array<TrackOptions>;
|
|
77
|
+
declare class MultiTrack extends EventEmitter<MultitrackEvents> {
|
|
78
|
+
private tracks;
|
|
79
|
+
private options;
|
|
80
|
+
private audios;
|
|
81
|
+
private wavesurfers;
|
|
82
|
+
private durations;
|
|
83
|
+
private currentTime;
|
|
84
|
+
private maxDuration;
|
|
85
|
+
private rendering;
|
|
86
|
+
private isDragging;
|
|
87
|
+
private frameRequest;
|
|
88
|
+
private timer;
|
|
89
|
+
private subscriptions;
|
|
90
|
+
private timeline;
|
|
91
|
+
static create(tracks: MultitrackTracks, options: MultitrackOptions): MultiTrack;
|
|
92
|
+
constructor(tracks: MultitrackTracks, options: MultitrackOptions);
|
|
93
|
+
private initDurations;
|
|
94
|
+
private initAudio;
|
|
95
|
+
private initAllAudios;
|
|
96
|
+
private initWavesurfer;
|
|
97
|
+
private initAllWavesurfers;
|
|
98
|
+
private initTimeline;
|
|
99
|
+
private updatePosition;
|
|
100
|
+
private setIsDragging;
|
|
101
|
+
private onDrag;
|
|
102
|
+
private findCurrentTracks;
|
|
103
|
+
private startSync;
|
|
104
|
+
play(): void;
|
|
105
|
+
pause(): void;
|
|
106
|
+
isPlaying(): boolean;
|
|
107
|
+
getCurrentTime(): number;
|
|
108
|
+
/** Position percentage from 0 to 1 */
|
|
109
|
+
seekTo(position: number): void;
|
|
110
|
+
/** Set time in seconds */
|
|
111
|
+
setTime(time: number): void;
|
|
112
|
+
zoom(pxPerSec: number): void;
|
|
113
|
+
addTrack(track: TrackOptions): void;
|
|
114
|
+
destroy(): void;
|
|
115
|
+
setSinkId(sinkId: string): Promise<void[]>;
|
|
116
|
+
}
|
|
117
|
+
export default MultiTrack;
|