wavesurfer.js 7.0.0-alpha.9 → 7.0.0-beta.1

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.
Files changed (50) hide show
  1. package/README.md +90 -18
  2. package/dist/base-plugin.d.ts +6 -4
  3. package/dist/base-plugin.js +9 -3
  4. package/dist/decoder.d.ts +8 -7
  5. package/dist/decoder.js +43 -38
  6. package/dist/event-emitter.d.ts +16 -10
  7. package/dist/event-emitter.js +38 -16
  8. package/dist/fetcher.d.ts +4 -3
  9. package/dist/fetcher.js +5 -15
  10. package/dist/player.d.ts +31 -11
  11. package/dist/player.js +58 -31
  12. package/dist/plugins/envelope.d.ts +71 -0
  13. package/dist/plugins/envelope.js +347 -0
  14. package/dist/plugins/envelope.min.js +1 -0
  15. package/dist/plugins/minimap.d.ts +39 -0
  16. package/dist/plugins/minimap.js +117 -0
  17. package/dist/plugins/minimap.min.js +1 -0
  18. package/dist/plugins/multitrack.d.ts +85 -12
  19. package/dist/plugins/multitrack.js +331 -84
  20. package/dist/plugins/multitrack.min.js +1 -0
  21. package/dist/plugins/record.d.ts +26 -0
  22. package/dist/plugins/record.js +126 -0
  23. package/dist/plugins/record.min.js +1 -0
  24. package/dist/plugins/regions.d.ts +83 -43
  25. package/dist/plugins/regions.js +362 -192
  26. package/dist/plugins/regions.min.js +1 -0
  27. package/dist/plugins/spectrogram.d.ts +69 -0
  28. package/dist/plugins/spectrogram.js +340 -0
  29. package/dist/plugins/spectrogram.min.js +1 -0
  30. package/dist/plugins/timeline.d.ts +25 -9
  31. package/dist/plugins/timeline.js +65 -24
  32. package/dist/plugins/timeline.min.js +1 -0
  33. package/dist/renderer.d.ts +29 -13
  34. package/dist/renderer.js +280 -161
  35. package/dist/timer.d.ts +1 -1
  36. package/dist/wavesurfer.d.ts +151 -0
  37. package/dist/wavesurfer.js +241 -0
  38. package/dist/wavesurfer.min.js +1 -1
  39. package/package.json +54 -27
  40. package/dist/index.d.ts +0 -117
  41. package/dist/index.js +0 -227
  42. package/dist/player-webaudio.d.ts +0 -8
  43. package/dist/player-webaudio.js +0 -32
  44. package/dist/plugins/xmultitrack.d.ts +0 -44
  45. package/dist/plugins/xmultitrack.js +0 -260
  46. package/dist/react/useWavesurfer.d.ts +0 -5
  47. package/dist/react/useWavesurfer.js +0 -20
  48. package/dist/wavesurfer.Multitrack.min.js +0 -1
  49. package/dist/wavesurfer.Regions.min.js +0 -1
  50. package/dist/wavesurfer.Timeline.min.js +0 -1
