wavesurfer.js 7.12.3 → 7.12.4

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.
@@ -1,699 +1 @@
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
- */
6
- import BasePlugin from '../base-plugin.js';
7
- import EventEmitter from '../event-emitter.js';
8
- import createElement from '../dom.js';
9
- import { createDragStream } from '../reactive/drag-stream.js';
10
- import { effect } from '../reactive/store.js';
11
- import { fromEvent, cleanup as cleanupStream } from '../reactive/event-streams.js';
12
- class SingleRegion extends EventEmitter {
13
- constructor(params, totalDuration, numberOfChannels = 0) {
14
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
15
- super();
16
- this.totalDuration = totalDuration;
17
- this.numberOfChannels = numberOfChannels;
18
- this.element = null; // Element is created on init
19
- this.minLength = 0;
20
- this.maxLength = Infinity;
21
- this.contentEditable = false;
22
- this.subscriptions = [];
23
- this.updatingSide = undefined;
24
- this.isRemoved = false;
25
- this.subscriptions = [];
26
- this.id = params.id || `region-${Math.random().toString(32).slice(2)}`;
27
- this.start = this.clampPosition(params.start);
28
- this.end = this.clampPosition((_a = params.end) !== null && _a !== void 0 ? _a : params.start);
29
- this.drag = (_b = params.drag) !== null && _b !== void 0 ? _b : true;
30
- this.resize = (_c = params.resize) !== null && _c !== void 0 ? _c : true;
31
- this.resizeStart = (_d = params.resizeStart) !== null && _d !== void 0 ? _d : true;
32
- this.resizeEnd = (_e = params.resizeEnd) !== null && _e !== void 0 ? _e : true;
33
- this.color = (_f = params.color) !== null && _f !== void 0 ? _f : 'rgba(0, 0, 0, 0.1)';
34
- this.minLength = (_g = params.minLength) !== null && _g !== void 0 ? _g : this.minLength;
35
- this.maxLength = (_h = params.maxLength) !== null && _h !== void 0 ? _h : this.maxLength;
36
- this.channelIdx = (_j = params.channelIdx) !== null && _j !== void 0 ? _j : -1;
37
- this.contentEditable = (_k = params.contentEditable) !== null && _k !== void 0 ? _k : this.contentEditable;
38
- this.element = this.initElement();
39
- this.setContent(params.content);
40
- this.setPart();
41
- this.renderPosition();
42
- this.initMouseEvents();
43
- }
44
- clampPosition(time) {
45
- return Math.max(0, Math.min(this.totalDuration, time));
46
- }
47
- setPart() {
48
- var _a;
49
- const isMarker = this.start === this.end;
50
- (_a = this.element) === null || _a === void 0 ? void 0 : _a.setAttribute('part', `${isMarker ? 'marker' : 'region'} ${this.id}`);
51
- }
52
- addResizeHandles(element) {
53
- const handleStyle = {
54
- position: 'absolute',
55
- zIndex: '2',
56
- width: '6px',
57
- height: '100%',
58
- top: '0',
59
- cursor: 'ew-resize',
60
- wordBreak: 'keep-all',
61
- };
62
- const leftHandle = createElement('div', {
63
- part: 'region-handle region-handle-left',
64
- style: Object.assign(Object.assign({}, handleStyle), { left: '0', borderLeft: '2px solid rgba(0, 0, 0, 0.5)', borderRadius: '2px 0 0 2px' }),
65
- }, element);
66
- const rightHandle = createElement('div', {
67
- part: 'region-handle region-handle-right',
68
- style: Object.assign(Object.assign({}, handleStyle), { right: '0', borderRight: '2px solid rgba(0, 0, 0, 0.5)', borderRadius: '0 2px 2px 0' }),
69
- }, element);
70
- // Resize
71
- const resizeThreshold = 1;
72
- const leftDragStream = createDragStream(leftHandle, { threshold: resizeThreshold });
73
- const rightDragStream = createDragStream(rightHandle, { threshold: resizeThreshold });
74
- const unsubscribeLeft = effect(() => {
75
- const drag = leftDragStream.signal.value;
76
- if (!drag)
77
- return;
78
- if (drag.type === 'move' && drag.deltaX !== undefined) {
79
- this.onResize(drag.deltaX, 'start');
80
- }
81
- else if (drag.type === 'end') {
82
- this.onEndResizing('start');
83
- }
84
- }, [leftDragStream.signal]);
85
- const unsubscribeRight = effect(() => {
86
- const drag = rightDragStream.signal.value;
87
- if (!drag)
88
- return;
89
- if (drag.type === 'move' && drag.deltaX !== undefined) {
90
- this.onResize(drag.deltaX, 'end');
91
- }
92
- else if (drag.type === 'end') {
93
- this.onEndResizing('end');
94
- }
95
- }, [rightDragStream.signal]);
96
- this.subscriptions.push(() => {
97
- unsubscribeLeft();
98
- unsubscribeRight();
99
- leftDragStream.cleanup();
100
- rightDragStream.cleanup();
101
- });
102
- }
103
- removeResizeHandles(element) {
104
- const leftHandle = element.querySelector('[part*="region-handle-left"]');
105
- const rightHandle = element.querySelector('[part*="region-handle-right"]');
106
- if (leftHandle) {
107
- element.removeChild(leftHandle);
108
- }
109
- if (rightHandle) {
110
- element.removeChild(rightHandle);
111
- }
112
- }
113
- initElement() {
114
- if (this.isRemoved)
115
- return null;
116
- const isMarker = this.start === this.end;
117
- let elementTop = 0;
118
- let elementHeight = 100;
119
- if (this.channelIdx >= 0 && this.numberOfChannels > 0 && this.channelIdx < this.numberOfChannels) {
120
- elementHeight = 100 / this.numberOfChannels;
121
- elementTop = elementHeight * this.channelIdx;
122
- }
123
- const element = createElement('div', {
124
- style: {
125
- position: 'absolute',
126
- top: `${elementTop}%`,
127
- height: `${elementHeight}%`,
128
- backgroundColor: isMarker ? 'none' : this.color,
129
- borderLeft: isMarker ? '2px solid ' + this.color : 'none',
130
- borderRadius: '2px',
131
- boxSizing: 'border-box',
132
- transition: 'background-color 0.2s ease',
133
- cursor: this.drag ? 'grab' : 'default',
134
- pointerEvents: 'all',
135
- },
136
- });
137
- // Add resize handles
138
- if (!isMarker && this.resize) {
139
- this.addResizeHandles(element);
140
- }
141
- return element;
142
- }
143
- renderPosition() {
144
- if (!this.element)
145
- return;
146
- const start = this.start / this.totalDuration;
147
- const end = (this.totalDuration - this.end) / this.totalDuration;
148
- this.element.style.left = `${start * 100}%`;
149
- this.element.style.right = `${end * 100}%`;
150
- }
151
- toggleCursor(toggle) {
152
- var _a;
153
- if (!this.drag || !((_a = this.element) === null || _a === void 0 ? void 0 : _a.style))
154
- return;
155
- this.element.style.cursor = toggle ? 'grabbing' : 'grab';
156
- }
157
- initMouseEvents() {
158
- const { element } = this;
159
- if (!element)
160
- return;
161
- // Create event streams
162
- const clicks = fromEvent(element, 'click');
163
- const mouseenters = fromEvent(element, 'mouseenter');
164
- const mouseleaves = fromEvent(element, 'mouseleave');
165
- const dblclicks = fromEvent(element, 'dblclick');
166
- const pointerdowns = fromEvent(element, 'pointerdown');
167
- const pointerups = fromEvent(element, 'pointerup');
168
- // Subscribe to streams
169
- const unsubscribeClick = clicks.subscribe((e) => e && this.emit('click', e));
170
- const unsubscribeMouseenter = mouseenters.subscribe((e) => e && this.emit('over', e));
171
- const unsubscribeMouseleave = mouseleaves.subscribe((e) => e && this.emit('leave', e));
172
- const unsubscribeDblclick = dblclicks.subscribe((e) => e && this.emit('dblclick', e));
173
- const unsubscribePointerdown = pointerdowns.subscribe((e) => e && this.toggleCursor(true));
174
- const unsubscribePointerup = pointerups.subscribe((e) => e && this.toggleCursor(false));
175
- // Store cleanup
176
- this.subscriptions.push(() => {
177
- unsubscribeClick();
178
- unsubscribeMouseenter();
179
- unsubscribeMouseleave();
180
- unsubscribeDblclick();
181
- unsubscribePointerdown();
182
- unsubscribePointerup();
183
- cleanupStream(clicks);
184
- cleanupStream(mouseenters);
185
- cleanupStream(mouseleaves);
186
- cleanupStream(dblclicks);
187
- cleanupStream(pointerdowns);
188
- cleanupStream(pointerups);
189
- });
190
- // Drag
191
- const dragStream = createDragStream(element);
192
- const unsubscribeDrag = effect(() => {
193
- const drag = dragStream.signal.value;
194
- if (!drag)
195
- return;
196
- if (drag.type === 'start') {
197
- this.toggleCursor(true);
198
- }
199
- else if (drag.type === 'move' && drag.deltaX !== undefined) {
200
- this.onMove(drag.deltaX);
201
- }
202
- else if (drag.type === 'end') {
203
- this.toggleCursor(false);
204
- if (this.drag)
205
- this.emit('update-end');
206
- }
207
- }, [dragStream.signal]);
208
- this.subscriptions.push(() => {
209
- unsubscribeDrag();
210
- dragStream.cleanup();
211
- });
212
- if (this.contentEditable && this.content) {
213
- this.contentClickListener = (e) => this.onContentClick(e);
214
- this.contentBlurListener = () => this.onContentBlur();
215
- this.content.addEventListener('click', this.contentClickListener);
216
- this.content.addEventListener('blur', this.contentBlurListener);
217
- }
218
- }
219
- _onUpdate(dx, side, startTime) {
220
- var _a;
221
- if (!((_a = this.element) === null || _a === void 0 ? void 0 : _a.parentElement))
222
- return;
223
- const { width } = this.element.parentElement.getBoundingClientRect();
224
- const deltaSeconds = (dx / width) * this.totalDuration;
225
- let newStart = !side || side === 'start' ? this.start + deltaSeconds : this.start;
226
- let newEnd = !side || side === 'end' ? this.end + deltaSeconds : this.end;
227
- const isRegionCreating = startTime !== undefined; // startTime is passed when the region is being created.
228
- if (isRegionCreating) {
229
- if (this.updatingSide && this.updatingSide !== side) {
230
- if (this.updatingSide === 'start') {
231
- newStart = startTime;
232
- }
233
- else {
234
- newEnd = startTime;
235
- }
236
- }
237
- }
238
- newStart = Math.max(0, newStart);
239
- newEnd = Math.min(this.totalDuration, newEnd);
240
- const length = newEnd - newStart;
241
- this.updatingSide = side;
242
- const resizeValid = length >= this.minLength && length <= this.maxLength;
243
- if (newStart <= newEnd && (resizeValid || isRegionCreating)) {
244
- this.start = newStart;
245
- this.end = newEnd;
246
- this.renderPosition();
247
- this.emit('update', side);
248
- }
249
- }
250
- onMove(dx) {
251
- if (!this.drag)
252
- return;
253
- this._onUpdate(dx);
254
- }
255
- onResize(dx, side) {
256
- if (!this.resize)
257
- return;
258
- if (!this.resizeStart && side === 'start')
259
- return;
260
- if (!this.resizeEnd && side === 'end')
261
- return;
262
- this._onUpdate(dx, side);
263
- }
264
- onEndResizing(side) {
265
- if (!this.resize)
266
- return;
267
- this.emit('update-end', side);
268
- this.updatingSide = undefined;
269
- }
270
- onContentClick(event) {
271
- event.stopPropagation();
272
- const contentContainer = event.target;
273
- contentContainer.focus();
274
- this.emit('click', event);
275
- }
276
- onContentBlur() {
277
- this.emit('update-end');
278
- }
279
- _setTotalDuration(totalDuration) {
280
- this.totalDuration = totalDuration;
281
- this.renderPosition();
282
- }
283
- /** Play the region from the start, pass `true` to stop at region end */
284
- play(stopAtEnd) {
285
- this.emit('play', stopAtEnd && this.end !== this.start ? this.end : undefined);
286
- }
287
- /** Get Content as html or string */
288
- getContent(asHTML = false) {
289
- var _a;
290
- if (asHTML) {
291
- return this.content || undefined;
292
- }
293
- if (this.element instanceof HTMLElement) {
294
- return ((_a = this.content) === null || _a === void 0 ? void 0 : _a.innerHTML) || undefined;
295
- }
296
- return '';
297
- }
298
- /** Set the HTML content of the region */
299
- setContent(content) {
300
- var _a;
301
- if (!this.element)
302
- return;
303
- // Remove event listeners from old content before removing it
304
- if (this.content && this.contentEditable) {
305
- if (this.contentClickListener) {
306
- this.content.removeEventListener('click', this.contentClickListener);
307
- }
308
- if (this.contentBlurListener) {
309
- this.content.removeEventListener('blur', this.contentBlurListener);
310
- }
311
- }
312
- (_a = this.content) === null || _a === void 0 ? void 0 : _a.remove();
313
- if (!content) {
314
- this.content = undefined;
315
- return;
316
- }
317
- if (typeof content === 'string') {
318
- const isMarker = this.start === this.end;
319
- this.content = createElement('div', {
320
- style: {
321
- padding: `0.2em ${isMarker ? 0.2 : 0.4}em`,
322
- display: 'inline-block',
323
- },
324
- textContent: content,
325
- });
326
- }
327
- else {
328
- this.content = content;
329
- }
330
- if (this.contentEditable) {
331
- this.content.contentEditable = 'true';
332
- // Re-add event listeners to new content
333
- this.contentClickListener = (e) => this.onContentClick(e);
334
- this.contentBlurListener = () => this.onContentBlur();
335
- this.content.addEventListener('click', this.contentClickListener);
336
- this.content.addEventListener('blur', this.contentBlurListener);
337
- }
338
- this.content.setAttribute('part', 'region-content');
339
- this.element.appendChild(this.content);
340
- this.emit('content-changed');
341
- }
342
- /** Update the region's options */
343
- setOptions(options) {
344
- var _a, _b;
345
- if (!this.element)
346
- return;
347
- if (options.color) {
348
- this.color = options.color;
349
- this.element.style.backgroundColor = this.color;
350
- }
351
- if (options.drag !== undefined) {
352
- this.drag = options.drag;
353
- this.element.style.cursor = this.drag ? 'grab' : 'default';
354
- }
355
- if (options.start !== undefined || options.end !== undefined) {
356
- const isMarker = this.start === this.end;
357
- this.start = this.clampPosition((_a = options.start) !== null && _a !== void 0 ? _a : this.start);
358
- this.end = this.clampPosition((_b = options.end) !== null && _b !== void 0 ? _b : (isMarker ? this.start : this.end));
359
- this.renderPosition();
360
- this.setPart();
361
- this.emit('render');
362
- }
363
- if (options.content) {
364
- this.setContent(options.content);
365
- }
366
- if (options.id) {
367
- this.id = options.id;
368
- this.setPart();
369
- }
370
- if (options.resize !== undefined && options.resize !== this.resize) {
371
- const isMarker = this.start === this.end;
372
- this.resize = options.resize;
373
- if (this.resize && !isMarker) {
374
- this.addResizeHandles(this.element);
375
- }
376
- else {
377
- this.removeResizeHandles(this.element);
378
- }
379
- }
380
- if (options.resizeStart !== undefined) {
381
- this.resizeStart = options.resizeStart;
382
- }
383
- if (options.resizeEnd !== undefined) {
384
- this.resizeEnd = options.resizeEnd;
385
- }
386
- }
387
- /** Remove the region */
388
- remove() {
389
- this.isRemoved = true;
390
- this.emit('remove');
391
- // Clean up all subscriptions (drag streams, event listeners, etc.)
392
- this.subscriptions.forEach((unsubscribe) => unsubscribe());
393
- this.subscriptions = [];
394
- // Clean up content event listeners
395
- if (this.content && this.contentEditable) {
396
- if (this.contentClickListener) {
397
- this.content.removeEventListener('click', this.contentClickListener);
398
- this.contentClickListener = undefined;
399
- }
400
- if (this.contentBlurListener) {
401
- this.content.removeEventListener('blur', this.contentBlurListener);
402
- this.contentBlurListener = undefined;
403
- }
404
- }
405
- // Remove DOM element
406
- if (this.element) {
407
- this.element.remove();
408
- this.element = null;
409
- }
410
- // Clear all event listeners from the EventEmitter
411
- this.unAll();
412
- }
413
- }
414
- class RegionsPlugin extends BasePlugin {
415
- /** Create an instance of RegionsPlugin */
416
- constructor(options) {
417
- super(options);
418
- this.regions = [];
419
- this.regionsContainer = this.initRegionsContainer();
420
- }
421
- /** Create an instance of RegionsPlugin */
422
- static create(options) {
423
- return new RegionsPlugin(options);
424
- }
425
- /** Called by wavesurfer, don't call manually */
426
- onInit() {
427
- if (!this.wavesurfer) {
428
- throw Error('WaveSurfer is not initialized');
429
- }
430
- this.wavesurfer.getWrapper().appendChild(this.regionsContainer);
431
- // Update region durations when a new audio file is loaded
432
- this.subscriptions.push(this.wavesurfer.on('ready', (duration) => {
433
- this.regions.forEach((region) => region._setTotalDuration(duration));
434
- }));
435
- let activeRegions = [];
436
- this.subscriptions.push(this.wavesurfer.on('timeupdate', (currentTime) => {
437
- // Detect when regions are being played
438
- const playedRegions = this.regions.filter((region) => region.start <= currentTime &&
439
- (region.end === region.start ? region.start + 0.05 : region.end) >= currentTime);
440
- // Trigger region-in when activeRegions doesn't include a played regions
441
- playedRegions.forEach((region) => {
442
- if (!activeRegions.includes(region)) {
443
- this.emit('region-in', region);
444
- }
445
- });
446
- // Trigger region-out when activeRegions include a un-played regions
447
- activeRegions.forEach((region) => {
448
- if (!playedRegions.includes(region)) {
449
- this.emit('region-out', region);
450
- }
451
- });
452
- // Update activeRegions only played regions
453
- activeRegions = playedRegions;
454
- }));
455
- }
456
- initRegionsContainer() {
457
- return createElement('div', {
458
- part: 'regions-container',
459
- style: {
460
- position: 'absolute',
461
- top: '0',
462
- left: '0',
463
- width: '100%',
464
- height: '100%',
465
- zIndex: '5',
466
- pointerEvents: 'none',
467
- },
468
- });
469
- }
470
- /** Get all created regions */
471
- getRegions() {
472
- return this.regions;
473
- }
474
- avoidOverlapping(region) {
475
- if (!region.content || region.isRemoved)
476
- return;
477
- setTimeout(() => {
478
- // Check that the label doesn't overlap with other labels
479
- // If it does, push it down until it doesn't
480
- // only check regions that are before us in the list -- otherwise
481
- // both overlapping regions will try to move down away from each other.
482
- const div = region.content;
483
- const box = div.getBoundingClientRect();
484
- const regionIndex = this.regions.indexOf(region);
485
- const overlap = this.regions
486
- .slice(0, regionIndex)
487
- .filter((reg) => !reg.isRemoved)
488
- .map((reg) => {
489
- if (reg === region || !reg.content)
490
- return 0;
491
- const otherBox = reg.content.getBoundingClientRect();
492
- if (box.left < otherBox.left + otherBox.width && otherBox.left < box.left + box.width) {
493
- return otherBox.height + 2;
494
- }
495
- return 0;
496
- })
497
- .reduce((sum, val) => sum + val, 0);
498
- div.style.marginTop = `${overlap}px`;
499
- }, 10);
500
- }
501
- adjustScroll(region) {
502
- var _a, _b;
503
- if (!region.element)
504
- return;
505
- const scrollContainer = (_b = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper()) === null || _b === void 0 ? void 0 : _b.parentElement;
506
- if (!scrollContainer)
507
- return;
508
- const { clientWidth, scrollWidth } = scrollContainer;
509
- if (scrollWidth <= clientWidth)
510
- return;
511
- const scrollBbox = scrollContainer.getBoundingClientRect();
512
- const bbox = region.element.getBoundingClientRect();
513
- const left = bbox.left - scrollBbox.left;
514
- const right = bbox.right - scrollBbox.left;
515
- if (left < 0) {
516
- scrollContainer.scrollLeft += left;
517
- }
518
- else if (right > clientWidth) {
519
- scrollContainer.scrollLeft += right - clientWidth;
520
- }
521
- }
522
- virtualAppend(region, container, element) {
523
- const renderIfVisible = () => {
524
- if (!this.wavesurfer)
525
- return;
526
- const clientWidth = this.wavesurfer.getWidth();
527
- const scrollLeft = this.wavesurfer.getScroll();
528
- const scrollWidth = container.clientWidth;
529
- const duration = this.wavesurfer.getDuration();
530
- const start = Math.round((region.start / duration) * scrollWidth);
531
- const width = Math.round(((region.end - region.start) / duration) * scrollWidth) || 1;
532
- // Check if the region is between the scrollLeft and scrollLeft + clientWidth
533
- const isVisible = start + width > scrollLeft && start < scrollLeft + clientWidth;
534
- if (isVisible && !element.parentElement) {
535
- container.appendChild(element);
536
- }
537
- else if (!isVisible && element.parentElement) {
538
- element.remove();
539
- }
540
- };
541
- setTimeout(() => {
542
- // Check if region was removed before setTimeout executed
543
- if (!this.wavesurfer || !region.element)
544
- return;
545
- renderIfVisible();
546
- const unsubscribeScroll = this.wavesurfer.on('scroll', renderIfVisible);
547
- const unsubscribeZoom = this.wavesurfer.on('zoom', renderIfVisible);
548
- const unsubscribeResize = this.wavesurfer.on('resize', renderIfVisible);
549
- const unsubscribeRender = region.on('render', renderIfVisible);
550
- // Only push the unsubscribe functions, not the once() return values
551
- this.subscriptions.push(unsubscribeScroll, unsubscribeZoom, unsubscribeResize, unsubscribeRender);
552
- // Clean up subscriptions when region is removed
553
- region.once('remove', () => {
554
- unsubscribeScroll();
555
- unsubscribeZoom();
556
- unsubscribeResize();
557
- unsubscribeRender();
558
- });
559
- }, 0);
560
- }
561
- saveRegion(region) {
562
- if (!region.element)
563
- return;
564
- this.virtualAppend(region, this.regionsContainer, region.element);
565
- this.avoidOverlapping(region);
566
- this.regions.push(region);
567
- const regionSubscriptions = [
568
- region.on('update', (side) => {
569
- // Undefined side indicates that we are dragging not resizing
570
- if (!side) {
571
- this.adjustScroll(region);
572
- }
573
- this.emit('region-update', region, side);
574
- }),
575
- region.on('update-end', (side) => {
576
- this.avoidOverlapping(region);
577
- this.emit('region-updated', region, side);
578
- }),
579
- region.on('play', (end) => {
580
- var _a;
581
- (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.play(region.start, end);
582
- }),
583
- region.on('click', (e) => {
584
- this.emit('region-clicked', region, e);
585
- }),
586
- region.on('dblclick', (e) => {
587
- this.emit('region-double-clicked', region, e);
588
- }),
589
- region.on('content-changed', () => {
590
- this.emit('region-content-changed', region);
591
- }),
592
- // Remove the region from the list when it's removed
593
- region.once('remove', () => {
594
- regionSubscriptions.forEach((unsubscribe) => unsubscribe());
595
- this.regions = this.regions.filter((reg) => reg !== region);
596
- this.emit('region-removed', region);
597
- }),
598
- ];
599
- this.subscriptions.push(...regionSubscriptions);
600
- this.emit('region-created', region);
601
- }
602
- /** Create a region with given parameters */
603
- addRegion(options) {
604
- var _a, _b;
605
- if (!this.wavesurfer) {
606
- throw Error('WaveSurfer is not initialized');
607
- }
608
- const duration = this.wavesurfer.getDuration();
609
- const numberOfChannels = (_b = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getDecodedData()) === null || _b === void 0 ? void 0 : _b.numberOfChannels;
610
- const region = new SingleRegion(options, duration, numberOfChannels);
611
- this.emit('region-initialized', region);
612
- if (!duration) {
613
- this.subscriptions.push(this.wavesurfer.once('ready', (duration) => {
614
- region._setTotalDuration(duration);
615
- this.saveRegion(region);
616
- }));
617
- }
618
- else {
619
- this.saveRegion(region);
620
- }
621
- return region;
622
- }
623
- /**
624
- * Enable creation of regions by dragging on an empty space on the waveform.
625
- * Returns a function to disable the drag selection.
626
- */
627
- enableDragSelection(options, threshold = 3) {
628
- var _a;
629
- const wrapper = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getWrapper();
630
- if (!wrapper || !(wrapper instanceof HTMLElement))
631
- return () => undefined;
632
- const initialSize = 5;
633
- let region = null;
634
- let startX = 0;
635
- let startTime = 0;
636
- const dragStream = createDragStream(wrapper, { threshold });
637
- const unsubscribe = effect(() => {
638
- var _a, _b;
639
- const drag = dragStream.signal.value;
640
- if (!drag)
641
- return;
642
- if (drag.type === 'start') {
643
- // On drag start
644
- startX = drag.x;
645
- if (!this.wavesurfer)
646
- return;
647
- const duration = this.wavesurfer.getDuration();
648
- const numberOfChannels = (_b = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getDecodedData()) === null || _b === void 0 ? void 0 : _b.numberOfChannels;
649
- const { width } = this.wavesurfer.getWrapper().getBoundingClientRect();
650
- startTime = (startX / width) * duration;
651
- // Calculate the start time of the region
652
- const start = (drag.x / width) * duration;
653
- // Give the region a small initial size
654
- const end = ((drag.x + initialSize) / width) * duration;
655
- // Create a region but don't save it until the drag ends
656
- region = new SingleRegion(Object.assign(Object.assign({}, options), { start,
657
- end }), duration, numberOfChannels);
658
- this.emit('region-initialized', region);
659
- // Just add it to the DOM for now
660
- if (region.element) {
661
- this.regionsContainer.appendChild(region.element);
662
- }
663
- }
664
- else if (drag.type === 'move' && drag.deltaX !== undefined) {
665
- // On drag move
666
- if (region) {
667
- // Update the end position of the region
668
- // If we're dragging to the left, we need to update the start instead
669
- region._onUpdate(drag.deltaX, drag.x > startX ? 'end' : 'start', startTime);
670
- }
671
- }
672
- else if (drag.type === 'end') {
673
- // On drag end
674
- if (region) {
675
- this.saveRegion(region);
676
- region.updatingSide = undefined;
677
- region = null;
678
- }
679
- }
680
- }, [dragStream.signal]);
681
- return () => {
682
- unsubscribe();
683
- dragStream.cleanup();
684
- };
685
- }
686
- /** Remove all regions */
687
- clearRegions() {
688
- const regions = this.regions.slice();
689
- regions.forEach((region) => region.remove());
690
- this.regions = [];
691
- }
692
- /** Destroy the plugin and clean up */
693
- destroy() {
694
- this.clearRegions();
695
- super.destroy();
696
- this.regionsContainer.remove();
697
- }
698
- }
699
- export default RegionsPlugin;
1
+ class t{constructor(){this.listeners={}}on(t,e,i){if(this.listeners[t]||(this.listeners[t]=new Set),null==i?void 0:i.once){const i=(...n)=>{this.un(t,i),e(...n)};return this.listeners[t].add(i),()=>this.un(t,i)}return this.listeners[t].add(e),()=>this.un(t,e)}un(t,e){var i;null===(i=this.listeners[t])||void 0===i||i.delete(e)}once(t,e){return this.on(t,e,{once:!0})}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.isDestroyed=!1,this.options=t}onInit(){}_init(t){this.isDestroyed&&(this.subscriptions=[],this.isDestroyed=!1),this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.subscriptions=[],this.isDestroyed=!0,this.wavesurfer=void 0}}function i(t,e){const n=e.xmlns?document.createElementNS(e.xmlns,t):document.createElement(t);for(const[t,s]of Object.entries(e))if("children"===t&&s)for(const[t,e]of Object.entries(s))e instanceof Node?n.appendChild(e):"string"==typeof e?n.appendChild(document.createTextNode(e)):n.appendChild(i(t,e));else"style"===t?Object.assign(n.style,s):"textContent"===t?n.textContent=s:n.setAttribute(t,s.toString());return n}function n(t,e,n){const s=i(t,e||{});return null==n||n.appendChild(s),s}function s(t){let e=t;const i=new Set;return{get value(){return e},set(t){Object.is(e,t)||(e=t,i.forEach((t=>t(e))))},update(t){this.set(t(e))},subscribe:t=>(i.add(t),()=>i.delete(t))}}function r(t,e){let i;const n=()=>{i&&(i(),i=void 0),i=t()},s=e.map((t=>t.subscribe(n)));return n(),()=>{i&&(i(),i=void 0),s.forEach((t=>t()))}}function o(t,e){const i=s(null),n=t=>{i.set(t)};return t.addEventListener(e,n),i._cleanup=()=>{t.removeEventListener(e,n)},i}function l(t){const e=t._cleanup;"function"==typeof e&&e()}function h(t,e={}){const{threshold:i=3,mouseButton:n=0,touchDelay:r=100}=e,o=s(null),h=new Map,a=matchMedia("(pointer: coarse)").matches;let d=()=>{};const c=e=>{if(e.button!==n)return;if(h.set(e.pointerId,e),h.size>1)return;let s=e.clientX,l=e.clientY,c=!1;const u=Date.now(),v=t.getBoundingClientRect(),{left:p,top:g}=v,m=t=>{if(t.defaultPrevented||h.size>1)return;if(a&&Date.now()-u<r)return;const e=t.clientX,n=t.clientY,d=e-s,v=n-l;(c||Math.abs(d)>i||Math.abs(v)>i)&&(t.preventDefault(),t.stopPropagation(),c||(o.set({type:"start",x:s-p,y:l-g}),c=!0),o.set({type:"move",x:e-p,y:n-g,deltaX:d,deltaY:v}),s=e,l=n)},f=t=>{if(h.delete(t.pointerId),c){const e=t.clientX,i=t.clientY;o.set({type:"end",x:e-p,y:i-g})}d()},b=t=>{h.delete(t.pointerId),t.relatedTarget&&t.relatedTarget!==document.documentElement||f(t)},E=t=>{c&&(t.stopPropagation(),t.preventDefault())},C=t=>{t.defaultPrevented||h.size>1||c&&t.preventDefault()};document.addEventListener("pointermove",m),document.addEventListener("pointerup",f),document.addEventListener("pointerout",b),document.addEventListener("pointercancel",b),document.addEventListener("touchmove",C,{passive:!1}),document.addEventListener("click",E,{capture:!0}),d=()=>{document.removeEventListener("pointermove",m),document.removeEventListener("pointerup",f),document.removeEventListener("pointerout",b),document.removeEventListener("pointercancel",b),document.removeEventListener("touchmove",C),setTimeout((()=>{document.removeEventListener("click",E,{capture:!0})}),10)}};t.addEventListener("pointerdown",c);return{signal:o,cleanup:()=>{d(),t.removeEventListener("pointerdown",c),h.clear(),l(o)}}}class a extends t{constructor(t,e,i=0){var n,s,r,o,l,h,a,d,c,u;super(),this.totalDuration=e,this.numberOfChannels=i,this.element=null,this.minLength=0,this.maxLength=1/0,this.contentEditable=!1,this.subscriptions=[],this.updatingSide=void 0,this.isRemoved=!1,this.subscriptions=[],this.id=t.id||`region-${Math.random().toString(32).slice(2)}`,this.start=this.clampPosition(t.start),this.end=this.clampPosition(null!==(n=t.end)&&void 0!==n?n:t.start),this.drag=null===(s=t.drag)||void 0===s||s,this.resize=null===(r=t.resize)||void 0===r||r,this.resizeStart=null===(o=t.resizeStart)||void 0===o||o,this.resizeEnd=null===(l=t.resizeEnd)||void 0===l||l,this.color=null!==(h=t.color)&&void 0!==h?h:"rgba(0, 0, 0, 0.1)",this.minLength=null!==(a=t.minLength)&&void 0!==a?a:this.minLength,this.maxLength=null!==(d=t.maxLength)&&void 0!==d?d:this.maxLength,this.channelIdx=null!==(c=t.channelIdx)&&void 0!==c?c:-1,this.contentEditable=null!==(u=t.contentEditable)&&void 0!==u?u:this.contentEditable,this.element=this.initElement(),this.setContent(t.content),this.setPart(),this.renderPosition(),this.initMouseEvents()}clampPosition(t){return Math.max(0,Math.min(this.totalDuration,t))}setPart(){var t;const e=this.start===this.end;null===(t=this.element)||void 0===t||t.setAttribute("part",`${e?"marker":"region"} ${this.id}`)}addResizeHandles(t){const e={position:"absolute",zIndex:"2",width:"6px",height:"100%",top:"0",cursor:"ew-resize",wordBreak:"keep-all"},i=n("div",{part:"region-handle region-handle-left",style:Object.assign(Object.assign({},e),{left:"0",borderLeft:"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"2px 0 0 2px"})},t),s=n("div",{part:"region-handle region-handle-right",style:Object.assign(Object.assign({},e),{right:"0",borderRight:"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"0 2px 2px 0"})},t),o=h(i,{threshold:1}),l=h(s,{threshold:1}),a=r((()=>{const t=o.signal.value;t&&("move"===t.type&&void 0!==t.deltaX?this.onResize(t.deltaX,"start"):"end"===t.type&&this.onEndResizing("start"))}),[o.signal]),d=r((()=>{const t=l.signal.value;t&&("move"===t.type&&void 0!==t.deltaX?this.onResize(t.deltaX,"end"):"end"===t.type&&this.onEndResizing("end"))}),[l.signal]);this.subscriptions.push((()=>{a(),d(),o.cleanup(),l.cleanup()}))}removeResizeHandles(t){const e=t.querySelector('[part*="region-handle-left"]'),i=t.querySelector('[part*="region-handle-right"]');e&&t.removeChild(e),i&&t.removeChild(i)}initElement(){if(this.isRemoved)return null;const t=this.start===this.end;let e=0,i=100;this.channelIdx>=0&&this.numberOfChannels>0&&this.channelIdx<this.numberOfChannels&&(i=100/this.numberOfChannels,e=i*this.channelIdx);const s=n("div",{style:{position:"absolute",top:`${e}%`,height:`${i}%`,backgroundColor:t?"none":this.color,borderLeft:t?"2px solid "+this.color:"none",borderRadius:"2px",boxSizing:"border-box",transition:"background-color 0.2s ease",cursor:this.drag?"grab":"default",pointerEvents:"all"}});return!t&&this.resize&&this.addResizeHandles(s),s}renderPosition(){if(!this.element)return;const t=this.start/this.totalDuration,e=(this.totalDuration-this.end)/this.totalDuration;this.element.style.left=100*t+"%",this.element.style.right=100*e+"%"}toggleCursor(t){var e;this.drag&&(null===(e=this.element)||void 0===e?void 0:e.style)&&(this.element.style.cursor=t?"grabbing":"grab")}initMouseEvents(){const{element:t}=this;if(!t)return;const e=o(t,"click"),i=o(t,"mouseenter"),n=o(t,"mouseleave"),s=o(t,"dblclick"),a=o(t,"pointerdown"),d=o(t,"pointerup"),c=e.subscribe((t=>t&&this.emit("click",t))),u=i.subscribe((t=>t&&this.emit("over",t))),v=n.subscribe((t=>t&&this.emit("leave",t))),p=s.subscribe((t=>t&&this.emit("dblclick",t))),g=a.subscribe((t=>t&&this.toggleCursor(!0))),m=d.subscribe((t=>t&&this.toggleCursor(!1)));this.subscriptions.push((()=>{c(),u(),v(),p(),g(),m(),l(e),l(i),l(n),l(s),l(a),l(d)}));const f=h(t),b=r((()=>{const t=f.signal.value;t&&("start"===t.type?this.toggleCursor(!0):"move"===t.type&&void 0!==t.deltaX?this.onMove(t.deltaX):"end"===t.type&&(this.toggleCursor(!1),this.drag&&this.emit("update-end")))}),[f.signal]);this.subscriptions.push((()=>{b(),f.cleanup()})),this.contentEditable&&this.content&&(this.contentClickListener=t=>this.onContentClick(t),this.contentBlurListener=()=>this.onContentBlur(),this.content.addEventListener("click",this.contentClickListener),this.content.addEventListener("blur",this.contentBlurListener))}_onUpdate(t,e,i){var n;if(!(null===(n=this.element)||void 0===n?void 0:n.parentElement))return;const{width:s}=this.element.parentElement.getBoundingClientRect(),r=t/s*this.totalDuration;let o=e&&"start"!==e?this.start:this.start+r,l=e&&"end"!==e?this.end:this.end+r;const h=void 0!==i;h&&this.updatingSide&&this.updatingSide!==e&&("start"===this.updatingSide?o=i:l=i),o=Math.max(0,o),l=Math.min(this.totalDuration,l);const a=l-o;this.updatingSide=e;const d=a>=this.minLength&&a<=this.maxLength;o<=l&&(d||h)&&(this.start=o,this.end=l,this.renderPosition(),this.emit("update",e))}onMove(t){this.drag&&this._onUpdate(t)}onResize(t,e){this.resize&&(this.resizeStart||"start"!==e)&&(this.resizeEnd||"end"!==e)&&this._onUpdate(t,e)}onEndResizing(t){this.resize&&(this.emit("update-end",t),this.updatingSide=void 0)}onContentClick(t){t.stopPropagation();t.target.focus(),this.emit("click",t)}onContentBlur(){this.emit("update-end")}_setTotalDuration(t){this.totalDuration=t,this.renderPosition()}play(t){this.emit("play",t&&this.end!==this.start?this.end:void 0)}getContent(t=!1){var e;return t?this.content||void 0:this.element instanceof HTMLElement?(null===(e=this.content)||void 0===e?void 0:e.innerHTML)||void 0:""}setContent(t){var e;if(this.element)if(this.content&&this.contentEditable&&(this.contentClickListener&&this.content.removeEventListener("click",this.contentClickListener),this.contentBlurListener&&this.content.removeEventListener("blur",this.contentBlurListener)),null===(e=this.content)||void 0===e||e.remove(),t){if("string"==typeof t){const e=this.start===this.end;this.content=n("div",{style:{padding:`0.2em ${e?.2:.4}em`,display:"inline-block"},textContent:t})}else this.content=t;this.contentEditable&&(this.content.contentEditable="true",this.contentClickListener=t=>this.onContentClick(t),this.contentBlurListener=()=>this.onContentBlur(),this.content.addEventListener("click",this.contentClickListener),this.content.addEventListener("blur",this.contentBlurListener)),this.content.setAttribute("part","region-content"),this.element.appendChild(this.content),this.emit("content-changed")}else this.content=void 0}setOptions(t){var e,i;if(this.element){if(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.start||void 0!==t.end){const n=this.start===this.end;this.start=this.clampPosition(null!==(e=t.start)&&void 0!==e?e:this.start),this.end=this.clampPosition(null!==(i=t.end)&&void 0!==i?i:n?this.start:this.end),this.renderPosition(),this.setPart(),this.emit("render")}if(t.content&&this.setContent(t.content),t.id&&(this.id=t.id,this.setPart()),void 0!==t.resize&&t.resize!==this.resize){const e=this.start===this.end;this.resize=t.resize,this.resize&&!e?this.addResizeHandles(this.element):this.removeResizeHandles(this.element)}void 0!==t.resizeStart&&(this.resizeStart=t.resizeStart),void 0!==t.resizeEnd&&(this.resizeEnd=t.resizeEnd)}}remove(){this.isRemoved=!0,this.emit("remove"),this.subscriptions.forEach((t=>t())),this.subscriptions=[],this.content&&this.contentEditable&&(this.contentClickListener&&(this.content.removeEventListener("click",this.contentClickListener),this.contentClickListener=void 0),this.contentBlurListener&&(this.content.removeEventListener("blur",this.contentBlurListener),this.contentBlurListener=void 0)),this.element&&(this.element.remove(),this.element=null),this.unAll()}}class d extends e{constructor(t){super(t),this.regions=[],this.regionsContainer=this.initRegionsContainer()}static create(t){return new d(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer),this.subscriptions.push(this.wavesurfer.on("ready",(t=>{this.regions.forEach((e=>e._setTotalDuration(t)))})));let t=[];this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{const i=this.regions.filter((t=>t.start<=e&&(t.end===t.start?t.start+.05:t.end)>=e));i.forEach((e=>{t.includes(e)||this.emit("region-in",e)})),t.forEach((t=>{i.includes(t)||this.emit("region-out",t)})),t=i})))}initRegionsContainer(){return n("div",{part:"regions-container",style:{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"5",pointerEvents:"none"}})}getRegions(){return this.regions}avoidOverlapping(t){t.content&&!t.isRemoved&&setTimeout((()=>{const e=t.content,i=e.getBoundingClientRect(),n=this.regions.indexOf(t),s=this.regions.slice(0,n).filter((t=>!t.isRemoved)).map((e=>{if(e===t||!e.content)return 0;const n=e.content.getBoundingClientRect();return i.left<n.left+n.width&&n.left<i.left+i.width?n.height+2:0})).reduce(((t,e)=>t+e),0);e.style.marginTop=`${s}px`}),10)}adjustScroll(t){var e,i;if(!t.element)return;const n=null===(i=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper())||void 0===i?void 0:i.parentElement;if(!n)return;const{clientWidth:s,scrollWidth:r}=n;if(r<=s)return;const o=n.getBoundingClientRect(),l=t.element.getBoundingClientRect(),h=l.left-o.left,a=l.right-o.left;h<0?n.scrollLeft+=h:a>s&&(n.scrollLeft+=a-s)}virtualAppend(t,e,i){const n=()=>{if(!this.wavesurfer)return;const n=this.wavesurfer.getWidth(),s=this.wavesurfer.getScroll(),r=e.clientWidth,o=this.wavesurfer.getDuration(),l=Math.round(t.start/o*r),h=l+(Math.round((t.end-t.start)/o*r)||1)>s&&l<s+n;h&&!i.parentElement?e.appendChild(i):!h&&i.parentElement&&i.remove()};setTimeout((()=>{if(!this.wavesurfer||!t.element)return;n();const e=this.wavesurfer.on("scroll",n),i=this.wavesurfer.on("zoom",n),s=this.wavesurfer.on("resize",n),r=t.on("render",n);this.subscriptions.push(e,i,s,r),t.once("remove",(()=>{e(),i(),s(),r()}))}),0)}saveRegion(t){if(!t.element)return;this.virtualAppend(t,this.regionsContainer,t.element),this.avoidOverlapping(t),this.regions.push(t);const e=[t.on("update",(e=>{e||this.adjustScroll(t),this.emit("region-update",t,e)})),t.on("update-end",(e=>{this.avoidOverlapping(t),this.emit("region-updated",t,e)})),t.on("play",(e=>{var i;null===(i=this.wavesurfer)||void 0===i||i.play(t.start,e)})),t.on("click",(e=>{this.emit("region-clicked",t,e)})),t.on("dblclick",(e=>{this.emit("region-double-clicked",t,e)})),t.on("content-changed",(()=>{this.emit("region-content-changed",t)})),t.once("remove",(()=>{e.forEach((t=>t())),this.regions=this.regions.filter((e=>e!==t)),this.emit("region-removed",t)}))];this.subscriptions.push(...e),this.emit("region-created",t)}addRegion(t){var e,i;if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const n=this.wavesurfer.getDuration(),s=null===(i=null===(e=this.wavesurfer)||void 0===e?void 0:e.getDecodedData())||void 0===i?void 0:i.numberOfChannels,r=new a(t,n,s);return this.emit("region-initialized",r),n?this.saveRegion(r):this.subscriptions.push(this.wavesurfer.once("ready",(t=>{r._setTotalDuration(t),this.saveRegion(r)}))),r}enableDragSelection(t,e=3){var i;const n=null===(i=this.wavesurfer)||void 0===i?void 0:i.getWrapper();if(!(n&&n instanceof HTMLElement))return()=>{};let s=null,o=0,l=0;const d=h(n,{threshold:e}),c=r((()=>{var e,i;const n=d.signal.value;if(n)if("start"===n.type){if(o=n.x,!this.wavesurfer)return;const r=this.wavesurfer.getDuration(),h=null===(i=null===(e=this.wavesurfer)||void 0===e?void 0:e.getDecodedData())||void 0===i?void 0:i.numberOfChannels,{width:d}=this.wavesurfer.getWrapper().getBoundingClientRect();l=o/d*r;const c=n.x/d*r,u=(n.x+5)/d*r;s=new a(Object.assign(Object.assign({},t),{start:c,end:u}),r,h),this.emit("region-initialized",s),s.element&&this.regionsContainer.appendChild(s.element)}else"move"===n.type&&void 0!==n.deltaX?s&&s._onUpdate(n.deltaX,n.x>o?"end":"start",l):"end"===n.type&&s&&(this.saveRegion(s),s.updatingSide=void 0,s=null)}),[d.signal]);return()=>{c(),d.cleanup()}}clearRegions(){this.regions.slice().forEach((t=>t.remove())),this.regions=[]}destroy(){this.clearRegions(),super.destroy(),this.regionsContainer.remove()}}export{d as default};