wavesurfer.js 7.0.0-alpha.1 → 7.0.0-alpha.11
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 +4 -3
- package/dist/base-plugin.js +3 -2
- package/dist/decoder.d.ts +2 -4
- package/dist/decoder.js +16 -16
- package/dist/event-emitter.d.ts +3 -1
- package/dist/event-emitter.js +6 -2
- package/dist/index.d.ts +43 -18
- package/dist/index.js +105 -44
- package/dist/player-webaudio.js +5 -4
- package/dist/player.d.ts +11 -2
- package/dist/player.js +50 -4
- package/dist/plugins/multitrack.d.ts +47 -0
- package/dist/plugins/multitrack.js +283 -0
- package/dist/plugins/regions.d.ts +11 -6
- package/dist/plugins/regions.js +106 -74
- package/dist/plugins/timeline.d.ts +36 -0
- package/dist/plugins/timeline.js +124 -0
- package/dist/plugins/xmultitrack.d.ts +44 -0
- package/dist/plugins/xmultitrack.js +260 -0
- package/dist/react/useWavesurfer.d.ts +5 -0
- package/dist/react/useWavesurfer.js +20 -0
- package/dist/renderer.d.ts +9 -5
- package/dist/renderer.js +137 -91
- package/dist/timer.d.ts +2 -1
- package/dist/timer.js +6 -1
- package/dist/wavesurfer.Multitrack.min.js +1 -0
- package/dist/wavesurfer.Regions.min.js +1 -0
- package/dist/wavesurfer.Timeline.min.js +1 -0
- package/dist/wavesurfer.min.js +1 -0
- package/package.json +19 -5
- package/.eslintrc.json +0 -24
- package/.prettierrc +0 -8
- package/examples/audio.ogg +0 -0
- package/examples/bars.js +0 -19
- package/examples/basic.js +0 -8
- package/examples/gradient.js +0 -28
- package/examples/regions.js +0 -63
- package/examples/video.js +0 -19
- package/examples/webaudio.js +0 -14
- package/src/base-plugin.ts +0 -20
- package/src/decoder.ts +0 -41
- package/src/event-emitter.ts +0 -35
- package/src/fetcher.ts +0 -7
- package/src/index.ts +0 -252
- package/src/player-webaudio.ts +0 -34
- package/src/player.ts +0 -50
- package/src/plugins/regions.ts +0 -240
- package/src/renderer.ts +0 -250
- package/src/timer.ts +0 -27
- package/tsconfig.json +0 -105
- package/tutorial/index.html +0 -47
- package/tutorial/src/editor.js +0 -70
- package/tutorial/src/init.js +0 -66
- package/tutorial/src/url.js +0 -25
- package/tutorial/style.css +0 -211
- package/yarn-error.log +0 -1049
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type WaveSurferOptions } from '../index.js';
|
|
2
|
+
type MultitrackTracks = Array<{
|
|
3
|
+
id: string | number;
|
|
4
|
+
url?: string;
|
|
5
|
+
peaks?: WaveSurferOptions['peaks'];
|
|
6
|
+
draggable?: boolean;
|
|
7
|
+
startPosition: number;
|
|
8
|
+
startCue?: number;
|
|
9
|
+
endCue?: number;
|
|
10
|
+
markers?: Array<{
|
|
11
|
+
id: string | number;
|
|
12
|
+
time: number;
|
|
13
|
+
label?: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
}>;
|
|
16
|
+
}>;
|
|
17
|
+
type MultitrackOptions = {
|
|
18
|
+
container: HTMLElement;
|
|
19
|
+
minPxPerSec: number;
|
|
20
|
+
} & WaveSurferOptions;
|
|
21
|
+
declare class MultiTrack {
|
|
22
|
+
private tracks;
|
|
23
|
+
private options;
|
|
24
|
+
private audios;
|
|
25
|
+
private wavesurfers;
|
|
26
|
+
private durations;
|
|
27
|
+
private currentTime;
|
|
28
|
+
private maxDuration;
|
|
29
|
+
private rendering;
|
|
30
|
+
private isDragging;
|
|
31
|
+
private frameRequest;
|
|
32
|
+
static create(tracks: MultitrackTracks, options: MultitrackOptions): MultiTrack;
|
|
33
|
+
constructor(tracks: MultitrackTracks, options: MultitrackOptions);
|
|
34
|
+
private initAudios;
|
|
35
|
+
private initWavesurfers;
|
|
36
|
+
private initTimeline;
|
|
37
|
+
private updatePosition;
|
|
38
|
+
private onSeek;
|
|
39
|
+
private onDrag;
|
|
40
|
+
private findCurrentTracks;
|
|
41
|
+
private startSync;
|
|
42
|
+
play(): void;
|
|
43
|
+
pause(): void;
|
|
44
|
+
isPlaying(): boolean;
|
|
45
|
+
destroy(): void;
|
|
46
|
+
}
|
|
47
|
+
export default MultiTrack;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import WaveSurfer from '../index.js';
|
|
2
|
+
import RegionsPlugin from './regions.js';
|
|
3
|
+
import TimelinePlugin from './timeline.js';
|
|
4
|
+
class MultiTrack {
|
|
5
|
+
static create(tracks, options) {
|
|
6
|
+
return new MultiTrack(tracks, options);
|
|
7
|
+
}
|
|
8
|
+
constructor(tracks, options) {
|
|
9
|
+
this.audios = [];
|
|
10
|
+
this.wavesurfers = [];
|
|
11
|
+
this.durations = [];
|
|
12
|
+
this.currentTime = 0;
|
|
13
|
+
this.maxDuration = 0;
|
|
14
|
+
this.isDragging = false;
|
|
15
|
+
this.frameRequest = null;
|
|
16
|
+
this.tracks = tracks.map((track) => (Object.assign(Object.assign({}, track), { startPosition: track.startPosition || 0, peaks: track.peaks || (track.url ? undefined : [new Float32Array()]) })));
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.rendering = initRendering(this.tracks, this.options);
|
|
19
|
+
this.initAudios().then((durations) => {
|
|
20
|
+
this.durations = durations;
|
|
21
|
+
const maxDuration = this.tracks.reduce((max, track, index) => {
|
|
22
|
+
return Math.max(max, track.startPosition + durations[index]);
|
|
23
|
+
}, 0);
|
|
24
|
+
this.rendering.setMainWidth(maxDuration * this.options.minPxPerSec);
|
|
25
|
+
this.rendering.addClickHandler((position) => {
|
|
26
|
+
this.onSeek(position * maxDuration);
|
|
27
|
+
});
|
|
28
|
+
this.maxDuration = maxDuration;
|
|
29
|
+
this.initTimeline();
|
|
30
|
+
});
|
|
31
|
+
this.initWavesurfers();
|
|
32
|
+
this.rendering.containers.forEach((container, index) => {
|
|
33
|
+
const drag = initDragging(container, (delta) => this.onDrag(index, delta));
|
|
34
|
+
this.wavesurfers[index].once('destroy', () => {
|
|
35
|
+
drag === null || drag === void 0 ? void 0 : drag.destroy();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
initAudios() {
|
|
40
|
+
this.audios = this.tracks.map((track) => new Audio(track.url));
|
|
41
|
+
return Promise.all(this.audios.map((audio) => {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
audio.src
|
|
44
|
+
? audio.addEventListener('canplay', () => {
|
|
45
|
+
resolve(audio.duration);
|
|
46
|
+
}, { once: true })
|
|
47
|
+
: resolve(0);
|
|
48
|
+
});
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
initWavesurfers() {
|
|
52
|
+
const wavesurfers = this.tracks.map((track, index) => {
|
|
53
|
+
// Create a wavesurfer instance
|
|
54
|
+
const ws = WaveSurfer.create(Object.assign(Object.assign({}, this.options), { container: this.rendering.containers[index], media: this.audios[index], peaks: track.peaks, cursorColor: 'transparent', fillParent: false, interactive: false }));
|
|
55
|
+
const wsRegions = ws.registerPlugin(RegionsPlugin, {
|
|
56
|
+
draggable: false,
|
|
57
|
+
resizable: false,
|
|
58
|
+
dragSelection: false,
|
|
59
|
+
});
|
|
60
|
+
ws.once('decode', () => {
|
|
61
|
+
// Render start and end cues
|
|
62
|
+
if (track.startCue) {
|
|
63
|
+
wsRegions.add(track.startCue, track.startCue, 'start');
|
|
64
|
+
}
|
|
65
|
+
if (track.endCue) {
|
|
66
|
+
wsRegions.add(track.endCue, track.endCue, 'end');
|
|
67
|
+
}
|
|
68
|
+
// Render markers
|
|
69
|
+
;
|
|
70
|
+
(track.markers || []).forEach((marker) => {
|
|
71
|
+
wsRegions.add(marker.time, marker.time, marker.label, marker.color);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return ws;
|
|
75
|
+
});
|
|
76
|
+
this.wavesurfers = wavesurfers;
|
|
77
|
+
}
|
|
78
|
+
initTimeline() {
|
|
79
|
+
this.wavesurfers[0].registerPlugin(TimelinePlugin, {
|
|
80
|
+
duration: this.maxDuration,
|
|
81
|
+
container: this.rendering.containers[0].parentElement,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
updatePosition(time) {
|
|
85
|
+
const precisionSeconds = 0.3;
|
|
86
|
+
this.currentTime = time;
|
|
87
|
+
this.rendering.updateCursor(time / this.maxDuration);
|
|
88
|
+
const isPaused = !this.isPlaying();
|
|
89
|
+
// Update the current time of each audio
|
|
90
|
+
this.tracks.forEach((track, index) => {
|
|
91
|
+
const audio = this.audios[index];
|
|
92
|
+
const duration = this.durations[index];
|
|
93
|
+
const newTime = time - track.startPosition;
|
|
94
|
+
if (Math.abs(audio.currentTime - newTime) > precisionSeconds) {
|
|
95
|
+
audio.currentTime = newTime;
|
|
96
|
+
}
|
|
97
|
+
// If the position is out of the track bounds, pause it
|
|
98
|
+
if (isPaused || newTime < 0 || newTime > duration) {
|
|
99
|
+
!audio.paused && audio.pause();
|
|
100
|
+
}
|
|
101
|
+
else if (!isPaused) {
|
|
102
|
+
// If the position is in the track bounds, play it
|
|
103
|
+
audio.paused && audio.play();
|
|
104
|
+
}
|
|
105
|
+
// Unmute if cue is reached
|
|
106
|
+
const newVolume = newTime >= (track.startCue || 0) && newTime < (track.endCue || Infinity) ? 1 : 0;
|
|
107
|
+
if (newVolume !== audio.volume)
|
|
108
|
+
audio.volume = newVolume;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
onSeek(time) {
|
|
112
|
+
if (this.isDragging)
|
|
113
|
+
return;
|
|
114
|
+
const wasPlaying = this.isPlaying();
|
|
115
|
+
this.updatePosition(time);
|
|
116
|
+
if (wasPlaying)
|
|
117
|
+
this.play();
|
|
118
|
+
}
|
|
119
|
+
onDrag(index, delta) {
|
|
120
|
+
const track = this.tracks[index];
|
|
121
|
+
if (!track.draggable)
|
|
122
|
+
return;
|
|
123
|
+
this.isDragging = true;
|
|
124
|
+
setTimeout(() => (this.isDragging = false), 300);
|
|
125
|
+
const newTime = track.startPosition + delta * this.maxDuration;
|
|
126
|
+
track.startPosition = newTime;
|
|
127
|
+
this.rendering.setContainerOffsets();
|
|
128
|
+
}
|
|
129
|
+
findCurrentTracks() {
|
|
130
|
+
// Find the audios at the current time
|
|
131
|
+
const indexes = [];
|
|
132
|
+
this.tracks.forEach((track, index) => {
|
|
133
|
+
if (track.url &&
|
|
134
|
+
this.currentTime >= track.startPosition &&
|
|
135
|
+
this.currentTime < track.startPosition + this.durations[index]) {
|
|
136
|
+
indexes.push(index);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (indexes.length === 0) {
|
|
140
|
+
const minStartTime = Math.min(...this.tracks.filter((t) => t.url).map((track) => track.startPosition));
|
|
141
|
+
indexes.push(this.tracks.findIndex((track) => track.startPosition === minStartTime));
|
|
142
|
+
}
|
|
143
|
+
return indexes;
|
|
144
|
+
}
|
|
145
|
+
startSync() {
|
|
146
|
+
const onFrame = () => {
|
|
147
|
+
const position = this.audios.reduce((pos, audio, index) => {
|
|
148
|
+
if (!audio.paused) {
|
|
149
|
+
pos = Math.max(pos, audio.currentTime + this.tracks[index].startPosition);
|
|
150
|
+
}
|
|
151
|
+
return pos;
|
|
152
|
+
}, this.currentTime);
|
|
153
|
+
if (position > this.currentTime) {
|
|
154
|
+
this.updatePosition(position);
|
|
155
|
+
}
|
|
156
|
+
this.frameRequest = requestAnimationFrame(onFrame);
|
|
157
|
+
};
|
|
158
|
+
onFrame();
|
|
159
|
+
}
|
|
160
|
+
play() {
|
|
161
|
+
this.startSync();
|
|
162
|
+
const indexes = this.findCurrentTracks();
|
|
163
|
+
indexes.forEach((index) => {
|
|
164
|
+
var _a;
|
|
165
|
+
(_a = this.audios[index]) === null || _a === void 0 ? void 0 : _a.play();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
pause() {
|
|
169
|
+
this.audios.forEach((audio) => audio.pause());
|
|
170
|
+
}
|
|
171
|
+
isPlaying() {
|
|
172
|
+
return this.audios.some((audio) => !audio.paused);
|
|
173
|
+
}
|
|
174
|
+
destroy() {
|
|
175
|
+
if (this.frameRequest)
|
|
176
|
+
cancelAnimationFrame(this.frameRequest);
|
|
177
|
+
this.rendering.destroy();
|
|
178
|
+
this.audios.forEach((audio) => {
|
|
179
|
+
audio.pause();
|
|
180
|
+
audio.src = '';
|
|
181
|
+
});
|
|
182
|
+
this.wavesurfers.forEach((ws) => {
|
|
183
|
+
ws.destroy();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function initRendering(tracks, options) {
|
|
188
|
+
// Create a common container for all tracks
|
|
189
|
+
let scroll = document.createElement('div');
|
|
190
|
+
scroll.setAttribute('style', 'width: 100%; overflow-x: scroll; overflow-y: hidden; user-select: none;');
|
|
191
|
+
const wrapper = document.createElement('div');
|
|
192
|
+
wrapper.style.position = 'relative';
|
|
193
|
+
scroll.appendChild(wrapper);
|
|
194
|
+
options.container.appendChild(scroll);
|
|
195
|
+
// Create a common cursor
|
|
196
|
+
const cursor = document.createElement('div');
|
|
197
|
+
cursor.setAttribute('style', 'width: 1px; height: 100%; position: absolute; z-index: 10; top: 0; left: 0');
|
|
198
|
+
cursor.style.backgroundColor = options.cursorColor || options.progressColor || '#000';
|
|
199
|
+
wrapper.appendChild(cursor);
|
|
200
|
+
// Create containers for each track
|
|
201
|
+
const containers = tracks.map(() => {
|
|
202
|
+
const container = document.createElement('div');
|
|
203
|
+
container.style.width = 'fit-content';
|
|
204
|
+
wrapper.appendChild(container);
|
|
205
|
+
return container;
|
|
206
|
+
});
|
|
207
|
+
// Set the positions of each container
|
|
208
|
+
const setContainerOffsets = () => {
|
|
209
|
+
containers.forEach((container, i) => {
|
|
210
|
+
const offset = (tracks[i].startPosition * options.minPxPerSec) / devicePixelRatio;
|
|
211
|
+
container.style.transform = `translateX(${offset}px)`;
|
|
212
|
+
if (tracks[i].draggable)
|
|
213
|
+
container.style.cursor = 'ew-resize';
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
setContainerOffsets();
|
|
217
|
+
return {
|
|
218
|
+
containers,
|
|
219
|
+
// Set the start offset
|
|
220
|
+
setContainerOffsets,
|
|
221
|
+
// Set the container width
|
|
222
|
+
setMainWidth: (width) => {
|
|
223
|
+
wrapper.style.width = `${width / devicePixelRatio}px`;
|
|
224
|
+
},
|
|
225
|
+
// Update cursor position
|
|
226
|
+
updateCursor: (position) => {
|
|
227
|
+
cursor.style.left = `${Math.min(100, position * 100)}%`;
|
|
228
|
+
},
|
|
229
|
+
// Click to seek
|
|
230
|
+
addClickHandler: (onClick) => {
|
|
231
|
+
wrapper.addEventListener('click', (e) => {
|
|
232
|
+
const rect = wrapper.getBoundingClientRect();
|
|
233
|
+
const x = e.clientX - rect.left;
|
|
234
|
+
const position = x / wrapper.offsetWidth;
|
|
235
|
+
onClick(position);
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
// Destroy the container
|
|
239
|
+
destroy: () => {
|
|
240
|
+
scroll.remove();
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function initDragging(container, onDrag) {
|
|
245
|
+
const wrapper = container.parentElement;
|
|
246
|
+
if (!wrapper)
|
|
247
|
+
return;
|
|
248
|
+
// Dragging tracks to set position
|
|
249
|
+
let dragStart = null;
|
|
250
|
+
// Drag start
|
|
251
|
+
container.addEventListener('mousedown', (e) => {
|
|
252
|
+
const rect = wrapper.getBoundingClientRect();
|
|
253
|
+
dragStart = e.clientX - rect.left;
|
|
254
|
+
});
|
|
255
|
+
// Drag end
|
|
256
|
+
const onMouseUp = (e) => {
|
|
257
|
+
if (dragStart != null) {
|
|
258
|
+
e.stopPropagation();
|
|
259
|
+
dragStart = null;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
// Drag move
|
|
263
|
+
const onMouseMove = (e) => {
|
|
264
|
+
if (dragStart == null)
|
|
265
|
+
return;
|
|
266
|
+
const rect = wrapper.getBoundingClientRect();
|
|
267
|
+
const x = e.clientX - rect.left;
|
|
268
|
+
const diff = x - dragStart;
|
|
269
|
+
if (diff > 1 || diff < -1) {
|
|
270
|
+
dragStart = x;
|
|
271
|
+
onDrag(diff / wrapper.offsetWidth);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
document.body.addEventListener('mouseup', onMouseUp);
|
|
275
|
+
document.body.addEventListener('mousemove', onMouseMove);
|
|
276
|
+
return {
|
|
277
|
+
destroy: () => {
|
|
278
|
+
document.body.removeEventListener('mouseup', onMouseUp);
|
|
279
|
+
document.body.removeEventListener('mousemove', onMouseMove);
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
export default MultiTrack;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
2
|
import { WaveSurferPluginParams } from '../index.js';
|
|
3
|
+
type RegionsPluginOptions = {
|
|
4
|
+
dragSelection?: boolean;
|
|
5
|
+
draggable?: boolean;
|
|
6
|
+
resizable?: boolean;
|
|
7
|
+
};
|
|
3
8
|
type Region = {
|
|
4
9
|
startTime: number;
|
|
5
10
|
endTime: number;
|
|
@@ -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 wrapper;
|
|
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 initWrapper;
|
|
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
|
}
|
package/dist/plugins/regions.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
2
|
const MIN_WIDTH = 10;
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
dragSelection: true,
|
|
5
|
+
draggable: true,
|
|
6
|
+
resizable: true,
|
|
7
|
+
};
|
|
8
|
+
const style = (element, styles) => {
|
|
9
|
+
for (const key in styles) {
|
|
10
|
+
element.style[key] = styles[key] || '';
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const el = (tagName, css) => {
|
|
14
|
+
const element = document.createElement(tagName);
|
|
15
|
+
style(element, css);
|
|
16
|
+
return element;
|
|
17
|
+
};
|
|
3
18
|
class RegionsPlugin extends BasePlugin {
|
|
4
19
|
/** Create an instance of RegionsPlugin */
|
|
5
|
-
constructor(params) {
|
|
6
|
-
super(params);
|
|
20
|
+
constructor(params, options) {
|
|
21
|
+
super(params, options);
|
|
7
22
|
this.dragStart = NaN;
|
|
8
23
|
this.regions = [];
|
|
9
24
|
this.createdRegion = null;
|
|
@@ -11,24 +26,26 @@ class RegionsPlugin extends BasePlugin {
|
|
|
11
26
|
this.isResizingLeft = false;
|
|
12
27
|
this.isMoving = false;
|
|
13
28
|
this.handleMouseDown = (e) => {
|
|
14
|
-
this.
|
|
29
|
+
if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
|
|
30
|
+
this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
|
|
31
|
+
}
|
|
15
32
|
};
|
|
16
33
|
this.handleMouseMove = (e) => {
|
|
17
34
|
const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
|
|
18
|
-
if (this.modifiedRegion && this.isMoving) {
|
|
35
|
+
if (this.options.draggable && this.modifiedRegion && this.isMoving) {
|
|
19
36
|
this.moveRegion(this.modifiedRegion, dragEnd - this.dragStart);
|
|
20
37
|
this.dragStart = dragEnd;
|
|
21
38
|
return;
|
|
22
39
|
}
|
|
23
|
-
if (this.modifiedRegion) {
|
|
40
|
+
if (this.options.resizable && this.modifiedRegion) {
|
|
24
41
|
this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
|
|
25
42
|
return;
|
|
26
43
|
}
|
|
27
|
-
if (!isNaN(this.dragStart)) {
|
|
28
|
-
const dragEnd = e.clientX - this.
|
|
44
|
+
if (this.options.dragSelection && !isNaN(this.dragStart)) {
|
|
45
|
+
const dragEnd = e.clientX - this.wrapper.getBoundingClientRect().left;
|
|
29
46
|
if (dragEnd - this.dragStart >= MIN_WIDTH) {
|
|
30
47
|
if (!this.createdRegion) {
|
|
31
|
-
this.
|
|
48
|
+
this.container.style.pointerEvents = 'none';
|
|
32
49
|
this.createdRegion = this.createRegion(this.dragStart, dragEnd);
|
|
33
50
|
}
|
|
34
51
|
else {
|
|
@@ -45,97 +62,107 @@ class RegionsPlugin extends BasePlugin {
|
|
|
45
62
|
this.modifiedRegion = null;
|
|
46
63
|
this.isMoving = false;
|
|
47
64
|
this.dragStart = NaN;
|
|
48
|
-
this.
|
|
65
|
+
this.container.style.pointerEvents = '';
|
|
49
66
|
};
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
unsubscribeReady();
|
|
67
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
68
|
+
this.wrapper = this.initWrapper();
|
|
69
|
+
const unsubscribe = this.wavesurfer.once('decode', () => {
|
|
70
|
+
this.container.appendChild(this.wrapper);
|
|
55
71
|
});
|
|
56
|
-
this.subscriptions.push(
|
|
57
|
-
|
|
58
|
-
wsContainer.addEventListener('mousedown', this.handleMouseDown);
|
|
72
|
+
this.subscriptions.push(unsubscribe);
|
|
73
|
+
this.container.addEventListener('mousedown', this.handleMouseDown);
|
|
59
74
|
document.addEventListener('mousemove', this.handleMouseMove);
|
|
60
75
|
document.addEventListener('mouseup', this.handleMouseUp);
|
|
61
76
|
}
|
|
62
|
-
/**
|
|
77
|
+
/** Unmount */
|
|
63
78
|
destroy() {
|
|
64
|
-
|
|
65
|
-
this.renderer.getContainer().removeEventListener('mousedown', this.handleMouseDown);
|
|
79
|
+
this.container.removeEventListener('mousedown', this.handleMouseDown);
|
|
66
80
|
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
67
81
|
document.removeEventListener('mouseup', this.handleMouseUp, true);
|
|
68
|
-
|
|
82
|
+
this.wrapper.remove();
|
|
69
83
|
super.destroy();
|
|
70
84
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
initWrapper() {
|
|
86
|
+
return el('div', {
|
|
87
|
+
position: 'absolute',
|
|
88
|
+
top: '0',
|
|
89
|
+
left: '0',
|
|
90
|
+
width: '100%',
|
|
91
|
+
height: '100%',
|
|
92
|
+
zIndex: '3',
|
|
93
|
+
pointerEvents: 'none',
|
|
94
|
+
});
|
|
81
95
|
}
|
|
82
96
|
createRegionElement(start, end, title = '') {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
leftHandle
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
rightHandle
|
|
110
|
-
|
|
97
|
+
const noWidth = start === end;
|
|
98
|
+
const div = el('div', {
|
|
99
|
+
position: 'absolute',
|
|
100
|
+
left: `${start}px`,
|
|
101
|
+
width: `${end - start}px`,
|
|
102
|
+
height: '100%',
|
|
103
|
+
backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
|
|
104
|
+
borderRadius: '2px',
|
|
105
|
+
boxSizing: 'border-box',
|
|
106
|
+
borderLeft: '2px solid rgba(0, 0, 0, 0.5)',
|
|
107
|
+
borderRight: noWidth ? '' : '2px solid rgba(0, 0, 0, 0.5)',
|
|
108
|
+
transition: 'background-color 0.2s ease',
|
|
109
|
+
cursor: this.options.draggable ? 'move' : '',
|
|
110
|
+
pointerEvents: 'all',
|
|
111
|
+
padding: '0.2em',
|
|
112
|
+
});
|
|
113
|
+
div.textContent = title;
|
|
114
|
+
const leftHandle = el('div', {
|
|
115
|
+
position: 'absolute',
|
|
116
|
+
left: '0',
|
|
117
|
+
width: '6px',
|
|
118
|
+
height: '100%',
|
|
119
|
+
cursor: this.options.resizable ? 'ew-resize' : '',
|
|
120
|
+
pointerEvents: 'all',
|
|
121
|
+
});
|
|
122
|
+
div.appendChild(leftHandle);
|
|
123
|
+
const rightHandle = leftHandle.cloneNode();
|
|
124
|
+
style(rightHandle, {
|
|
125
|
+
left: '',
|
|
126
|
+
right: '0',
|
|
127
|
+
borderLeft: '',
|
|
128
|
+
});
|
|
129
|
+
div.appendChild(rightHandle);
|
|
111
130
|
leftHandle.addEventListener('mousedown', (e) => {
|
|
131
|
+
if (!this.options.resizable)
|
|
132
|
+
return;
|
|
112
133
|
e.stopPropagation();
|
|
113
|
-
this.modifiedRegion = this.regions.find((r) => r.element ===
|
|
134
|
+
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
114
135
|
this.isResizingLeft = true;
|
|
115
136
|
this.isMoving = false;
|
|
116
137
|
});
|
|
117
138
|
rightHandle.addEventListener('mousedown', (e) => {
|
|
139
|
+
if (!this.options.resizable)
|
|
140
|
+
return;
|
|
118
141
|
e.stopPropagation();
|
|
119
|
-
this.modifiedRegion = this.regions.find((r) => r.element ===
|
|
142
|
+
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
120
143
|
this.isResizingLeft = false;
|
|
121
144
|
this.isMoving = false;
|
|
122
145
|
});
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
div.addEventListener('mousedown', () => {
|
|
147
|
+
if (!this.options.draggable)
|
|
148
|
+
return;
|
|
149
|
+
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
125
150
|
this.isMoving = true;
|
|
126
151
|
});
|
|
127
|
-
|
|
128
|
-
const region = this.regions.find((r) => r.element ===
|
|
152
|
+
div.addEventListener('click', () => {
|
|
153
|
+
const region = this.regions.find((r) => r.element === div);
|
|
129
154
|
if (region) {
|
|
130
155
|
this.emit('region-clicked', { region });
|
|
131
156
|
}
|
|
132
157
|
});
|
|
133
|
-
this.
|
|
134
|
-
return
|
|
158
|
+
this.wrapper.appendChild(div);
|
|
159
|
+
return div;
|
|
135
160
|
}
|
|
136
161
|
createRegion(start, end, title = '') {
|
|
137
162
|
const duration = this.wavesurfer.getDuration();
|
|
138
|
-
const width = this.
|
|
163
|
+
const width = this.wrapper.clientWidth;
|
|
164
|
+
start = Math.max(0, Math.min(start, width - 1));
|
|
165
|
+
end = Math.max(0, Math.min(end, width - 1));
|
|
139
166
|
return {
|
|
140
167
|
element: this.createRegionElement(start, end, title),
|
|
141
168
|
start,
|
|
@@ -150,15 +177,18 @@ class RegionsPlugin extends BasePlugin {
|
|
|
150
177
|
this.emit('region-created', { region });
|
|
151
178
|
}
|
|
152
179
|
updateRegion(region, start, end) {
|
|
180
|
+
const width = this.wrapper.clientWidth;
|
|
153
181
|
if (start != null) {
|
|
154
|
-
|
|
182
|
+
start = Math.max(0, Math.min(start, width));
|
|
183
|
+
region.start = start;
|
|
155
184
|
region.element.style.left = `${region.start}px`;
|
|
156
|
-
region.startTime = (start / this.
|
|
185
|
+
region.startTime = (start / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
|
|
157
186
|
}
|
|
158
187
|
if (end != null) {
|
|
188
|
+
end = Math.max(0, Math.min(end, width));
|
|
159
189
|
region.end = end;
|
|
160
190
|
region.element.style.width = `${region.end - region.start}px`;
|
|
161
|
-
region.endTime = (end / this.
|
|
191
|
+
region.endTime = (end / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
|
|
162
192
|
}
|
|
163
193
|
this.emit('region-updated', { region });
|
|
164
194
|
}
|
|
@@ -166,18 +196,20 @@ class RegionsPlugin extends BasePlugin {
|
|
|
166
196
|
this.updateRegion(region, region.start + delta, region.end + delta);
|
|
167
197
|
}
|
|
168
198
|
/** Create a region at a given start and end time, with an optional title */
|
|
169
|
-
|
|
199
|
+
add(startTime, endTime, title = '', color = '') {
|
|
170
200
|
const duration = this.wavesurfer.getDuration();
|
|
171
|
-
const width = this.
|
|
201
|
+
const width = this.wrapper.clientWidth;
|
|
172
202
|
const start = (startTime / duration) * width;
|
|
173
203
|
const end = (endTime / duration) * width;
|
|
174
204
|
const region = this.createRegion(start, end, title);
|
|
175
205
|
this.addRegion(region);
|
|
206
|
+
if (color)
|
|
207
|
+
this.setRegionColor(region, color);
|
|
176
208
|
return region;
|
|
177
209
|
}
|
|
178
210
|
/** Set the background color of a region */
|
|
179
211
|
setRegionColor(region, color) {
|
|
180
|
-
region.element.style.backgroundColor = color;
|
|
212
|
+
region.element.style[region.startTime === region.endTime ? 'borderColor' : 'backgroundColor'] = color;
|
|
181
213
|
}
|
|
182
214
|
}
|
|
183
215
|
export default RegionsPlugin;
|