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

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 (58) 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.cjs +1 -0
  15. package/dist/plugins/envelope.min.js +1 -0
  16. package/dist/plugins/minimap.d.ts +39 -0
  17. package/dist/plugins/minimap.js +114 -0
  18. package/dist/plugins/minimap.min.cjs +1 -0
  19. package/dist/plugins/minimap.min.js +1 -0
  20. package/dist/plugins/multitrack.d.ts +85 -12
  21. package/dist/plugins/multitrack.js +331 -84
  22. package/dist/plugins/multitrack.min.cjs +1 -0
  23. package/dist/plugins/multitrack.min.js +1 -0
  24. package/dist/plugins/record.d.ts +26 -0
  25. package/dist/plugins/record.js +126 -0
  26. package/dist/plugins/record.min.cjs +1 -0
  27. package/dist/plugins/record.min.js +1 -0
  28. package/dist/plugins/regions.d.ts +83 -43
  29. package/dist/plugins/regions.js +362 -192
  30. package/dist/plugins/regions.min.cjs +1 -0
  31. package/dist/plugins/regions.min.js +1 -0
  32. package/dist/plugins/spectrogram.d.ts +69 -0
  33. package/dist/plugins/spectrogram.js +340 -0
  34. package/dist/plugins/spectrogram.min.cjs +1 -0
  35. package/dist/plugins/spectrogram.min.js +1 -0
  36. package/dist/plugins/timeline.d.ts +25 -9
  37. package/dist/plugins/timeline.js +65 -24
  38. package/dist/plugins/timeline.min.cjs +1 -0
  39. package/dist/plugins/timeline.min.js +1 -0
  40. package/dist/renderer.d.ts +29 -13
  41. package/dist/renderer.js +280 -161
  42. package/dist/timer.d.ts +1 -1
  43. package/dist/wavesurfer.d.ts +151 -0
  44. package/dist/wavesurfer.js +241 -0
  45. package/dist/wavesurfer.min.cjs +1 -0
  46. package/dist/wavesurfer.min.js +1 -1
  47. package/package.json +55 -28
  48. package/dist/index.d.ts +0 -117
  49. package/dist/index.js +0 -227
  50. package/dist/player-webaudio.d.ts +0 -8
  51. package/dist/player-webaudio.js +0 -32
  52. package/dist/plugins/xmultitrack.d.ts +0 -44
  53. package/dist/plugins/xmultitrack.js +0 -260
  54. package/dist/react/useWavesurfer.d.ts +0 -5
  55. package/dist/react/useWavesurfer.js +0 -20
  56. package/dist/wavesurfer.Multitrack.min.js +0 -1
  57. package/dist/wavesurfer.Regions.min.js +0 -1
  58. 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})()));
@@ -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})()));