wavesurfer.js 7.0.0-beta.9 → 7.0.0

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 +5 -2
  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 +29 -9
  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 +115 -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 +25 -4
  45. package/dist/plugins/regions.esm.js +1 -0
  46. package/dist/plugins/regions.js +60 -60
  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
1
  import EventEmitter, { type GeneralEventTypes } 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
+ } & GeneralEventTypes;
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, { type GeneralEventTypes } from './event-emitter.js';
2
+ import type WaveSurfer from './wavesurfer.js';
3
+ export type BasePluginEvents = {
4
+ destroy: [];
5
+ } & GeneralEventTypes;
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(),this.subscriptions.push(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,d=r.clientY,u=!1;const h=n=>{n.preventDefault(),n.stopPropagation();const o=n.clientX,r=n.clientY;if(u||Math.abs(o-a)>=s||Math.abs(r-d)>=s){const{left:n,top:s}=t.getBoundingClientRect();u||(u=!0,null==i||i(a-n,d-s)),e(o-a,r-d,o-n,r-s),a=o,d=r}},l=t=>{u&&(t.preventDefault(),t.stopPropagation())},p=()=>{u&&(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 d=this.svg.querySelector("polyline"),{points:u}=d;u.getItem(0).x=t*o,u.getItem(0).y=r,u.getItem(1).x=e*o,u.getItem(1).y=a,u.getItem(2).x=i*o,u.getItem(2).y=a,u.getItem(3).x=n*o,u.getItem(3).y=r;const h=this.svg.querySelector("line");h.setAttribute("x1",u.getItem(1).x.toString()),h.setAttribute("x2",u.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=u.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(),this.subscriptions.push(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 */
@@ -152,7 +154,8 @@ class EnvelopePlugin extends BasePlugin {
152
154
  this.initSvg();
153
155
  this.initFadeEffects();
154
156
  this.subscriptions.push(this.wavesurfer.on('redraw', () => {
155
- const duration = this.wavesurfer?.getDuration();
157
+ var _a;
158
+ const duration = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getDuration();
156
159
  if (!duration)
157
160
  return;
158
161
  this.options.fadeInStart = this.options.fadeInStart || 0;
@@ -170,7 +173,8 @@ class EnvelopePlugin extends BasePlugin {
170
173
  this.subscriptions.push(this.polyline.on('line-move', (relativeY) => {
171
174
  this.setVolume(this.naturalVolume(relativeY));
172
175
  }), this.polyline.on('point-move', (index, relativeX) => {
173
- const duration = this.wavesurfer?.getDuration() || 0;
176
+ var _a;
177
+ const duration = ((_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getDuration()) || 0;
174
178
  const newTime = relativeX * duration;
175
179
  // Fade-in end point
176
180
  if (index === 1) {
@@ -204,10 +208,11 @@ class EnvelopePlugin extends BasePlugin {
204
208
  });
205
209
  }
206
210
  initWebAudio() {
207
- const audio = this.wavesurfer?.getMediaElement();
211
+ var _a, _b;
212
+ const audio = (_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.getMediaElement();
208
213
  if (!audio)
209
214
  return null;
210
- this.volume = this.options.volume ?? audio.volume;
215
+ this.volume = (_b = this.options.volume) !== null && _b !== void 0 ? _b : audio.volume;
211
216
  // Create an AudioContext
212
217
  const audioContext = new window.AudioContext();
213
218
  // Create a GainNode for controlling the volume
@@ -241,9 +246,10 @@ class EnvelopePlugin extends BasePlugin {
241
246
  if (!this.audioContext || !this.wavesurfer)
242
247
  return;
243
248
  const unsub = this.wavesurfer.on('timeupdate', (currentTime) => {
249
+ var _a;
244
250
  if (!this.audioContext || !this.gainNode)
245
251
  return;
246
- if (!this.wavesurfer?.isPlaying())
252
+ if (!((_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.isPlaying()))
247
253
  return;
248
254
  if (this.audioContext.state === 'suspended') {
249
255
  this.audioContext.resume();
@@ -303,8 +309,8 @@ class EnvelopePlugin extends BasePlugin {
303
309
  this.options.fadeInStart = time;
304
310
  this.renderPolyline();
305
311
  }
306
- /** Set the fade-in end time.
307
- * @param time The time (in seconds) to set the fade-in end time to
312
+ /** Set the fade-out end time.
313
+ * @param time The time (in seconds) to set the fade-out end time to
308
314
  * @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
309
315
  */
310
316
  setEndTime(time, moveFadeOutStart = false) {
@@ -315,6 +321,20 @@ class EnvelopePlugin extends BasePlugin {
315
321
  this.options.fadeOutEnd = time;
316
322
  this.renderPolyline();
317
323
  }
324
+ /** Set the fade-in end time.
325
+ * @param time The time (in seconds) to set the fade-in end time to
326
+ */
327
+ setFadeInEnd(time) {
328
+ this.options.fadeInEnd = time;
329
+ this.renderPolyline();
330
+ }
331
+ /** Set the fade-out start time.
332
+ * @param time The time (in seconds) to set the fade-out start time to
333
+ */
334
+ setFadeOutStart(time) {
335
+ this.options.fadeOutStart = time;
336
+ this.renderPolyline();
337
+ }
318
338
  /** Set the volume of the audio */
319
339
  setVolume(volume) {
320
340
  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(),this.subscriptions.push(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}));