@@ -1,215 +1,385 @@
1
+ /**
2
+ * Regions are visual overlays on the waveform that can be used to mark segments of audio.
3
+ * Regions can be clicked on, dragged and resized.
4
+ * You can set the color and content of each region, as well as their HTML content.
5
+ */
1
6
  import BasePlugin from '../base-plugin.js';
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
- };
18
- class RegionsPlugin extends BasePlugin {
19
- /** Create an instance of RegionsPlugin */
20
- constructor(params, options) {
21
- super(params, options);
22
- this.dragStart = NaN;
23
- this.regions = [];
24
- this.createdRegion = null;
25
- this.modifiedRegion = null;
26
- this.isResizingLeft = false;
27
- this.isMoving = false;
28
- this.handleMouseDown = (e) => {
29
- if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
30
- this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
7
+ import EventEmitter from '../event-emitter.js';
8
+ function makeDraggable(element, onStart, onMove, onEnd, threshold = 5) {
9
+ if (!element)
10
+ return () => undefined;
11
+ let isDragging = false;
12
+ const onClick = (e) => {
13
+ isDragging && e.stopPropagation();
14
+ };
15
+ const onMouseDown = (e) => {
16
+ e.stopPropagation();
17
+ let x = e.clientX;
18
+ let sumDx = 0;
19
+ onStart(x);
20
+ const onMouseMove = (e) => {
21
+ const newX = e.clientX;
22
+ const dx = newX - x;
23
+ sumDx += dx;
24
+ x = newX;
25
+ if (isDragging || Math.abs(sumDx) >= threshold) {
26
+ onMove(isDragging ? dx : sumDx);
27
+ isDragging = true;
31
28
  }
32
29
  };
33
- this.handleMouseMove = (e) => {
34
- const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
35
- if (this.options.draggable && this.modifiedRegion && this.isMoving) {
36
- this.moveRegion(this.modifiedRegion, dragEnd - this.dragStart);
37
- this.dragStart = dragEnd;
38
- return;
39
- }
40
- if (this.options.resizable && this.modifiedRegion) {
41
- this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
42
- return;
43
- }
44
- if (this.options.dragSelection && !isNaN(this.dragStart)) {
45
- const dragEnd = e.clientX - this.wrapper.getBoundingClientRect().left;
46
- if (dragEnd - this.dragStart >= MIN_WIDTH) {
47
- if (!this.createdRegion) {
48
- this.container.style.pointerEvents = 'none';
49
- this.createdRegion = this.createRegion(this.dragStart, dragEnd);
50
- }
51
- else {
52
- this.updateRegion(this.createdRegion, this.dragStart, dragEnd);
53
- }
54
- }
30
+ const onMouseUp = () => {
31
+ if (isDragging) {
32
+ onEnd();
33
+ setTimeout(() => (isDragging = false), 10);
55
34
  }
35
+ document.removeEventListener('mousemove', onMouseMove);
36
+ document.removeEventListener('mouseup', onMouseUp);
56
37
  };
57
- this.handleMouseUp = () => {
58
- if (this.createdRegion) {
59
- this.addRegion(this.createdRegion);
60
- this.createdRegion = null;
38
+ document.addEventListener('mousemove', onMouseMove);
39
+ document.addEventListener('mouseup', onMouseUp);
40
+ };
41
+ element.addEventListener('click', onClick);
42
+ element.addEventListener('mousedown', onMouseDown);
43
+ return () => {
44
+ element.removeEventListener('click', onClick);
45
+ element.removeEventListener('mousedown', onMouseDown);
46
+ };
47
+ }
48
+ export class Region extends EventEmitter {
49
+ constructor(params, totalDuration) {
50
+ super();
51
+ this.totalDuration = totalDuration;
52
+ this.id = params.id || `region-${Math.random().toString(32).slice(2)}`;
53
+ this.start = params.start;
54
+ this.end = params.end ?? params.start;
55
+ this.drag = params.drag ?? true;
56
+ this.resize = params.resize ?? true;
57
+ this.color = params.color ?? 'rgba(0, 0, 0, 0.1)';
58
+ this.element = this.initElement(params.content);
59
+ this.renderPosition();
60
+ this.initMouseEvents();
61
+ }
62
+ initElement(content) {
63
+ const element = document.createElement('div');
64
+ const isMarker = this.start === this.end;
65
+ element.setAttribute('part', `${isMarker ? 'marker' : 'region'} ${this.id}`);
66
+ element.setAttribute('style', `
67
+ position: absolute;
68
+ height: 100%;
69
+ background-color: ${isMarker ? 'none' : this.color};
70
+ border-left: ${isMarker ? '2px solid ' + this.color : 'none'};
71
+ border-radius: 2px;
72
+ box-sizing: border-box;
73
+ transition: background-color 0.2s ease;
74
+ cursor: ${this.drag ? 'grab' : 'default'};
75
+ pointer-events: all;
76
+ padding: 0.2em ${isMarker ? 0.2 : 0.4}em;
77
+ pointer-events: all;
78
+ `);
79
+ // Init content
80
+ if (content) {
81
+ if (typeof content === 'string') {
82
+ this.content = document.createElement('div');
83
+ this.content.textContent = content;
61
84
  }
62
- this.modifiedRegion = null;
63
- this.isMoving = false;
64
- this.dragStart = NaN;
65
- this.container.style.pointerEvents = '';
66
- };
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);
71
- });
72
- this.subscriptions.push(unsubscribe);
73
- this.container.addEventListener('mousedown', this.handleMouseDown);
74
- document.addEventListener('mousemove', this.handleMouseMove);
75
- document.addEventListener('mouseup', this.handleMouseUp);
85
+ else {
86
+ this.content = content;
87
+ }
88
+ this.content.setAttribute('part', 'region-content');
89
+ element.appendChild(this.content);
90
+ }
91
+ // Add resize handles
92
+ if (!isMarker) {
93
+ const leftHandle = document.createElement('div');
94
+ leftHandle.setAttribute('data-resize', 'left');
95
+ leftHandle.setAttribute('style', `
96
+ position: absolute;
97
+ z-index: 2;
98
+ width: 6px;
99
+ height: 100%;
100
+ top: 0;
101
+ left: 0;
102
+ border-left: 2px solid rgba(0, 0, 0, 0.5);
103
+ border-radius: 2px 0 0 2px;
104
+ cursor: ${this.resize ? 'ew-resize' : 'default'};
105
+ word-break: keep-all;
106
+ `);
107
+ leftHandle.setAttribute('part', 'region-handle region-handle-left');
108
+ const rightHandle = leftHandle.cloneNode();
109
+ rightHandle.setAttribute('data-resize', 'right');
110
+ rightHandle.style.left = '';
111
+ rightHandle.style.right = '0';
112
+ rightHandle.style.borderRight = rightHandle.style.borderLeft;
113
+ rightHandle.style.borderLeft = '';
114
+ rightHandle.style.borderRadius = '0 2px 2px 0';
115
+ rightHandle.setAttribute('part', 'region-handle region-handle-right');
116
+ element.appendChild(leftHandle);
117
+ element.appendChild(rightHandle);
118
+ }
119
+ return element;
76
120
  }
77
- /** Unmount */
78
- destroy() {
79
- this.container.removeEventListener('mousedown', this.handleMouseDown);
80
- document.removeEventListener('mousemove', this.handleMouseMove);
81
- document.removeEventListener('mouseup', this.handleMouseUp, true);
82
- this.wrapper.remove();
83
- super.destroy();
121
+ renderPosition() {
122
+ const start = this.start / this.totalDuration;
123
+ const end = this.end / this.totalDuration;
124
+ this.element.style.left = `${start * 100}%`;
125
+ this.element.style.width = `${(end - start) * 100}%`;
84
126
  }
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
- });
127
+ initMouseEvents() {
128
+ const { element } = this;
129
+ element.addEventListener('click', (e) => this.emit('click', e));
130
+ element.addEventListener('mouseenter', (e) => this.emit('over', e));
131
+ element.addEventListener('mouseleave', (e) => this.emit('leave', e));
132
+ element.addEventListener('dblclick', (e) => this.emit('dblclick', e));
133
+ // Drag
134
+ makeDraggable(element, () => this.onStartMoving(), (dx) => this.onMove(dx), () => this.onEndMoving());
135
+ // Resize
136
+ const resizeThreshold = 1;
137
+ makeDraggable(element.querySelector('[data-resize="left"]'), () => null, (dx) => this.onResize(dx, 'start'), () => this.onEndResizing(), resizeThreshold);
138
+ makeDraggable(element.querySelector('[data-resize="right"]'), () => null, (dx) => this.onResize(dx, 'end'), () => this.onEndResizing(), resizeThreshold);
95
139
  }
96
- createRegionElement(start, end, title = '') {
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);
130
- leftHandle.addEventListener('mousedown', (e) => {
131
- if (!this.options.resizable)
132
- return;
133
- e.stopPropagation();
134
- this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
135
- this.isResizingLeft = true;
136
- this.isMoving = false;
137
- });
138
- rightHandle.addEventListener('mousedown', (e) => {
139
- if (!this.options.resizable)
140
- return;
141
- e.stopPropagation();
142
- this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
143
- this.isResizingLeft = false;
144
- this.isMoving = false;
145
- });
146
- div.addEventListener('mousedown', () => {
147
- if (!this.options.draggable)
148
- return;
149
- this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
150
- this.isMoving = true;
151
- });
152
- div.addEventListener('click', () => {
153
- const region = this.regions.find((r) => r.element === div);
154
- if (region) {
155
- this.emit('region-clicked', { region });
140
+ onStartMoving() {
141
+ if (!this.drag)
142
+ return;
143
+ this.element.style.cursor = 'grabbing';
144
+ }
145
+ onEndMoving() {
146
+ if (!this.drag)
147
+ return;
148
+ this.element.style.cursor = 'grab';
149
+ this.emit('update-end');
150
+ }
151
+ onUpdate(dx, sides) {
152
+ if (!this.element.parentElement)
153
+ return;
154
+ const deltaSeconds = (dx / this.element.parentElement.clientWidth) * this.totalDuration;
155
+ sides.forEach((side) => {
156
+ this[side] += deltaSeconds;
157
+ if (side === 'start') {
158
+ this.start = Math.max(0, Math.min(this.start, this.end));
159
+ }
160
+ else {
161
+ this.end = Math.max(this.start, Math.min(this.end, this.totalDuration));
156
162
  }
157
163
  });
158
- this.wrapper.appendChild(div);
159
- return div;
164
+ this.renderPosition();
165
+ this.emit('update');
160
166
  }
161
- createRegion(start, end, title = '') {
162
- const duration = this.wavesurfer.getDuration();
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));
166
- return {
167
- element: this.createRegionElement(start, end, title),
168
- start,
169
- end,
170
- startTime: (start / width) * duration,
171
- endTime: (end / width) * duration,
172
- title,
173
- };
167
+ onMove(dx) {
168
+ if (!this.drag)
169
+ return;
170
+ this.onUpdate(dx, ['start', 'end']);
174
171
  }
175
- addRegion(region) {
176
- this.regions.push(region);
177
- this.emit('region-created', { region });
178
- }
179
- updateRegion(region, start, end) {
180
- const width = this.wrapper.clientWidth;
181
- if (start != null) {
182
- start = Math.max(0, Math.min(start, width));
183
- region.start = start;
184
- region.element.style.left = `${region.start}px`;
185
- region.startTime = (start / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
172
+ onResize(dx, side) {
173
+ if (!this.resize)
174
+ return;
175
+ this.onUpdate(dx, [side]);
176
+ }
177
+ onEndResizing() {
178
+ if (!this.resize)
179
+ return;
180
+ this.emit('update-end');
181
+ }
182
+ _setTotalDuration(totalDuration) {
183
+ this.totalDuration = totalDuration;
184
+ this.renderPosition();
185
+ }
186
+ /** Play the region from start to end */
187
+ play() {
188
+ this.emit('play');
189
+ }
190
+ /** Update the region's options */
191
+ setOptions(options) {
192
+ if (options.color) {
193
+ this.color = options.color;
194
+ this.element.style.backgroundColor = this.color;
195
+ }
196
+ if (options.drag !== undefined) {
197
+ this.drag = options.drag;
198
+ this.element.style.cursor = this.drag ? 'grab' : 'default';
186
199
  }
187
- if (end != null) {
188
- end = Math.max(0, Math.min(end, width));
189
- region.end = end;
190
- region.element.style.width = `${region.end - region.start}px`;
191
- region.endTime = (end / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
200
+ if (options.resize !== undefined) {
201
+ this.resize = options.resize;
202
+ this.element.querySelectorAll('[data-resize]').forEach((handle) => {
203
+ ;
204
+ handle.style.cursor = this.resize ? 'ew-resize' : 'default';
205
+ });
192
206
  }
193
- this.emit('region-updated', { region });
207
+ if (options.start !== undefined || options.end !== undefined) {
208
+ this.start = options.start ?? this.start;
209
+ this.end = options.end ?? this.end;
210
+ this.renderPosition();
211
+ }
212
+ }
213
+ /** Remove the region */
214
+ remove() {
215
+ this.emit('remove');
216
+ this.element.remove();
217
+ // This violates the type but we want to clean up the DOM reference
218
+ // w/o having to have a nullable type of the element
219
+ this.element = null;
220
+ }
221
+ }
222
+ class RegionsPlugin extends BasePlugin {
223
+ /** Create an instance of RegionsPlugin */
224
+ constructor(options) {
225
+ super(options);
226
+ this.regions = [];
227
+ this.regionsContainer = this.initRegionsContainer();
228
+ }
229
+ /** Create an instance of RegionsPlugin */
230
+ static create(options) {
231
+ return new RegionsPlugin(options);
232
+ }
233
+ /** Called by wavesurfer, don't call manually */
234
+ onInit() {
235
+ if (!this.wavesurfer) {
236
+ throw Error('WaveSurfer is not initialized');
237
+ }
238
+ this.wavesurfer.getWrapper().appendChild(this.regionsContainer);
239
+ }
240
+ initRegionsContainer() {
241
+ const div = document.createElement('div');
242
+ div.setAttribute('style', `
243
+ position: absolute;
244
+ top: 0;
245
+ left: 0;
246
+ width: 100%;
247
+ height: 100%;
248
+ z-index: 3;
249
+ pointer-events: none;
250
+ `);
251
+ return div;
252
+ }
253
+ /** Get all created regions */
254
+ getRegions() {
255
+ return this.regions;
194
256
  }
195
- moveRegion(region, delta) {
196
- this.updateRegion(region, region.start + delta, region.end + delta);
257
+ avoidOverlapping(region) {
258
+ if (!region.content)
259
+ return;
260
+ // Check that the label doesn't overlap with other labels
261
+ // If it does, push it down until it doesn't
262
+ const div = region.content;
263
+ const labelLeft = div.getBoundingClientRect().left;
264
+ const labelWidth = region.element.scrollWidth;
265
+ const overlap = this.regions
266
+ .filter((reg) => {
267
+ if (reg === region || !reg.content)
268
+ return false;
269
+ const left = reg.content.getBoundingClientRect().left;
270
+ const width = reg.element.scrollWidth;
271
+ return labelLeft < left + width && left < labelLeft + labelWidth;
272
+ })
273
+ .map((reg) => reg.content?.getBoundingClientRect().height || 0)
274
+ .reduce((sum, val) => sum + val, 0);
275
+ div.style.marginTop = `${overlap}px`;
197
276
  }
198
- /** Create a region at a given start and end time, with an optional title */
199
- add(startTime, endTime, title = '', color = '') {
277
+ saveRegion(region) {
278
+ this.regionsContainer.appendChild(region.element);
279
+ this.avoidOverlapping(region);
280
+ this.regions.push(region);
281
+ this.emit('region-created', region);
282
+ const regionSubscriptions = [
283
+ region.on('update-end', () => {
284
+ this.avoidOverlapping(region);
285
+ this.emit('region-updated', region);
286
+ }),
287
+ region.on('play', () => {
288
+ this.wavesurfer?.play();
289
+ this.wavesurfer?.setTime(region.start);
290
+ }),
291
+ region.on('click', (e) => {
292
+ this.emit('region-clicked', region, e);
293
+ }),
294
+ // Remove the region from the list when it's removed
295
+ region.once('remove', () => {
296
+ regionSubscriptions.forEach((unsubscribe) => unsubscribe());
297
+ this.regions = this.regions.filter((reg) => reg !== region);
298
+ }),
299
+ ];
300
+ this.subscriptions.push(...regionSubscriptions);
301
+ }
302
+ /** Create a region with given parameters */
303
+ addRegion(options) {
304
+ if (!this.wavesurfer) {
305
+ throw Error('WaveSurfer is not initialized');
306
+ }
200
307
  const duration = this.wavesurfer.getDuration();
201
- const width = this.wrapper.clientWidth;
202
- const start = (startTime / duration) * width;
203
- const end = (endTime / duration) * width;
204
- const region = this.createRegion(start, end, title);
205
- this.addRegion(region);
206
- if (color)
207
- this.setRegionColor(region, color);
308
+ const region = new Region(options, duration);
309
+ if (!duration) {
310
+ this.subscriptions.push(this.wavesurfer.once('canplay', (duration) => {
311
+ region._setTotalDuration(duration);
312
+ this.saveRegion(region);
313
+ }));
314
+ }
315
+ else {
316
+ this.saveRegion(region);
317
+ }
208
318
  return region;
209
319
  }
210
- /** Set the background color of a region */
211
- setRegionColor(region, color) {
212
- region.element.style[region.startTime === region.endTime ? 'borderColor' : 'backgroundColor'] = color;
320
+ /**
321
+ * Enable creation of regions by dragging on an empty space on the waveform.
322
+ * Returns a function to disable the drag selection.
323
+ */
324
+ enableDragSelection(options) {
325
+ const wrapper = this.wavesurfer?.getWrapper()?.querySelector('div');
326
+ if (!wrapper)
327
+ return () => undefined;
328
+ let region = null;
329
+ let startX = 0;
330
+ let sumDx = 0;
331
+ return makeDraggable(wrapper,
332
+ // On mousedown
333
+ (x) => (startX = x),
334
+ // On mousemove
335
+ (dx) => {
336
+ if (!this.wavesurfer)
337
+ return;
338
+ if (!region) {
339
+ const duration = this.wavesurfer.getDuration();
340
+ const box = wrapper.getBoundingClientRect();
341
+ let start = ((startX - box.left) / box.width) * duration;
342
+ let end = ((startX + dx - box.left) / box.width) * duration;
343
+ if (start > end)
344
+ [start, end] = [end, start];
345
+ region = new Region({
346
+ ...options,
347
+ start,
348
+ end,
349
+ }, duration);
350
+ this.regionsContainer.appendChild(region.element);
351
+ }
352
+ sumDx += dx;
353
+ if (region) {
354
+ const privateRegion = region;
355
+ privateRegion.onUpdate(dx, [sumDx > 0 ? 'end' : 'start']);
356
+ }
357
+ },
358
+ // On mouseup
359
+ () => {
360
+ if (region) {
361
+ this.saveRegion(region);
362
+ region = null;
363
+ sumDx = 0;
364
+ // Prevent a click event on the waveform
365
+ if (this.wavesurfer) {
366
+ const { interact } = this.wavesurfer.options;
367
+ if (interact) {
368
+ this.wavesurfer.toggleInteraction(false);
369
+ setTimeout(() => this.wavesurfer?.toggleInteraction(interact), 10);
370
+ }
371
+ }
372
+ }
373
+ });
374
+ }
375
+ /** Remove all regions */
376
+ clearRegions() {
377
+ this.regions.forEach((region) => region.remove());
378
+ }
379
+ /** Destroy the plugin and clean up */
380
+ destroy() {
381
+ this.clearRegions();
382
+ super.destroy();
213
383
  }
214
384
  }
215
385
  export default RegionsPlugin;
@@ -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.Regions=e():t.Regions=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={284:(t,e,i)=>{i.d(e,{Z:()=>r});var n=i(139);class s extends n.Z{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}}const r=s},139:(t,e,i)=>{i.d(e,{Z:()=>n});const n=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),n=this.on(t,(()=>{i(),n()}));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(n){var s=e[n];if(void 0!==s)return s.exports;var r=e[n]={exports:{}};return t[n](r,r.exports,i),r.exports}i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var n={};return(()=>{i.d(n,{default:()=>a});var t=i(284),e=i(139);function s(t,e,i,n,s=5){if(!t)return()=>{};let r=!1;const o=t=>{r&&t.stopPropagation()},a=t=>{t.stopPropagation();let o=t.clientX,a=0;e(o);const h=t=>{const e=t.clientX,n=e-o;a+=n,o=e,(r||Math.abs(a)>=s)&&(i(r?n:a),r=!0)},l=()=>{r&&(n(),setTimeout((()=>r=!1),10)),document.removeEventListener("mousemove",h),document.removeEventListener("mouseup",l)};document.addEventListener("mousemove",h),document.addEventListener("mouseup",l)};return t.addEventListener("click",o),t.addEventListener("mousedown",a),()=>{t.removeEventListener("click",o),t.removeEventListener("mousedown",a)}}class r extends e.Z{constructor(t,e){super(),this.totalDuration=e,this.id=t.id||`region-${Math.random().toString(32).slice(2)}`,this.start=t.start,this.end=t.end??t.start,this.drag=t.drag??!0,this.resize=t.resize??!0,this.color=t.color??"rgba(0, 0, 0, 0.1)",this.element=this.initElement(t.content),this.renderPosition(),this.initMouseEvents()}initElement(t){const e=document.createElement("div"),i=this.start===this.end;if(e.setAttribute("part",`${i?"marker":"region"} ${this.id}`),e.setAttribute("style",`\n position: absolute;\n height: 100%;\n background-color: ${i?"none":this.color};\n border-left: ${i?"2px solid "+this.color:"none"};\n border-radius: 2px;\n box-sizing: border-box;\n transition: background-color 0.2s ease;\n cursor: ${this.drag?"grab":"default"};\n pointer-events: all;\n padding: 0.2em ${i?.2:.4}em;\n pointer-events: all;\n `),t&&("string"==typeof t?(this.content=document.createElement("div"),this.content.textContent=t):this.content=t,this.content.setAttribute("part","region-content"),e.appendChild(this.content)),!i){const t=document.createElement("div");t.setAttribute("data-resize","left"),t.setAttribute("style",`\n position: absolute;\n z-index: 2;\n width: 6px;\n height: 100%;\n top: 0;\n left: 0;\n border-left: 2px solid rgba(0, 0, 0, 0.5);\n border-radius: 2px 0 0 2px;\n cursor: ${this.resize?"ew-resize":"default"};\n word-break: keep-all;\n `),t.setAttribute("part","region-handle region-handle-left");const i=t.cloneNode();i.setAttribute("data-resize","right"),i.style.left="",i.style.right="0",i.style.borderRight=i.style.borderLeft,i.style.borderLeft="",i.style.borderRadius="0 2px 2px 0",i.setAttribute("part","region-handle region-handle-right"),e.appendChild(t),e.appendChild(i)}return e}renderPosition(){const t=this.start/this.totalDuration,e=this.end/this.totalDuration;this.element.style.left=100*t+"%",this.element.style.width=100*(e-t)+"%"}initMouseEvents(){const{element:t}=this;t.addEventListener("click",(t=>this.emit("click",t))),t.addEventListener("mouseenter",(t=>this.emit("over",t))),t.addEventListener("mouseleave",(t=>this.emit("leave",t))),t.addEventListener("dblclick",(t=>this.emit("dblclick",t))),s(t,(()=>this.onStartMoving()),(t=>this.onMove(t)),(()=>this.onEndMoving())),s(t.querySelector('[data-resize="left"]'),(()=>null),(t=>this.onResize(t,"start")),(()=>this.onEndResizing()),1),s(t.querySelector('[data-resize="right"]'),(()=>null),(t=>this.onResize(t,"end")),(()=>this.onEndResizing()),1)}onStartMoving(){this.drag&&(this.element.style.cursor="grabbing")}onEndMoving(){this.drag&&(this.element.style.cursor="grab",this.emit("update-end"))}onUpdate(t,e){if(!this.element.parentElement)return;const i=t/this.element.parentElement.clientWidth*this.totalDuration;e.forEach((t=>{this[t]+=i,"start"===t?this.start=Math.max(0,Math.min(this.start,this.end)):this.end=Math.max(this.start,Math.min(this.end,this.totalDuration))})),this.renderPosition(),this.emit("update")}onMove(t){this.drag&&this.onUpdate(t,["start","end"])}onResize(t,e){this.resize&&this.onUpdate(t,[e])}onEndResizing(){this.resize&&this.emit("update-end")}_setTotalDuration(t){this.totalDuration=t,this.renderPosition()}play(){this.emit("play")}setOptions(t){t.color&&(this.color=t.color,this.element.style.backgroundColor=this.color),void 0!==t.drag&&(this.drag=t.drag,this.element.style.cursor=this.drag?"grab":"default"),void 0!==t.resize&&(this.resize=t.resize,this.element.querySelectorAll("[data-resize]").forEach((t=>{t.style.cursor=this.resize?"ew-resize":"default"}))),void 0===t.start&&void 0===t.end||(this.start=t.start??this.start,this.end=t.end??this.end,this.renderPosition())}remove(){this.emit("remove"),this.element.remove(),this.element=null}}class o extends t.Z{constructor(t){super(t),this.regions=[],this.regionsContainer=this.initRegionsContainer()}static create(t){return new o(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer)}initRegionsContainer(){const t=document.createElement("div");return t.setAttribute("style","\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 3;\n pointer-events: none;\n "),t}getRegions(){return this.regions}avoidOverlapping(t){if(!t.content)return;const e=t.content,i=e.getBoundingClientRect().left,n=t.element.scrollWidth,s=this.regions.filter((e=>{if(e===t||!e.content)return!1;const s=e.content.getBoundingClientRect().left,r=e.element.scrollWidth;return i<s+r&&s<i+n})).map((t=>t.content?.getBoundingClientRect().height||0)).reduce(((t,e)=>t+e),0);e.style.marginTop=`${s}px`}saveRegion(t){this.regionsContainer.appendChild(t.element),this.avoidOverlapping(t),this.regions.push(t),this.emit("region-created",t);const e=[t.on("update-end",(()=>{this.avoidOverlapping(t),this.emit("region-updated",t)})),t.on("play",(()=>{this.wavesurfer?.play(),this.wavesurfer?.setTime(t.start)})),t.on("click",(e=>{this.emit("region-clicked",t,e)})),t.once("remove",(()=>{e.forEach((t=>t())),this.regions=this.regions.filter((e=>e!==t))}))];this.subscriptions.push(...e)}addRegion(t){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const e=this.wavesurfer.getDuration(),i=new r(t,e);return e?this.saveRegion(i):this.subscriptions.push(this.wavesurfer.once("canplay",(t=>{i._setTotalDuration(t),this.saveRegion(i)}))),i}enableDragSelection(t){const e=this.wavesurfer?.getWrapper()?.querySelector("div");if(!e)return()=>{};let i=null,n=0,o=0;return s(e,(t=>n=t),(s=>{if(this.wavesurfer){if(!i){const o=this.wavesurfer.getDuration(),a=e.getBoundingClientRect();let h=(n-a.left)/a.width*o,l=(n+s-a.left)/a.width*o;h>l&&([h,l]=[l,h]),i=new r({...t,start:h,end:l},o),this.regionsContainer.appendChild(i.element)}o+=s,i&&i.onUpdate(s,[o>0?"end":"start"])}}),(()=>{if(i&&(this.saveRegion(i),i=null,o=0,this.wavesurfer)){const{interact:t}=this.wavesurfer.options;t&&(this.wavesurfer.toggleInteraction(!1),setTimeout((()=>this.wavesurfer?.toggleInteraction(t)),10))}}))}clearRegions(){this.regions.forEach((t=>t.remove()))}destroy(){this.clearRegions(),super.destroy()}}const a=o})(),n.default})()));