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,489 @@
|
|
|
1
|
+
import WaveSurfer from '../index.js';
|
|
2
|
+
import RegionsPlugin from './regions.js';
|
|
3
|
+
import TimelinePlugin from './timeline.js';
|
|
4
|
+
import EnvelopePlugin from './envelope.js';
|
|
5
|
+
import EventEmitter from '../event-emitter.js';
|
|
6
|
+
class MultiTrack extends EventEmitter {
|
|
7
|
+
tracks;
|
|
8
|
+
options;
|
|
9
|
+
audios = [];
|
|
10
|
+
wavesurfers = [];
|
|
11
|
+
durations = [];
|
|
12
|
+
currentTime = 0;
|
|
13
|
+
maxDuration = 0;
|
|
14
|
+
rendering;
|
|
15
|
+
isDragging = false;
|
|
16
|
+
frameRequest = null;
|
|
17
|
+
timer = null;
|
|
18
|
+
subscriptions = [];
|
|
19
|
+
timeline = null;
|
|
20
|
+
static create(tracks, options) {
|
|
21
|
+
return new MultiTrack(tracks, options);
|
|
22
|
+
}
|
|
23
|
+
constructor(tracks, options) {
|
|
24
|
+
super();
|
|
25
|
+
this.tracks = tracks.map((track) => ({
|
|
26
|
+
...track,
|
|
27
|
+
startPosition: track.startPosition || 0,
|
|
28
|
+
peaks: track.peaks || (track.url ? undefined : [new Float32Array()]),
|
|
29
|
+
}));
|
|
30
|
+
this.options = options;
|
|
31
|
+
this.rendering = initRendering(this.tracks, this.options);
|
|
32
|
+
this.rendering.addDropHandler((trackId) => {
|
|
33
|
+
this.emit('drop', { id: trackId });
|
|
34
|
+
});
|
|
35
|
+
this.initAllAudios().then((durations) => {
|
|
36
|
+
this.initDurations(durations);
|
|
37
|
+
this.initAllWavesurfers();
|
|
38
|
+
this.rendering.containers.forEach((container, index) => {
|
|
39
|
+
const drag = initDragging(container, (delta) => this.onDrag(index, delta), options.rightButtonDrag);
|
|
40
|
+
this.wavesurfers[index].once('destroy', () => drag?.destroy());
|
|
41
|
+
});
|
|
42
|
+
this.emit('canplay');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
initDurations(durations) {
|
|
46
|
+
this.durations = durations;
|
|
47
|
+
this.maxDuration = this.tracks.reduce((max, track, index) => {
|
|
48
|
+
return Math.max(max, track.startPosition + durations[index]);
|
|
49
|
+
}, 0);
|
|
50
|
+
this.rendering.setMainWidth(durations, this.maxDuration);
|
|
51
|
+
this.rendering.addClickHandler((position) => {
|
|
52
|
+
if (this.isDragging)
|
|
53
|
+
return;
|
|
54
|
+
this.seekTo(position * this.maxDuration);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
initAudio(track) {
|
|
58
|
+
const audio = new Audio(track.url);
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
if (!audio.src)
|
|
61
|
+
return resolve(audio);
|
|
62
|
+
audio.addEventListener('loadedmetadata', () => resolve(audio), { once: true });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async initAllAudios() {
|
|
66
|
+
this.audios = await Promise.all(this.tracks.map((track) => this.initAudio(track)));
|
|
67
|
+
return this.audios.map((a) => (a.src ? a.duration : 0));
|
|
68
|
+
}
|
|
69
|
+
initWavesurfer(track, index) {
|
|
70
|
+
const container = this.rendering.containers[index];
|
|
71
|
+
// Create a wavesurfer instance
|
|
72
|
+
const ws = WaveSurfer.create({
|
|
73
|
+
...track.options,
|
|
74
|
+
container,
|
|
75
|
+
minPxPerSec: 0,
|
|
76
|
+
media: this.audios[index],
|
|
77
|
+
peaks: track.peaks,
|
|
78
|
+
cursorColor: 'transparent',
|
|
79
|
+
cursorWidth: 0,
|
|
80
|
+
interactive: false,
|
|
81
|
+
});
|
|
82
|
+
// Regions and markers
|
|
83
|
+
const wsRegions = ws.registerPlugin(RegionsPlugin, {
|
|
84
|
+
draggable: false,
|
|
85
|
+
resizable: true,
|
|
86
|
+
dragSelection: false,
|
|
87
|
+
});
|
|
88
|
+
this.subscriptions.push(ws.once('decode', () => {
|
|
89
|
+
// Start and end cues
|
|
90
|
+
if (track.startCue != null || track.endCue != null) {
|
|
91
|
+
const { startCue = 0, endCue = this.durations[index] } = track;
|
|
92
|
+
const startCueRegion = wsRegions.add(0, startCue, '', 'rgba(0, 0, 0, 0.7)');
|
|
93
|
+
const endCueRegion = wsRegions.add(endCue, endCue + this.durations[index], '', 'rgba(0, 0, 0, 0.7)');
|
|
94
|
+
// Allow resizing only from one side
|
|
95
|
+
startCueRegion.element.firstElementChild?.remove();
|
|
96
|
+
endCueRegion.element.lastChild?.remove();
|
|
97
|
+
// Update the start and end cues on resize
|
|
98
|
+
this.subscriptions.push(wsRegions.on('region-updated', ({ region }) => {
|
|
99
|
+
this.setIsDragging();
|
|
100
|
+
if (region === startCueRegion || region === endCueRegion) {
|
|
101
|
+
if (region === startCueRegion) {
|
|
102
|
+
track.startCue = region.endTime;
|
|
103
|
+
this.emit('start-cue-change', { id: track.id, startCue: track.startCue });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
track.endCue = region.startTime;
|
|
107
|
+
this.emit('end-cue-change', { id: track.id, endCue: track.endCue });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
// Intro
|
|
113
|
+
if (track.intro) {
|
|
114
|
+
const introRegion = wsRegions.add(0, track.intro.endTime, track.intro.label, this.options.trackBackground);
|
|
115
|
+
introRegion.element.firstElementChild?.remove();
|
|
116
|
+
introRegion.element.style.backgroundColor = this.options.trackBackground || 'transparent';
|
|
117
|
+
introRegion.element.parentElement.style.mixBlendMode = 'plus-lighter';
|
|
118
|
+
if (track.intro.color) {
|
|
119
|
+
;
|
|
120
|
+
introRegion.element.lastElementChild.style.borderColor = track.intro.color;
|
|
121
|
+
}
|
|
122
|
+
this.subscriptions.push(wsRegions.on('region-updated', ({ region }) => {
|
|
123
|
+
if (region === introRegion) {
|
|
124
|
+
this.emit('intro-end-change', { id: track.id, endTime: region.endTime });
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
// Render markers
|
|
129
|
+
if (track.markers) {
|
|
130
|
+
track.markers.forEach((marker) => {
|
|
131
|
+
wsRegions.add(marker.time, marker.time, marker.label, marker.color);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
// Envelope
|
|
136
|
+
const envelope = ws.registerPlugin(EnvelopePlugin, {
|
|
137
|
+
...this.options.envelopeOptions,
|
|
138
|
+
startTime: track.startCue,
|
|
139
|
+
endTime: track.endCue,
|
|
140
|
+
fadeInEnd: track.fadeInEnd,
|
|
141
|
+
fadeOutStart: track.fadeOutStart,
|
|
142
|
+
volume: track.volume,
|
|
143
|
+
});
|
|
144
|
+
this.subscriptions.push(envelope.on('volume-change', ({ volume }) => {
|
|
145
|
+
this.setIsDragging();
|
|
146
|
+
this.emit('volume-change', { id: track.id, volume });
|
|
147
|
+
}), envelope.on('fade-in-change', ({ time }) => {
|
|
148
|
+
this.setIsDragging();
|
|
149
|
+
this.emit('fade-in-change', { id: track.id, fadeInEnd: time });
|
|
150
|
+
}), envelope.on('fade-out-change', ({ time }) => {
|
|
151
|
+
this.setIsDragging();
|
|
152
|
+
this.emit('fade-out-change', { id: track.id, fadeOutStart: time });
|
|
153
|
+
}), this.on('start-cue-change', ({ id, startCue }) => {
|
|
154
|
+
if (id === track.id) {
|
|
155
|
+
envelope.setStartTime(startCue);
|
|
156
|
+
}
|
|
157
|
+
}), this.on('end-cue-change', ({ id, endCue }) => {
|
|
158
|
+
if (id === track.id) {
|
|
159
|
+
envelope.setEndTime(endCue);
|
|
160
|
+
}
|
|
161
|
+
}));
|
|
162
|
+
return ws;
|
|
163
|
+
}
|
|
164
|
+
initAllWavesurfers() {
|
|
165
|
+
const wavesurfers = this.tracks.map((track, index) => {
|
|
166
|
+
return this.initWavesurfer(track, index);
|
|
167
|
+
});
|
|
168
|
+
this.wavesurfers = wavesurfers;
|
|
169
|
+
this.initTimeline();
|
|
170
|
+
}
|
|
171
|
+
initTimeline() {
|
|
172
|
+
if (this.timeline)
|
|
173
|
+
this.timeline.destroy();
|
|
174
|
+
this.timeline = this.wavesurfers[0].registerPlugin(TimelinePlugin, {
|
|
175
|
+
duration: this.maxDuration,
|
|
176
|
+
container: this.rendering.containers[0].parentElement,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
updatePosition(time, autoCenter = false) {
|
|
180
|
+
const precisionSeconds = 0.3;
|
|
181
|
+
this.currentTime = time;
|
|
182
|
+
this.rendering.updateCursor(time / this.maxDuration, autoCenter);
|
|
183
|
+
const isPaused = !this.isPlaying();
|
|
184
|
+
// Update the current time of each audio
|
|
185
|
+
this.tracks.forEach((track, index) => {
|
|
186
|
+
const audio = this.audios[index];
|
|
187
|
+
const duration = this.durations[index];
|
|
188
|
+
const newTime = time - track.startPosition;
|
|
189
|
+
if (Math.abs(audio.currentTime - newTime) > precisionSeconds) {
|
|
190
|
+
audio.currentTime = newTime;
|
|
191
|
+
}
|
|
192
|
+
// If the position is out of the track bounds, pause it
|
|
193
|
+
if (isPaused || newTime < 0 || newTime > duration) {
|
|
194
|
+
!audio.paused && audio.pause();
|
|
195
|
+
}
|
|
196
|
+
else if (!isPaused) {
|
|
197
|
+
// If the position is in the track bounds, play it
|
|
198
|
+
audio.paused && audio.play();
|
|
199
|
+
}
|
|
200
|
+
// Unmute if cue is reached
|
|
201
|
+
const newVolume = newTime >= (track.startCue || 0) && newTime < (track.endCue || Infinity) ? 1 : 0;
|
|
202
|
+
if (newVolume !== audio.volume)
|
|
203
|
+
audio.volume = newVolume;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
setIsDragging() {
|
|
207
|
+
// Prevent click events when dragging
|
|
208
|
+
this.isDragging = true;
|
|
209
|
+
if (this.timer)
|
|
210
|
+
clearTimeout(this.timer);
|
|
211
|
+
this.timer = setTimeout(() => (this.isDragging = false), 300);
|
|
212
|
+
}
|
|
213
|
+
onDrag(index, delta) {
|
|
214
|
+
this.setIsDragging();
|
|
215
|
+
const newTime = this.tracks[index].startPosition + delta * this.maxDuration;
|
|
216
|
+
this.onMove(index, newTime);
|
|
217
|
+
}
|
|
218
|
+
onMove(index, newStartPosition) {
|
|
219
|
+
const track = this.tracks[index];
|
|
220
|
+
if (!track.draggable)
|
|
221
|
+
return;
|
|
222
|
+
const mainIndex = this.tracks.findIndex((item) => item.url && !item.draggable);
|
|
223
|
+
const mainTrack = this.tracks[mainIndex];
|
|
224
|
+
const minStart = (mainTrack ? mainTrack.startPosition : 0) - this.durations[index];
|
|
225
|
+
const maxStart = mainTrack ? mainTrack.startPosition + this.durations[mainIndex] : this.maxDuration;
|
|
226
|
+
if (newStartPosition >= minStart && newStartPosition <= maxStart) {
|
|
227
|
+
track.startPosition = newStartPosition;
|
|
228
|
+
this.rendering.setContainerOffsets();
|
|
229
|
+
this.updatePosition(this.currentTime);
|
|
230
|
+
this.emit('start-position-change', { id: track.id, startPosition: newStartPosition });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
findCurrentTracks() {
|
|
234
|
+
// Find the audios at the current time
|
|
235
|
+
const indexes = [];
|
|
236
|
+
this.tracks.forEach((track, index) => {
|
|
237
|
+
if (track.url &&
|
|
238
|
+
this.currentTime >= track.startPosition &&
|
|
239
|
+
this.currentTime < track.startPosition + this.durations[index]) {
|
|
240
|
+
indexes.push(index);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (indexes.length === 0) {
|
|
244
|
+
const minStartTime = Math.min(...this.tracks.filter((t) => t.url).map((track) => track.startPosition));
|
|
245
|
+
indexes.push(this.tracks.findIndex((track) => track.startPosition === minStartTime));
|
|
246
|
+
}
|
|
247
|
+
return indexes;
|
|
248
|
+
}
|
|
249
|
+
startSync() {
|
|
250
|
+
const onFrame = () => {
|
|
251
|
+
const position = this.audios.reduce((pos, audio, index) => {
|
|
252
|
+
if (!audio.paused) {
|
|
253
|
+
pos = Math.max(pos, audio.currentTime + this.tracks[index].startPosition);
|
|
254
|
+
}
|
|
255
|
+
return pos;
|
|
256
|
+
}, this.currentTime);
|
|
257
|
+
if (position > this.currentTime) {
|
|
258
|
+
this.updatePosition(position, true);
|
|
259
|
+
}
|
|
260
|
+
this.frameRequest = requestAnimationFrame(onFrame);
|
|
261
|
+
};
|
|
262
|
+
onFrame();
|
|
263
|
+
}
|
|
264
|
+
play() {
|
|
265
|
+
this.startSync();
|
|
266
|
+
const indexes = this.findCurrentTracks();
|
|
267
|
+
indexes.forEach((index) => {
|
|
268
|
+
this.audios[index]?.play();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
pause() {
|
|
272
|
+
this.audios.forEach((audio) => audio.pause());
|
|
273
|
+
}
|
|
274
|
+
isPlaying() {
|
|
275
|
+
return this.audios.some((audio) => !audio.paused);
|
|
276
|
+
}
|
|
277
|
+
getCurrentTime() {
|
|
278
|
+
return this.currentTime;
|
|
279
|
+
}
|
|
280
|
+
seekTo(time) {
|
|
281
|
+
const wasPlaying = this.isPlaying();
|
|
282
|
+
this.updatePosition(time);
|
|
283
|
+
if (wasPlaying)
|
|
284
|
+
this.play();
|
|
285
|
+
}
|
|
286
|
+
zoom(pxPerSec) {
|
|
287
|
+
this.options.minPxPerSec = pxPerSec;
|
|
288
|
+
this.wavesurfers.forEach((ws, index) => this.tracks[index].url && ws.zoom(pxPerSec));
|
|
289
|
+
this.rendering.setMainWidth(this.durations, this.maxDuration);
|
|
290
|
+
this.rendering.setContainerOffsets();
|
|
291
|
+
}
|
|
292
|
+
addTrack(track) {
|
|
293
|
+
const index = this.tracks.findIndex((t) => t.id === track.id);
|
|
294
|
+
if (index !== -1) {
|
|
295
|
+
this.tracks[index] = track;
|
|
296
|
+
this.initAudio(track).then((audio) => {
|
|
297
|
+
this.audios[index] = audio;
|
|
298
|
+
this.durations[index] = audio.duration;
|
|
299
|
+
this.initDurations(this.durations);
|
|
300
|
+
const container = this.rendering.containers[index];
|
|
301
|
+
container.innerHTML = '';
|
|
302
|
+
this.wavesurfers[index].destroy();
|
|
303
|
+
this.wavesurfers[index] = this.initWavesurfer(track, index);
|
|
304
|
+
const drag = initDragging(container, (delta) => this.onDrag(index, delta), this.options.rightButtonDrag);
|
|
305
|
+
this.wavesurfers[index].once('destroy', () => drag?.destroy());
|
|
306
|
+
this.initTimeline();
|
|
307
|
+
this.emit('canplay');
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
destroy() {
|
|
312
|
+
if (this.frameRequest)
|
|
313
|
+
cancelAnimationFrame(this.frameRequest);
|
|
314
|
+
this.rendering.destroy();
|
|
315
|
+
this.audios.forEach((audio) => {
|
|
316
|
+
audio.pause();
|
|
317
|
+
audio.src = '';
|
|
318
|
+
});
|
|
319
|
+
this.wavesurfers.forEach((ws) => {
|
|
320
|
+
ws.destroy();
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
|
|
324
|
+
setSinkId(sinkId) {
|
|
325
|
+
return Promise.all(this.audios.map((item) => {
|
|
326
|
+
const audio = item;
|
|
327
|
+
return audio.setSinkId ? audio.setSinkId(sinkId) : Promise.resolve();
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function initRendering(tracks, options) {
|
|
332
|
+
let pxPerSec = 0;
|
|
333
|
+
let durations = [];
|
|
334
|
+
let mainWidth = 0;
|
|
335
|
+
// Create a common container for all tracks
|
|
336
|
+
const scroll = document.createElement('div');
|
|
337
|
+
scroll.setAttribute('style', 'width: 100%; overflow-x: scroll; overflow-y: hidden; user-select: none;');
|
|
338
|
+
const wrapper = document.createElement('div');
|
|
339
|
+
wrapper.style.position = 'relative';
|
|
340
|
+
scroll.appendChild(wrapper);
|
|
341
|
+
options.container.appendChild(scroll);
|
|
342
|
+
// Create a common cursor
|
|
343
|
+
const cursor = document.createElement('div');
|
|
344
|
+
cursor.setAttribute('style', 'height: 100%; position: absolute; z-index: 10; top: 0; left: 0');
|
|
345
|
+
cursor.style.backgroundColor = options.cursorColor || '#000';
|
|
346
|
+
cursor.style.width = `${options.cursorWidth ?? 1}px`;
|
|
347
|
+
wrapper.appendChild(cursor);
|
|
348
|
+
const { clientWidth } = wrapper;
|
|
349
|
+
// Create containers for each track
|
|
350
|
+
const containers = tracks.map((track, index) => {
|
|
351
|
+
const container = document.createElement('div');
|
|
352
|
+
container.style.position = 'relative';
|
|
353
|
+
if (options.trackBorderColor && index > 0) {
|
|
354
|
+
const borderDiv = document.createElement('div');
|
|
355
|
+
borderDiv.setAttribute('style', `width: 100%; height: 2px; background-color: ${options.trackBorderColor}`);
|
|
356
|
+
wrapper.appendChild(borderDiv);
|
|
357
|
+
}
|
|
358
|
+
if (options.trackBackground && track.url) {
|
|
359
|
+
container.style.background = options.trackBackground;
|
|
360
|
+
}
|
|
361
|
+
// No audio on this track, so make it droppable
|
|
362
|
+
if (!track.url) {
|
|
363
|
+
const dropArea = document.createElement('div');
|
|
364
|
+
dropArea.setAttribute('style', `position: absolute; z-index: 10; left: 10px; top: 10px; right: 10px; bottom: 10px; border: 2px dashed ${options.trackBorderColor};`);
|
|
365
|
+
dropArea.addEventListener('dragover', (e) => {
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
dropArea.style.background = options.trackBackground || '';
|
|
368
|
+
});
|
|
369
|
+
dropArea.addEventListener('dragleave', (e) => {
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
dropArea.style.background = '';
|
|
372
|
+
});
|
|
373
|
+
dropArea.addEventListener('drop', (e) => {
|
|
374
|
+
e.preventDefault();
|
|
375
|
+
dropArea.style.background = '';
|
|
376
|
+
});
|
|
377
|
+
container.appendChild(dropArea);
|
|
378
|
+
}
|
|
379
|
+
wrapper.appendChild(container);
|
|
380
|
+
return container;
|
|
381
|
+
});
|
|
382
|
+
// Set the positions of each container
|
|
383
|
+
const setContainerOffsets = () => {
|
|
384
|
+
containers.forEach((container, i) => {
|
|
385
|
+
const offset = tracks[i].startPosition * pxPerSec;
|
|
386
|
+
if (durations[i]) {
|
|
387
|
+
container.style.width = `${durations[i] * pxPerSec}px`;
|
|
388
|
+
}
|
|
389
|
+
container.style.transform = `translateX(${offset}px)`;
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
return {
|
|
393
|
+
containers,
|
|
394
|
+
// Set the start offset
|
|
395
|
+
setContainerOffsets,
|
|
396
|
+
// Set the container width
|
|
397
|
+
setMainWidth: (trackDurations, maxDuration) => {
|
|
398
|
+
durations = trackDurations;
|
|
399
|
+
pxPerSec = Math.max(options.minPxPerSec || 0, clientWidth / maxDuration);
|
|
400
|
+
mainWidth = pxPerSec * maxDuration;
|
|
401
|
+
wrapper.style.width = `${mainWidth}px`;
|
|
402
|
+
setContainerOffsets();
|
|
403
|
+
},
|
|
404
|
+
// Update cursor position
|
|
405
|
+
updateCursor: (position, autoCenter) => {
|
|
406
|
+
cursor.style.left = `${Math.min(100, position * 100)}%`;
|
|
407
|
+
// Update scroll
|
|
408
|
+
const { clientWidth, scrollLeft } = scroll;
|
|
409
|
+
const center = clientWidth / 2;
|
|
410
|
+
const minScroll = autoCenter ? center : clientWidth;
|
|
411
|
+
const pos = position * mainWidth;
|
|
412
|
+
if (pos > scrollLeft + minScroll || pos < scrollLeft) {
|
|
413
|
+
scroll.scrollLeft = pos - center;
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
// Click to seek
|
|
417
|
+
addClickHandler: (onClick) => {
|
|
418
|
+
wrapper.addEventListener('click', (e) => {
|
|
419
|
+
const rect = wrapper.getBoundingClientRect();
|
|
420
|
+
const x = e.clientX - rect.left;
|
|
421
|
+
const position = x / wrapper.offsetWidth;
|
|
422
|
+
onClick(position);
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
// Destroy the container
|
|
426
|
+
destroy: () => {
|
|
427
|
+
scroll.remove();
|
|
428
|
+
},
|
|
429
|
+
// Do something on drop
|
|
430
|
+
addDropHandler: (onDrop) => {
|
|
431
|
+
tracks.forEach((track, index) => {
|
|
432
|
+
if (!track.url) {
|
|
433
|
+
const droppable = containers[index].querySelector('div');
|
|
434
|
+
droppable?.addEventListener('drop', (e) => {
|
|
435
|
+
e.preventDefault();
|
|
436
|
+
onDrop(track.id);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function initDragging(container, onDrag, rightButtonDrag = false) {
|
|
444
|
+
const wrapper = container.parentElement;
|
|
445
|
+
if (!wrapper)
|
|
446
|
+
return;
|
|
447
|
+
// Dragging tracks to set position
|
|
448
|
+
let dragStart = null;
|
|
449
|
+
container.addEventListener('contextmenu', (e) => {
|
|
450
|
+
rightButtonDrag && e.preventDefault();
|
|
451
|
+
});
|
|
452
|
+
// Drag start
|
|
453
|
+
container.addEventListener('mousedown', (e) => {
|
|
454
|
+
if (rightButtonDrag && e.button !== 2)
|
|
455
|
+
return;
|
|
456
|
+
const rect = wrapper.getBoundingClientRect();
|
|
457
|
+
dragStart = e.clientX - rect.left;
|
|
458
|
+
container.style.cursor = 'grabbing';
|
|
459
|
+
});
|
|
460
|
+
// Drag end
|
|
461
|
+
const onMouseUp = (e) => {
|
|
462
|
+
if (dragStart != null) {
|
|
463
|
+
e.stopPropagation();
|
|
464
|
+
dragStart = null;
|
|
465
|
+
container.style.cursor = '';
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
// Drag move
|
|
469
|
+
const onMouseMove = (e) => {
|
|
470
|
+
if (dragStart == null)
|
|
471
|
+
return;
|
|
472
|
+
const rect = wrapper.getBoundingClientRect();
|
|
473
|
+
const x = e.clientX - rect.left;
|
|
474
|
+
const diff = x - dragStart;
|
|
475
|
+
if (diff > 1 || diff < -1) {
|
|
476
|
+
dragStart = x;
|
|
477
|
+
onDrag(diff / wrapper.offsetWidth);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
document.body.addEventListener('mouseup', onMouseUp);
|
|
481
|
+
document.body.addEventListener('mousemove', onMouseMove);
|
|
482
|
+
return {
|
|
483
|
+
destroy: () => {
|
|
484
|
+
document.body.removeEventListener('mouseup', onMouseUp);
|
|
485
|
+
document.body.removeEventListener('mousemove', onMouseMove);
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
export default MultiTrack;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
|
-
import { WaveSurferPluginParams } from '../index.js';
|
|
3
|
-
type
|
|
2
|
+
import type { WaveSurferPluginParams } from '../index.js';
|
|
3
|
+
export type RegionsPluginOptions = {
|
|
4
|
+
dragSelection?: boolean;
|
|
5
|
+
draggable?: boolean;
|
|
6
|
+
resizable?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type Region = {
|
|
4
9
|
startTime: number;
|
|
5
10
|
endTime: number;
|
|
6
11
|
title: string;
|
|
@@ -8,7 +13,7 @@ type Region = {
|
|
|
8
13
|
end: number;
|
|
9
14
|
element: HTMLElement;
|
|
10
15
|
};
|
|
11
|
-
type RegionsPluginEvents = {
|
|
16
|
+
export type RegionsPluginEvents = {
|
|
12
17
|
'region-created': {
|
|
13
18
|
region: Region;
|
|
14
19
|
};
|
|
@@ -19,19 +24,19 @@ type RegionsPluginEvents = {
|
|
|
19
24
|
region: Region;
|
|
20
25
|
};
|
|
21
26
|
};
|
|
22
|
-
declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
|
|
27
|
+
declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
|
|
23
28
|
private dragStart;
|
|
24
|
-
private
|
|
29
|
+
private regionsContainer;
|
|
25
30
|
private regions;
|
|
26
31
|
private createdRegion;
|
|
27
32
|
private modifiedRegion;
|
|
28
33
|
private isResizingLeft;
|
|
29
34
|
private isMoving;
|
|
30
35
|
/** Create an instance of RegionsPlugin */
|
|
31
|
-
constructor(params: WaveSurferPluginParams);
|
|
32
|
-
/**
|
|
36
|
+
constructor(params: WaveSurferPluginParams, options: RegionsPluginOptions);
|
|
37
|
+
/** Unmount */
|
|
33
38
|
destroy(): void;
|
|
34
|
-
private
|
|
39
|
+
private initRegionsContainer;
|
|
35
40
|
private handleMouseDown;
|
|
36
41
|
private handleMouseMove;
|
|
37
42
|
private handleMouseUp;
|
|
@@ -41,7 +46,7 @@ declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
|
|
|
41
46
|
private updateRegion;
|
|
42
47
|
private moveRegion;
|
|
43
48
|
/** Create a region at a given start and end time, with an optional title */
|
|
44
|
-
|
|
49
|
+
add(startTime: number, endTime: number, title?: string, color?: string): Region;
|
|
45
50
|
/** Set the background color of a region */
|
|
46
51
|
setRegionColor(region: Region, color: string): void;
|
|
47
52
|
}
|