wavesurfer.js 7.0.0-alpha.0 → 7.0.0-alpha.10

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 (57) hide show
  1. package/README.md +13 -15
  2. package/dist/base-plugin.d.ts +4 -3
  3. package/dist/base-plugin.js +3 -2
  4. package/dist/decoder.d.ts +2 -4
  5. package/dist/decoder.js +16 -16
  6. package/dist/event-emitter.d.ts +3 -1
  7. package/dist/event-emitter.js +6 -2
  8. package/dist/index.d.ts +43 -18
  9. package/dist/index.js +105 -44
  10. package/dist/player-webaudio.js +5 -4
  11. package/dist/player.d.ts +11 -2
  12. package/dist/player.js +50 -4
  13. package/dist/plugins/multitrack.d.ts +46 -0
  14. package/dist/plugins/multitrack.js +274 -0
  15. package/dist/plugins/regions.d.ts +11 -6
  16. package/dist/plugins/regions.js +106 -74
  17. package/dist/plugins/timeline.d.ts +29 -0
  18. package/dist/plugins/timeline.js +119 -0
  19. package/dist/plugins/xmultitrack.d.ts +44 -0
  20. package/dist/plugins/xmultitrack.js +260 -0
  21. package/dist/react/useWavesurfer.d.ts +5 -0
  22. package/dist/react/useWavesurfer.js +20 -0
  23. package/dist/renderer.d.ts +9 -5
  24. package/dist/renderer.js +137 -91
  25. package/dist/timer.d.ts +2 -1
  26. package/dist/timer.js +6 -1
  27. package/dist/wavesurfer.Multitrack.min.js +1 -0
  28. package/dist/wavesurfer.Regions.min.js +1 -0
  29. package/dist/wavesurfer.Timeline.min.js +1 -0
  30. package/dist/wavesurfer.min.js +1 -0
  31. package/package.json +19 -5
  32. package/.eslintrc.json +0 -24
  33. package/.prettierrc +0 -8
  34. package/examples/audio.ogg +0 -0
  35. package/examples/bars.js +0 -19
  36. package/examples/basic.js +0 -8
  37. package/examples/gradient.js +0 -28
  38. package/examples/regions.js +0 -63
  39. package/examples/video.js +0 -19
  40. package/examples/webaudio.js +0 -14
  41. package/src/base-plugin.ts +0 -20
  42. package/src/decoder.ts +0 -41
  43. package/src/event-emitter.ts +0 -35
  44. package/src/fetcher.ts +0 -7
  45. package/src/index.ts +0 -252
  46. package/src/player-webaudio.ts +0 -34
  47. package/src/player.ts +0 -50
  48. package/src/plugins/regions.ts +0 -240
  49. package/src/renderer.ts +0 -250
  50. package/src/timer.ts +0 -27
  51. package/tsconfig.json +0 -105
  52. package/tutorial/index.html +0 -47
  53. package/tutorial/src/editor.js +0 -70
  54. package/tutorial/src/init.js +0 -66
  55. package/tutorial/src/url.js +0 -25
  56. package/tutorial/style.css +0 -211
  57. package/yarn-error.log +0 -1049
