wavesurfer.js 7.0.0-beta.9 → 7.0.1

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 (76) hide show
  1. package/README.md +56 -24
  2. package/dist/base-plugin.d.ts +6 -3
  3. package/dist/base-plugin.js +1 -0
  4. package/dist/decoder.js +17 -6
  5. package/dist/draggable.js +10 -7
  6. package/dist/fetcher.d.ts +1 -3
  7. package/dist/fetcher.js +13 -6
  8. package/dist/player.d.ts +2 -2
  9. package/dist/player.js +10 -8
  10. package/dist/plugins/base-plugin.d.ts +16 -0
  11. package/dist/plugins/decoder.d.ts +9 -0
  12. package/dist/plugins/draggable.d.ts +1 -0
  13. package/dist/plugins/envelope.cjs +1 -0
  14. package/dist/plugins/envelope.d.ts +12 -4
  15. package/dist/plugins/envelope.esm.js +1 -0
  16. package/dist/plugins/envelope.js +34 -10
  17. package/dist/plugins/envelope.min.js +1 -0
  18. package/dist/plugins/event-emitter.d.ts +19 -0
  19. package/dist/plugins/fetcher.d.ts +5 -0
  20. package/dist/plugins/hover.cjs +1 -0
  21. package/dist/plugins/hover.d.ts +35 -0
  22. package/dist/plugins/hover.esm.js +1 -0
  23. package/dist/plugins/hover.js +101 -0
  24. package/dist/plugins/hover.min.js +1 -0
  25. package/dist/plugins/minimap.cjs +1 -0
  26. package/dist/plugins/minimap.d.ts +2 -2
  27. package/dist/plugins/minimap.esm.js +1 -0
  28. package/dist/plugins/minimap.js +8 -13
  29. package/dist/plugins/minimap.min.js +1 -0
  30. package/dist/plugins/player.d.ts +45 -0
  31. package/dist/plugins/plugins/envelope.d.ts +79 -0
  32. package/dist/plugins/plugins/hover.d.ts +35 -0
  33. package/dist/plugins/plugins/minimap.d.ts +39 -0
  34. package/dist/plugins/plugins/record.d.ts +31 -0
  35. package/dist/plugins/plugins/regions.d.ts +118 -0
  36. package/dist/plugins/plugins/spectrogram.d.ts +76 -0
  37. package/dist/plugins/plugins/timeline.d.ts +47 -0
  38. package/dist/plugins/record.cjs +1 -0
  39. package/dist/plugins/record.d.ts +7 -4
  40. package/dist/plugins/record.esm.js +1 -0
  41. package/dist/plugins/record.js +73 -66
  42. package/dist/plugins/record.min.js +1 -0
  43. package/dist/plugins/regions.cjs +1 -0
  44. package/dist/plugins/regions.d.ts +28 -4
  45. package/dist/plugins/regions.esm.js +1 -0
  46. package/dist/plugins/regions.js +74 -61
  47. package/dist/plugins/regions.min.js +1 -0
  48. package/dist/plugins/renderer.d.ts +44 -0
  49. package/dist/plugins/spectrogram.cjs +1 -0
  50. package/dist/plugins/spectrogram.d.ts +10 -3
  51. package/dist/plugins/spectrogram.esm.js +1 -0
  52. package/dist/plugins/spectrogram.js +166 -20
  53. package/dist/plugins/spectrogram.min.js +1 -0
  54. package/dist/plugins/timeline.cjs +1 -0
  55. package/dist/plugins/timeline.d.ts +5 -3
  56. package/dist/plugins/timeline.esm.js +1 -0
  57. package/dist/plugins/timeline.js +27 -21
  58. package/dist/plugins/timeline.min.js +1 -0
  59. package/dist/plugins/timer.d.ts +11 -0
  60. package/dist/plugins/wavesurfer.d.ts +156 -0
  61. package/dist/renderer.js +11 -7
  62. package/dist/wavesurfer.cjs +1 -0
  63. package/dist/wavesurfer.d.ts +5 -3
  64. package/dist/wavesurfer.esm.js +1 -0
  65. package/dist/wavesurfer.js +62 -41
  66. package/dist/wavesurfer.min.js +1 -0
  67. package/package.json +21 -22
  68. package/dist/plugins/envelope.min.cjs +0 -1
  69. package/dist/plugins/minimap.min.cjs +0 -1
  70. package/dist/plugins/record.min.cjs +0 -1
  71. package/dist/plugins/regions.min.cjs +0 -1
  72. package/dist/plugins/spectrogram-fft.d.ts +0 -9
  73. package/dist/plugins/spectrogram-fft.js +0 -150
  74. package/dist/plugins/spectrogram.min.cjs +0 -1
  75. package/dist/plugins/timeline.min.cjs +0 -1
  76. package/dist/wavesurfer.min.cjs +0 -1
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # <img src="https://user-images.githubusercontent.com/381895/226091100-f5567a28-7736-4d37-8f84-e08f297b7e1a.png" alt="logo" height="60" valign="middle" /> wavesurfer.js
2
2
 
