wavesurfer.js 7.0.0-alpha.2 → 7.0.0-alpha.21
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 +13 -15
- package/dist/base-plugin.d.ts +6 -4
- package/dist/base-plugin.js +19 -0
- package/dist/decoder.d.ts +2 -4
- package/dist/decoder.js +31 -0
- package/dist/event-emitter.d.ts +1 -1
- package/dist/event-emitter.js +26 -0
- package/dist/fetcher.js +6 -0
- package/dist/index.d.ts +66 -20
- package/dist/index.js +238 -0
- package/dist/player.d.ts +13 -2
- package/dist/player.js +90 -0
- package/dist/plugins/envelope.d.ts +58 -0
- package/dist/plugins/envelope.js +292 -0
- package/dist/plugins/minimap.d.ts +25 -0
- package/dist/plugins/minimap.js +63 -0
- package/dist/plugins/multitrack.d.ts +111 -0
- package/dist/plugins/multitrack.js +489 -0
- package/dist/plugins/regions.d.ts +14 -9
- package/dist/plugins/regions.js +231 -0
- package/dist/plugins/timeline.d.ts +36 -0
- package/dist/plugins/timeline.js +125 -0
- package/dist/renderer.d.ts +16 -10
- package/dist/renderer.js +239 -0
- package/dist/timer.d.ts +2 -1
- package/dist/timer.js +19 -0
- package/dist/wavesurfer.Minimap.min.js +1 -0
- package/dist/wavesurfer.Multitrack.min.js +1 -0
- package/dist/wavesurfer.Regions.min.js +1 -1
- package/dist/wavesurfer.Timeline.min.js +1 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +32 -20
- package/dist/player-webaudio.d.ts +0 -8
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
import type { WaveSurferPluginParams } from '../index.js';
|
|
3
|
+
export type EnvelopePluginOptions = {
|
|
4
|
+
startTime?: number;
|
|
5
|
+
endTime?: number;
|
|
6
|
+
fadeInEnd?: number;
|
|
7
|
+
fadeOutStart?: number;
|
|
8
|
+
volume?: number;
|
|
9
|
+
lineWidth?: string;
|
|
10
|
+
lineColor?: string;
|
|
11
|
+
dragPointSize?: number;
|
|
12
|
+
dragPointFill?: string;
|
|
13
|
+
dragPointStroke?: string;
|
|
14
|
+
};
|
|
15
|
+
declare const defaultOptions: {
|
|
16
|
+
startTime: number;
|
|
17
|
+
endTime: number;
|
|
18
|
+
fadeInEnd: number;
|
|
19
|
+
fadeOutStart: number;
|
|
20
|
+
lineWidth: number;
|
|
21
|
+
lineColor: string;
|
|
22
|
+
dragPointSize: number;
|
|
23
|
+
dragPointFill: string;
|
|
24
|
+
dragPointStroke: string;
|
|
25
|
+
};
|
|
26
|
+
export type EnvelopePluginEvents = {
|
|
27
|
+
'fade-in-change': {
|
|
28
|
+
time: number;
|
|
29
|
+
};
|
|
30
|
+
'fade-out-change': {
|
|
31
|
+
time: number;
|
|
32
|
+
};
|
|
33
|
+
'volume-change': {
|
|
34
|
+
volume: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePluginOptions> {
|
|
38
|
+
protected options: EnvelopePluginOptions & typeof defaultOptions;
|
|
39
|
+
private svg;
|
|
40
|
+
private audioContext;
|
|
41
|
+
private gainNode;
|
|
42
|
+
private volume;
|
|
43
|
+
private isFadingIn;
|
|
44
|
+
private isFadingOut;
|
|
45
|
+
constructor(params: WaveSurferPluginParams, options: EnvelopePluginOptions);
|
|
46
|
+
private makeDraggable;
|
|
47
|
+
private renderPolyline;
|
|
48
|
+
private initSvg;
|
|
49
|
+
destroy(): void;
|
|
50
|
+
private initWebAudio;
|
|
51
|
+
private naturalVolume;
|
|
52
|
+
private onVolumeChange;
|
|
53
|
+
private initFadeEffects;
|
|
54
|
+
getCurrentVolume(): number;
|
|
55
|
+
setStartTime(time: number): void;
|
|
56
|
+
setEndTime(time: number): void;
|
|
57
|
+
}
|
|
58
|
+
export default EnvelopePlugin;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
const defaultOptions = {
|
|
3
|
+
startTime: 0,
|
|
4
|
+
endTime: 0,
|
|
5
|
+
fadeInEnd: 0,
|
|
6
|
+
fadeOutStart: 0,
|
|
7
|
+
lineWidth: 4,
|
|
8
|
+
lineColor: 'rgba(0, 0, 255, 0.5)',
|
|
9
|
+
dragPointSize: 10,
|
|
10
|
+
dragPointFill: 'rgba(255, 255, 255, 0.8)',
|
|
11
|
+
dragPointStroke: 'rgba(255, 255, 255, 0.8)',
|
|
12
|
+
};
|
|
13
|
+
class EnvelopePlugin extends BasePlugin {
|
|
14
|
+
options;
|
|
15
|
+
svg = null;
|
|
16
|
+
audioContext = null;
|
|
17
|
+
gainNode = null;
|
|
18
|
+
volume = 1;
|
|
19
|
+
isFadingIn = false;
|
|
20
|
+
isFadingOut = false;
|
|
21
|
+
constructor(params, options) {
|
|
22
|
+
super(params, options);
|
|
23
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
24
|
+
this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
|
|
25
|
+
this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
|
|
26
|
+
this.options.dragPointStroke = this.options.dragPointStroke || defaultOptions.dragPointStroke;
|
|
27
|
+
this.volume = this.options.volume ?? 1;
|
|
28
|
+
this.subscriptions.push(this.wavesurfer.once('decode', ({ duration }) => {
|
|
29
|
+
this.options.startTime = this.options.startTime || 0;
|
|
30
|
+
this.options.endTime = this.options.endTime || duration;
|
|
31
|
+
this.options.fadeInEnd = this.options.fadeInEnd || this.options.startTime;
|
|
32
|
+
this.options.fadeOutStart = this.options.fadeOutStart || this.options.endTime;
|
|
33
|
+
this.initWebAudio();
|
|
34
|
+
this.initSvg();
|
|
35
|
+
this.initFadeEffects();
|
|
36
|
+
}));
|
|
37
|
+
let delay;
|
|
38
|
+
this.subscriptions.push(this.wavesurfer.on('zoom', () => {
|
|
39
|
+
if (delay)
|
|
40
|
+
clearTimeout(delay);
|
|
41
|
+
delay = setTimeout(() => {
|
|
42
|
+
this.svg?.remove();
|
|
43
|
+
this.initSvg();
|
|
44
|
+
}, 100);
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
makeDraggable(draggable, onDrag) {
|
|
48
|
+
draggable.addEventListener('mousedown', (e) => {
|
|
49
|
+
let x = e.clientX;
|
|
50
|
+
let y = e.clientY;
|
|
51
|
+
const wasInteractive = this.wavesurfer.options.interactive;
|
|
52
|
+
let delay;
|
|
53
|
+
// Make the wavesurfer ignore clicks when we're dragging
|
|
54
|
+
this.wavesurfer.toggleInteractive(false);
|
|
55
|
+
const move = (e) => {
|
|
56
|
+
const dx = e.clientX - x;
|
|
57
|
+
const dy = e.clientY - y;
|
|
58
|
+
x = e.clientX;
|
|
59
|
+
y = e.clientY;
|
|
60
|
+
onDrag(dx, dy);
|
|
61
|
+
};
|
|
62
|
+
const up = () => {
|
|
63
|
+
document.removeEventListener('mousemove', move);
|
|
64
|
+
document.removeEventListener('mouseup', up);
|
|
65
|
+
// Restore interactive state
|
|
66
|
+
if (delay)
|
|
67
|
+
clearTimeout(delay);
|
|
68
|
+
delay = setTimeout(() => {
|
|
69
|
+
this.wavesurfer.toggleInteractive(wasInteractive);
|
|
70
|
+
}, 100);
|
|
71
|
+
};
|
|
72
|
+
document.addEventListener('mousemove', move);
|
|
73
|
+
document.addEventListener('mouseup', up);
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
renderPolyline() {
|
|
79
|
+
if (!this.svg)
|
|
80
|
+
return;
|
|
81
|
+
const polyline = this.svg.querySelector('polyline');
|
|
82
|
+
const points = polyline.points;
|
|
83
|
+
const top = points.getItem(1).y;
|
|
84
|
+
const width = this.wrapper.clientWidth;
|
|
85
|
+
const duration = this.wavesurfer.getDuration();
|
|
86
|
+
points.getItem(0).x = (this.options.startTime / duration) * width;
|
|
87
|
+
points.getItem(3).x = (this.options.endTime / duration) * width;
|
|
88
|
+
const line = this.svg.querySelector('line');
|
|
89
|
+
line.setAttribute('x1', points.getItem(1).x.toString());
|
|
90
|
+
line.setAttribute('x2', points.getItem(2).x.toString());
|
|
91
|
+
line.setAttribute('y1', top.toString());
|
|
92
|
+
line.setAttribute('y2', top.toString());
|
|
93
|
+
const circles = this.svg.querySelectorAll('circle');
|
|
94
|
+
for (let i = 0; i < circles.length; i++) {
|
|
95
|
+
const circle = circles[i];
|
|
96
|
+
const point = polyline.points.getItem(i + 1);
|
|
97
|
+
circle.setAttribute('cx', point.x.toString());
|
|
98
|
+
circle.setAttribute('cy', top.toString());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
initSvg() {
|
|
102
|
+
const width = this.wrapper.clientWidth;
|
|
103
|
+
const height = this.wrapper.clientHeight;
|
|
104
|
+
const duration = this.wavesurfer.getDuration();
|
|
105
|
+
// SVG element
|
|
106
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
107
|
+
svg.setAttribute('width', '100%');
|
|
108
|
+
svg.setAttribute('height', '100%');
|
|
109
|
+
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
110
|
+
svg.setAttribute('preserveAspectRatio', 'none');
|
|
111
|
+
svg.setAttribute('style', 'position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;');
|
|
112
|
+
this.svg = svg;
|
|
113
|
+
// A polyline representing the envelope
|
|
114
|
+
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
115
|
+
polyline.setAttribute('points', '0,0 0,0 0,0 0,0');
|
|
116
|
+
polyline.setAttribute('stroke', this.options.lineColor);
|
|
117
|
+
polyline.setAttribute('stroke-width', this.options.lineWidth);
|
|
118
|
+
polyline.setAttribute('fill', 'none');
|
|
119
|
+
polyline.setAttribute('style', 'pointer-events: none');
|
|
120
|
+
svg.appendChild(polyline);
|
|
121
|
+
// Draggable top line
|
|
122
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
123
|
+
line.setAttribute('stroke', 'none');
|
|
124
|
+
line.setAttribute('stroke-width', (this.options.lineWidth * 3).toString());
|
|
125
|
+
line.setAttribute('style', 'cursor: ns-resize; pointer-events: all;');
|
|
126
|
+
svg.appendChild(line);
|
|
127
|
+
const points = polyline.points;
|
|
128
|
+
const offset = this.options.dragPointSize / 2;
|
|
129
|
+
const top = height - this.volume * height + offset;
|
|
130
|
+
points.getItem(0).x = (this.options.startTime / duration) * width;
|
|
131
|
+
points.getItem(0).y = height;
|
|
132
|
+
points.getItem(1).x = (this.options.fadeInEnd / duration) * width;
|
|
133
|
+
points.getItem(1).y = top;
|
|
134
|
+
points.getItem(2).x = (this.options.fadeOutStart / duration) * width;
|
|
135
|
+
points.getItem(2).y = top;
|
|
136
|
+
points.getItem(3).x = (this.options.endTime / duration) * width;
|
|
137
|
+
points.getItem(3).y = height;
|
|
138
|
+
// Drag points
|
|
139
|
+
const dragPoints = [1, 2];
|
|
140
|
+
dragPoints.forEach(() => {
|
|
141
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
142
|
+
circle.setAttribute('r', (this.options.dragPointSize / 2).toString());
|
|
143
|
+
circle.setAttribute('fill', this.options.dragPointFill);
|
|
144
|
+
circle.setAttribute('stroke', this.options.dragPointStroke || this.options.dragPointFill);
|
|
145
|
+
circle.setAttribute('stroke-width', '2');
|
|
146
|
+
circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
|
|
147
|
+
svg.appendChild(circle);
|
|
148
|
+
});
|
|
149
|
+
this.wrapper.appendChild(svg);
|
|
150
|
+
// Initial polyline
|
|
151
|
+
this.renderPolyline();
|
|
152
|
+
// On top line drag
|
|
153
|
+
const onDragY = (dy) => {
|
|
154
|
+
const newTop = points.getItem(1).y + dy - offset;
|
|
155
|
+
if (newTop < -0.5 || newTop > height)
|
|
156
|
+
return;
|
|
157
|
+
points.getItem(1).y = newTop + offset;
|
|
158
|
+
points.getItem(2).y = newTop + offset;
|
|
159
|
+
this.renderPolyline();
|
|
160
|
+
const newVolume = Math.min(1, Math.max(0, (height - newTop) / height));
|
|
161
|
+
this.onVolumeChange(newVolume);
|
|
162
|
+
this.renderPolyline();
|
|
163
|
+
};
|
|
164
|
+
// On points drag
|
|
165
|
+
const onDragX = (dx, dy, index) => {
|
|
166
|
+
const point = polyline.points.getItem(index);
|
|
167
|
+
const newX = point.x + dx;
|
|
168
|
+
const newTime = (newX / width) * duration;
|
|
169
|
+
if ((index === 1 && newTime > this.options.fadeOutStart) || newTime < this.options.startTime)
|
|
170
|
+
return;
|
|
171
|
+
if ((index === 2 && newTime < this.options.fadeInEnd) || newTime > this.options.endTime)
|
|
172
|
+
return;
|
|
173
|
+
point.x = newX;
|
|
174
|
+
if (index === 1) {
|
|
175
|
+
this.options.fadeInEnd = newTime;
|
|
176
|
+
this.emit('fade-in-change', { time: newTime });
|
|
177
|
+
}
|
|
178
|
+
else if (index === 2) {
|
|
179
|
+
this.options.fadeOutStart = newTime;
|
|
180
|
+
this.emit('fade-out-change', { time: newTime });
|
|
181
|
+
}
|
|
182
|
+
// Also allow dragging points vertically
|
|
183
|
+
if (dy > 1) {
|
|
184
|
+
onDragY(dy);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.renderPolyline();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
// Draggable top line of the polyline
|
|
191
|
+
this.makeDraggable(line, (_, dy) => onDragY(dy));
|
|
192
|
+
// Make each point draggable
|
|
193
|
+
const draggables = svg.querySelectorAll('circle');
|
|
194
|
+
for (let i = 0; i < draggables.length; i++) {
|
|
195
|
+
const index = i + 1;
|
|
196
|
+
this.makeDraggable(draggables[i], (dx, dy) => onDragX(dx, dy, index));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
destroy() {
|
|
200
|
+
this.svg?.remove();
|
|
201
|
+
super.destroy();
|
|
202
|
+
}
|
|
203
|
+
initWebAudio() {
|
|
204
|
+
const audio = this.wavesurfer.getMediaElement();
|
|
205
|
+
if (!audio)
|
|
206
|
+
return null;
|
|
207
|
+
this.volume = this.options.volume ?? audio.volume;
|
|
208
|
+
// Create an AudioContext
|
|
209
|
+
const audioContext = new window.AudioContext();
|
|
210
|
+
// Create a GainNode for controlling the volume
|
|
211
|
+
this.gainNode = audioContext.createGain();
|
|
212
|
+
this.gainNode.gain.value = this.volume;
|
|
213
|
+
// Create a MediaElementAudioSourceNode using the audio element
|
|
214
|
+
const source = audioContext.createMediaElementSource(audio);
|
|
215
|
+
// Connect the source to the GainNode, and the GainNode to the destination (speakers)
|
|
216
|
+
source.connect(this.gainNode);
|
|
217
|
+
this.gainNode.connect(audioContext.destination);
|
|
218
|
+
this.audioContext = audioContext;
|
|
219
|
+
}
|
|
220
|
+
naturalVolume(value) {
|
|
221
|
+
const minValue = 0.0001;
|
|
222
|
+
const maxValue = 1;
|
|
223
|
+
const exponent = 3; // Adjust the exponent to change the curve of the volume control
|
|
224
|
+
const interpolatedValue = minValue + (maxValue - minValue) * Math.pow(value, exponent);
|
|
225
|
+
return interpolatedValue;
|
|
226
|
+
}
|
|
227
|
+
onVolumeChange(volume) {
|
|
228
|
+
volume = this.naturalVolume(volume);
|
|
229
|
+
this.volume = volume;
|
|
230
|
+
this.emit('volume-change', { volume });
|
|
231
|
+
if (!this.gainNode)
|
|
232
|
+
return;
|
|
233
|
+
this.gainNode.gain.value = volume;
|
|
234
|
+
}
|
|
235
|
+
initFadeEffects() {
|
|
236
|
+
if (!this.audioContext)
|
|
237
|
+
return;
|
|
238
|
+
const unsub = this.wavesurfer.on('timeupdate', ({ currentTime }) => {
|
|
239
|
+
if (!this.audioContext || !this.gainNode)
|
|
240
|
+
return;
|
|
241
|
+
if (!this.wavesurfer.isPlaying())
|
|
242
|
+
return;
|
|
243
|
+
if (this.audioContext.state === 'suspended') {
|
|
244
|
+
this.audioContext.resume();
|
|
245
|
+
}
|
|
246
|
+
// Fade in
|
|
247
|
+
if (!this.isFadingIn && currentTime >= this.options.startTime && currentTime <= this.options.fadeInEnd) {
|
|
248
|
+
this.isFadingIn = true;
|
|
249
|
+
// Set the initial gain (volume) to 0 (silent)
|
|
250
|
+
this.gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
|
|
251
|
+
// Set the target gain (volume) to 1 (full volume) over N seconds
|
|
252
|
+
this.gainNode.gain.linearRampToValueAtTime(this.volume, this.audioContext.currentTime + (this.options.fadeInEnd - currentTime));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Fade out
|
|
256
|
+
if (!this.isFadingOut && currentTime >= this.options.fadeOutStart && currentTime <= this.options.endTime) {
|
|
257
|
+
this.isFadingOut = true;
|
|
258
|
+
// Set the target gain (volume) to 0 (silent) over N seconds
|
|
259
|
+
this.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + (this.options.endTime - currentTime));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// Reset fade in/out
|
|
263
|
+
let cancelRamp = false;
|
|
264
|
+
if (this.isFadingIn && (currentTime < this.options.startTime || currentTime > this.options.fadeInEnd)) {
|
|
265
|
+
this.isFadingIn = false;
|
|
266
|
+
cancelRamp = true;
|
|
267
|
+
}
|
|
268
|
+
if (this.isFadingOut && (currentTime < this.options.fadeOutStart || currentTime >= this.options.endTime)) {
|
|
269
|
+
this.isFadingOut = false;
|
|
270
|
+
cancelRamp = true;
|
|
271
|
+
}
|
|
272
|
+
if (cancelRamp) {
|
|
273
|
+
this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);
|
|
274
|
+
this.gainNode.gain.value = this.volume;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
this.subscriptions.push(unsub);
|
|
278
|
+
}
|
|
279
|
+
getCurrentVolume() {
|
|
280
|
+
return this.gainNode ? this.gainNode.gain.value : this.volume;
|
|
281
|
+
}
|
|
282
|
+
setStartTime(time) {
|
|
283
|
+
this.options.startTime = time;
|
|
284
|
+
console.log(time);
|
|
285
|
+
this.renderPolyline();
|
|
286
|
+
}
|
|
287
|
+
setEndTime(time) {
|
|
288
|
+
this.options.endTime = time;
|
|
289
|
+
this.renderPolyline();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export default EnvelopePlugin;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
import { type WaveSurferPluginParams, type WaveSurferOptions } from '../index.js';
|
|
3
|
+
export type MinimapPluginOptions = {
|
|
4
|
+
overlayColor?: string;
|
|
5
|
+
} & WaveSurferOptions;
|
|
6
|
+
declare const defaultOptions: {
|
|
7
|
+
height: number;
|
|
8
|
+
overlayColor: string;
|
|
9
|
+
};
|
|
10
|
+
export type MinimapPluginEvents = {
|
|
11
|
+
ready: void;
|
|
12
|
+
};
|
|
13
|
+
declare class TimelinePlugin extends BasePlugin<MinimapPluginEvents, MinimapPluginOptions> {
|
|
14
|
+
protected options: MinimapPluginOptions & typeof defaultOptions;
|
|
15
|
+
private minimapWrapper;
|
|
16
|
+
private miniWavesurfer;
|
|
17
|
+
private overlay;
|
|
18
|
+
constructor(params: WaveSurferPluginParams, options: MinimapPluginOptions);
|
|
19
|
+
private initMinimapWrapper;
|
|
20
|
+
private initOverlay;
|
|
21
|
+
private initMinimap;
|
|
22
|
+
/** Unmount */
|
|
23
|
+
destroy(): void;
|
|
24
|
+
}
|
|
25
|
+
export default TimelinePlugin;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
import WaveSurfer from '../index.js';
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
height: 50,
|
|
5
|
+
overlayColor: 'rgba(100, 100, 100, 0.1)',
|
|
6
|
+
};
|
|
7
|
+
class TimelinePlugin extends BasePlugin {
|
|
8
|
+
options;
|
|
9
|
+
minimapWrapper;
|
|
10
|
+
miniWavesurfer = null;
|
|
11
|
+
overlay;
|
|
12
|
+
constructor(params, options) {
|
|
13
|
+
super(params, options);
|
|
14
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
15
|
+
this.minimapWrapper = this.initMinimapWrapper();
|
|
16
|
+
this.overlay = this.initOverlay();
|
|
17
|
+
this.subscriptions.push(this.wavesurfer.on('decode', () => {
|
|
18
|
+
this.initMinimap();
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
initMinimapWrapper() {
|
|
22
|
+
const div = document.createElement('div');
|
|
23
|
+
div.style.position = 'relative';
|
|
24
|
+
this.container.insertAdjacentElement('afterend', div);
|
|
25
|
+
return div;
|
|
26
|
+
}
|
|
27
|
+
initOverlay() {
|
|
28
|
+
const div = document.createElement('div');
|
|
29
|
+
div.setAttribute('style', 'position: absolute; z-index: 2; left: 0; top: 0; bottom: 0;');
|
|
30
|
+
div.style.backgroundColor = this.options.overlayColor;
|
|
31
|
+
this.minimapWrapper.appendChild(div);
|
|
32
|
+
return div;
|
|
33
|
+
}
|
|
34
|
+
initMinimap() {
|
|
35
|
+
const data = this.wavesurfer.getDecodedData();
|
|
36
|
+
const media = this.wavesurfer.getMediaElement();
|
|
37
|
+
if (!data || !media)
|
|
38
|
+
return;
|
|
39
|
+
this.miniWavesurfer = WaveSurfer.create({
|
|
40
|
+
...this.options,
|
|
41
|
+
container: this.minimapWrapper,
|
|
42
|
+
minPxPerSec: 1,
|
|
43
|
+
fillParent: true,
|
|
44
|
+
media,
|
|
45
|
+
url: media.src,
|
|
46
|
+
peaks: [data.getChannelData(0)],
|
|
47
|
+
duration: data.duration,
|
|
48
|
+
});
|
|
49
|
+
const overlayWidth = Math.round((this.minimapWrapper.clientWidth / this.wrapper.clientWidth) * 100);
|
|
50
|
+
this.overlay.style.width = `${overlayWidth}%`;
|
|
51
|
+
this.wavesurfer.on('timeupdate', ({ currentTime }) => {
|
|
52
|
+
const offset = Math.max(0, Math.min((currentTime / this.wavesurfer.getDuration()) * 100 - overlayWidth / 2, 100 - overlayWidth)).toFixed(2);
|
|
53
|
+
this.overlay.style.left = `${offset}%`;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Unmount */
|
|
57
|
+
destroy() {
|
|
58
|
+
this.miniWavesurfer?.destroy();
|
|
59
|
+
this.minimapWrapper.remove();
|
|
60
|
+
super.destroy();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export default TimelinePlugin;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type WaveSurferOptions } from '../index.js';
|
|
2
|
+
import { type EnvelopePluginOptions } from './envelope.js';
|
|
3
|
+
import EventEmitter from '../event-emitter.js';
|
|
4
|
+
export type TrackId = string | number;
|
|
5
|
+
export type TrackOptions = {
|
|
6
|
+
id: TrackId;
|
|
7
|
+
url?: string;
|
|
8
|
+
peaks?: WaveSurferOptions['peaks'];
|
|
9
|
+
draggable?: boolean;
|
|
10
|
+
startPosition: number;
|
|
11
|
+
startCue?: number;
|
|
12
|
+
endCue?: number;
|
|
13
|
+
fadeInEnd?: number;
|
|
14
|
+
fadeOutStart?: number;
|
|
15
|
+
volume?: number;
|
|
16
|
+
markers?: Array<{
|
|
17
|
+
time: number;
|
|
18
|
+
label?: string;
|
|
19
|
+
color?: string;
|
|
20
|
+
}>;
|
|
21
|
+
intro?: {
|
|
22
|
+
endTime: number;
|
|
23
|
+
label?: string;
|
|
24
|
+
color?: string;
|
|
25
|
+
};
|
|
26
|
+
options?: WaveSurferOptions;
|
|
27
|
+
};
|
|
28
|
+
export type MultitrackOptions = {
|
|
29
|
+
container: HTMLElement;
|
|
30
|
+
minPxPerSec?: number;
|
|
31
|
+
cursorColor?: string;
|
|
32
|
+
cursorWidth?: number;
|
|
33
|
+
trackBackground?: string;
|
|
34
|
+
trackBorderColor?: string;
|
|
35
|
+
rightButtonDrag?: boolean;
|
|
36
|
+
envelopeOptions?: EnvelopePluginOptions;
|
|
37
|
+
};
|
|
38
|
+
export type MultitrackEvents = {
|
|
39
|
+
canplay: void;
|
|
40
|
+
'start-position-change': {
|
|
41
|
+
id: TrackId;
|
|
42
|
+
startPosition: number;
|
|
43
|
+
};
|
|
44
|
+
'start-cue-change': {
|
|
45
|
+
id: TrackId;
|
|
46
|
+
startCue: number;
|
|
47
|
+
};
|
|
48
|
+
'end-cue-change': {
|
|
49
|
+
id: TrackId;
|
|
50
|
+
endCue: number;
|
|
51
|
+
};
|
|
52
|
+
'fade-in-change': {
|
|
53
|
+
id: TrackId;
|
|
54
|
+
fadeInEnd: number;
|
|
55
|
+
};
|
|
56
|
+
'fade-out-change': {
|
|
57
|
+
id: TrackId;
|
|
58
|
+
fadeOutStart: number;
|
|
59
|
+
};
|
|
60
|
+
'volume-change': {
|
|
61
|
+
id: TrackId;
|
|
62
|
+
volume: number;
|
|
63
|
+
};
|
|
64
|
+
'intro-end-change': {
|
|
65
|
+
id: TrackId;
|
|
66
|
+
endTime: number;
|
|
67
|
+
};
|
|
68
|
+
drop: {
|
|
69
|
+
id: TrackId;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export type MultitrackTracks = Array<TrackOptions>;
|
|
73
|
+
declare class MultiTrack extends EventEmitter<MultitrackEvents> {
|
|
74
|
+
private tracks;
|
|
75
|
+
private options;
|
|
76
|
+
private audios;
|
|
77
|
+
private wavesurfers;
|
|
78
|
+
private durations;
|
|
79
|
+
private currentTime;
|
|
80
|
+
private maxDuration;
|
|
81
|
+
private rendering;
|
|
82
|
+
private isDragging;
|
|
83
|
+
private frameRequest;
|
|
84
|
+
private timer;
|
|
85
|
+
private subscriptions;
|
|
86
|
+
private timeline;
|
|
87
|
+
static create(tracks: MultitrackTracks, options: MultitrackOptions): MultiTrack;
|
|
88
|
+
constructor(tracks: MultitrackTracks, options: MultitrackOptions);
|
|
89
|
+
private initDurations;
|
|
90
|
+
private initAudio;
|
|
91
|
+
private initAllAudios;
|
|
92
|
+
private initWavesurfer;
|
|
93
|
+
private initAllWavesurfers;
|
|
94
|
+
private initTimeline;
|
|
95
|
+
private updatePosition;
|
|
96
|
+
private setIsDragging;
|
|
97
|
+
private onDrag;
|
|
98
|
+
private onMove;
|
|
99
|
+
private findCurrentTracks;
|
|
100
|
+
private startSync;
|
|
101
|
+
play(): void;
|
|
102
|
+
pause(): void;
|
|
103
|
+
isPlaying(): boolean;
|
|
104
|
+
getCurrentTime(): number;
|
|
105
|
+
seekTo(time: number): void;
|
|
106
|
+
zoom(pxPerSec: number): void;
|
|
107
|
+
addTrack(track: TrackOptions): void;
|
|
108
|
+
destroy(): void;
|
|
109
|
+
setSinkId(sinkId: string): Promise<void[]>;
|
|
110
|
+
}
|
|
111
|
+
export default MultiTrack;
|