@@ -0,0 +1,46 @@
1
+ import { type WaveSurferOptions } from '../index.js';
2
+ type MultitrackTracks = Array<{
3
+ id: string | number;
4
+ url?: string;
5
+ peaks?: WaveSurferOptions['peaks'];
6
+ draggable?: boolean;
7
+ startPosition: number;
8
+ startCue?: number;
9
+ endCue?: number;
10
+ markers?: Array<{
11
+ id: string | number;
12
+ time: number;
13
+ label?: string;
14
+ color?: string;
15
+ }>;
16
+ }>;
17
+ type MultitrackOptions = {
18
+ container: HTMLElement;
19
+ minPxPerSec: number;
20
+ } & WaveSurferOptions;
21
+ declare class MultiTrack {
22
+ private tracks;
23
+ private options;
24
+ private audios;
25
+ private wavesurfers;
26
+ private durations;
27
+ private currentTime;
28
+ private maxDuration;
29
+ private rendering;
30
+ private isDragging;
31
+ private frameRequest;
32
+ static create(tracks: MultitrackTracks, options: MultitrackOptions): MultiTrack;
33
+ constructor(tracks: MultitrackTracks, options: MultitrackOptions);
34
+ private initAudios;
35
+ private initWavesurfers;
36
+ private updatePosition;
37
+ private onSeek;
38
+ private onDrag;
39
+ private findCurrentTracks;
40
+ private startSync;
41
+ play(): void;
42
+ pause(): void;
43
+ isPlaying(): boolean;
44
+ destroy(): void;
45
+ }
46
+ export default MultiTrack;
@@ -0,0 +1,274 @@
1
+ import WaveSurfer from '../index.js';
2
+ import RegionsPlugin from './regions.js';
3
+ class MultiTrack {
4
+ static create(tracks, options) {
5
+ return new MultiTrack(tracks, options);
6
+ }
7
+ constructor(tracks, options) {
8
+ this.audios = [];
9
+ this.wavesurfers = [];
10
+ this.durations = [];
11
+ this.currentTime = 0;
12
+ this.maxDuration = 0;
13
+ this.isDragging = false;
14
+ this.frameRequest = null;
15
+ this.tracks = tracks.map((track) => (Object.assign(Object.assign({}, track), { startPosition: track.startPosition || 0, peaks: track.peaks || (track.url ? undefined : [new Float32Array()]) })));
16
+ this.options = options;
17
+ this.rendering = initRendering(this.tracks, this.options);
18
+ this.initAudios().then((durations) => {
19
+ this.durations = durations;
20
+ const maxDuration = this.tracks.reduce((max, track, index) => {
21
+ return Math.max(max, track.startPosition + durations[index]);
22
+ }, 0);
23
+ this.rendering.setMainWidth(maxDuration * this.options.minPxPerSec);
24
+ this.rendering.addClickHandler((position) => {
25
+ this.onSeek(position * maxDuration);
26
+ });
27
+ this.maxDuration = maxDuration;
28
+ });
29
+ this.initWavesurfers();
30
+ this.rendering.containers.forEach((container, index) => {
31
+ const drag = initDragging(container, (delta) => this.onDrag(index, delta));
32
+ this.wavesurfers[index].once('destroy', () => {
33
+ drag === null || drag === void 0 ? void 0 : drag.destroy();
34
+ });
35
+ });
36
+ }
37
+ initAudios() {
38
+ const emptyTrackUrl = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';
39
+ this.audios = this.tracks.map((track) => new Audio(track.url || emptyTrackUrl));
40
+ return Promise.all(this.audios.map((audio) => {
41
+ return new Promise((resolve) => {
42
+ audio.addEventListener('canplay', () => {
43
+ resolve(audio.duration);
44
+ }, { once: true });
45
+ });
46
+ }));
47
+ }
48
+ initWavesurfers() {
49
+ const wavesurfers = this.tracks.map((track, index) => {
50
+ // Create a wavesurfer instance
51
+ const ws = WaveSurfer.create(Object.assign(Object.assign({}, this.options), { container: this.rendering.containers[index], media: this.audios[index], peaks: track.peaks, cursorColor: 'transparent', fillParent: false, interactive: false }));
52
+ const wsRegions = ws.registerPlugin(RegionsPlugin, {
53
+ draggable: false,
54
+ resizable: false,
55
+ dragSelection: false,
56
+ });
57
+ ws.once('decode', () => {
58
+ // Render start and end cues
59
+ if (track.startCue) {
60
+ wsRegions.add(track.startCue, track.startCue, 'start');
61
+ }
62
+ if (track.endCue) {
63
+ wsRegions.add(track.endCue, track.endCue, 'end');
64
+ }
65
+ // Render markers
66
+ ;
67
+ (track.markers || []).forEach((marker) => {
68
+ wsRegions.add(marker.time, marker.time, marker.label, marker.color);
69
+ });
70
+ });
71
+ return ws;
72
+ });
73
+ this.wavesurfers = wavesurfers;
74
+ }
75
+ updatePosition(time) {
76
+ const precisionSeconds = 0.3;
77
+ this.currentTime = time;
78
+ this.rendering.updateCursor(time / this.maxDuration);
79
+ const isPaused = !this.isPlaying();
80
+ // Update the current time of each audio
81
+ this.tracks.forEach((track, index) => {
82
+ const audio = this.audios[index];
83
+ const duration = this.durations[index];
84
+ const newTime = time - track.startPosition;
85
+ if (Math.abs(audio.currentTime - newTime) > precisionSeconds) {
86
+ audio.currentTime = newTime;
87
+ }
88
+ // If the position is out of the track bounds, pause it
89
+ if (isPaused || newTime < 0 || newTime > duration) {
90
+ !audio.paused && audio.pause();
91
+ }
92
+ else if (!isPaused) {
93
+ // If the position is in the track bounds, play it
94
+ audio.paused && audio.play();
95
+ }
96
+ // Unmute if cue is reached
97
+ const newVolume = newTime >= (track.startCue || 0) && newTime < (track.endCue || Infinity) ? 1 : 0;
98
+ if (newVolume !== audio.volume)
99
+ audio.volume = newVolume;
100
+ });
101
+ }
102
+ onSeek(time) {
103
+ if (this.isDragging)
104
+ return;
105
+ const wasPlaying = this.isPlaying();
106
+ this.updatePosition(time);
107
+ if (wasPlaying)
108
+ this.play();
109
+ }
110
+ onDrag(index, delta) {
111
+ const track = this.tracks[index];
112
+ if (!track.draggable)
113
+ return;
114
+ this.isDragging = true;
115
+ setTimeout(() => (this.isDragging = false), 300);
116
+ const newTime = track.startPosition + delta * this.maxDuration;
117
+ track.startPosition = newTime;
118
+ this.rendering.setContainerOffsets();
119
+ }
120
+ findCurrentTracks() {
121
+ // Find the audios at the current time
122
+ const indexes = [];
123
+ this.tracks.forEach((track, index) => {
124
+ if (track.url &&
125
+ this.currentTime >= track.startPosition &&
126
+ this.currentTime < track.startPosition + this.durations[index]) {
127
+ indexes.push(index);
128
+ }
129
+ });
130
+ if (indexes.length === 0) {
131
+ const minStartTime = Math.min(...this.tracks.filter((t) => t.url).map((track) => track.startPosition));
132
+ indexes.push(this.tracks.findIndex((track) => track.startPosition === minStartTime));
133
+ }
134
+ return indexes;
135
+ }
136
+ startSync() {
137
+ const onFrame = () => {
138
+ const position = this.audios.reduce((pos, audio, index) => {
139
+ if (!audio.paused) {
140
+ pos = Math.max(pos, audio.currentTime + this.tracks[index].startPosition);
141
+ }
142
+ return pos;
143
+ }, this.currentTime);
144
+ if (position > this.currentTime) {
145
+ this.updatePosition(position);
146
+ }
147
+ this.frameRequest = requestAnimationFrame(onFrame);
148
+ };
149
+ onFrame();
150
+ }
151
+ play() {
152
+ this.startSync();
153
+ const indexes = this.findCurrentTracks();
154
+ indexes.forEach((index) => {
155
+ var _a;
156
+ (_a = this.audios[index]) === null || _a === void 0 ? void 0 : _a.play();
157
+ });
158
+ }
159
+ pause() {
160
+ this.audios.forEach((audio) => audio.pause());
161
+ }
162
+ isPlaying() {
163
+ return this.audios.some((audio) => !audio.paused);
164
+ }
165
+ destroy() {
166
+ if (this.frameRequest)
167
+ cancelAnimationFrame(this.frameRequest);
168
+ this.rendering.destroy();
169
+ this.audios.forEach((audio) => {
170
+ audio.pause();
171
+ audio.src = '';
172
+ });
173
+ this.wavesurfers.forEach((ws) => {
174
+ ws.destroy();
175
+ });
176
+ }
177
+ }
178
+ function initRendering(tracks, options) {
179
+ // Create a common container for all tracks
180
+ let scroll = document.createElement('div');
181
+ scroll.setAttribute('style', 'width: 100%; overflow-x: scroll; overflow-y: hidden; user-select: none;');
182
+ const wrapper = document.createElement('div');
183
+ wrapper.style.position = 'relative';
184
+ scroll.appendChild(wrapper);
185
+ options.container.appendChild(scroll);
186
+ // Create a common cursor
187
+ const cursor = document.createElement('div');
188
+ cursor.setAttribute('style', 'width: 1px; height: 100%; position: absolute; z-index: 10; top: 0; left: 0');
189
+ cursor.style.backgroundColor = options.cursorColor || options.progressColor || '#000';
190
+ wrapper.appendChild(cursor);
191
+ // Create containers for each track
192
+ const containers = tracks.map(() => {
193
+ const container = document.createElement('div');
194
+ container.style.width = 'fit-content';
195
+ wrapper.appendChild(container);
196
+ return container;
197
+ });
198
+ // Set the positions of each container
199
+ const setContainerOffsets = () => {
200
+ containers.forEach((container, i) => {
201
+ const offset = (tracks[i].startPosition * options.minPxPerSec) / devicePixelRatio;
202
+ container.style.transform = `translateX(${offset}px)`;
203
+ if (tracks[i].draggable)
204
+ container.style.cursor = 'ew-resize';
205
+ });
206
+ };
207
+ setContainerOffsets();
208
+ return {
209
+ containers,
210
+ // Set the start offset
211
+ setContainerOffsets,
212
+ // Set the container width
213
+ setMainWidth: (width) => {
214
+ wrapper.style.width = `${width / devicePixelRatio}px`;
215
+ },
216
+ // Update cursor position
217
+ updateCursor: (position) => {
218
+ cursor.style.left = `${Math.min(100, position * 100)}%`;
219
+ },
220
+ // Click to seek
221
+ addClickHandler: (onClick) => {
222
+ wrapper.addEventListener('click', (e) => {
223
+ const rect = wrapper.getBoundingClientRect();
224
+ const x = e.clientX - rect.left;
225
+ const position = x / wrapper.offsetWidth;
226
+ onClick(position);
227
+ });
228
+ },
229
+ // Destroy the container
230
+ destroy: () => {
231
+ scroll.remove();
232
+ },
233
+ };
234
+ }
235
+ function initDragging(container, onDrag) {
236
+ const wrapper = container.parentElement;
237
+ if (!wrapper)
238
+ return;
239
+ // Dragging tracks to set position
240
+ let dragStart = null;
241
+ // Drag start
242
+ container.addEventListener('mousedown', (e) => {
243
+ const rect = wrapper.getBoundingClientRect();
244
+ dragStart = e.clientX - rect.left;
245
+ });
246
+ // Drag end
247
+ const onMouseUp = (e) => {
248
+ if (dragStart != null) {
249
+ e.stopPropagation();
250
+ dragStart = null;
251
+ }
252
+ };
253
+ // Drag move
254
+ const onMouseMove = (e) => {
255
+ if (dragStart == null)
256
+ return;
257
+ const rect = wrapper.getBoundingClientRect();
258
+ const x = e.clientX - rect.left;
259
+ const diff = x - dragStart;
260
+ if (diff > 1 || diff < -1) {
261
+ dragStart = x;
262
+ onDrag(diff / wrapper.offsetWidth);
263
+ }
264
+ };
265
+ document.body.addEventListener('mouseup', onMouseUp);
266
+ document.body.addEventListener('mousemove', onMouseMove);
267
+ return {
268
+ destroy: () => {
269
+ document.body.removeEventListener('mouseup', onMouseUp);
270
+ document.body.removeEventListener('mousemove', onMouseMove);
271
+ },
272
+ };
273
+ }
274
+ export default MultiTrack;
@@ -1,5 +1,10 @@
1
1
  import BasePlugin from '../base-plugin.js';
