wavesurfer.js 7.0.0-alpha.47 → 7.0.0-alpha.49

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 CHANGED
@@ -22,7 +22,7 @@ Or, as a script tag which exposes `WaveSurfer` as a global variable:
22
22
 
23
23
  To import a plugin, e.g. the Timeline plugin:
24
24
  ```
25
- import Timeline from 'https://unpkg.com/wavesurfer.js@alpha/plugins/timeline.js'
25
+ import Timeline from 'https://unpkg.com/wavesurfer.js@alpha/dist/plugins/timeline.js'
26
26
  ```
27
27
 
28
28
  TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
@@ -38,12 +38,33 @@ wavesurfer.js v7 (aka wavesurfer.ts) brings several improvements:
38
38
  ## Plugins
39
39
  The "official" plugins have been completely rewritten and enhanced:
40
40
 
41
- * [Regions](https://wavesurfer-ts.pages.dev/tutorial/#/examples/regions.js) – now also replaces the old Markers plugin
42
- * [Timeline](https://wavesurfer-ts.pages.dev/tutorial/#/examples/timeline.js) – displays notches and time labels below the waveform
43
- * [Minimap](https://wavesurfer-ts.pages.dev/tutorial/#/examples/minimap.js) – a small waveform that serves as a scrollbar for the main waveform
44
- * [Envelope](https://wavesurfer-ts.pages.dev/tutorial/#/examples/envelope.js) – a graphical interface to add fade-in and -out effects and control volume
45
- * [Record](https://wavesurfer-ts.pages.dev/tutorial/#/examples/record.js) – records audio from the microphone and renders a waveform
46
- * [Spectrogram](https://wavesurfer-ts.pages.dev/tutorial/#/examples/spectrogram.js) – visualization of an audio frequency spectrum
41
+ * [Regions](https://wavesurfer.pages.dev/examples/#regions.js) – now also replaces the old Markers plugin
42
+ * [Timeline](https://wavesurfer.pages.dev/examples/#timeline.js) – displays notches and time labels below the waveform
43
+ * [Minimap](https://wavesurfer.pages.dev/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
44
+ * [Envelope](https://wavesurfer.pages.dev/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
45
+ * [Record](https://wavesurfer.pages.dev/examples/#record.js) – records audio from the microphone and renders a waveform
46
+ * [Spectrogram](https://wavesurfer.pages.dev/examples/#spectrogram.js) – visualization of an audio frequency spectrum
47
+
48
+ ## CSS styling
49
+
50
+ wavesurfer.js v7 is rendered into a Shadow DOM tree. This isolates its CSS from the rest of the web page.
51
+ However, it's still possible to style various wavesurfer.js elements via CSS using the `::part()` pseudo-selector.
52
+ For example:
53
+
54
+ ```css
55
+ #waveform ::part(cursor):before {
56
+ content: '🏄';
57
+ }
58
+ #waveform ::part(region) {
59
+ font-family: fantasy;
60
+ }
61
+ ```
62
+
63
+ You can see which elements you can style in the DOM inspector – they will have a `part` attribute.
64
+
65
+ <img width="466" alt="DOM inspector screenshot" src="https://github.com/katspaugh/wavesurfer.ts/assets/381895/fcfb4e4d-9572-4931-811f-9615b7e3aa85">
66
+
67
+ See [this example](https://wavesurfer.pages.dev/examples/#styling.js) for play around with styling.
47
68
 
48
69
  ## Documentation
49
70
  See the documentation on wavesurfer.js [methods](https://wavesurfer-ts.pages.dev/docs/classes/wavesurfer.WaveSurfer), [options](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferOptions) and [events](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferEvents) on our website.
@@ -53,7 +74,7 @@ See the documentation on wavesurfer.js [methods](https://wavesurfer-ts.pages.dev
53
74
  Most options, events, and methods are similar to those in previous versions.
54
75
 
55
76
  ### Notable differences
56
- * The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer-ts.pages.dev/tutorial/#/examples/webaudio.js).
77
+ * The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer.pages.dev/examples/#webaudio.js).
57
78
  * The Markers plugin is removed – use the Regions plugin with `startTime` equal to `endTime`.
58
79
  * No Microphone plugn – superseded by the new Record plugin with more features.
59
80
  * No Cursor and Playhead plugins yet – to be done.
@@ -68,7 +89,7 @@ Most options, events, and methods are similar to those in previous versions.
68
89
  * `toggleMute` is now called `setMuted(true | false)`
69
90
  * `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
70
91
 
71
- See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/classes/wavesurfer.WaveSurfer).
92
+ See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/modules/wavesurfer).
72
93
 
73
94
  ## Development
74
95
 
package/dist/decoder.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Decode an array buffer into an audio buffer */
2
- declare function decode(audioData: ArrayBuffer, sampleRate?: number): Promise<AudioBuffer>;
2
+ declare function decode(audioData: ArrayBuffer, sampleRate: number): Promise<AudioBuffer>;
3
3
  /** Create an audio buffer from pre-decoded audio data */
4
4
  declare function createBuffer(channelData: Float32Array[] | Array<number[]>, duration: number): AudioBuffer;
5
5
  declare const Decoder: {
package/dist/decoder.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Decode an array buffer into an audio buffer */
2
- async function decode(audioData, sampleRate = 8000) {
2
+ async function decode(audioData, sampleRate) {
3
3
  const audioCtx = new AudioContext({ sampleRate });
4
4
  const decode = audioCtx.decodeAudioData(audioData);
5
5
  decode.finally(() => audioCtx.close());
package/dist/fetcher.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- declare class Fetcher {
2
- load(url: string): Promise<ArrayBuffer>;
3
- }
1
+ declare function load(url: string): Promise<ArrayBuffer>;
2
+ declare const Fetcher: {
3
+ load: typeof load;
4
+ };
4
5
  export default Fetcher;
package/dist/fetcher.js CHANGED
@@ -1,6 +1,7 @@
1
- class Fetcher {
2
- async load(url) {
3
- return fetch(url).then((response) => response.arrayBuffer());
4
- }
1
+ async function load(url) {
2
+ return fetch(url).then((response) => response.arrayBuffer());
5
3
  }
4
+ const Fetcher = {
5
+ load,
6
+ };
6
7
  export default Fetcher;
@@ -25,14 +25,15 @@ declare const defaultOptions: {
25
25
  dragPointFill: string;
26
26
  dragPointStroke: string;
27
27
  };
28
+ type Options = EnvelopePluginOptions & typeof defaultOptions;
28
29
  export type EnvelopePluginEvents = {
29
30
  'fade-in-change': [time: number];
30
31
  'fade-out-change': [time: number];
31
32
  'volume-change': [volume: number];
32
33
  };
33
34
  declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePluginOptions> {
34
- protected options: EnvelopePluginOptions & typeof defaultOptions;
35
- private svg;
35
+ protected options: Options;
36
+ private polyline;
36
37
  private audioContext;
37
38
  private gainNode;
38
39
  private volume;
@@ -41,24 +42,29 @@ declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePl
41
42
  private readonly naturalVolumeExponent;
42
43
  constructor(options: EnvelopePluginOptions);
43
44
  static create(options: EnvelopePluginOptions): EnvelopePlugin;
45
+ destroy(): void;
44
46
  /** Called by wavesurfer, don't call manually */
45
47
  onInit(): void;
46
- private makeDraggable;
47
- private renderPolyline;
48
48
  private initSvg;
49
- destroy(): void;
49
+ private renderPolyline;
50
50
  private initWebAudio;
51
51
  private invertNaturalVolume;
52
52
  private naturalVolume;
53
53
  private setGainValue;
54
- private onVolumeDrag;
55
54
  private initFadeEffects;
56
55
  /** Get the current audio volume */
57
56
  getCurrentVolume(): number;
58
- /** Set the fade-in start time */
59
- setStartTime(time: number, moveDragPoint?: boolean): void;
60
- /** Set the fade-out end time */
61
- setEndTime(time: number, moveDragPoint?: boolean): void;
57
+ /**
58
+ * Set the fade-in start time.
59
+ * @param time The time (in seconds) to set the fade-in start time to
60
+ * @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
61
+ */
62
+ setStartTime(time: number, moveFadeInEnd?: boolean): void;
63
+ /** Set the fade-in end time.
64
+ * @param time The time (in seconds) to set the fade-in end time to
65
+ * @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
66
+ */
67
+ setEndTime(time: number, moveFadeOutStart?: boolean): void;
62
68
  /** Set the volume of the audio */
63
69
  setVolume(volume: number): void;
64
70
  }
@@ -2,6 +2,7 @@
2
2
  * Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
3
3
  */
4
4
  import BasePlugin from '../base-plugin.js';
5
+ import EventEmitter from '../event-emitter.js';
5
6
  const defaultOptions = {
6
7
  fadeInStart: 0,
7
8
  fadeOutEnd: 0,
@@ -13,58 +14,85 @@ const defaultOptions = {
13
14
  dragPointFill: 'rgba(255, 255, 255, 0.8)',
14
15
  dragPointStroke: 'rgba(255, 255, 255, 0.8)',
15
16
  };
16
- class EnvelopePlugin extends BasePlugin {
17
- constructor(options) {
18
- super(options);
19
- this.svg = null;
20
- this.audioContext = null;
21
- this.gainNode = null;
22
- this.volume = 1;
23
- this.isFadingIn = false;
24
- this.isFadingOut = false;
25
- // Adjust the exponent to change the curve of the volume control
26
- this.naturalVolumeExponent = 1.5;
27
- this.options = Object.assign({}, defaultOptions, options);
28
- this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
29
- this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
30
- this.options.dragPointStroke = this.options.dragPointStroke || defaultOptions.dragPointStroke;
31
- this.volume = this.options.volume ?? 1;
32
- }
33
- static create(options) {
34
- return new EnvelopePlugin(options);
35
- }
36
- /** Called by wavesurfer, don't call manually */
37
- onInit() {
38
- if (!this.wavesurfer) {
39
- throw Error('WaveSurfer is not initialized');
17
+ class Polyline extends EventEmitter {
18
+ constructor(options, wrapper) {
19
+ super();
20
+ this.top = 0;
21
+ // An padding to make the envelope fit into the SVG
22
+ this.padding = options.dragPointSize / 2 + 1;
23
+ // SVG element
24
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
25
+ svg.setAttribute('width', '100%');
26
+ svg.setAttribute('height', '100%');
27
+ svg.setAttribute('viewBox', '0 0 0 0');
28
+ svg.setAttribute('preserveAspectRatio', 'none');
29
+ svg.setAttribute('style', 'position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;');
30
+ svg.setAttribute('part', 'envelope');
31
+ this.svg = svg;
32
+ // A polyline representing the envelope
33
+ const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
34
+ polyline.setAttribute('points', '0,0 0,0 0,0 0,0');
35
+ polyline.setAttribute('stroke', options.lineColor);
36
+ polyline.setAttribute('stroke-width', options.lineWidth);
37
+ polyline.setAttribute('fill', 'none');
38
+ polyline.setAttribute('style', 'pointer-events: none;');
39
+ polyline.setAttribute('part', 'polyline');
40
+ svg.appendChild(polyline);
41
+ // Draggable top line
42
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
43
+ line.setAttribute('stroke', 'transparent');
44
+ line.setAttribute('stroke-width', (options.lineWidth * 3).toString());
45
+ line.setAttribute('style', 'cursor: ns-resize; pointer-events: all;');
46
+ line.setAttribute('part', 'line');
47
+ svg.appendChild(line);
48
+ [0, 1].forEach(() => {
49
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
50
+ circle.setAttribute('r', (options.dragPointSize / 2).toString());
51
+ circle.setAttribute('fill', options.dragPointFill);
52
+ circle.setAttribute('stroke', options.dragPointStroke || options.dragPointFill);
53
+ circle.setAttribute('stroke-width', '2');
54
+ circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
55
+ circle.setAttribute('part', 'circle');
56
+ svg.appendChild(circle);
57
+ });
58
+ wrapper.appendChild(svg);
59
+ // Init dtagging
60
+ {
61
+ // On top line drag
62
+ const onDragY = (dy) => {
63
+ const newTop = this.top + dy;
64
+ const { height } = svg.viewBox.baseVal;
65
+ if (newTop < -0.5 || newTop > height)
66
+ return;
67
+ const relativeY = Math.min(1, Math.max(0, (height - newTop) / height));
68
+ this.emit('line-move', relativeY);
69
+ };
70
+ // On points drag
71
+ const onDragX = (index, dx) => {
72
+ const point = polyline.points.getItem(index);
73
+ const newX = point.x + dx;
74
+ const { width } = svg.viewBox.baseVal;
75
+ this.emit('point-move', index, newX / width);
76
+ };
77
+ // Draggable top line of the polyline
78
+ this.makeDraggable(line, (_, dy) => onDragY(dy));
79
+ // Make each point draggable
80
+ const draggables = this.svg.querySelectorAll('circle');
81
+ Array.from(draggables).forEach((draggable, index) => {
82
+ this.makeDraggable(draggable, (dx) => onDragX(index + 1, dx));
83
+ });
40
84
  }
41
- this.subscriptions.push(this.wavesurfer.once('decode', (duration) => {
42
- this.options.fadeInStart = this.options.fadeInStart || 0;
43
- this.options.fadeOutEnd = this.options.fadeOutEnd || duration;
44
- this.options.fadeInEnd = this.options.fadeInEnd || this.options.fadeInStart;
45
- this.options.fadeOutStart = this.options.fadeOutStart || this.options.fadeOutEnd;
46
- this.initWebAudio();
47
- this.initSvg();
48
- this.initFadeEffects();
49
- }));
50
- let delay;
51
- this.subscriptions.push(this.wavesurfer.on('zoom', () => {
52
- if (delay)
53
- clearTimeout(delay);
54
- delay = setTimeout(() => {
55
- this.svg?.remove();
56
- this.initSvg();
57
- }, 100);
58
- }));
59
85
  }
60
86
  makeDraggable(draggable, onDrag) {
87
+ draggable.addEventListener('click', (e) => {
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ });
61
91
  draggable.addEventListener('mousedown', (e) => {
92
+ e.preventDefault();
93
+ e.stopPropagation();
62
94
  let x = e.clientX;
63
95
  let y = e.clientY;
64
- const wasInteractive = this.wavesurfer?.options.interact || true;
65
- let delay;
66
- // Make the wavesurfer ignore clicks when we're dragging
67
- this.wavesurfer?.toggleInteraction(false);
68
96
  const move = (e) => {
69
97
  const dx = e.clientX - x;
70
98
  const dy = e.clientY - y;
@@ -75,145 +103,126 @@ class EnvelopePlugin extends BasePlugin {
75
103
  const up = () => {
76
104
  document.removeEventListener('mousemove', move);
77
105
  document.removeEventListener('mouseup', up);
78
- // Restore interactive state
79
- if (delay)
80
- clearTimeout(delay);
81
- delay = setTimeout(() => {
82
- this.wavesurfer?.toggleInteraction(wasInteractive);
83
- }, 100);
84
106
  };
85
107
  document.addEventListener('mousemove', move);
86
108
  document.addEventListener('mouseup', up);
87
- e.preventDefault();
88
- e.stopPropagation();
89
109
  });
90
110
  }
91
- renderPolyline() {
92
- if (!this.svg || !this.wavesurfer)
93
- return;
94
- const polyline = this.svg.querySelector('polyline');
95
- const points = polyline.points;
111
+ update({ x1, x2, x3, x4, y }) {
96
112
  const width = this.svg.clientWidth;
97
113
  const height = this.svg.clientHeight;
98
- const duration = this.wavesurfer.getDuration();
99
- const offset = this.options.dragPointSize / 2;
100
- const top = height - this.invertNaturalVolume(this.volume) * height + offset;
101
- points.getItem(1).y = top;
102
- points.getItem(2).y = top;
103
- points.getItem(0).x = (this.options.fadeInStart / duration) * width;
104
- points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
114
+ this.top = height - y * height;
115
+ const paddedTop = Math.max(this.padding, Math.min(this.top, height - this.padding));
116
+ this.svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
117
+ const polyline = this.svg.querySelector('polyline');
118
+ const { points } = polyline;
119
+ points.getItem(0).x = x1 * width;
120
+ points.getItem(0).y = height;
121
+ points.getItem(1).x = x2 * width;
122
+ points.getItem(1).y = paddedTop;
123
+ points.getItem(2).x = x3 * width;
124
+ points.getItem(2).y = paddedTop;
125
+ points.getItem(3).x = x4 * width;
126
+ points.getItem(3).y = height;
105
127
  const line = this.svg.querySelector('line');
106
128
  line.setAttribute('x1', points.getItem(1).x.toString());
107
129
  line.setAttribute('x2', points.getItem(2).x.toString());
108
- line.setAttribute('y1', top.toString());
109
- line.setAttribute('y2', top.toString());
130
+ line.setAttribute('y1', paddedTop.toString());
131
+ line.setAttribute('y2', paddedTop.toString());
110
132
  const circles = this.svg.querySelectorAll('circle');
111
- for (let i = 0; i < circles.length; i++) {
112
- const circle = circles[i];
113
- const point = polyline.points.getItem(i + 1);
133
+ Array.from(circles).forEach((circle, i) => {
134
+ const point = points.getItem(i + 1);
114
135
  circle.setAttribute('cx', point.x.toString());
115
- circle.setAttribute('cy', top.toString());
136
+ circle.setAttribute('cy', point.y.toString());
137
+ });
138
+ }
139
+ destroy() {
140
+ this.svg.remove();
141
+ }
142
+ }
143
+ class EnvelopePlugin extends BasePlugin {
144
+ constructor(options) {
145
+ super(options);
146
+ this.polyline = null;
147
+ this.audioContext = null;
148
+ this.gainNode = null;
149
+ this.volume = 1;
150
+ this.isFadingIn = false;
151
+ this.isFadingOut = false;
152
+ // Adjust the exponent to change the curve of the volume control
153
+ this.naturalVolumeExponent = 1.5;
154
+ this.options = Object.assign({}, defaultOptions, options);
155
+ this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
156
+ this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
157
+ this.options.dragPointStroke = this.options.dragPointStroke || defaultOptions.dragPointStroke;
158
+ this.volume = this.options.volume ?? 1;
159
+ }
160
+ static create(options) {
161
+ return new EnvelopePlugin(options);
162
+ }
163
+ destroy() {
164
+ this.polyline?.destroy();
165
+ super.destroy();
166
+ }
167
+ /** Called by wavesurfer, don't call manually */
168
+ onInit() {
169
+ if (!this.wavesurfer) {
170
+ throw Error('WaveSurfer is not initialized');
116
171
  }
172
+ this.initWebAudio();
173
+ this.initSvg();
174
+ this.initFadeEffects();
175
+ this.subscriptions.push(this.wavesurfer.on('redraw', () => {
176
+ const duration = this.wavesurfer?.getDuration();
177
+ if (!duration)
178
+ return;
179
+ this.options.fadeInStart = this.options.fadeInStart || 0;
180
+ this.options.fadeOutEnd = this.options.fadeOutEnd || duration;
181
+ this.options.fadeInEnd = this.options.fadeInEnd || this.options.fadeInStart;
182
+ this.options.fadeOutStart = this.options.fadeOutStart || this.options.fadeOutEnd;
183
+ this.renderPolyline();
184
+ }));
117
185
  }
118
186
  initSvg() {
119
187
  if (!this.wavesurfer)
120
188
  return;
121
189
  const wrapper = this.wavesurfer.getWrapper();
122
- const width = wrapper.clientWidth;
123
- const height = wrapper.clientHeight;
124
- const duration = this.wavesurfer.getDuration();
125
- // SVG element
126
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
127
- svg.setAttribute('width', '100%');
128
- svg.setAttribute('height', '100%');
129
- svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
130
- svg.setAttribute('preserveAspectRatio', 'none');
131
- svg.setAttribute('style', 'position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;');
132
- this.svg = svg;
133
- // A polyline representing the envelope
134
- const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
135
- polyline.setAttribute('points', '0,0 0,0 0,0 0,0');
136
- polyline.setAttribute('stroke', this.options.lineColor);
137
- polyline.setAttribute('stroke-width', this.options.lineWidth);
138
- polyline.setAttribute('fill', 'none');
139
- polyline.setAttribute('style', 'pointer-events: none');
140
- svg.appendChild(polyline);
141
- // Draggable top line
142
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
143
- line.setAttribute('stroke', 'none');
144
- line.setAttribute('stroke-width', (this.options.lineWidth * 3).toString());
145
- line.setAttribute('style', 'cursor: ns-resize; pointer-events: all;');
146
- svg.appendChild(line);
147
- const points = polyline.points;
148
- const offset = this.options.dragPointSize / 2;
149
- points.getItem(0).x = (this.options.fadeInStart / duration) * width;
150
- points.getItem(0).y = height;
151
- points.getItem(1).x = (this.options.fadeInEnd / duration) * width;
152
- points.getItem(1).y = 0;
153
- points.getItem(2).x = (this.options.fadeOutStart / duration) * width;
154
- points.getItem(2).y = 0;
155
- points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
156
- points.getItem(3).y = height;
157
- // Drag points
158
- const dragPoints = [1, 2];
159
- dragPoints.forEach(() => {
160
- const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
161
- circle.setAttribute('r', (this.options.dragPointSize / 2).toString());
162
- circle.setAttribute('fill', this.options.dragPointFill);
163
- circle.setAttribute('stroke', this.options.dragPointStroke || this.options.dragPointFill);
164
- circle.setAttribute('stroke-width', '2');
165
- circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
166
- svg.appendChild(circle);
167
- });
168
- this.wavesurfer.getWrapper().appendChild(svg);
169
- // Initial polyline
170
- this.renderPolyline();
171
- // On top line drag
172
- const onDragY = (dy) => {
173
- const newTop = points.getItem(1).y + dy - offset;
174
- if (newTop < -0.5 || newTop > height)
175
- return;
176
- const newVolume = Math.min(1, Math.max(0, (height - newTop) / height));
177
- this.onVolumeDrag(newVolume);
178
- };
179
- // On points drag
180
- const onDragX = (dx, dy, index) => {
181
- const point = polyline.points.getItem(index);
182
- const newX = point.x + dx;
183
- const newTime = (newX / width) * duration;
184
- if ((index === 1 && newTime > this.options.fadeOutStart) || newTime < this.options.fadeInStart)
185
- return;
186
- if ((index === 2 && newTime < this.options.fadeInEnd) || newTime > this.options.fadeOutEnd)
187
- return;
188
- point.x = newX;
190
+ this.polyline = new Polyline(this.options, wrapper);
191
+ this.subscriptions.push(this.polyline.on('line-move', (relativeY) => {
192
+ this.setVolume(this.naturalVolume(relativeY));
193
+ }), this.polyline.on('point-move', (index, relativeX) => {
194
+ const duration = this.wavesurfer?.getDuration() || 0;
195
+ const newTime = relativeX * duration;
196
+ // Fade-in end point
189
197
  if (index === 1) {
198
+ if (newTime < this.options.fadeInStart || newTime > this.options.fadeOutStart)
199
+ return;
190
200
  this.options.fadeInEnd = newTime;
191
201
  this.emit('fade-in-change', newTime);
192
202
  }
193
203
  else if (index === 2) {
204
+ // Fade-out start point
205
+ if (newTime > this.options.fadeOutEnd || newTime < this.options.fadeInEnd)
206
+ return;
194
207
  this.options.fadeOutStart = newTime;
195
208
  this.emit('fade-out-change', newTime);
196
209
  }
197
- // Also allow dragging points vertically
198
- if (dy > 1 || dy < -1) {
199
- onDragY(dy);
200
- }
201
- else {
202
- this.renderPolyline();
203
- }
204
- };
205
- // Draggable top line of the polyline
206
- this.makeDraggable(line, (_, dy) => onDragY(dy));
207
- // Make each point draggable
208
- const draggables = svg.querySelectorAll('circle');
209
- for (let i = 0; i < draggables.length; i++) {
210
- const index = i + 1;
211
- this.makeDraggable(draggables[i], (dx, dy) => onDragX(dx, dy, index));
212
- }
210
+ this.renderPolyline();
211
+ }));
213
212
  }
214
- destroy() {
215
- this.svg?.remove();
216
- super.destroy();
213
+ renderPolyline() {
214
+ if (!this.polyline || !this.wavesurfer)
215
+ return;
216
+ const duration = this.wavesurfer.getDuration();
217
+ if (!duration)
218
+ return;
219
+ this.polyline.update({
220
+ x1: this.options.fadeInStart / duration,
221
+ x2: this.options.fadeInEnd / duration,
222
+ x3: this.options.fadeOutStart / duration,
223
+ x4: this.options.fadeOutEnd / duration,
224
+ y: this.invertNaturalVolume(this.volume),
225
+ });
217
226
  }
218
227
  initWebAudio() {
219
228
  const audio = this.wavesurfer?.getMediaElement();
@@ -249,9 +258,6 @@ class EnvelopePlugin extends BasePlugin {
249
258
  this.gainNode.gain.value = this.volume;
250
259
  }
251
260
  }
252
- onVolumeDrag(volume) {
253
- this.setVolume(this.naturalVolume(volume));
254
- }
255
261
  initFadeEffects() {
256
262
  if (!this.audioContext || !this.wavesurfer)
257
263
  return;
@@ -305,22 +311,29 @@ class EnvelopePlugin extends BasePlugin {
305
311
  getCurrentVolume() {
306
312
  return this.gainNode ? this.gainNode.gain.value : this.volume;
307
313
  }
308
- /** Set the fade-in start time */
309
- setStartTime(time, moveDragPoint) {
310
- const rampLength = this.options.fadeInEnd - this.options.fadeInStart;
311
- this.options.fadeInStart = time;
312
- if (moveDragPoint) {
314
+ /**
315
+ * Set the fade-in start time.
316
+ * @param time The time (in seconds) to set the fade-in start time to
317
+ * @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
318
+ */
319
+ setStartTime(time, moveFadeInEnd = false) {
320
+ if (moveFadeInEnd) {
321
+ const rampLength = this.options.fadeInEnd - this.options.fadeInStart;
313
322
  this.options.fadeInEnd = time + rampLength;
314
323
  }
324
+ this.options.fadeInStart = time;
315
325
  this.renderPolyline();
316
326
  }
317
- /** Set the fade-out end time */
318
- setEndTime(time, moveDragPoint) {
319
- const rampLength = this.options.fadeOutEnd - this.options.fadeOutStart;
320
- this.options.fadeOutEnd = time;
321
- if (moveDragPoint) {
327
+ /** Set the fade-in end time.
328
+ * @param time The time (in seconds) to set the fade-in end time to
329
+ * @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
330
+ */
331
+ setEndTime(time, moveFadeOutStart = false) {
332
+ if (moveFadeOutStart) {
333
+ const rampLength = this.options.fadeOutEnd - this.options.fadeOutStart;
322
334
  this.options.fadeOutStart = time - rampLength;
323
335
  }
336
+ this.options.fadeOutEnd = time;
324
337
  this.renderPolyline();
325
338
  }
326
339
  /** Set the volume of the audio */