wavesurfer.js 7.0.0-alpha.3 → 7.0.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -26
- package/dist/base-plugin.d.ts +9 -5
- package/dist/base-plugin.js +11 -3
- package/dist/decoder.d.ts +2 -4
- package/dist/decoder.js +20 -31
- package/dist/event-emitter.d.ts +1 -1
- package/dist/event-emitter.js +4 -7
- package/dist/fetcher.js +2 -13
- package/dist/legacy-adapter.d.ts +7 -0
- package/dist/legacy-adapter.js +15 -0
- package/dist/player.d.ts +39 -9
- package/dist/player.js +91 -15
- package/dist/plugin/wavesurfer.envelope.min.js +1 -0
- package/dist/plugin/wavesurfer.minimap.min.js +1 -0
- package/dist/plugin/wavesurfer.multitrack.min.js +1 -0
- package/dist/plugin/wavesurfer.regions.min.js +1 -0
- package/dist/plugin/wavesurfer.timeline.min.js +1 -0
- package/dist/plugins/envelope.d.ts +60 -0
- package/dist/plugins/envelope.js +302 -0
- package/dist/plugins/envelope.min.js +1 -0
- package/dist/plugins/minimap.d.ts +27 -0
- package/dist/plugins/minimap.js +83 -0
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/multitrack.d.ts +113 -0
- package/dist/plugins/multitrack.js +494 -0
- package/dist/plugins/multitrack.min.js +1 -0
- package/dist/plugins/record.d.ts +23 -0
- package/dist/plugins/record.js +119 -0
- package/dist/plugins/record.min.js +1 -0
- package/dist/plugins/regions.d.ts +23 -9
- package/dist/plugins/regions.js +196 -120
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/timeline.d.ts +38 -0
- package/dist/plugins/timeline.js +134 -0
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/renderer.d.ts +25 -12
- package/dist/renderer.js +222 -138
- package/dist/timer.d.ts +2 -1
- package/dist/timer.js +5 -3
- package/dist/wavesurfer.d.ts +131 -0
- package/dist/wavesurfer.js +209 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +50 -20
- package/dist/index.d.ts +0 -92
- package/dist/index.js +0 -166
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -30
- package/dist/wavesurfer.Regions.min.js +0 -1
package/dist/plugins/regions.js
CHANGED
|
@@ -1,147 +1,210 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
2
|
const MIN_WIDTH = 10;
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
dragSelection: true,
|
|
5
|
+
draggable: true,
|
|
6
|
+
resizable: true,
|
|
7
|
+
};
|
|
8
|
+
const style = (element, styles) => {
|
|
9
|
+
for (const key in styles) {
|
|
10
|
+
element.style[key] = styles[key] || '';
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const el = (tagName, css) => {
|
|
14
|
+
const element = document.createElement(tagName);
|
|
15
|
+
style(element, css);
|
|
16
|
+
return element;
|
|
17
|
+
};
|
|
3
18
|
class RegionsPlugin extends BasePlugin {
|
|
19
|
+
dragStart = NaN;
|
|
20
|
+
regionsContainer;
|
|
21
|
+
regions = [];
|
|
22
|
+
createdRegion = null;
|
|
23
|
+
modifiedRegion = null;
|
|
24
|
+
isResizingLeft = false;
|
|
25
|
+
isMoving = false;
|
|
26
|
+
wasInteractive = true;
|
|
4
27
|
/** Create an instance of RegionsPlugin */
|
|
5
|
-
constructor(
|
|
6
|
-
super(
|
|
7
|
-
this.
|
|
8
|
-
this.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.dragStart = dragEnd;
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (this.modifiedRegion) {
|
|
24
|
-
this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (!isNaN(this.dragStart)) {
|
|
28
|
-
const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
|
|
29
|
-
if (dragEnd - this.dragStart >= MIN_WIDTH) {
|
|
30
|
-
if (!this.createdRegion) {
|
|
31
|
-
this.renderer.getContainer().style.pointerEvents = 'none';
|
|
32
|
-
this.createdRegion = this.createRegion(this.dragStart, dragEnd);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
this.updateRegion(this.createdRegion, this.dragStart, dragEnd);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
this.handleMouseUp = () => {
|
|
41
|
-
if (this.createdRegion) {
|
|
42
|
-
this.addRegion(this.createdRegion);
|
|
43
|
-
this.createdRegion = null;
|
|
44
|
-
}
|
|
45
|
-
this.modifiedRegion = null;
|
|
46
|
-
this.isMoving = false;
|
|
47
|
-
this.dragStart = NaN;
|
|
48
|
-
this.renderer.getContainer().style.pointerEvents = '';
|
|
49
|
-
};
|
|
50
|
-
this.container = this.initContainer();
|
|
51
|
-
const unsubscribeReady = this.wavesurfer.on('ready', () => {
|
|
52
|
-
const wrapper = this.renderer.getContainer();
|
|
53
|
-
wrapper.appendChild(this.container);
|
|
54
|
-
unsubscribeReady();
|
|
55
|
-
});
|
|
56
|
-
this.subscriptions.push(unsubscribeReady);
|
|
57
|
-
const wsContainer = this.renderer.getContainer();
|
|
58
|
-
wsContainer.addEventListener('mousedown', this.handleMouseDown);
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super(options || {});
|
|
30
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
31
|
+
this.regionsContainer = this.initRegionsContainer();
|
|
32
|
+
}
|
|
33
|
+
static create(options) {
|
|
34
|
+
return new RegionsPlugin(options);
|
|
35
|
+
}
|
|
36
|
+
init(params) {
|
|
37
|
+
super.init(params);
|
|
38
|
+
if (!this.wavesurfer || !this.wrapper) {
|
|
39
|
+
throw Error('WaveSurfer is not initialized');
|
|
40
|
+
}
|
|
41
|
+
this.wrapper?.appendChild(this.regionsContainer);
|
|
42
|
+
this.wrapper.addEventListener('mousedown', this.handleMouseDown);
|
|
59
43
|
document.addEventListener('mousemove', this.handleMouseMove);
|
|
60
44
|
document.addEventListener('mouseup', this.handleMouseUp);
|
|
61
45
|
}
|
|
62
|
-
/**
|
|
46
|
+
/** Set options */
|
|
47
|
+
setOptions(options) {
|
|
48
|
+
this.options = { ...this.options, ...options };
|
|
49
|
+
}
|
|
50
|
+
/** Unmount */
|
|
63
51
|
destroy() {
|
|
64
|
-
|
|
65
|
-
this.renderer.getContainer().removeEventListener('mousedown', this.handleMouseDown);
|
|
52
|
+
this.wrapper?.removeEventListener('mousedown', this.handleMouseDown);
|
|
66
53
|
document.removeEventListener('mousemove', this.handleMouseMove);
|
|
67
54
|
document.removeEventListener('mouseup', this.handleMouseUp, true);
|
|
68
|
-
|
|
55
|
+
this.regionsContainer.remove();
|
|
69
56
|
super.destroy();
|
|
70
57
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
58
|
+
initRegionsContainer() {
|
|
59
|
+
return el('div', {
|
|
60
|
+
position: 'absolute',
|
|
61
|
+
top: '0',
|
|
62
|
+
left: '0',
|
|
63
|
+
width: '100%',
|
|
64
|
+
height: '100%',
|
|
65
|
+
zIndex: '3',
|
|
66
|
+
pointerEvents: 'none',
|
|
67
|
+
});
|
|
81
68
|
}
|
|
69
|
+
handleMouseDown = (e) => {
|
|
70
|
+
if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
|
|
71
|
+
if (!this.wrapper)
|
|
72
|
+
return;
|
|
73
|
+
this.dragStart = e.clientX - this.wrapper.getBoundingClientRect().left;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
handleMouseMove = (e) => {
|
|
77
|
+
if (!this.wrapper)
|
|
78
|
+
return;
|
|
79
|
+
const box = this.wrapper.getBoundingClientRect();
|
|
80
|
+
const { width } = box;
|
|
81
|
+
const dragEnd = e.clientX - box.left;
|
|
82
|
+
if (this.options.draggable && this.modifiedRegion && this.isMoving) {
|
|
83
|
+
this.moveRegion(this.modifiedRegion, (dragEnd - this.dragStart) / width);
|
|
84
|
+
this.dragStart = dragEnd;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.options.resizable && this.modifiedRegion) {
|
|
88
|
+
this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd / width : undefined, this.isResizingLeft ? undefined : dragEnd / width);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (this.options.dragSelection && !isNaN(this.dragStart)) {
|
|
92
|
+
const dragEnd = e.clientX - this.regionsContainer.getBoundingClientRect().left;
|
|
93
|
+
if (dragEnd - this.dragStart >= MIN_WIDTH) {
|
|
94
|
+
if (!this.createdRegion) {
|
|
95
|
+
this.wasInteractive = this.wavesurfer?.options.interact || true;
|
|
96
|
+
this.wavesurfer?.toggleInteraction(false);
|
|
97
|
+
this.createdRegion = this.createRegion(this.dragStart / width, dragEnd / width);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.updateRegion(this.createdRegion, this.dragStart / width, dragEnd / width);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
handleMouseUp = () => {
|
|
106
|
+
if (this.createdRegion) {
|
|
107
|
+
this.addRegion(this.createdRegion);
|
|
108
|
+
this.createdRegion = null;
|
|
109
|
+
}
|
|
110
|
+
this.modifiedRegion = null;
|
|
111
|
+
this.isMoving = false;
|
|
112
|
+
this.dragStart = NaN;
|
|
113
|
+
this.wavesurfer?.toggleInteraction(this.wasInteractive);
|
|
114
|
+
};
|
|
82
115
|
createRegionElement(start, end, title = '') {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
leftHandle
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
const noWidth = start === end;
|
|
117
|
+
const div = el('div', {
|
|
118
|
+
position: 'absolute',
|
|
119
|
+
left: `${start * 100}%`,
|
|
120
|
+
width: `${(end - start) * 100}%`,
|
|
121
|
+
height: '100%',
|
|
122
|
+
backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
|
|
123
|
+
borderRadius: '2px',
|
|
124
|
+
boxSizing: 'border-box',
|
|
125
|
+
borderLeft: noWidth ? '2px solid rgba(0, 0, 0, 0.5)' : '',
|
|
126
|
+
transition: 'background-color 0.2s ease',
|
|
127
|
+
cursor: this.options.draggable ? 'move' : '',
|
|
128
|
+
pointerEvents: 'all',
|
|
129
|
+
whiteSpace: noWidth ? 'nowrap' : '',
|
|
130
|
+
padding: '0.2em',
|
|
131
|
+
});
|
|
132
|
+
div.textContent = title;
|
|
133
|
+
const leftHandle = el('div', {
|
|
134
|
+
position: 'absolute',
|
|
135
|
+
left: '0',
|
|
136
|
+
top: '0',
|
|
137
|
+
width: '6px',
|
|
138
|
+
height: '100%',
|
|
139
|
+
cursor: this.options.resizable ? 'ew-resize' : '',
|
|
140
|
+
pointerEvents: 'all',
|
|
141
|
+
borderLeft: noWidth ? '' : '2px solid rgba(0, 0, 0, 0.5)',
|
|
142
|
+
borderRadius: '2px 0 0 2px',
|
|
143
|
+
});
|
|
144
|
+
div.appendChild(leftHandle);
|
|
145
|
+
const rightHandle = leftHandle.cloneNode();
|
|
146
|
+
style(rightHandle, {
|
|
147
|
+
left: '',
|
|
148
|
+
right: '0',
|
|
149
|
+
borderLeft: '',
|
|
150
|
+
borderRight: noWidth ? '' : '2px solid rgba(0, 0, 0, 0.5)',
|
|
151
|
+
borderRadius: '0 2px 2px 0',
|
|
152
|
+
});
|
|
153
|
+
div.appendChild(rightHandle);
|
|
111
154
|
leftHandle.addEventListener('mousedown', (e) => {
|
|
155
|
+
if (!this.options.resizable)
|
|
156
|
+
return;
|
|
112
157
|
e.stopPropagation();
|
|
113
|
-
this.modifiedRegion = this.regions.find((r) => r.element ===
|
|
158
|
+
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
114
159
|
this.isResizingLeft = true;
|
|
115
160
|
this.isMoving = false;
|
|
116
161
|
});
|
|
117
162
|
rightHandle.addEventListener('mousedown', (e) => {
|
|
163
|
+
if (!this.options.resizable)
|
|
164
|
+
return;
|
|
118
165
|
e.stopPropagation();
|
|
119
|
-
this.modifiedRegion = this.regions.find((r) => r.element ===
|
|
166
|
+
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
120
167
|
this.isResizingLeft = false;
|
|
121
168
|
this.isMoving = false;
|
|
122
169
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
170
|
+
div.addEventListener('mousedown', () => {
|
|
171
|
+
if (!this.options.draggable)
|
|
172
|
+
return;
|
|
173
|
+
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
174
|
+
this.isMoving = this.modifiedRegion !== null;
|
|
126
175
|
});
|
|
127
|
-
|
|
128
|
-
const region = this.regions.find((r) => r.element ===
|
|
176
|
+
div.addEventListener('click', () => {
|
|
177
|
+
const region = this.regions.find((r) => r.element === div);
|
|
129
178
|
if (region) {
|
|
130
179
|
this.emit('region-clicked', { region });
|
|
131
180
|
}
|
|
132
181
|
});
|
|
133
|
-
this.
|
|
134
|
-
|
|
182
|
+
this.regionsContainer.appendChild(div);
|
|
183
|
+
// Check that the label doesn't overlap with other labels
|
|
184
|
+
// If it does, push it down
|
|
185
|
+
const labelLeft = div.getBoundingClientRect().left;
|
|
186
|
+
const labelWidth = div.scrollWidth;
|
|
187
|
+
const overlap = this.regions
|
|
188
|
+
.filter((reg) => {
|
|
189
|
+
const { left } = reg.element.getBoundingClientRect();
|
|
190
|
+
const width = reg.element.scrollWidth;
|
|
191
|
+
return labelLeft < left + width && left < labelLeft + labelWidth;
|
|
192
|
+
})
|
|
193
|
+
.map((reg) => parseFloat(reg.element.style.paddingTop))
|
|
194
|
+
.reduce((sum, val) => sum + val, 0);
|
|
195
|
+
if (overlap > 0) {
|
|
196
|
+
div.style.paddingTop = `${overlap + 1}em`;
|
|
197
|
+
}
|
|
198
|
+
return div;
|
|
135
199
|
}
|
|
136
200
|
createRegion(start, end, title = '') {
|
|
137
|
-
const duration = this.wavesurfer
|
|
138
|
-
const width = this.container.clientWidth;
|
|
201
|
+
const duration = this.wavesurfer?.getDuration() || 0;
|
|
139
202
|
return {
|
|
140
203
|
element: this.createRegionElement(start, end, title),
|
|
141
204
|
start,
|
|
142
205
|
end,
|
|
143
|
-
startTime:
|
|
144
|
-
endTime:
|
|
206
|
+
startTime: start * duration,
|
|
207
|
+
endTime: end * duration,
|
|
145
208
|
title,
|
|
146
209
|
};
|
|
147
210
|
}
|
|
@@ -150,15 +213,17 @@ class RegionsPlugin extends BasePlugin {
|
|
|
150
213
|
this.emit('region-created', { region });
|
|
151
214
|
}
|
|
152
215
|
updateRegion(region, start, end) {
|
|
216
|
+
const duration = this.wavesurfer?.getDuration() || 0;
|
|
153
217
|
if (start != null) {
|
|
154
|
-
region.start = start
|
|
155
|
-
region.element.style.left = `${region.start}
|
|
156
|
-
region.
|
|
218
|
+
region.start = start;
|
|
219
|
+
region.element.style.left = `${region.start * 100}%`;
|
|
220
|
+
region.element.style.width = `${(region.end - region.start) * 100}%`;
|
|
221
|
+
region.startTime = start * duration;
|
|
157
222
|
}
|
|
158
223
|
if (end != null) {
|
|
159
224
|
region.end = end;
|
|
160
|
-
region.element.style.width = `${region.end - region.start}
|
|
161
|
-
region.endTime =
|
|
225
|
+
region.element.style.width = `${(region.end - region.start) * 100}%`;
|
|
226
|
+
region.endTime = end * duration;
|
|
162
227
|
}
|
|
163
228
|
this.emit('region-updated', { region });
|
|
164
229
|
}
|
|
@@ -166,18 +231,29 @@ class RegionsPlugin extends BasePlugin {
|
|
|
166
231
|
this.updateRegion(region, region.start + delta, region.end + delta);
|
|
167
232
|
}
|
|
168
233
|
/** Create a region at a given start and end time, with an optional title */
|
|
169
|
-
|
|
170
|
-
const duration = this.wavesurfer
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const end = (endTime / duration) * width;
|
|
234
|
+
add(startTime, endTime, title = '', color = '') {
|
|
235
|
+
const duration = this.wavesurfer?.getDuration() || 0;
|
|
236
|
+
const start = startTime / duration;
|
|
237
|
+
const end = endTime / duration;
|
|
174
238
|
const region = this.createRegion(start, end, title);
|
|
175
239
|
this.addRegion(region);
|
|
240
|
+
if (color)
|
|
241
|
+
this.setRegionColor(region, color);
|
|
176
242
|
return region;
|
|
177
243
|
}
|
|
244
|
+
/** Remove a region */
|
|
245
|
+
remove(region) {
|
|
246
|
+
region.element.remove();
|
|
247
|
+
region.element = null;
|
|
248
|
+
this.regions = this.regions.filter((r) => r !== region);
|
|
249
|
+
}
|
|
178
250
|
/** Set the background color of a region */
|
|
179
251
|
setRegionColor(region, color) {
|
|
180
|
-
region.element.style.backgroundColor = color;
|
|
252
|
+
region.element.style[region.startTime === region.endTime ? 'borderColor' : 'backgroundColor'] = color;
|
|
253
|
+
}
|
|
254
|
+
/** Disable or enable mouse events for this region */
|
|
255
|
+
setRegionInteractive(region, isInteractive) {
|
|
256
|
+
region.element.style.pointerEvents = isInteractive ? 'all' : 'none';
|
|
181
257
|
}
|
|
182
258
|
}
|
|
183
259
|
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:()=>o});var n=i(139);class s extends n.Z{wavesurfer;container;wrapper;subscriptions=[];options;constructor(e){super(),this.options=e}init(e){this.wavesurfer=e.wavesurfer,this.container=e.container,this.wrapper=e.wrapper}destroy(){this.subscriptions.forEach((e=>e()))}}const o=s},139:(e,t,i)=>{i.d(t,{Z:()=>n});const n=class{eventTarget;constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t,i){const n=e=>{e instanceof CustomEvent&&t(e.detail)},s=String(e);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(e,t){return this.on(e,t,!0)}}}},t={};function i(n){var s=t[n];if(void 0!==s)return s.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,i),o.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);const t={dragSelection:!0,draggable:!0,resizable:!0},s=(e,t)=>{for(const i in t)e.style[i]=t[i]||""},o=(e,t)=>{const i=document.createElement(e);return s(i,t),i};class r extends e.Z{dragStart=NaN;regionsContainer;regions=[];createdRegion=null;modifiedRegion=null;isResizingLeft=!1;isMoving=!1;wasInteractive=!0;constructor(e){super(e||{}),this.options=Object.assign({},t,e),this.regionsContainer=this.initRegionsContainer()}static create(e){return new r(e)}init(e){if(super.init(e),!this.wavesurfer||!this.wrapper)throw Error("WaveSurfer is not initialized");this.wrapper?.appendChild(this.regionsContainer),this.wrapper.addEventListener("mousedown",this.handleMouseDown),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}setOptions(e){this.options={...this.options,...e}}destroy(){this.wrapper?.removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp,!0),this.regionsContainer.remove(),super.destroy()}initRegionsContainer(){return o("div",{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"3",pointerEvents:"none"})}handleMouseDown=e=>{if(this.options.draggable||this.options.resizable||this.options.dragSelection){if(!this.wrapper)return;this.dragStart=e.clientX-this.wrapper.getBoundingClientRect().left}};handleMouseMove=e=>{if(!this.wrapper)return;const t=this.wrapper.getBoundingClientRect(),{width:i}=t,n=e.clientX-t.left;if(this.options.draggable&&this.modifiedRegion&&this.isMoving)return this.moveRegion(this.modifiedRegion,(n-this.dragStart)/i),void(this.dragStart=n);if(this.options.resizable&&this.modifiedRegion)this.updateRegion(this.modifiedRegion,this.isResizingLeft?n/i:void 0,this.isResizingLeft?void 0:n/i);else if(this.options.dragSelection&&!isNaN(this.dragStart)){const t=e.clientX-this.regionsContainer.getBoundingClientRect().left;t-this.dragStart>=10&&(this.createdRegion?this.updateRegion(this.createdRegion,this.dragStart/i,t/i):(this.wasInteractive=this.wavesurfer?.options.interact||!0,this.wavesurfer?.toggleInteraction(!1),this.createdRegion=this.createRegion(this.dragStart/i,t/i)))}};handleMouseUp=()=>{this.createdRegion&&(this.addRegion(this.createdRegion),this.createdRegion=null),this.modifiedRegion=null,this.isMoving=!1,this.dragStart=NaN,this.wavesurfer?.toggleInteraction(this.wasInteractive)};createRegionElement(e,t,i=""){const n=e===t,r=o("div",{position:"absolute",left:100*e+"%",width:100*(t-e)+"%",height:"100%",backgroundColor:n?"":"rgba(0, 0, 0, 0.1)",borderRadius:"2px",boxSizing:"border-box",borderLeft:n?"2px solid rgba(0, 0, 0, 0.5)":"",transition:"background-color 0.2s ease",cursor:this.options.draggable?"move":"",pointerEvents:"all",whiteSpace:n?"nowrap":"",padding:"0.2em"});r.textContent=i;const a=o("div",{position:"absolute",left:"0",top:"0",width:"6px",height:"100%",cursor:this.options.resizable?"ew-resize":"",pointerEvents:"all",borderLeft:n?"":"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"2px 0 0 2px"});r.appendChild(a);const d=a.cloneNode();s(d,{left:"",right:"0",borderLeft:"",borderRight:n?"":"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"0 2px 2px 0"}),r.appendChild(d),a.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===r))||null,this.isResizingLeft=!0,this.isMoving=!1)})),d.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===r))||null,this.isResizingLeft=!1,this.isMoving=!1)})),r.addEventListener("mousedown",(()=>{this.options.draggable&&(this.modifiedRegion=this.regions.find((e=>e.element===r))||null,this.isMoving=null!==this.modifiedRegion)})),r.addEventListener("click",(()=>{const e=this.regions.find((e=>e.element===r));e&&this.emit("region-clicked",{region:e})})),this.regionsContainer.appendChild(r);const h=r.getBoundingClientRect().left,l=r.scrollWidth,g=this.regions.filter((e=>{const{left:t}=e.element.getBoundingClientRect(),i=e.element.scrollWidth;return h<t+i&&t<h+l})).map((e=>parseFloat(e.element.style.paddingTop))).reduce(((e,t)=>e+t),0);return g>0&&(r.style.paddingTop=`${g+1}em`),r}createRegion(e,t,i=""){const n=this.wavesurfer?.getDuration()||0;return{element:this.createRegionElement(e,t,i),start:e,end:t,startTime:e*n,endTime:t*n,title:i}}addRegion(e){this.regions.push(e),this.emit("region-created",{region:e})}updateRegion(e,t,i){const n=this.wavesurfer?.getDuration()||0;null!=t&&(e.start=t,e.element.style.left=100*e.start+"%",e.element.style.width=100*(e.end-e.start)+"%",e.startTime=t*n),null!=i&&(e.end=i,e.element.style.width=100*(e.end-e.start)+"%",e.endTime=i*n),this.emit("region-updated",{region:e})}moveRegion(e,t){this.updateRegion(e,e.start+t,e.end+t)}add(e,t,i="",n=""){const s=this.wavesurfer?.getDuration()||0,o=e/s,r=t/s,a=this.createRegion(o,r,i);return this.addRegion(a),n&&this.setRegionColor(a,n),a}remove(e){e.element.remove(),e.element=null,this.regions=this.regions.filter((t=>t!==e))}setRegionColor(e,t){e.element.style[e.startTime===e.endTime?"borderColor":"backgroundColor"]=t}setRegionInteractive(e,t){e.element.style.pointerEvents=t?"all":"none"}}const a=r})(),n.default})()));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
import type { WaveSurferPluginParams } from '../wavesurfer.js';
|
|
3
|
+
export type TimelinePluginOptions = {
|
|
4
|
+
/** The height of the timeline in pixels, defaults to 20 */
|
|
5
|
+
height?: number;
|
|
6
|
+
/** HTML container for the timeline, defaults to wavesufer's container */
|
|
7
|
+
container?: HTMLElement;
|
|
8
|
+
/** The duration of the timeline in seconds, defaults to wavesurfer's duration */
|
|
9
|
+
duration?: number;
|
|
10
|
+
/** Interval between ticks in seconds */
|
|
11
|
+
timeInterval?: number;
|
|
12
|
+
/** Interval between numeric labels */
|
|
13
|
+
primaryLabelInterval?: number;
|
|
14
|
+
/** Interval between secondary numeric labels */
|
|
15
|
+
secondaryLabelInterval?: number;
|
|
16
|
+
};
|
|
17
|
+
declare const defaultOptions: {
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
export type TimelinePluginEvents = {
|
|
21
|
+
ready: void;
|
|
22
|
+
};
|
|
23
|
+
declare class TimelinePlugin extends BasePlugin<TimelinePluginEvents, TimelinePluginOptions> {
|
|
24
|
+
private timelineWrapper;
|
|
25
|
+
protected options: TimelinePluginOptions & typeof defaultOptions;
|
|
26
|
+
constructor(options: TimelinePluginOptions);
|
|
27
|
+
static create(options: TimelinePluginOptions): TimelinePlugin;
|
|
28
|
+
init(params: WaveSurferPluginParams): void;
|
|
29
|
+
/** Unmount */
|
|
30
|
+
destroy(): void;
|
|
31
|
+
private initTimelineWrapper;
|
|
32
|
+
private formatTime;
|
|
33
|
+
private defaultTimeInterval;
|
|
34
|
+
defaultPrimaryLabelInterval(pxPerSec: number): number;
|
|
35
|
+
defaultSecondaryLabelInterval(pxPerSec: number): number;
|
|
36
|
+
private initTimeline;
|
|
37
|
+
}
|
|
38
|
+
export default TimelinePlugin;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import BasePlugin from '../base-plugin.js';
|
|
2
|
+
const defaultOptions = {
|
|
3
|
+
height: 20,
|
|
4
|
+
};
|
|
5
|
+
class TimelinePlugin extends BasePlugin {
|
|
6
|
+
timelineWrapper;
|
|
7
|
+
options;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
11
|
+
this.timelineWrapper = this.initTimelineWrapper();
|
|
12
|
+
}
|
|
13
|
+
static create(options) {
|
|
14
|
+
return new TimelinePlugin(options);
|
|
15
|
+
}
|
|
16
|
+
init(params) {
|
|
17
|
+
super.init(params);
|
|
18
|
+
if (!this.wavesurfer || !this.wrapper) {
|
|
19
|
+
throw Error('WaveSurfer is not initialized');
|
|
20
|
+
}
|
|
21
|
+
const container = this.options.container ?? this.wrapper;
|
|
22
|
+
container.appendChild(this.timelineWrapper);
|
|
23
|
+
if (this.options.duration) {
|
|
24
|
+
this.initTimeline(this.options.duration);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.subscriptions.push(this.wavesurfer.on('decode', ({ duration }) => {
|
|
28
|
+
this.initTimeline(duration);
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Unmount */
|
|
33
|
+
destroy() {
|
|
34
|
+
this.timelineWrapper.remove();
|
|
35
|
+
super.destroy();
|
|
36
|
+
}
|
|
37
|
+
initTimelineWrapper() {
|
|
38
|
+
return document.createElement('div');
|
|
39
|
+
}
|
|
40
|
+
formatTime(seconds) {
|
|
41
|
+
if (seconds / 60 > 1) {
|
|
42
|
+
// calculate minutes and seconds from seconds count
|
|
43
|
+
const minutes = Math.round(seconds / 60);
|
|
44
|
+
seconds = Math.round(seconds % 60);
|
|
45
|
+
const paddedSeconds = `${seconds < 10 ? '0' : ''}${seconds}`;
|
|
46
|
+
return `${minutes}:${paddedSeconds}`;
|
|
47
|
+
}
|
|
48
|
+
const rounded = Math.round(seconds * 1000) / 1000;
|
|
49
|
+
return `${rounded}`;
|
|
50
|
+
}
|
|
51
|
+
// Return how many seconds should be between each notch
|
|
52
|
+
defaultTimeInterval(pxPerSec) {
|
|
53
|
+
if (pxPerSec >= 25) {
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
else if (pxPerSec * 5 >= 25) {
|
|
57
|
+
return 5;
|
|
58
|
+
}
|
|
59
|
+
else if (pxPerSec * 15 >= 25) {
|
|
60
|
+
return 15;
|
|
61
|
+
}
|
|
62
|
+
return Math.ceil(0.5 / pxPerSec) * 60;
|
|
63
|
+
}
|
|
64
|
+
// Return the cadence of notches that get labels in the primary color.
|
|
65
|
+
defaultPrimaryLabelInterval(pxPerSec) {
|
|
66
|
+
if (pxPerSec >= 25) {
|
|
67
|
+
return 10;
|
|
68
|
+
}
|
|
69
|
+
else if (pxPerSec * 5 >= 25) {
|
|
70
|
+
return 6;
|
|
71
|
+
}
|
|
72
|
+
else if (pxPerSec * 15 >= 25) {
|
|
73
|
+
return 4;
|
|
74
|
+
}
|
|
75
|
+
return 4;
|
|
76
|
+
}
|
|
77
|
+
// Return the cadence of notches that get labels in the secondary color.
|
|
78
|
+
defaultSecondaryLabelInterval(pxPerSec) {
|
|
79
|
+
if (pxPerSec >= 25) {
|
|
80
|
+
return 5;
|
|
81
|
+
}
|
|
82
|
+
else if (pxPerSec * 5 >= 25) {
|
|
83
|
+
return 2;
|
|
84
|
+
}
|
|
85
|
+
else if (pxPerSec * 15 >= 25) {
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
return 2;
|
|
89
|
+
}
|
|
90
|
+
initTimeline(duration) {
|
|
91
|
+
const width = Math.round(this.timelineWrapper.scrollWidth * devicePixelRatio);
|
|
92
|
+
const pxPerSec = width / duration;
|
|
93
|
+
const timeInterval = this.options.timeInterval ?? this.defaultTimeInterval(pxPerSec);
|
|
94
|
+
const primaryLabelInterval = this.options.primaryLabelInterval ?? this.defaultPrimaryLabelInterval(pxPerSec);
|
|
95
|
+
const secondaryLabelInterval = this.options.secondaryLabelInterval ?? this.defaultSecondaryLabelInterval(pxPerSec);
|
|
96
|
+
const timeline = document.createElement('div');
|
|
97
|
+
timeline.setAttribute('style', `
|
|
98
|
+
height: ${this.options.height}px;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
display: flex;
|
|
101
|
+
justify-content: space-between;
|
|
102
|
+
align-items: flex-end;
|
|
103
|
+
font-size: ${this.options.height / 2}px;
|
|
104
|
+
white-space: nowrap;
|
|
105
|
+
`);
|
|
106
|
+
const notchEl = document.createElement('div');
|
|
107
|
+
notchEl.setAttribute('style', `
|
|
108
|
+
width: 1px;
|
|
109
|
+
height: 50%;
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
justify-content: flex-end;
|
|
113
|
+
overflow: visible;
|
|
114
|
+
border-left: 1px solid currentColor;
|
|
115
|
+
opacity: 0.25;
|
|
116
|
+
`);
|
|
117
|
+
for (let i = 0; i < duration; i += timeInterval) {
|
|
118
|
+
const notch = notchEl.cloneNode();
|
|
119
|
+
const isPrimary = i % primaryLabelInterval === 0;
|
|
120
|
+
const isSecondary = i % secondaryLabelInterval === 0;
|
|
121
|
+
if (isPrimary || isSecondary) {
|
|
122
|
+
notch.style.height = '100%';
|
|
123
|
+
notch.style.textIndent = '3px';
|
|
124
|
+
notch.textContent = this.formatTime(i);
|
|
125
|
+
if (isPrimary)
|
|
126
|
+
notch.style.opacity = '1';
|
|
127
|
+
}
|
|
128
|
+
timeline.appendChild(notch);
|
|
129
|
+
}
|
|
130
|
+
this.timelineWrapper.appendChild(timeline);
|
|
131
|
+
this.emit('ready');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export default TimelinePlugin;
|
|
@@ -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.Timeline=t():e.Timeline=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={284:(e,t,i)=>{i.d(t,{Z:()=>s});var n=i(139);class r extends n.Z{wavesurfer;container;wrapper;subscriptions=[];options;constructor(e){super(),this.options=e}init(e){this.wavesurfer=e.wavesurfer,this.container=e.container,this.wrapper=e.wrapper}destroy(){this.subscriptions.forEach((e=>e()))}}const s=r},139:(e,t,i)=>{i.d(t,{Z:()=>n});const n=class{eventTarget;constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t,i){const n=e=>{e instanceof CustomEvent&&t(e.detail)},r=String(e);return this.eventTarget.addEventListener(r,n,{once:i}),()=>this.eventTarget.removeEventListener(r,n)}once(e,t){return this.on(e,t,!0)}}}},t={};function i(n){var r=t[n];if(void 0!==r)return r.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,i),s.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:()=>s});var e=i(284);const t={height:20};class r extends e.Z{timelineWrapper;options;constructor(e){super(e),this.options=Object.assign({},t,e),this.timelineWrapper=this.initTimelineWrapper()}static create(e){return new r(e)}init(e){if(super.init(e),!this.wavesurfer||!this.wrapper)throw Error("WaveSurfer is not initialized");(this.options.container??this.wrapper).appendChild(this.timelineWrapper),this.options.duration?this.initTimeline(this.options.duration):this.subscriptions.push(this.wavesurfer.on("decode",(({duration:e})=>{this.initTimeline(e)})))}destroy(){this.timelineWrapper.remove(),super.destroy()}initTimelineWrapper(){return document.createElement("div")}formatTime(e){return e/60>1?`${Math.round(e/60)}:${(e=Math.round(e%60))<10?"0":""}${e}`:""+Math.round(1e3*e)/1e3}defaultTimeInterval(e){return e>=25?1:5*e>=25?5:15*e>=25?15:60*Math.ceil(.5/e)}defaultPrimaryLabelInterval(e){return e>=25?10:5*e>=25?6:4}defaultSecondaryLabelInterval(e){return e>=25?5:2}initTimeline(e){const t=Math.round(this.timelineWrapper.scrollWidth*devicePixelRatio)/e,i=this.options.timeInterval??this.defaultTimeInterval(t),n=this.options.primaryLabelInterval??this.defaultPrimaryLabelInterval(t),r=this.options.secondaryLabelInterval??this.defaultSecondaryLabelInterval(t),s=document.createElement("div");s.setAttribute("style",`\n height: ${this.options.height}px;\n overflow: hidden;\n display: flex;\n justify-content: space-between;\n align-items: flex-end;\n font-size: ${this.options.height/2}px;\n white-space: nowrap;\n `);const o=document.createElement("div");o.setAttribute("style","\n width: 1px;\n height: 50%;\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n overflow: visible;\n border-left: 1px solid currentColor;\n opacity: 0.25;\n ");for(let t=0;t<e;t+=i){const e=o.cloneNode(),i=t%n==0;(i||t%r==0)&&(e.style.height="100%",e.style.textIndent="3px",e.textContent=this.formatTime(t),i&&(e.style.opacity="1")),s.appendChild(e)}this.timelineWrapper.appendChild(s),this.emit("ready")}}const s=r})(),n.default})()));
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
|
-
type
|
|
2
|
+
type RendererRequiredParams = {
|
|
3
3
|
container: HTMLElement | string | null;
|
|
4
|
+
};
|
|
5
|
+
export type RendererStyleOptions = {
|
|
4
6
|
height: number;
|
|
5
7
|
waveColor: string;
|
|
6
8
|
progressColor: string;
|
|
9
|
+
cursorColor?: string;
|
|
10
|
+
cursorWidth: number;
|
|
7
11
|
minPxPerSec: number;
|
|
12
|
+
fillParent: boolean;
|
|
8
13
|
barWidth?: number;
|
|
9
14
|
barGap?: number;
|
|
10
15
|
barRadius?: number;
|
|
16
|
+
hideScrollbar?: boolean;
|
|
17
|
+
autoCenter?: boolean;
|
|
11
18
|
};
|
|
12
19
|
type RendererEvents = {
|
|
13
20
|
click: {
|
|
@@ -18,18 +25,24 @@ declare class Renderer extends EventEmitter<RendererEvents> {
|
|
|
18
25
|
private options;
|
|
19
26
|
private container;
|
|
20
27
|
private scrollContainer;
|
|
21
|
-
private
|
|
22
|
-
private
|
|
23
|
-
private
|
|
24
|
-
private
|
|
25
|
-
|
|
28
|
+
private wrapper;
|
|
29
|
+
private canvasWrapper;
|
|
30
|
+
private progressWrapper;
|
|
31
|
+
private timeout;
|
|
32
|
+
private isScrolling;
|
|
33
|
+
private audioData;
|
|
34
|
+
private resizeObserver;
|
|
35
|
+
constructor(params: RendererRequiredParams, options: RendererStyleOptions);
|
|
36
|
+
private initHtml;
|
|
37
|
+
setOptions(options: RendererStyleOptions): void;
|
|
38
|
+
getContainer(): HTMLElement;
|
|
39
|
+
getWrapper(): HTMLElement;
|
|
26
40
|
destroy(): void;
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
zoom(
|
|
41
|
+
private delay;
|
|
42
|
+
private renderPeaks;
|
|
43
|
+
render(audioData: AudioBuffer): void;
|
|
44
|
+
reRender(): void;
|
|
45
|
+
zoom(minPxPerSec: number): void;
|
|
32
46
|
renderProgress(progress: number, autoCenter?: boolean): void;
|
|
33
|
-
getContainer(): HTMLElement;
|
|
34
47
|
}
|
|
35
48
|
export default Renderer;
|