2
2
  import { WaveSurferPluginParams } from '../index.js';
3
+ type RegionsPluginOptions = {
4
+ dragSelection?: boolean;
5
+ draggable?: boolean;
6
+ resizable?: boolean;
7
+ };
3
8
  type Region = {
4
9
  startTime: number;
5
10
  endTime: number;
@@ -19,19 +24,19 @@ type RegionsPluginEvents = {
19
24
  region: Region;
20
25
  };
21
26
  };
22
- declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
27
+ declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
23
28
  private dragStart;
24
- private container;
29
+ private wrapper;
25
30
  private regions;
26
31
  private createdRegion;
27
32
  private modifiedRegion;
28
33
  private isResizingLeft;
29
34
  private isMoving;
30
35
  /** Create an instance of RegionsPlugin */
31
- constructor(params: WaveSurferPluginParams);
32
- /** Unmounts the regions */
36
+ constructor(params: WaveSurferPluginParams, options: RegionsPluginOptions);
37
+ /** Unmount */
33
38
  destroy(): void;
34
- private initContainer;
39
+ private initWrapper;
35
40
  private handleMouseDown;
36
41
  private handleMouseMove;
37
42
  private handleMouseUp;
@@ -41,7 +46,7 @@ declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
41
46
  private updateRegion;