3
- [![npm](https://img.shields.io/npm/v/wavesurfer.js/beta)](https://www.npmjs.com/package/wavesurfer.js)
3
+ [![npm](https://img.shields.io/npm/v/wavesurfer.js)](https://www.npmjs.com/package/wavesurfer.js)
4
4
 
5
5
  ## New TypeScript version
6
6
 
7
- wavesurfer.js v7 beta is a TypeScript rewrite of wavesurfer.js that brings several improvements:
7
+ wavesurfer.js v7 is a TypeScript rewrite of wavesurfer.js that brings several improvements:
8
8
 
9
9
  * Typed API for better development experience
10
10
  * Enhanced decoding and rendering performance
@@ -21,30 +21,43 @@ wavesurfer.js v7 beta is a TypeScript rewrite of wavesurfer.js that brings sever
21
21
  Try it out:
22
22
 
23
23
  ```bash
24
- npm install --save wavesurfer.js@beta
24
+ npm install --save wavesurfer.js
25
25
  ```
26
26
  ```js
27
27
  import WaveSurfer from 'wavesurfer.js'
28
28
  ```
29
29
 
30
- Alternatively, import it from a CDN as a ES6 module directly in the browser:
30
+ Alternatively, import it from a CDN as a ES6 module:
31
31
 
32
32
  ```html
33
33
  <script type="module">
34
- import WaveSurfer from 'https://unpkg.com/wavesurfer.js@beta'
35
-
36
- const wavesurfer = WaveSurfer.create({ ... })
34
+ import WaveSurfer from 'https://unpkg.com/wavesurfer.js'
35
+
36
+ const wavesurfer = WaveSurfer.create({
37
+ container: '#waveform',
38
+ waveColor: '#4F4A85',
39
+ progressColor: '#383351',
40
+ url: '/audio.mp3',
41
+ })
37
42
  </script>
38
43
  ```
39
44
 
40
45
  Or, as a UMD script tag which exports the library as a global `WaveSurfer` variable:
41
46
  ```html
42
- <script src="https://unpkg.com/wavesurfer.js@beta/dist/wavesurfer.min.cjs"></script>
47
+ <script type="text/javascript" src="https://unpkg.com/wavesurfer.js/dist/wavesurfer.min.js"></script>
43
48
  ```
44
49
 
45
- To import a plugin, e.g. the Timeline plugin:
50
+ To import one of the plugins, e.g. the Timeline plugin:
46
51
  ```js
47
- import Timeline from 'https://unpkg.com/wavesurfer.js@beta/plugins/timeline.js'
52
+ import Timeline from 'wavesurfer.js/dist/plugins/timeline.js'
53
+
54
+ // or with a CDN:
55
+
56
+ import Timeline from 'https://unpkg.com/wavesurfer.js/dist/plugins/timeline.js'
57
+
58
+ // or as a script tag
59
+
60
+ <script type="text/javascript" src="https://unpkg.com/wavesurfer.js/dist/plugins/timeline.min.js"></script>
48
61
  ```
49
62
 
50
63
  TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
@@ -64,12 +77,13 @@ The "official" plugins have been completely rewritten and enhanced:
64
77
  * [Minimap](https://wavesurfer-js.org/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
65
78
  * [Envelope](https://wavesurfer-js.org/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
66
79
  * [Record](https://wavesurfer-js.org/examples/#record.js) – records audio from the microphone and renders a waveform
67
- * [Spectrogram](https://wavesurfer-js.org/examples/#spectrogram.js) – visualization of an audio frequency spectrum
80
+ * [Spectrogram](https://wavesurfer-js.org/examples/#spectrogram.js) – visualization of an audio frequency spectrum (written by @akreal)
81
+ * [Hover](https://wavesurfer-js.org/examples/#hover.js) – shows a vertical line and timestmap on waveform hover
68
82
 
69
83
  ## CSS styling
70
84
 
71
85
  wavesurfer.js v7 is rendered into a Shadow DOM tree. This isolates its CSS from the rest of the web page.
72
- However, it's still possible to style various wavesurfer.js elements via CSS using the `::part()` pseudo-selector.
86
+ However, it's still possible to style various wavesurfer.js elements with CSS via the `::part()` pseudo-selector.
73
87
  For example:
74
88
 
75
89
  ```css
@@ -90,12 +104,12 @@ Most options, events, and methods are similar to those in previous versions.
90
104
 
91
105
  ### Notable differences
92
106
  * The `backend` option is removed – [HTML5 audio (or video) is the only playback mechanism](https://github.com/katspaugh/wavesurfer.js/discussions/2762#discussioncomment-5669347). However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer-js.org/examples/#webaudio.js).
93
- * The Markers plugin is removed – use the Regions plugin with just a `startTime`.
94
- * No Microphone plugn – superseded by the new Record plugin with more features.
95
- * No Cursor and Playhead plugins yet to be done.
107
+ * The Markers plugin is removed – you should use the Regions plugin with just a `startTime`.
108
+ * No Microphone plugin – superseded by the new Record plugin with more features.
109
+ * The Cursor plugin is replaced by the Hover plugin.
96
110
 
97
111
  ### Removed options
98
- * `backend`, `audioContext`, `closeAudioContext', 'audioScriptProcessor` – there's no Web Audio backend, so no AudioContext
112
+ * `backend`, `audioContext`, `closeAudioContext`, `audioScriptProcessor` – there's no Web Audio backend, so no AudioContext
99
113
  * `autoCenterImmediately` – `autoCenter` is now always immediate unless the audio is playing
100
114
  * `backgroundColor`, `hideCursor` – this can be easily set via CSS
101
115
  * `mediaType`, `mediaControls` – you should instead pass an entire media element in the `media` option. [Example](https://wavesurfer-js.org/examples/#video.js).
@@ -106,19 +120,21 @@ Most options, events, and methods are similar to those in previous versions.
106
120
  * `scrollParent` – the container will scroll if `minPxPerSec` is set to a higher value
107
121
  * `skipLength` – there's no `skipForward` and `skipBackward` methods anymore
108
122
  * `splitChannelsOptions` – you should now use `splitChannels` to pass the channel options. Pass `height: 0` to hide a channel. See [this example](https://wavesurfer-js.org/examples/#split-channels.js).
109
- * `xhr`, `drawingContextAttributes`, `maxCanvasWidth`, `forceDecode` – removed to reduce code complexity
123
+ * `drawingContextAttributes`, `maxCanvasWidth`, `forceDecode` – removed to reduce code complexity
124
+ * `xhr` - please use `fetchParams` instead
125
+ * `barMinHeight` - the minimum bar height is now 1 pixel by default
110
126
 
111
127
  ### Removed methods
112
128
  * `getFilters`, `setFilter` – as there's no Web Audio "backend"
113
129
  * `drawBuffer` – to redraw the waveform, use `setOptions` instead and pass new rendering options
114
- * `cancelAjax` – ajax is replaced by `fetch`
130
+ * `cancelAjax` – you can pass an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) in `fetchParams`
115
131
  * `loadBlob` – use `URL.createObjectURL()` to convert a blob to a URL and call `load(url)` instead
116
132
  * `skipForward`, `skipBackward`, `setPlayEnd` – can be implemented using `setTime(time)`
117
- * `exportPCM` is renamed to `getDecodedData` and doesn't take any params
133
+ * `exportPCM` is replaced with `getDecodedData` that returns a decoded audio buffer
118
134
  * `toggleMute` is now called `setMuted(true | false)`
119
135
  * `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
120
136
 
121
- See the complete [documentation of the new API](http://wavesurfer-js.org/docs/methods).
137
+ See the complete [documentation of the new API](http://wavesurfer-js.org/docs).
122
138
 
123
139
  ## Questions
124
140
 
@@ -127,7 +143,18 @@ Have a question about integrating wavesurfer.js on your website? Feel free to as
127
143
  ### FAQ
128
144
 
129
145
  * **Q**: Does wavesurfer support large files?
130
- * **A**: Since wavesurfer decodes audio entirely in the browser, large files may fail to decode due to memory constraints. We recommend using pre-decoded peaks for large files (see [this example](https://wavesurfer-js.org/examples/#predecoded.js)). You can use a tool like [bbc/audiowaveform](https://github.com/bbc/audiowaveform) to generate peaks.
146
+ * **A**: Since wavesurfer decodes audio entirely in the browser using Web Audio, large clips may fail to decode due to memory constraints. We recommend using pre-decoded peaks for large files (see [this example](https://wavesurfer-js.org/examples/#predecoded.js)). You can use a tool like [bbc/audiowaveform](https://github.com/bbc/audiowaveform) to generate peaks.
147
+
148
+ ---
149
+
150
+ * **Q**: What about streaming audio?
151
+ * **A**: Streaming isn't supported because wavesurfer needs to download the entire audio file to decode and render it.
152
+
153
+ ---
154
+
155
+ * **Q**: There is a mismatch between my audio and the waveform.
156
+ * **A**: If you're using a VBR (variable bit rate) mp3 file, there might be a mismatch between the audio and the waveform. This can be fixed by converting your file to CBR (constant bit rate). See [this issue](https://github.com/katspaugh/wavesurfer.js/issues/2890#issuecomment-1601067822) for details.
157
+
131
158
 
132
159
  ## Development
133
160
 
@@ -150,16 +177,21 @@ This command will open http://localhost:9090 in your browser with live reload, a
150
177
  ## Tests
151
178
 
152
179
  The tests are written in the Cypress framework. They are a mix of e2e and visual regression tests.
153
- To run the test suite locally:
154
180
 
181
+ To run the test suite locally, first build the project:
182
+ ```
183
+ yarn build
184
+ ```
185
+
186
+ Then launch the tests:
155
187
  ```
156
188
  yarn cypress
157
189
  ```
158
190
 
159
191
  ## Feedback
160
192
 
161
- We appreciate your feedback and contributions! Join the conversation and share your thoughts here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2789
193
+ We appreciate your feedback and contributions!
162
194
 
163
- If you encounter any issues or have suggestions for improvements, please don't hesitate to open an issue or submit a pull request on the GitHub repository.
195
+ If you encounter any issues or have suggestions for improvements, please don't hesitate to post in our [forum](https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/q-a).
164
196
 
165
197
  We hope you enjoy using wavesurfer.ts and look forward to hearing about your experiences with the library!
@@ -1,7 +1,10 @@
1
- import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
1
+ import EventEmitter from './event-emitter.js';
2
2
  import type WaveSurfer from './wavesurfer.js';
3
- export type GenericPlugin = BasePlugin<GeneralEventTypes, unknown>;
4
- export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
3
+ export type BasePluginEvents = {
4
+ destroy: [];
5
+ };
6
+ export type GenericPlugin = BasePlugin<BasePluginEvents, unknown>;
7
+ export declare class BasePlugin<EventTypes extends BasePluginEvents, Options> extends EventEmitter<EventTypes> {
5
8
  protected wavesurfer?: WaveSurfer;
6
9
  protected subscriptions: (() => void)[];
7
10
  protected options: Options;
@@ -14,6 +14,7 @@ export class BasePlugin extends EventEmitter {
14
14
  this.onInit();
15
15
  }
16
16
  destroy() {
17
+ this.emit('destroy');
17
18
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
18
19
  }
19
20
  }
package/dist/decoder.js CHANGED
@@ -1,9 +1,20 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  /** Decode an array buffer into an audio buffer */
2
- async function decode(audioData, sampleRate) {
3
- const audioCtx = new AudioContext({ sampleRate });
4
- const decode = audioCtx.decodeAudioData(audioData);
5
- decode.finally(() => audioCtx.close());
6
- return decode;
11
+ function decode(audioData, sampleRate) {
12
+ return __awaiter(this, void 0, void 0, function* () {
13
+ const audioCtx = new AudioContext({ sampleRate });
14
+ const decode = audioCtx.decodeAudioData(audioData);
15
+ decode.finally(() => audioCtx.close());
16
+ return decode;
17
+ });
7
18
  }
8
19
  /** Normalize peaks to -1..1 */
9
20
  function normalize(channelData) {
@@ -36,7 +47,7 @@ function createBuffer(channelData, duration) {
36
47
  length: channelData[0].length,
37
48
  sampleRate: channelData[0].length / duration,
38
49
  numberOfChannels: channelData.length,
39
- getChannelData: (i) => channelData?.[i],
50
+ getChannelData: (i) => channelData === null || channelData === void 0 ? void 0 : channelData[i],
40
51
  copyFromChannel: AudioBuffer.prototype.copyFromChannel,
41
52
  copyToChannel: AudioBuffer.prototype.copyToChannel,
42
53
  };
package/dist/draggable.js CHANGED
@@ -5,6 +5,9 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 5) {
5
5
  if (!element)
6
6
  return unsub;
7
7
  const down = (e) => {
8
+ // Ignore the right mouse button
9
+ if (e.button === 2)
10
+ return;
8
11
  e.preventDefault();
9
12
  e.stopPropagation();
10
13
  let startX = e.clientX;
@@ -16,11 +19,11 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 5) {
16
19
  const x = e.clientX;
17
20
  const y = e.clientY;
18
21
  if (isDragging || Math.abs(x - startX) >= threshold || Math.abs(y - startY) >= threshold) {
22
+ const { left, top } = element.getBoundingClientRect();
19
23
  if (!isDragging) {
20
24
  isDragging = true;
21
- onStart?.(startX, startY);
25
+ onStart === null || onStart === void 0 ? void 0 : onStart(startX - left, startY - top);
22
26
  }
23
- const { left, top } = element.getBoundingClientRect();
24
27
  onDrag(x - startX, y - startY, x - left, y - top);
25
28
  startX = x;
26
29
  startY = y;
@@ -34,10 +37,14 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 5) {
34
37
  };
35
38
  const up = () => {
36
39
  if (isDragging) {
37
- onEnd?.();
40
+ onEnd === null || onEnd === void 0 ? void 0 : onEnd();
38
41
  }
39
42
  unsub();
40
43
  };
44
+ document.addEventListener('pointermove', move);
45
+ document.addEventListener('pointerup', up);
46
+ document.addEventListener('pointerleave', up);
47
+ document.addEventListener('click', click, true);
41
48
  unsub = () => {
42
49
  document.removeEventListener('pointermove', move);
43
50
  document.removeEventListener('pointerup', up);
@@ -46,10 +53,6 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 5) {
46
53
  document.removeEventListener('click', click, true);
47
54
  }, 10);
48
55
  };
49
- document.addEventListener('pointermove', move);
50
- document.addEventListener('pointerup', up);
51
- document.addEventListener('pointerleave', up);
52
- document.addEventListener('click', click, true);
53
56
  };
54
57
  element.addEventListener('pointerdown', down);
55
58
  return () => {
package/dist/fetcher.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- declare function fetchArrayBuffer(url: string): Promise<ArrayBuffer>;
2
- declare function fetchBlob(url: string): Promise<Blob>;
1
+ declare function fetchBlob(url: string, init?: RequestInit): Promise<Blob>;
3
2
  declare const Fetcher: {
4
- fetchArrayBuffer: typeof fetchArrayBuffer;
5
3
  fetchBlob: typeof fetchBlob;
6
4
  };
7
5
  export default Fetcher;
package/dist/fetcher.js CHANGED
@@ -1,11 +1,18 @@
1
- async function fetchArrayBuffer(url) {
2
- return fetch(url).then((response) => response.arrayBuffer());
3
- }
4
- async function fetchBlob(url) {
5
- return fetch(url).then((response) => response.blob());
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ function fetchBlob(url, init) {
11
+ return __awaiter(this, void 0, void 0, function* () {
12
+ return fetch(url, init).then((response) => response.blob());
13
+ });
6
14
  }
7
15
  const Fetcher = {
8
- fetchArrayBuffer,
9
16
  fetchBlob,
10
17
  };
11
18
  export default Fetcher;
package/dist/player.d.ts CHANGED
@@ -6,13 +6,13 @@ type PlayerOptions = {
6
6
  };
7
7
  declare class Player<T extends GeneralEventTypes> extends EventEmitter<T> {
8
8
  protected media: HTMLMediaElement;
9
- private isExternalMedia;
10
9
  constructor(options: PlayerOptions);
11
10
  protected onMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
12
11
  protected onceMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
12
+ private getSrc;
13
13
  private revokeSrc;
14
14
  protected setSrc(url: string, blob?: Blob): void;
15
- destroy(): void;
15
+ protected destroy(): void;
16
16
  /** Start playing the audio */
17
17
  play(): Promise<void>;
18
18
  /** Pause the audio */
package/dist/player.js CHANGED
@@ -2,10 +2,8 @@ import EventEmitter from './event-emitter.js';
2
2
  class Player extends EventEmitter {
3
3
  constructor(options) {
4
4
  super();
5
- this.isExternalMedia = false;
6
5
  if (options.media) {
7
6
  this.media = options.media;
8
- this.isExternalMedia = true;
9
7
  }
10
8
  else {
11
9
  this.media = document.createElement('audio');
@@ -26,26 +24,30 @@ class Player extends EventEmitter {
26
24
  onceMediaEvent(event, callback) {
27
25
  return this.onMediaEvent(event, callback, { once: true });
28
26
  }
27
+ getSrc() {
28
+ return this.media.currentSrc || this.media.src || '';
29
+ }
29
30
  revokeSrc() {
30
- const src = this.media.currentSrc || this.media.src || '';
31
+ const src = this.getSrc();
31
32
  if (src.startsWith('blob:')) {
32
- URL.revokeObjectURL(this.media.currentSrc);
33
+ URL.revokeObjectURL(src);
33
34
  }
34
35
  }
35
36
  setSrc(url, blob) {
36
- const src = this.media.currentSrc || this.media.src || '';
37
+ const src = this.getSrc();
37
38
  if (src === url)
38
39
  return;
39
40
  this.revokeSrc();
40
41
  const newSrc = blob instanceof Blob ? URL.createObjectURL(blob) : url;
41
42
  this.media.src = newSrc;
43
+ this.media.load();
42
44
  }
43
45
  destroy() {
44
46
  this.media.pause();
45
47
  this.revokeSrc();
46
- if (!this.isExternalMedia) {
47
- this.media.remove();
48
- }
48
+ this.media.src = '';
49
+ // Load resets the media element to its initial state
50
+ this.media.load();
49
51
  }
50
52
  /** Start playing the audio */
51
53
  play() {
@@ -0,0 +1,16 @@
1
+ import EventEmitter from './event-emitter.js';
2
+ import type WaveSurfer from './wavesurfer.js';
3
+ export type BasePluginEvents = {
4
+ destroy: [];
5
+ };
6
+ export type GenericPlugin = BasePlugin<BasePluginEvents, unknown>;
7
+ export declare class BasePlugin<EventTypes extends BasePluginEvents, Options> extends EventEmitter<EventTypes> {
8
+ protected wavesurfer?: WaveSurfer;
9
+ protected subscriptions: (() => void)[];
10
+ protected options: Options;
11
+ constructor(options: Options);
12
+ onInit(): void;
13
+ init(wavesurfer: WaveSurfer): void;
14
+ destroy(): void;
15
+ }
16
+ export default BasePlugin;
@@ -0,0 +1,9 @@
1
+ /** Decode an array buffer into an audio buffer */
2
+ declare function decode(audioData: ArrayBuffer, sampleRate: number): Promise<AudioBuffer>;
3
+ /** Create an audio buffer from pre-decoded audio data */
4
+ declare function createBuffer(channelData: Array<Float32Array | number[]>, duration: number): AudioBuffer;
5
+ declare const Decoder: {
6
+ decode: typeof decode;
7
+ createBuffer: typeof createBuffer;
8
+ };
9
+ export default Decoder;
@@ -0,0 +1 @@
1
+ export declare function makeDraggable(element: HTMLElement | null, onDrag: (dx: number, dy: number, x: number, y: number) => void, onStart?: (x: number, y: number) => void, onEnd?: () => void, threshold?: number): () => void;
@@ -0,0 +1 @@
1
+ "use strict";class t{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}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.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}const i={fadeInStart:0,fadeOutEnd:0,fadeInEnd:0,fadeOutStart:0,lineWidth:4,lineColor:"rgba(0, 0, 255, 0.5)",dragPointSize:10,dragPointFill:"rgba(255, 255, 255, 0.8)",dragPointStroke:"rgba(255, 255, 255, 0.8)"};class n extends t{constructor(t,e){super(),this.top=0,this.padding=t.dragPointSize/2+1;const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("width","100%"),i.setAttribute("height","100%"),i.setAttribute("viewBox","0 0 0 0"),i.setAttribute("preserveAspectRatio","none"),i.setAttribute("style","position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;"),i.setAttribute("part","envelope"),this.svg=i;const n=document.createElementNS("http://www.w3.org/2000/svg","polyline");n.setAttribute("points","0,0 0,0 0,0 0,0"),n.setAttribute("stroke",t.lineColor),n.setAttribute("stroke-width",t.lineWidth),n.setAttribute("fill","none"),n.setAttribute("style","pointer-events: none;"),n.setAttribute("part","polyline"),i.appendChild(n);const s=document.createElementNS("http://www.w3.org/2000/svg","line");s.setAttribute("stroke","transparent"),s.setAttribute("stroke-width",(3*t.lineWidth).toString()),s.setAttribute("style","cursor: ns-resize; pointer-events: all;"),s.setAttribute("part","line"),i.appendChild(s),[0,1].forEach((()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","circle");e.setAttribute("r",(t.dragPointSize/2).toString()),e.setAttribute("fill",t.dragPointFill),e.setAttribute("stroke",t.dragPointStroke||t.dragPointFill),e.setAttribute("stroke-width","2"),e.setAttribute("style","cursor: ew-resize; pointer-events: all;"),e.setAttribute("part","circle"),i.appendChild(e)})),e.appendChild(i);{const t=t=>{const e=this.top+t,{height:n}=i.viewBox.baseVal;if(e<-.5||e>n)return;const s=Math.min(1,Math.max(0,(n-e)/n));this.emit("line-move",s)},e=(t,e)=>{const s=n.points.getItem(t).x+e,{width:o}=i.viewBox.baseVal;this.emit("point-move",t,s/o)};this.makeDraggable(s,((e,i)=>t(i)));const o=this.svg.querySelectorAll("circle");Array.from(o).forEach(((t,i)=>{this.makeDraggable(t,(t=>e(i+1,t)))}))}}makeDraggable(t,e){!function(t,e,i,n,s=5){let o=()=>{};if(!t)return o;const r=r=>{if(2===r.button)return;r.preventDefault(),r.stopPropagation();let a=r.clientX,u=r.clientY,d=!1;const h=n=>{n.preventDefault(),n.stopPropagation();const o=n.clientX,r=n.clientY;if(d||Math.abs(o-a)>=s||Math.abs(r-u)>=s){const{left:n,top:s}=t.getBoundingClientRect();d||(d=!0,null==i||i(a-n,u-s)),e(o-a,r-u,o-n,r-s),a=o,u=r}},l=t=>{d&&(t.preventDefault(),t.stopPropagation())},p=()=>{d&&(null==n||n()),o()};document.addEventListener("pointermove",h),document.addEventListener("pointerup",p),document.addEventListener("pointerleave",p),document.addEventListener("click",l,!0),o=()=>{document.removeEventListener("pointermove",h),document.removeEventListener("pointerup",p),document.removeEventListener("pointerleave",p),setTimeout((()=>{document.removeEventListener("click",l,!0)}),10)}};t.addEventListener("pointerdown",r)}(t,e)}update({x1:t,x2:e,x3:i,x4:n,y:s}){const o=this.svg.clientWidth,r=this.svg.clientHeight;this.top=r-s*r;const a=Math.max(this.padding,Math.min(this.top,r-this.padding));this.svg.setAttribute("viewBox",`0 0 ${o} ${r}`);const u=this.svg.querySelector("polyline"),{points:d}=u;d.getItem(0).x=t*o,d.getItem(0).y=r,d.getItem(1).x=e*o,d.getItem(1).y=a,d.getItem(2).x=i*o,d.getItem(2).y=a,d.getItem(3).x=n*o,d.getItem(3).y=r;const h=this.svg.querySelector("line");h.setAttribute("x1",d.getItem(1).x.toString()),h.setAttribute("x2",d.getItem(2).x.toString()),h.setAttribute("y1",a.toString()),h.setAttribute("y2",a.toString());const l=this.svg.querySelectorAll("circle");Array.from(l).forEach(((t,e)=>{const i=d.getItem(e+1);t.setAttribute("cx",i.x.toString()),t.setAttribute("cy",i.y.toString())}))}destroy(){this.svg.remove()}}class s extends e{constructor(t){var e;super(t),this.polyline=null,this.audioContext=null,this.gainNode=null,this.volume=1,this.isFadingIn=!1,this.isFadingOut=!1,this.naturalVolumeExponent=1.5,this.options=Object.assign({},i,t),this.options.lineColor=this.options.lineColor||i.lineColor,this.options.dragPointFill=this.options.dragPointFill||i.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||i.dragPointStroke,this.volume=null!==(e=this.options.volume)&&void 0!==e?e:1}static create(t){return new s(t)}destroy(){var t;null===(t=this.polyline)||void 0===t||t.destroy(),super.destroy()}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.initWebAudio(),this.initSvg(),this.initFadeEffects();const t=this.wavesurfer.getMediaElement(),e=()=>this.setVolume(t.volume);t.addEventListener("volumechange",e),this.subscriptions.push((()=>t.removeEventListener("volumechange",e)),this.wavesurfer.on("redraw",(()=>{var t;const e=null===(t=this.wavesurfer)||void 0===t?void 0:t.getDuration();e&&(this.options.fadeInStart=this.options.fadeInStart||0,this.options.fadeOutEnd=this.options.fadeOutEnd||e,this.options.fadeInEnd=this.options.fadeInEnd||this.options.fadeInStart,this.options.fadeOutStart=this.options.fadeOutStart||this.options.fadeOutEnd,this.renderPolyline())})))}initSvg(){if(!this.wavesurfer)return;const t=this.wavesurfer.getWrapper();this.polyline=new n(this.options,t),this.subscriptions.push(this.polyline.on("line-move",(t=>{this.setVolume(this.naturalVolume(t))})),this.polyline.on("point-move",((t,e)=>{var i;const n=e*((null===(i=this.wavesurfer)||void 0===i?void 0:i.getDuration())||0);if(1===t){if(n<this.options.fadeInStart||n>this.options.fadeOutStart)return;this.options.fadeInEnd=n,this.emit("fade-in-change",n)}else if(2===t){if(n>this.options.fadeOutEnd||n<this.options.fadeInEnd)return;this.options.fadeOutStart=n,this.emit("fade-out-change",n)}this.renderPolyline()})))}renderPolyline(){if(!this.polyline||!this.wavesurfer)return;const t=this.wavesurfer.getDuration();t&&this.polyline.update({x1:this.options.fadeInStart/t,x2:this.options.fadeInEnd/t,x3:this.options.fadeOutStart/t,x4:this.options.fadeOutEnd/t,y:this.invertNaturalVolume(this.volume)})}initWebAudio(){var t,e;const i=null===(t=this.wavesurfer)||void 0===t?void 0:t.getMediaElement();if(!i)return null;this.volume=null!==(e=this.options.volume)&&void 0!==e?e:i.volume;const n=new window.AudioContext;this.gainNode=n.createGain(),this.setGainValue();n.createMediaElementSource(i).connect(this.gainNode),this.gainNode.connect(n.destination),this.audioContext=n}invertNaturalVolume(t){const e=1e-4;return Math.pow((t-e)/.9999,1/this.naturalVolumeExponent)}naturalVolume(t){const e=1e-4;return e+.9999*Math.pow(t,this.naturalVolumeExponent)}setGainValue(){this.gainNode&&(this.gainNode.gain.value=this.volume)}initFadeEffects(){if(!this.audioContext||!this.wavesurfer)return;const t=this.wavesurfer.on("timeupdate",(t=>{var e;if(!this.audioContext||!this.gainNode)return;if(!(null===(e=this.wavesurfer)||void 0===e?void 0:e.isPlaying()))return;if("suspended"===this.audioContext.state&&this.audioContext.resume(),!this.isFadingIn&&t>=this.options.fadeInStart&&t<=this.options.fadeInEnd)return this.isFadingIn=!0,this.gainNode.gain.setValueAtTime(0,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(this.volume,this.audioContext.currentTime+(this.options.fadeInEnd-t));if(!this.isFadingOut&&t>=this.options.fadeOutStart&&t<=this.options.fadeOutEnd)return this.isFadingOut=!0,this.gainNode.gain.setValueAtTime(this.volume,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+(this.options.fadeOutEnd-t));let i=!1;this.isFadingIn&&(t<this.options.fadeInStart||t>this.options.fadeInEnd)&&(this.isFadingIn=!1,i=!0),this.isFadingOut&&(t<this.options.fadeOutStart||t>=this.options.fadeOutEnd)&&(this.isFadingOut=!1,i=!0),i&&(this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime),this.setGainValue())}));this.subscriptions.push(t)}getCurrentVolume(){return this.gainNode?this.gainNode.gain.value:this.volume}setStartTime(t,e=!1){if(e){const e=this.options.fadeInEnd-this.options.fadeInStart;this.options.fadeInEnd=t+e}this.options.fadeInStart=t,this.renderPolyline()}setEndTime(t,e=!1){if(e){const e=this.options.fadeOutEnd-this.options.fadeOutStart;this.options.fadeOutStart=t-e}this.options.fadeOutEnd=t,this.renderPolyline()}setFadeInEnd(t){this.options.fadeInEnd=t,this.renderPolyline()}setFadeOutStart(t){this.options.fadeOutStart=t,this.renderPolyline()}setVolume(t){this.volume=t,this.setGainValue(),this.renderPolyline(),this.emit("volume-change",t)}}module.exports=s;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
3
3
  */
4
- import BasePlugin from '../base-plugin.js';
4
+ import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
5
5
  export type EnvelopePluginOptions = {
6
6
  fadeInStart?: number;
7
7
  fadeInEnd?: number;
@@ -26,7 +26,7 @@ declare const defaultOptions: {
26
26
  dragPointStroke: string;
27
27
  };
28
28
  type Options = EnvelopePluginOptions & typeof defaultOptions;
29
- export type EnvelopePluginEvents = {
29
+ export type EnvelopePluginEvents = BasePluginEvents & {
30
30
  'fade-in-change': [time: number];
31
31
  'fade-out-change': [time: number];
32
32
  'volume-change': [volume: number];
@@ -60,11 +60,19 @@ declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePl
60
60
  * @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
61
61
  */
62
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
63
+ /** Set the fade-out end time.
64
+ * @param time The time (in seconds) to set the fade-out end time to
65
65
  * @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
66
66
  */
67
67
  setEndTime(time: number, moveFadeOutStart?: boolean): void;
68
+ /** Set the fade-in end time.
69
+ * @param time The time (in seconds) to set the fade-in end time to
70
+ */
71
+ setFadeInEnd(time: number): void;
72
+ /** Set the fade-out start time.
73
+ * @param time The time (in seconds) to set the fade-out start time to
74
+ */
75
+ setFadeOutStart(time: number): void;
68
76
  /** Set the volume of the audio */
69
77
  setVolume(volume: number): void;
70
78
  }
@@ -0,0 +1 @@
1
+ class t{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}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.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}const i={fadeInStart:0,fadeOutEnd:0,fadeInEnd:0,fadeOutStart:0,lineWidth:4,lineColor:"rgba(0, 0, 255, 0.5)",dragPointSize:10,dragPointFill:"rgba(255, 255, 255, 0.8)",dragPointStroke:"rgba(255, 255, 255, 0.8)"};class n extends t{constructor(t,e){super(),this.top=0,this.padding=t.dragPointSize/2+1;const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("width","100%"),i.setAttribute("height","100%"),i.setAttribute("viewBox","0 0 0 0"),i.setAttribute("preserveAspectRatio","none"),i.setAttribute("style","position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;"),i.setAttribute("part","envelope"),this.svg=i;const n=document.createElementNS("http://www.w3.org/2000/svg","polyline");n.setAttribute("points","0,0 0,0 0,0 0,0"),n.setAttribute("stroke",t.lineColor),n.setAttribute("stroke-width",t.lineWidth),n.setAttribute("fill","none"),n.setAttribute("style","pointer-events: none;"),n.setAttribute("part","polyline"),i.appendChild(n);const s=document.createElementNS("http://www.w3.org/2000/svg","line");s.setAttribute("stroke","transparent"),s.setAttribute("stroke-width",(3*t.lineWidth).toString()),s.setAttribute("style","cursor: ns-resize; pointer-events: all;"),s.setAttribute("part","line"),i.appendChild(s),[0,1].forEach((()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","circle");e.setAttribute("r",(t.dragPointSize/2).toString()),e.setAttribute("fill",t.dragPointFill),e.setAttribute("stroke",t.dragPointStroke||t.dragPointFill),e.setAttribute("stroke-width","2"),e.setAttribute("style","cursor: ew-resize; pointer-events: all;"),e.setAttribute("part","circle"),i.appendChild(e)})),e.appendChild(i);{const t=t=>{const e=this.top+t,{height:n}=i.viewBox.baseVal;if(e<-.5||e>n)return;const s=Math.min(1,Math.max(0,(n-e)/n));this.emit("line-move",s)},e=(t,e)=>{const s=n.points.getItem(t).x+e,{width:o}=i.viewBox.baseVal;this.emit("point-move",t,s/o)};this.makeDraggable(s,((e,i)=>t(i)));const o=this.svg.querySelectorAll("circle");Array.from(o).forEach(((t,i)=>{this.makeDraggable(t,(t=>e(i+1,t)))}))}}makeDraggable(t,e){!function(t,e,i,n,s=5){let o=()=>{};if(!t)return o;const r=r=>{if(2===r.button)return;r.preventDefault(),r.stopPropagation();let a=r.clientX,u=r.clientY,d=!1;const h=n=>{n.preventDefault(),n.stopPropagation();const o=n.clientX,r=n.clientY;if(d||Math.abs(o-a)>=s||Math.abs(r-u)>=s){const{left:n,top:s}=t.getBoundingClientRect();d||(d=!0,null==i||i(a-n,u-s)),e(o-a,r-u,o-n,r-s),a=o,u=r}},l=t=>{d&&(t.preventDefault(),t.stopPropagation())},p=()=>{d&&(null==n||n()),o()};document.addEventListener("pointermove",h),document.addEventListener("pointerup",p),document.addEventListener("pointerleave",p),document.addEventListener("click",l,!0),o=()=>{document.removeEventListener("pointermove",h),document.removeEventListener("pointerup",p),document.removeEventListener("pointerleave",p),setTimeout((()=>{document.removeEventListener("click",l,!0)}),10)}};t.addEventListener("pointerdown",r)}(t,e)}update({x1:t,x2:e,x3:i,x4:n,y:s}){const o=this.svg.clientWidth,r=this.svg.clientHeight;this.top=r-s*r;const a=Math.max(this.padding,Math.min(this.top,r-this.padding));this.svg.setAttribute("viewBox",`0 0 ${o} ${r}`);const u=this.svg.querySelector("polyline"),{points:d}=u;d.getItem(0).x=t*o,d.getItem(0).y=r,d.getItem(1).x=e*o,d.getItem(1).y=a,d.getItem(2).x=i*o,d.getItem(2).y=a,d.getItem(3).x=n*o,d.getItem(3).y=r;const h=this.svg.querySelector("line");h.setAttribute("x1",d.getItem(1).x.toString()),h.setAttribute("x2",d.getItem(2).x.toString()),h.setAttribute("y1",a.toString()),h.setAttribute("y2",a.toString());const l=this.svg.querySelectorAll("circle");Array.from(l).forEach(((t,e)=>{const i=d.getItem(e+1);t.setAttribute("cx",i.x.toString()),t.setAttribute("cy",i.y.toString())}))}destroy(){this.svg.remove()}}class s extends e{constructor(t){var e;super(t),this.polyline=null,this.audioContext=null,this.gainNode=null,this.volume=1,this.isFadingIn=!1,this.isFadingOut=!1,this.naturalVolumeExponent=1.5,this.options=Object.assign({},i,t),this.options.lineColor=this.options.lineColor||i.lineColor,this.options.dragPointFill=this.options.dragPointFill||i.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||i.dragPointStroke,this.volume=null!==(e=this.options.volume)&&void 0!==e?e:1}static create(t){return new s(t)}destroy(){var t;null===(t=this.polyline)||void 0===t||t.destroy(),super.destroy()}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.initWebAudio(),this.initSvg(),this.initFadeEffects();const t=this.wavesurfer.getMediaElement(),e=()=>this.setVolume(t.volume);t.addEventListener("volumechange",e),this.subscriptions.push((()=>t.removeEventListener("volumechange",e)),this.wavesurfer.on("redraw",(()=>{var t;const e=null===(t=this.wavesurfer)||void 0===t?void 0:t.getDuration();e&&(this.options.fadeInStart=this.options.fadeInStart||0,this.options.fadeOutEnd=this.options.fadeOutEnd||e,this.options.fadeInEnd=this.options.fadeInEnd||this.options.fadeInStart,this.options.fadeOutStart=this.options.fadeOutStart||this.options.fadeOutEnd,this.renderPolyline())})))}initSvg(){if(!this.wavesurfer)return;const t=this.wavesurfer.getWrapper();this.polyline=new n(this.options,t),this.subscriptions.push(this.polyline.on("line-move",(t=>{this.setVolume(this.naturalVolume(t))})),this.polyline.on("point-move",((t,e)=>{var i;const n=e*((null===(i=this.wavesurfer)||void 0===i?void 0:i.getDuration())||0);if(1===t){if(n<this.options.fadeInStart||n>this.options.fadeOutStart)return;this.options.fadeInEnd=n,this.emit("fade-in-change",n)}else if(2===t){if(n>this.options.fadeOutEnd||n<this.options.fadeInEnd)return;this.options.fadeOutStart=n,this.emit("fade-out-change",n)}this.renderPolyline()})))}renderPolyline(){if(!this.polyline||!this.wavesurfer)return;const t=this.wavesurfer.getDuration();t&&this.polyline.update({x1:this.options.fadeInStart/t,x2:this.options.fadeInEnd/t,x3:this.options.fadeOutStart/t,x4:this.options.fadeOutEnd/t,y:this.invertNaturalVolume(this.volume)})}initWebAudio(){var t,e;const i=null===(t=this.wavesurfer)||void 0===t?void 0:t.getMediaElement();if(!i)return null;this.volume=null!==(e=this.options.volume)&&void 0!==e?e:i.volume;const n=new window.AudioContext;this.gainNode=n.createGain(),this.setGainValue();n.createMediaElementSource(i).connect(this.gainNode),this.gainNode.connect(n.destination),this.audioContext=n}invertNaturalVolume(t){const e=1e-4;return Math.pow((t-e)/.9999,1/this.naturalVolumeExponent)}naturalVolume(t){const e=1e-4;return e+.9999*Math.pow(t,this.naturalVolumeExponent)}setGainValue(){this.gainNode&&(this.gainNode.gain.value=this.volume)}initFadeEffects(){if(!this.audioContext||!this.wavesurfer)return;const t=this.wavesurfer.on("timeupdate",(t=>{var e;if(!this.audioContext||!this.gainNode)return;if(!(null===(e=this.wavesurfer)||void 0===e?void 0:e.isPlaying()))return;if("suspended"===this.audioContext.state&&this.audioContext.resume(),!this.isFadingIn&&t>=this.options.fadeInStart&&t<=this.options.fadeInEnd)return this.isFadingIn=!0,this.gainNode.gain.setValueAtTime(0,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(this.volume,this.audioContext.currentTime+(this.options.fadeInEnd-t));if(!this.isFadingOut&&t>=this.options.fadeOutStart&&t<=this.options.fadeOutEnd)return this.isFadingOut=!0,this.gainNode.gain.setValueAtTime(this.volume,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+(this.options.fadeOutEnd-t));let i=!1;this.isFadingIn&&(t<this.options.fadeInStart||t>this.options.fadeInEnd)&&(this.isFadingIn=!1,i=!0),this.isFadingOut&&(t<this.options.fadeOutStart||t>=this.options.fadeOutEnd)&&(this.isFadingOut=!1,i=!0),i&&(this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime),this.setGainValue())}));this.subscriptions.push(t)}getCurrentVolume(){return this.gainNode?this.gainNode.gain.value:this.volume}setStartTime(t,e=!1){if(e){const e=this.options.fadeInEnd-this.options.fadeInStart;this.options.fadeInEnd=t+e}this.options.fadeInStart=t,this.renderPolyline()}setEndTime(t,e=!1){if(e){const e=this.options.fadeOutEnd-this.options.fadeOutStart;this.options.fadeOutStart=t-e}this.options.fadeOutEnd=t,this.renderPolyline()}setFadeInEnd(t){this.options.fadeInEnd=t,this.renderPolyline()}setFadeOutStart(t){this.options.fadeOutStart=t,this.renderPolyline()}setVolume(t){this.volume=t,this.setGainValue(),this.renderPolyline(),this.emit("volume-change",t)}}export{s as default};
@@ -121,6 +121,7 @@ class Polyline extends EventEmitter {
121
121
  }
122
122
  class EnvelopePlugin extends BasePlugin {
123
123
  constructor(options) {
124
+ var _a;
124
125
  super(options);
125
126
  this.polyline = null;
126
127
  this.audioContext = null;
@@ -134,13 +135,14 @@ class EnvelopePlugin extends BasePlugin {
134
135
  this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
135
136
  this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
136
137
  this.options.dragPointStroke = this.options.dragPointStroke || defaultOptions.dragPointStroke;
137
- this.volume = this.options.volume ?? 1;
138
+ this.volume = (_a = this.options.volume) !== null && _a !== void 0 ? _a : 1;
138
139
  }
139
140
  static create(options) {
140
141
  return new EnvelopePlugin(options);
141
142
  }
142
143
  destroy() {
143
- this.polyline?.destroy();
144
+ var _a;
145
+ (_a = this.polyline) === null || _a === void 0 ? void 0 : _a.destroy();
144
146
  super.destroy();
145
147
  }
146
148
  /** Called by wavesurfer, don't call manually */
@@ -151,8 +153,13 @@ class EnvelopePlugin extends BasePlugin {
151
153
  this.initWebAudio();
152
154
  this.initSvg();
153
155
  this.initFadeEffects();
154
- this.subscriptions.push(this.wavesurfer.on('redraw', () => {
155
- const duration = this.wavesurfer?.getDuration();
156
+ // Sync the plugin's volume with the media element volume
157
+ const media = this.wavesurfer.getMediaElement();
158
+ const onMediaVolumeChange = () => this.setVolume(media.volume);
159
+ media.addEventListener('volumechange', onMediaVolumeChange);
160
+ this.subscriptions.push(() => media.removeEventListener('volumechange', onMediaVolumeChange), this.wavesurfer.on('redraw', () => {
161
+ var _a;
162
+ const duration = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getDuration();
156
163
  if (!duration)
157
164
  return;
158
165
  this.options.fadeInStart = this.options.fadeInStart || 0;
@@ -170,7 +177,8 @@ class EnvelopePlugin extends BasePlugin {
170
177
  this.subscriptions.push(this.polyline.on('line-move', (relativeY) => {
171
178
  this.setVolume(this.naturalVolume(relativeY));
172
179
  }), this.polyline.on('point-move', (index, relativeX) => {
173
- const duration = this.wavesurfer?.getDuration() || 0;
180
+ var _a;
181
+ const duration = ((_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getDuration()) || 0;
174
182
  const newTime = relativeX * duration;
175
183
  // Fade-in end point
176
184
  if (index === 1) {
@@ -204,10 +212,11 @@ class EnvelopePlugin extends BasePlugin {
204
212
  });
205
213
  }
206
214
  initWebAudio() {
207
- const audio = this.wavesurfer?.getMediaElement();
215
+ var _a, _b;
216
+ const audio = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getMediaElement();
208
217
  if (!audio)
209
218
  return null;
210
- this.volume = this.options.volume ?? audio.volume;
219
+ this.volume = (_b = this.options.volume) !== null && _b !== void 0 ? _b : audio.volume;
211
220
  // Create an AudioContext
212
221
  const audioContext = new window.AudioContext();
213
222
  // Create a GainNode for controlling the volume
@@ -241,9 +250,10 @@ class EnvelopePlugin extends BasePlugin {
241
250
  if (!this.audioContext || !this.wavesurfer)
242
251
  return;
243
252
  const unsub = this.wavesurfer.on('timeupdate', (currentTime) => {
253
+ var _a;
244
254
  if (!this.audioContext || !this.gainNode)
245
255
  return;
246
- if (!this.wavesurfer?.isPlaying())
256
+ if (!((_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.isPlaying()))
247
257
  return;
248
258
  if (this.audioContext.state === 'suspended') {
249
259
  this.audioContext.resume();
@@ -303,8 +313,8 @@ class EnvelopePlugin extends BasePlugin {
303
313
  this.options.fadeInStart = time;
304
314
  this.renderPolyline();
305
315
  }
306
- /** Set the fade-in end time.
307
- * @param time The time (in seconds) to set the fade-in end time to
316
+ /** Set the fade-out end time.
317
+ * @param time The time (in seconds) to set the fade-out end time to
308
318
  * @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
309
319
  */
310
320
  setEndTime(time, moveFadeOutStart = false) {
@@ -315,6 +325,20 @@ class EnvelopePlugin extends BasePlugin {
315
325
  this.options.fadeOutEnd = time;
316
326
  this.renderPolyline();
317
327
  }
328
+ /** Set the fade-in end time.
329
+ * @param time The time (in seconds) to set the fade-in end time to
330
+ */
331
+ setFadeInEnd(time) {
332
+ this.options.fadeInEnd = time;
333
+ this.renderPolyline();
334
+ }
335
+ /** Set the fade-out start time.
336
+ * @param time The time (in seconds) to set the fade-out start time to
337
+ */
338
+ setFadeOutStart(time) {
339
+ this.options.fadeOutStart = time;
340
+ this.renderPolyline();
341
+ }
318
342
  /** Set the volume of the audio */
319
343
  setVolume(volume) {
320
344
  this.volume = volume;
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):((t="undefined"!=typeof globalThis?globalThis:t||self).WaveSurfer=t.WaveSurfer||{},t.WaveSurfer.Envelope=e())}(this,(function(){"use strict";class t{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}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.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}const i={fadeInStart:0,fadeOutEnd:0,fadeInEnd:0,fadeOutStart:0,lineWidth:4,lineColor:"rgba(0, 0, 255, 0.5)",dragPointSize:10,dragPointFill:"rgba(255, 255, 255, 0.8)",dragPointStroke:"rgba(255, 255, 255, 0.8)"};class n extends t{constructor(t,e){super(),this.top=0,this.padding=t.dragPointSize/2+1;const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("width","100%"),i.setAttribute("height","100%"),i.setAttribute("viewBox","0 0 0 0"),i.setAttribute("preserveAspectRatio","none"),i.setAttribute("style","position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;"),i.setAttribute("part","envelope"),this.svg=i;const n=document.createElementNS("http://www.w3.org/2000/svg","polyline");n.setAttribute("points","0,0 0,0 0,0 0,0"),n.setAttribute("stroke",t.lineColor),n.setAttribute("stroke-width",t.lineWidth),n.setAttribute("fill","none"),n.setAttribute("style","pointer-events: none;"),n.setAttribute("part","polyline"),i.appendChild(n);const s=document.createElementNS("http://www.w3.org/2000/svg","line");s.setAttribute("stroke","transparent"),s.setAttribute("stroke-width",(3*t.lineWidth).toString()),s.setAttribute("style","cursor: ns-resize; pointer-events: all;"),s.setAttribute("part","line"),i.appendChild(s),[0,1].forEach((()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","circle");e.setAttribute("r",(t.dragPointSize/2).toString()),e.setAttribute("fill",t.dragPointFill),e.setAttribute("stroke",t.dragPointStroke||t.dragPointFill),e.setAttribute("stroke-width","2"),e.setAttribute("style","cursor: ew-resize; pointer-events: all;"),e.setAttribute("part","circle"),i.appendChild(e)})),e.appendChild(i);{const t=t=>{const e=this.top+t,{height:n}=i.viewBox.baseVal;if(e<-.5||e>n)return;const s=Math.min(1,Math.max(0,(n-e)/n));this.emit("line-move",s)},e=(t,e)=>{const s=n.points.getItem(t).x+e,{width:o}=i.viewBox.baseVal;this.emit("point-move",t,s/o)};this.makeDraggable(s,((e,i)=>t(i)));const o=this.svg.querySelectorAll("circle");Array.from(o).forEach(((t,i)=>{this.makeDraggable(t,(t=>e(i+1,t)))}))}}makeDraggable(t,e){!function(t,e,i,n,s=5){let o=()=>{};if(!t)return o;const r=r=>{if(2===r.button)return;r.preventDefault(),r.stopPropagation();let a=r.clientX,u=r.clientY,d=!1;const l=n=>{n.preventDefault(),n.stopPropagation();const o=n.clientX,r=n.clientY;if(d||Math.abs(o-a)>=s||Math.abs(r-u)>=s){const{left:n,top:s}=t.getBoundingClientRect();d||(d=!0,null==i||i(a-n,u-s)),e(o-a,r-u,o-n,r-s),a=o,u=r}},h=t=>{d&&(t.preventDefault(),t.stopPropagation())},p=()=>{d&&(null==n||n()),o()};document.addEventListener("pointermove",l),document.addEventListener("pointerup",p),document.addEventListener("pointerleave",p),document.addEventListener("click",h,!0),o=()=>{document.removeEventListener("pointermove",l),document.removeEventListener("pointerup",p),document.removeEventListener("pointerleave",p),setTimeout((()=>{document.removeEventListener("click",h,!0)}),10)}};t.addEventListener("pointerdown",r)}(t,e)}update({x1:t,x2:e,x3:i,x4:n,y:s}){const o=this.svg.clientWidth,r=this.svg.clientHeight;this.top=r-s*r;const a=Math.max(this.padding,Math.min(this.top,r-this.padding));this.svg.setAttribute("viewBox",`0 0 ${o} ${r}`);const u=this.svg.querySelector("polyline"),{points:d}=u;d.getItem(0).x=t*o,d.getItem(0).y=r,d.getItem(1).x=e*o,d.getItem(1).y=a,d.getItem(2).x=i*o,d.getItem(2).y=a,d.getItem(3).x=n*o,d.getItem(3).y=r;const l=this.svg.querySelector("line");l.setAttribute("x1",d.getItem(1).x.toString()),l.setAttribute("x2",d.getItem(2).x.toString()),l.setAttribute("y1",a.toString()),l.setAttribute("y2",a.toString());const h=this.svg.querySelectorAll("circle");Array.from(h).forEach(((t,e)=>{const i=d.getItem(e+1);t.setAttribute("cx",i.x.toString()),t.setAttribute("cy",i.y.toString())}))}destroy(){this.svg.remove()}}class s extends e{constructor(t){var e;super(t),this.polyline=null,this.audioContext=null,this.gainNode=null,this.volume=1,this.isFadingIn=!1,this.isFadingOut=!1,this.naturalVolumeExponent=1.5,this.options=Object.assign({},i,t),this.options.lineColor=this.options.lineColor||i.lineColor,this.options.dragPointFill=this.options.dragPointFill||i.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||i.dragPointStroke,this.volume=null!==(e=this.options.volume)&&void 0!==e?e:1}static create(t){return new s(t)}destroy(){var t;null===(t=this.polyline)||void 0===t||t.destroy(),super.destroy()}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.initWebAudio(),this.initSvg(),this.initFadeEffects();const t=this.wavesurfer.getMediaElement(),e=()=>this.setVolume(t.volume);t.addEventListener("volumechange",e),this.subscriptions.push((()=>t.removeEventListener("volumechange",e)),this.wavesurfer.on("redraw",(()=>{var t;const e=null===(t=this.wavesurfer)||void 0===t?void 0:t.getDuration();e&&(this.options.fadeInStart=this.options.fadeInStart||0,this.options.fadeOutEnd=this.options.fadeOutEnd||e,this.options.fadeInEnd=this.options.fadeInEnd||this.options.fadeInStart,this.options.fadeOutStart=this.options.fadeOutStart||this.options.fadeOutEnd,this.renderPolyline())})))}initSvg(){if(!this.wavesurfer)return;const t=this.wavesurfer.getWrapper();this.polyline=new n(this.options,t),this.subscriptions.push(this.polyline.on("line-move",(t=>{this.setVolume(this.naturalVolume(t))})),this.polyline.on("point-move",((t,e)=>{var i;const n=e*((null===(i=this.wavesurfer)||void 0===i?void 0:i.getDuration())||0);if(1===t){if(n<this.options.fadeInStart||n>this.options.fadeOutStart)return;this.options.fadeInEnd=n,this.emit("fade-in-change",n)}else if(2===t){if(n>this.options.fadeOutEnd||n<this.options.fadeInEnd)return;this.options.fadeOutStart=n,this.emit("fade-out-change",n)}this.renderPolyline()})))}renderPolyline(){if(!this.polyline||!this.wavesurfer)return;const t=this.wavesurfer.getDuration();t&&this.polyline.update({x1:this.options.fadeInStart/t,x2:this.options.fadeInEnd/t,x3:this.options.fadeOutStart/t,x4:this.options.fadeOutEnd/t,y:this.invertNaturalVolume(this.volume)})}initWebAudio(){var t,e;const i=null===(t=this.wavesurfer)||void 0===t?void 0:t.getMediaElement();if(!i)return null;this.volume=null!==(e=this.options.volume)&&void 0!==e?e:i.volume;const n=new window.AudioContext;this.gainNode=n.createGain(),this.setGainValue();n.createMediaElementSource(i).connect(this.gainNode),this.gainNode.connect(n.destination),this.audioContext=n}invertNaturalVolume(t){const e=1e-4;return Math.pow((t-e)/.9999,1/this.naturalVolumeExponent)}naturalVolume(t){const e=1e-4;return e+.9999*Math.pow(t,this.naturalVolumeExponent)}setGainValue(){this.gainNode&&(this.gainNode.gain.value=this.volume)}initFadeEffects(){if(!this.audioContext||!this.wavesurfer)return;const t=this.wavesurfer.on("timeupdate",(t=>{var e;if(!this.audioContext||!this.gainNode)return;if(!(null===(e=this.wavesurfer)||void 0===e?void 0:e.isPlaying()))return;if("suspended"===this.audioContext.state&&this.audioContext.resume(),!this.isFadingIn&&t>=this.options.fadeInStart&&t<=this.options.fadeInEnd)return this.isFadingIn=!0,this.gainNode.gain.setValueAtTime(0,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(this.volume,this.audioContext.currentTime+(this.options.fadeInEnd-t));if(!this.isFadingOut&&t>=this.options.fadeOutStart&&t<=this.options.fadeOutEnd)return this.isFadingOut=!0,this.gainNode.gain.setValueAtTime(this.volume,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+(this.options.fadeOutEnd-t));let i=!1;this.isFadingIn&&(t<this.options.fadeInStart||t>this.options.fadeInEnd)&&(this.isFadingIn=!1,i=!0),this.isFadingOut&&(t<this.options.fadeOutStart||t>=this.options.fadeOutEnd)&&(this.isFadingOut=!1,i=!0),i&&(this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime),this.setGainValue())}));this.subscriptions.push(t)}getCurrentVolume(){return this.gainNode?this.gainNode.gain.value:this.volume}setStartTime(t,e=!1){if(e){const e=this.options.fadeInEnd-this.options.fadeInStart;this.options.fadeInEnd=t+e}this.options.fadeInStart=t,this.renderPolyline()}setEndTime(t,e=!1){if(e){const e=this.options.fadeOutEnd-this.options.fadeOutStart;this.options.fadeOutStart=t-e}this.options.fadeOutEnd=t,this.renderPolyline()}setFadeInEnd(t){this.options.fadeInEnd=t,this.renderPolyline()}setFadeOutStart(t){this.options.fadeOutStart=t,this.renderPolyline()}setVolume(t){this.volume=t,this.setGainValue(),this.renderPolyline(),this.emit("volume-change",t)}}return s}));