wavesurfer.js 7.0.0-alpha.4 → 7.0.0-alpha.40

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