42
47
  private moveRegion;
43
48
  /** Create a region at a given start and end time, with an optional title */
44
- addRegionAtTime(startTime: number, endTime: number, title?: string): Region;
49
+ add(startTime: number, endTime: number, title?: string, color?: string): Region;
45
50
  /** Set the background color of a region */
46
51
  setRegionColor(region: Region, color: string): void;
47
52
  }
@@ -1,9 +1,24 @@
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 {
4
19
  /** Create an instance of RegionsPlugin */
5
- constructor(params) {
6
- super(params);
20
+ constructor(params, options) {
21
+ super(params, options);
7
22
  this.dragStart = NaN;
8
23
  this.regions = [];
9
24
  this.createdRegion = null;
@@ -11,24 +26,26 @@ class RegionsPlugin extends BasePlugin {
11
26
  this.isResizingLeft = false;
12
27
  this.isMoving = false;
13
28
  this.handleMouseDown = (e) => {
14
- this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
29
+ if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
30
+ this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
31
+ }
15
32
  };
16
33
  this.handleMouseMove = (e) => {
17
34
  const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
18
- if (this.modifiedRegion && this.isMoving) {
35
+ if (this.options.draggable && this.modifiedRegion && this.isMoving) {
19
36
  this.moveRegion(this.modifiedRegion, dragEnd - this.dragStart);
20
37
  this.dragStart = dragEnd;
21
38
  return;
22
39
  }
23
- if (this.modifiedRegion) {
40
+ if (this.options.resizable && this.modifiedRegion) {
24
41
  this.updateRegion(this.modifiedRegion, this.isResizingLeft ? dragEnd : undefined, this.isResizingLeft ? undefined : dragEnd);
25
42
  return;
26
43
  }
27
- if (!isNaN(this.dragStart)) {
28
- const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
44
+ if (this.options.dragSelection && !isNaN(this.dragStart)) {
45
+ const dragEnd = e.clientX - this.wrapper.getBoundingClientRect().left;
29
46
  if (dragEnd - this.dragStart >= MIN_WIDTH) {
30
47
  if (!this.createdRegion) {
31
- this.renderer.getContainer().style.pointerEvents = 'none';
48
+ this.container.style.pointerEvents = 'none';
32
49
  this.createdRegion = this.createRegion(this.dragStart, dragEnd);
33
50
  }
34
51
  else {
@@ -45,97 +62,107 @@ class RegionsPlugin extends BasePlugin {
45
62
  this.modifiedRegion = null;
46
63
  this.isMoving = false;
47
64
  this.dragStart = NaN;
48
- this.renderer.getContainer().style.pointerEvents = '';
65
+ this.container.style.pointerEvents = '';
49
66
  };
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();
67
+ this.options = Object.assign({}, defaultOptions, options);
68
+ this.wrapper = this.initWrapper();
69
+ const unsubscribe = this.wavesurfer.once('decode', () => {
70
+ this.container.appendChild(this.wrapper);
55
71
  });
56
- this.subscriptions.push(unsubscribeReady);
57
- const wsContainer = this.renderer.getContainer();
58
- wsContainer.addEventListener('mousedown', this.handleMouseDown);
72
+ this.subscriptions.push(unsubscribe);
73
+ this.container.addEventListener('mousedown', this.handleMouseDown);
59
74
  document.addEventListener('mousemove', this.handleMouseMove);
60
75
  document.addEventListener('mouseup', this.handleMouseUp);
61
76
  }
62
- /** Unmounts the regions */
77
+ /** Unmount */
63
78
  destroy() {
64
- var _a;
65
- this.renderer.getContainer().removeEventListener('mousedown', this.handleMouseDown);
79
+ this.container.removeEventListener('mousedown', this.handleMouseDown);
66
80
  document.removeEventListener('mousemove', this.handleMouseMove);
67
81
  document.removeEventListener('mouseup', this.handleMouseUp, true);
68
- (_a = this.container) === null || _a === void 0 ? void 0 : _a.remove();
82
+ this.wrapper.remove();
69
83
  super.destroy();
70
84
  }
71
- initContainer() {
72
- const div = document.createElement('div');
73
- div.style.position = 'absolute';
74
- div.style.top = '0';
75
- div.style.left = '0';
76
- div.style.width = '100%';
77
- div.style.height = '100%';
78
- div.style.zIndex = '3';
79
- div.style.pointerEvents = 'none';
80
- return div;
85
+ initWrapper() {
86
+ return el('div', {
87
+ position: 'absolute',
88
+ top: '0',
89
+ left: '0',
90
+ width: '100%',
91
+ height: '100%',
92
+ zIndex: '3',
93
+ pointerEvents: 'none',
94
+ });
81
95
  }
82
96
  createRegionElement(start, end, title = '') {
83
- const el = document.createElement('div');
84
- el.style.position = 'absolute';
85
- el.style.left = `${start}px`;
86
- el.style.width = `${end - start}px`;
87
- el.style.height = '100%';
88
- el.style.backgroundColor = 'rgba(0, 0, 0, 0.1)';
89
- el.style.transition = 'background-color 0.2s ease';
90
- el.style.cursor = 'move';
91
- el.style.pointerEvents = 'all';
92
- el.title = title;
93
- const leftHandle = document.createElement('div');
94
- leftHandle.style.position = 'absolute';
95
- leftHandle.style.left = '0';
96
- leftHandle.style.width = '6px';
97
- leftHandle.style.height = '100%';
98
- leftHandle.style.borderLeft = '2px solid rgba(0, 0, 0, 0.5)';
99
- leftHandle.style.cursor = 'ew-resize';
100
- leftHandle.style.pointerEvents = 'all';
101
- el.appendChild(leftHandle);
102
- const rightHandle = document.createElement('div');
103
- rightHandle.style.position = 'absolute';
104
- rightHandle.style.right = '0';
105
- rightHandle.style.width = '6px';
106
- rightHandle.style.height = '100%';
107
- rightHandle.style.borderRight = '2px solid rgba(0, 0, 0, 0.5)';
108
- rightHandle.style.cursor = 'ew-resize';
109
- rightHandle.style.pointerEvents = 'all';
110
- el.appendChild(rightHandle);
97
+ const noWidth = start === end;
98
+ const div = el('div', {
99
+ position: 'absolute',
100
+ left: `${start}px`,
101
+ width: `${end - start}px`,
102
+ height: '100%',
103
+ backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
104
+ borderRadius: '2px',
105
+ boxSizing: 'border-box',
106
+ borderLeft: '2px solid rgba(0, 0, 0, 0.5)',
107
+ borderRight: noWidth ? '' : '2px solid rgba(0, 0, 0, 0.5)',
108
+ transition: 'background-color 0.2s ease',
109
+ cursor: this.options.draggable ? 'move' : '',
110
+ pointerEvents: 'all',
111
+ padding: '0.2em',
112
+ });
113
+ div.textContent = title;
114
+ const leftHandle = el('div', {
115
+ position: 'absolute',
116
+ left: '0',
117
+ width: '6px',
118
+ height: '100%',
119
+ cursor: this.options.resizable ? 'ew-resize' : '',
120
+ pointerEvents: 'all',
121
+ });
122
+ div.appendChild(leftHandle);
123
+ const rightHandle = leftHandle.cloneNode();
124
+ style(rightHandle, {
125
+ left: '',
126
+ right: '0',
127
+ borderLeft: '',
128
+ });
129
+ div.appendChild(rightHandle);
111
130
  leftHandle.addEventListener('mousedown', (e) => {
131
+ if (!this.options.resizable)
132
+ return;
112
133
  e.stopPropagation();
113
- this.modifiedRegion = this.regions.find((r) => r.element === el) || null;
134
+ this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
114
135
  this.isResizingLeft = true;
115
136
  this.isMoving = false;
116
137
  });
117
138
  rightHandle.addEventListener('mousedown', (e) => {
139
+ if (!this.options.resizable)
140
+ return;
118
141
  e.stopPropagation();
119
- this.modifiedRegion = this.regions.find((r) => r.element === el) || null;
142
+ this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
120
143
  this.isResizingLeft = false;
121
144
  this.isMoving = false;
122
145
  });
123
- el.addEventListener('mousedown', () => {
124
- this.modifiedRegion = this.regions.find((r) => r.element === el) || null;
146
+ div.addEventListener('mousedown', () => {
147
+ if (!this.options.draggable)
148
+ return;
149
+ this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
125
150
  this.isMoving = true;
126
151
  });
127
- el.addEventListener('click', () => {
128
- const region = this.regions.find((r) => r.element === el);
152
+ div.addEventListener('click', () => {
153
+ const region = this.regions.find((r) => r.element === div);
129
154
  if (region) {
130
155
  this.emit('region-clicked', { region });
131
156
  }
132
157
  });
133
- this.container.appendChild(el);
134
- return el;
158
+ this.wrapper.appendChild(div);
159
+ return div;
135
160
  }
136
161
  createRegion(start, end, title = '') {
137
162
  const duration = this.wavesurfer.getDuration();
138
- const width = this.container.clientWidth;
163
+ const width = this.wrapper.clientWidth;
164
+ start = Math.max(0, Math.min(start, width - 1));
165
+ end = Math.max(0, Math.min(end, width - 1));
139
166
  return {
140
167
  element: this.createRegionElement(start, end, title),
141
168
  start,
@@ -150,15 +177,18 @@ class RegionsPlugin extends BasePlugin {
150
177
  this.emit('region-created', { region });
151
178
  }
152
179
  updateRegion(region, start, end) {
180
+ const width = this.wrapper.clientWidth;
153
181
  if (start != null) {
154
- region.start = start !== null && start !== void 0 ? start : region.start;
182
+ start = Math.max(0, Math.min(start, width));
183
+ region.start = start;
155
184
  region.element.style.left = `${region.start}px`;
156
- region.startTime = (start / this.container.clientWidth) * this.wavesurfer.getDuration();
185
+ region.startTime = (start / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
157
186
  }
158
187
  if (end != null) {
188
+ end = Math.max(0, Math.min(end, width));
159
189
  region.end = end;
160
190
  region.element.style.width = `${region.end - region.start}px`;
161
- region.endTime = (end / this.container.clientWidth) * this.wavesurfer.getDuration();
191
+ region.endTime = (end / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
162
192
  }
163
193
  this.emit('region-updated', { region });
164
194
  }
@@ -166,18 +196,20 @@ class RegionsPlugin extends BasePlugin {
166
196
  this.updateRegion(region, region.start + delta, region.end + delta);
167
197
  }
168
198
  /** Create a region at a given start and end time, with an optional title */
169
- addRegionAtTime(startTime, endTime, title = '') {
199
+ add(startTime, endTime, title = '', color = '') {
170
200
  const duration = this.wavesurfer.getDuration();
171
- const width = this.container.clientWidth;
201
+ const width = this.wrapper.clientWidth;
172
202
  const start = (startTime / duration) * width;
173
203
  const end = (endTime / duration) * width;
174
204
  const region = this.createRegion(start, end, title);
175
205
  this.addRegion(region);
206
+ if (color)
207
+ this.setRegionColor(region, color);
176
208
  return region;
177
209
  }
178
210
  /** Set the background color of a region */
179
211
  setRegionColor(region, color) {
180
- region.element.style.backgroundColor = color;
212
+ region.element.style[region.startTime === region.endTime ? 'borderColor' : 'backgroundColor'] = color;
181
213
  }
182
214
  }
183
215
  export default RegionsPlugin;
@@ -0,0 +1,29 @@
1
+ import BasePlugin from '../base-plugin.js';
2
+ import { WaveSurferPluginParams } from '../index.js';
3
+ type TimelinePluginOptions = {
4
+ height?: number;
5
+ container?: HTMLElement;
6
+ timeInterval?: number;
7
+ primaryLabelInterval?: number;
8
+ secondaryLabelInterval?: number;
9
+ };
10
+ declare const defaultOptions: {
11
+ height: number;
12
+ };
13
+ type TimelinePluginEvents = {
14
+ ready: void;
15
+ };
16
+ declare class TimelinePlugin extends BasePlugin<TimelinePluginEvents, TimelinePluginOptions> {
17
+ private wrapper;
18
+ protected options: TimelinePluginOptions & typeof defaultOptions;
19
+ constructor(params: WaveSurferPluginParams, options: TimelinePluginOptions);
20
+ /** Unmount */
21
+ destroy(): void;
22
+ private initWrapper;
23
+ private formatTime;
24
+ private defaultTimeInterval;
25
+ defaultPrimaryLabelInterval(pxPerSec: number): number;
26
+ defaultSecondaryLabelInterval(pxPerSec: number): number;
27
+ private initTimeline;
28
+ }
29
+ export default TimelinePlugin;