wavesurfer.js 7.0.0-beta.1 → 7.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +81 -36
  3. package/dist/decoder.d.ts +1 -1
  4. package/dist/draggable.d.ts +1 -0
  5. package/dist/draggable.js +59 -0
  6. package/dist/fetcher.d.ts +2 -0
  7. package/dist/fetcher.js +4 -0
  8. package/dist/player.d.ts +1 -1
  9. package/dist/player.js +2 -2
  10. package/dist/plugins/envelope.js +4 -25
  11. package/dist/plugins/envelope.min.cjs +1 -0
  12. package/dist/plugins/minimap.js +0 -3
  13. package/dist/plugins/minimap.min.cjs +1 -0
  14. package/dist/plugins/record.d.ts +3 -1
  15. package/dist/plugins/record.js +11 -5
  16. package/dist/plugins/record.min.cjs +1 -0
  17. package/dist/plugins/regions.d.ts +1 -0
  18. package/dist/plugins/regions.js +17 -52
  19. package/dist/plugins/regions.min.cjs +1 -0
  20. package/dist/plugins/spectrogram-fft.d.ts +9 -0
  21. package/dist/plugins/spectrogram-fft.js +150 -0
  22. package/dist/plugins/spectrogram.d.ts +3 -0
  23. package/dist/plugins/spectrogram.js +3 -2
  24. package/dist/plugins/{spectrogram.min.js → spectrogram.min.cjs} +1 -1
  25. package/dist/plugins/timeline.min.cjs +1 -0
  26. package/dist/renderer.d.ts +11 -21
  27. package/dist/renderer.js +213 -136
  28. package/dist/wavesurfer.d.ts +17 -14
  29. package/dist/wavesurfer.js +54 -65
  30. package/dist/wavesurfer.min.cjs +1 -0
  31. package/package.json +17 -11
  32. package/dist/plugins/envelope.min.js +0 -1
  33. package/dist/plugins/minimap.min.js +0 -1
  34. package/dist/plugins/multitrack.d.ts +0 -117
  35. package/dist/plugins/multitrack.js +0 -507
  36. package/dist/plugins/multitrack.min.js +0 -1
  37. package/dist/plugins/record.min.js +0 -1
  38. package/dist/plugins/regions.min.js +0 -1
  39. package/dist/plugins/timeline.min.js +0 -1
  40. package/dist/wavesurfer.min.js +0 -1
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2012-2023, katspaugh and contributors
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,51 +1,70 @@
1
- # <img src="https://user-images.githubusercontent.com/381895/226091100-f5567a28-7736-4d37-8f84-e08f297b7e1a.png" alt="logo" height="60" valign="middle" /> wavesurfer.ts
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)
3
+ [![npm](https://img.shields.io/npm/v/wavesurfer.js/beta)](https://www.npmjs.com/package/wavesurfer.js)
4
4
 
5
- A rewrite of [wavesufer.js](https://github.com/wavesurfer-js/wavesurfer.js) in TypeScript with better performance.
5
+ ## New TypeScript version
6
6
 
7
- <img alt="screenshot" src="https://user-images.githubusercontent.com/381895/225539680-fc724acd-8657-458e-a558-ff1c6758ba30.png" width="800" />
7
+ wavesurfer.js v7 beta is a TypeScript rewrite of wavesurfer.js that brings several improvements:
8
+
9
+ * Typed API for better development experience
10
+ * Enhanced decoding and rendering performance
11
+ * New and improved plugins
12
+
13
+ <img width="626" alt="waveform screenshot" src="https://github.com/katspaugh/wavesurfer.js/assets/381895/05f03bed-800e-4fa1-b09a-82a39a1c62ce">
14
+
15
+ ---
16
+
17
+ ℹ️ Looking for the old stable version? V6 is here: https://github.com/katspaugh/wavesurfer.js/tree/v6
18
+
19
+ ---
8
20
 
9
21
  Try it out:
10
- ```
22
+
23
+ ```bash
11
24
  npm install --save wavesurfer.js@beta
12
25
  ```
26
+ ```js
27
+ import WaveSurfer from 'wavesurfer.js'
28
+ ```
13
29
 
14
- Alternatively, import from a CDN or a local file as an ES6 module:
30
+ Alternatively, import it from a CDN as a ES6 module directly in the browser:
15
31
 
16
- ```
17
- import WaveSurfer from 'https://unpkg.com/wavesurfer.js@beta'
18
- ```
32
+ ```html
33
+ <script type="module">
34
+ import WaveSurfer from 'https://unpkg.com/wavesurfer.js@beta'
19
35
 
20
- Or, as a UMD script tag which exposes `WaveSurfer` as a global variable:
36
+ const wavesurfer = WaveSurfer.create({ ... })
37
+ </script>
21
38
  ```
22
- <script src="https://unpkg.com/wavesurfer.js@beta/dist/wavesurfer.min.js"></script>
39
+
40
+ Or, as a UMD script tag which exports the library as a global `WaveSurfer` variable:
41
+ ```html
42
+ <script src="https://unpkg.com/wavesurfer.js@beta/dist/wavesurfer.min.cjs"></script>
23
43
  ```
24
44
 
25
45
  To import a plugin, e.g. the Timeline plugin:
26
- ```
46
+ ```js
27
47
  import Timeline from 'https://unpkg.com/wavesurfer.js@beta/dist/plugins/timeline.js'
28
48
  ```
29
49
 
30
50
  TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
31
51
 
32
- ## Why upgrade to wavesurfer.ts?
52
+ See more [examples](https://wavesurfer-js.org/examples).
33
53
 
34
- wavesurfer.js v7 (aka wavesurfer.ts) brings several improvements:
54
+ ## Documentation
35
55
 
36
- * Typed API for better development experience
37
- * Enhanced decoding and rendering performance
38
- * New and improved plugins
56
+ See the documentation on wavesurfer.js [methods](http://wavesurfer-js.org/docs/methods), [options](http://wavesurfer-js.org/docs/options) and [events](http://wavesurfer-js.org/docs/events) on our website.
39
57
 
40
58
  ## Plugins
59
+
41
60
  The "official" plugins have been completely rewritten and enhanced:
42
61
 
43
- * [Regions](https://wavesurfer.pages.dev/examples/#regions.js) – now also replaces the old Markers plugin
44
- * [Timeline](https://wavesurfer.pages.dev/examples/#timeline.js) – displays notches and time labels below the waveform
45
- * [Minimap](https://wavesurfer.pages.dev/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
46
- * [Envelope](https://wavesurfer.pages.dev/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
47
- * [Record](https://wavesurfer.pages.dev/examples/#record.js) – records audio from the microphone and renders a waveform
48
- * [Spectrogram](https://wavesurfer.pages.dev/examples/#spectrogram.js) – visualization of an audio frequency spectrum
62
+ * [Regions](https://wavesurfer-js.org/examples/#regions.js) – visual overlays and markers for regions of audio
63
+ * [Timeline](https://wavesurfer-js.org/examples/#timeline.js) – displays notches and time labels below the waveform
64
+ * [Minimap](https://wavesurfer-js.org/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
65
+ * [Envelope](https://wavesurfer-js.org/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
66
+ * [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
49
68
 
50
69
  ## CSS styling
51
70
 
@@ -63,24 +82,32 @@ For example:
63
82
  ```
64
83
 
65
84
  You can see which elements you can style in the DOM inspector – they will have a `part` attribute.
85
+ See [this example](https://wavesurfer-js.org/examples/#styling.js) for play around with styling.
66
86
 
67
- <img width="466" alt="DOM inspector screenshot" src="https://github.com/katspaugh/wavesurfer.ts/assets/381895/fcfb4e4d-9572-4931-811f-9615b7e3aa85">
68
-
69
- See [this example](https://wavesurfer.pages.dev/examples/#styling.js) for play around with styling.
70
-
71
- ## Documentation
72
- See the documentation on wavesurfer.js [methods](https://wavesurfer-ts.pages.dev/docs/classes/wavesurfer.WaveSurfer), [options](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferOptions) and [events](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferEvents) on our website.
73
-
74
- ## Migrating from v6 and lower
87
+ ## Upgrading from v6
75
88
 
76
89
  Most options, events, and methods are similar to those in previous versions.
77
90
 
78
91
  ### Notable differences
79
- * The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer.pages.dev/examples/#webaudio.js).
80
- * The Markers plugin is removed – use the Regions plugin with `startTime` equal to `endTime`.
92
+ * 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`.
81
94
  * No Microphone plugn – superseded by the new Record plugin with more features.
82
95
  * No Cursor and Playhead plugins yet – to be done.
83
96
 
97
+ ### Removed options
98
+ * `backend`, `audioContext`, `closeAudioContext', 'audioScriptProcessor` – there's no Web Audio backend, so no AudioContext
99
+ * `autoCenterImmediately` – `autoCenter` is now always immediate unless the audio is playing
100
+ * `backgroundColor`, `hideCursor` – this can be easily set via CSS
101
+ * `mediaType`, `mediaControls` – you should instead pass an entire media element in the `media` option. [Example](https://wavesurfer-js.org/examples/#video.js).
102
+ * `partialRender` – done by default
103
+ * `pixelRatio` – `window.devicePixelRatio` is used by default
104
+ * `renderer` – there's just one renderer for now, so no need for this option
105
+ * `responsive` – responsiveness is enabled by default
106
+ * `scrollParent` – the container will scroll if `minPxPerSec` is set to a higher value
107
+ * `skipLength` – there's no `skipForward` and `skipBackward` methods anymore
108
+ * `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
110
+
84
111
  ### Removed methods
85
112
  * `getFilters`, `setFilter` – as there's no Web Audio "backend"
86
113
  * `drawBuffer` – to redraw the waveform, use `setOptions` instead and pass new rendering options
@@ -91,7 +118,16 @@ Most options, events, and methods are similar to those in previous versions.
91
118
  * `toggleMute` is now called `setMuted(true | false)`
92
119
  * `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
93
120
 
94
- See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/modules/wavesurfer).
121
+ See the complete [documentation of the new API](http://wavesurfer-js.org/docs/methods).
122
+
123
+ ## Questions
124
+
125
+ Have a question about integrating wavesurfer.js on your website? Feel free to ask in our [Discussions forum](https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/q-a).
126
+
127
+ ### FAQ
128
+
129
+ * **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.
95
131
 
96
132
  ## Development
97
133
 
@@ -109,11 +145,20 @@ yarn
109
145
  yarn start
110
146
  ```
111
147
 
112
- This command will open http://localhost:9090/tutorial in your browser with live reload, allowing you to see the changes as you develop.
148
+ This command will open http://localhost:9090 in your browser with live reload, allowing you to see the changes as you develop.
149
+
150
+ ## Tests
151
+
152
+ 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
+
155
+ ```
156
+ yarn cypress
157
+ ```
113
158
 
114
159
  ## Feedback
115
160
 
116
- We appreciate your feedback and contributions! Join the conversation and share your thoughts here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
161
+ We appreciate your feedback and contributions! Join the conversation and share your thoughts here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2789
117
162
 
118
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.
119
164
 
package/dist/decoder.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** Decode an array buffer into an audio buffer */
2
2
  declare function decode(audioData: ArrayBuffer, sampleRate: number): Promise<AudioBuffer>;
3
3
  /** Create an audio buffer from pre-decoded audio data */
4
- declare function createBuffer(channelData: Float32Array[] | Array<number[]>, duration: number): AudioBuffer;
4
+ declare function createBuffer(channelData: Array<Float32Array | number[]>, duration: number): AudioBuffer;
5
5
  declare const Decoder: {
6
6
  decode: typeof decode;
7
7
  createBuffer: typeof createBuffer;
@@ -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,59 @@
1
+ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 5) {
2
+ let unsub = () => {
3
+ return;
4
+ };
5
+ if (!element)
6
+ return unsub;
7
+ const down = (e) => {
8
+ e.preventDefault();
9
+ e.stopPropagation();
10
+ let startX = e.clientX;
11
+ let startY = e.clientY;
12
+ let isDragging = false;
13
+ const move = (e) => {
14
+ e.preventDefault();
15
+ e.stopPropagation();
16
+ const x = e.clientX;
17
+ const y = e.clientY;
18
+ if (isDragging || Math.abs(x - startX) >= threshold || Math.abs(y - startY) >= threshold) {
19
+ if (!isDragging) {
20
+ isDragging = true;
21
+ onStart?.(startX, startY);
22
+ }
23
+ const { left, top } = element.getBoundingClientRect();
24
+ onDrag(x - startX, y - startY, x - left, y - top);
25
+ startX = x;
26
+ startY = y;
27
+ }
28
+ };
29
+ const click = (e) => {
30
+ if (isDragging) {
31
+ e.preventDefault();
32
+ e.stopPropagation();
33
+ }
34
+ };
35
+ const up = () => {
36
+ if (isDragging) {
37
+ onEnd?.();
38
+ }
39
+ unsub();
40
+ };
41
+ unsub = () => {
42
+ document.removeEventListener('pointermove', move);
43
+ document.removeEventListener('pointerup', up);
44
+ document.removeEventListener('pointerleave', up);
45
+ setTimeout(() => {
46
+ document.removeEventListener('click', click, true);
47
+ }, 10);
48
+ };
49
+ document.addEventListener('pointermove', move);
50
+ document.addEventListener('pointerup', up);
51
+ document.addEventListener('pointerleave', up);
52
+ document.addEventListener('click', click, true);
53
+ };
54
+ element.addEventListener('pointerdown', down);
55
+ return () => {
56
+ unsub();
57
+ element.removeEventListener('pointerdown', down);
58
+ };
59
+ }
package/dist/fetcher.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  declare function fetchArrayBuffer(url: string): Promise<ArrayBuffer>;
2
+ declare function fetchBlob(url: string): Promise<Blob>;
2
3
  declare const Fetcher: {
3
4
  fetchArrayBuffer: typeof fetchArrayBuffer;
5
+ fetchBlob: typeof fetchBlob;
4
6
  };
5
7
  export default Fetcher;
package/dist/fetcher.js CHANGED
@@ -1,7 +1,11 @@
1
1
  async function fetchArrayBuffer(url) {
2
2
  return fetch(url).then((response) => response.arrayBuffer());
3
3
  }
4
+ async function fetchBlob(url) {
5
+ return fetch(url).then((response) => response.blob());
6
+ }
4
7
  const Fetcher = {
5
8
  fetchArrayBuffer,
9
+ fetchBlob,
6
10
  };
7
11
  export default Fetcher;
package/dist/player.d.ts CHANGED
@@ -11,7 +11,7 @@ declare class Player<T extends GeneralEventTypes> extends EventEmitter<T> {
11
11
  protected onMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
12
12
  protected onceMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
13
13
  private revokeSrc;
14
- protected setSrc(url: string, arrayBuffer?: ArrayBuffer): void;
14
+ protected setSrc(url: string, blob?: Blob): void;
15
15
  destroy(): void;
16
16
  /** Start playing the audio */
17
17
  play(): Promise<void>;
package/dist/player.js CHANGED
@@ -32,12 +32,12 @@ class Player extends EventEmitter {
32
32
  URL.revokeObjectURL(this.media.currentSrc);
33
33
  }
34
34
  }
35
- setSrc(url, arrayBuffer) {
35
+ setSrc(url, blob) {
36
36
  const src = this.media.currentSrc || this.media.src || '';
37
37
  if (src === url)
38
38
  return;
39
39
  this.revokeSrc();
40
- const newSrc = arrayBuffer ? URL.createObjectURL(new Blob([arrayBuffer], { type: 'audio/wav' })) : url;
40
+ const newSrc = blob instanceof Blob ? URL.createObjectURL(blob) : url;
41
41
  this.media.src = newSrc;
42
42
  }
43
43
  destroy() {
@@ -2,6 +2,7 @@
2
2
  * Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
3
3
  */
4
4
  import BasePlugin from '../base-plugin.js';
5
+ import { makeDraggable } from '../draggable.js';
5
6
  import EventEmitter from '../event-emitter.js';
6
7
  const defaultOptions = {
7
8
  fadeInStart: 0,
@@ -75,38 +76,16 @@ class Polyline extends EventEmitter {
75
76
  this.emit('point-move', index, newX / width);
76
77
  };
77
78
  // Draggable top line of the polyline
78
- this.makeDraggable(line, (_, dy) => onDragY(dy));
79
+ this.makeDraggable(line, (_, y) => onDragY(y));
79
80
  // Make each point draggable
80
81
  const draggables = this.svg.querySelectorAll('circle');
81
82
  Array.from(draggables).forEach((draggable, index) => {
82
- this.makeDraggable(draggable, (dx) => onDragX(index + 1, dx));
83
+ this.makeDraggable(draggable, (x) => onDragX(index + 1, x));
83
84
  });
84
85
  }
85
86
  }
86
87
  makeDraggable(draggable, onDrag) {
87
- draggable.addEventListener('click', (e) => {
88
- e.preventDefault();
89
- e.stopPropagation();
90
- });
91
- draggable.addEventListener('mousedown', (e) => {
92
- e.preventDefault();
93
- e.stopPropagation();
94
- let x = e.clientX;
95
- let y = e.clientY;
96
- const move = (e) => {
97
- const dx = e.clientX - x;
98
- const dy = e.clientY - y;
99
- x = e.clientX;
100
- y = e.clientY;
101
- onDrag(dx, dy);
102
- };
103
- const up = () => {
104
- document.removeEventListener('mousemove', move);
105
- document.removeEventListener('mouseup', up);
106
- };
107
- document.addEventListener('mousemove', move);
108
- document.addEventListener('mouseup', up);
109
- });
88
+ makeDraggable(draggable, onDrag);
110
89
  }
111
90
  update({ x1, x2, x3, x4, y }) {
112
91
  const width = this.svg.clientWidth;
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Envelope=e():t.Envelope=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>a});const i=class{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)))}},n=class extends i{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}},s={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 o extends i{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;t.addEventListener("pointerdown",(r=>{r.preventDefault(),r.stopPropagation();let a=r.clientX,u=r.clientY,h=!1;const d=n=>{n.preventDefault(),n.stopPropagation();const o=n.clientX,r=n.clientY;if(h||Math.abs(o-a)>=s||Math.abs(r-u)>=s){h||(h=!0,i?.(a,u));const{left:n,top:s}=t.getBoundingClientRect();e(o-a,r-u,o-n,r-s),a=o,u=r}},l=t=>{h&&(t.preventDefault(),t.stopPropagation())},p=()=>{h&&n?.(),o()};o=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",p),document.removeEventListener("pointerleave",p),setTimeout((()=>{document.removeEventListener("click",l,!0)}),10)},document.addEventListener("pointermove",d),document.addEventListener("pointerup",p),document.addEventListener("pointerleave",p),document.addEventListener("click",l,!0)}))}(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:h}=u;h.getItem(0).x=t*o,h.getItem(0).y=r,h.getItem(1).x=e*o,h.getItem(1).y=a,h.getItem(2).x=i*o,h.getItem(2).y=a,h.getItem(3).x=n*o,h.getItem(3).y=r;const d=this.svg.querySelector("line");d.setAttribute("x1",h.getItem(1).x.toString()),d.setAttribute("x2",h.getItem(2).x.toString()),d.setAttribute("y1",a.toString()),d.setAttribute("y2",a.toString());const l=this.svg.querySelectorAll("circle");Array.from(l).forEach(((t,e)=>{const i=h.getItem(e+1);t.setAttribute("cx",i.x.toString()),t.setAttribute("cy",i.y.toString())}))}destroy(){this.svg.remove()}}class r extends n{constructor(t){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({},s,t),this.options.lineColor=this.options.lineColor||s.lineColor,this.options.dragPointFill=this.options.dragPointFill||s.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||s.dragPointStroke,this.volume=this.options.volume??1}static create(t){return new r(t)}destroy(){this.polyline?.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",(()=>{const t=this.wavesurfer?.getDuration();t&&(this.options.fadeInStart=this.options.fadeInStart||0,this.options.fadeOutEnd=this.options.fadeOutEnd||t,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 o(this.options,t),this.subscriptions.push(this.polyline.on("line-move",(t=>{this.setVolume(this.naturalVolume(t))})),this.polyline.on("point-move",((t,e)=>{const i=e*(this.wavesurfer?.getDuration()||0);if(1===t){if(i<this.options.fadeInStart||i>this.options.fadeOutStart)return;this.options.fadeInEnd=i,this.emit("fade-in-change",i)}else if(2===t){if(i>this.options.fadeOutEnd||i<this.options.fadeInEnd)return;this.options.fadeOutStart=i,this.emit("fade-out-change",i)}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(){const t=this.wavesurfer?.getMediaElement();if(!t)return null;this.volume=this.options.volume??t.volume;const e=new window.AudioContext;this.gainNode=e.createGain(),this.setGainValue(),e.createMediaElementSource(t).connect(this.gainNode),this.gainNode.connect(e.destination),this.audioContext=e}invertNaturalVolume(t){return Math.pow((t-1e-4)/.9999,1/this.naturalVolumeExponent)}naturalVolume(t){return 1e-4+.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=>{if(!this.audioContext||!this.gainNode)return;if(!this.wavesurfer?.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 e=!1;this.isFadingIn&&(t<this.options.fadeInStart||t>this.options.fadeInEnd)&&(this.isFadingIn=!1,e=!0),this.isFadingOut&&(t<this.options.fadeOutStart||t>=this.options.fadeOutEnd)&&(this.isFadingOut=!1,e=!0),e&&(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()}setVolume(t){this.volume=t,this.setGainValue(),this.renderPolyline(),this.emit("volume-change",t)}}const a=r;return e.default})()));
@@ -76,9 +76,6 @@ class MinimapPlugin extends BasePlugin {
76
76
  this.subscriptions.push(this.miniWavesurfer.on('ready', () => {
77
77
  this.emit('ready');
78
78
  }), this.miniWavesurfer.on('interaction', () => {
79
- if (this.wavesurfer && this.miniWavesurfer) {
80
- this.wavesurfer.setTime(this.miniWavesurfer.getCurrentTime());
81
- }
82
79
  this.emit('interaction');
83
80
  }));
84
81
  }
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Minimap=e():t.Minimap=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>g});const i=class{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),s=this.on(t,(()=>{i(),s()}));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)))}},s=class extends i{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}},r={decode:async function(t,e){const i=new AudioContext({sampleRate:e}),s=i.decodeAudioData(t);return s.finally((()=>i.close())),s},createBuffer:function(t,e){return"number"==typeof t[0]&&(t=[t]),function(t){const e=t[0];if(e.some((t=>t>1||t<-1))){const i=e.length;let s=0;for(let t=0;t<i;t++){const i=Math.abs(e[t]);i>s&&(s=i)}for(const e of t)for(let t=0;t<i;t++)e[t]/=s}}(t),{duration:e,length:t[0].length,sampleRate:t[0].length/e,numberOfChannels:t.length,getChannelData:e=>t?.[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}},n=async function(t){return fetch(t).then((t=>t.blob()))},o=class extends i{constructor(t){super(),this.isExternalMedia=!1,t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}onceMediaEvent(t,e){return this.onMediaEvent(t,e,{once:!0})}revokeSrc(){(this.media.currentSrc||this.media.src||"").startsWith("blob:")&&URL.revokeObjectURL(this.media.currentSrc)}setSrc(t,e){if((this.media.currentSrc||this.media.src||"")===t)return;this.revokeSrc();const i=e instanceof Blob?URL.createObjectURL(e):t;this.media.src=i}destroy(){this.media.pause(),this.revokeSrc(),this.isExternalMedia||this.media.remove()}play(){return this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}setTime(t){this.media.currentTime=t}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}};class a extends i{constructor(t){let e;if(super(),this.timeouts=[],this.isScrolling=!1,this.audioData=null,this.resizeObserver=null,this.isDragging=!1,this.options=t,"string"==typeof t.container?e=document.querySelector(t.container):t.container instanceof HTMLElement&&(e=t.container),!e)throw new Error("Container not found");this.parent=e;const[i,s]=this.initHtml();e.appendChild(i),this.container=i,this.scrollContainer=s.querySelector(".scroll"),this.wrapper=s.querySelector(".wrapper"),this.canvasWrapper=s.querySelector(".canvases"),this.progressWrapper=s.querySelector(".progress"),this.cursor=s.querySelector(".cursor"),this.initEvents()}initEvents(){this.wrapper.addEventListener("click",(t=>{const e=this.wrapper.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",i)})),this.initDrag(),this.scrollContainer.addEventListener("scroll",(()=>{const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer,s=t/e,r=(t+i)/e;this.emit("scroll",s,r)}));const t=this.createDelay(100);this.resizeObserver=new ResizeObserver((()=>{t((()=>this.reRender()))})),this.resizeObserver.observe(this.scrollContainer)}initDrag(){!function(t,e,i,s,r=5){let n=()=>{};if(!t)return n;t.addEventListener("pointerdown",(o=>{o.preventDefault(),o.stopPropagation();let a=o.clientX,h=o.clientY,l=!1;const c=s=>{s.preventDefault(),s.stopPropagation();const n=s.clientX,o=s.clientY;if(l||Math.abs(n-a)>=r||Math.abs(o-h)>=r){l||(l=!0,i?.(a,h));const{left:s,top:r}=t.getBoundingClientRect();e(n-a,o-h,n-s,o-r),a=n,h=o}},d=t=>{l&&(t.preventDefault(),t.stopPropagation())},u=()=>{l&&s?.(),n()};n=()=>{document.removeEventListener("pointermove",c),document.removeEventListener("pointerup",u),document.removeEventListener("pointerleave",u),setTimeout((()=>{document.removeEventListener("click",d,!0)}),10)},document.addEventListener("pointermove",c),document.addEventListener("pointerup",u),document.addEventListener("pointerleave",u),document.addEventListener("click",d,!0)}))}(this.wrapper,((t,e,i)=>{this.emit("drag",Math.max(0,Math.min(1,i/this.wrapper.clientWidth)))}),(()=>this.isDragging=!0),(()=>this.isDragging=!1))}getHeight(){return null==this.options.height?128:isNaN(Number(this.options.height))?"auto"===this.options.height&&this.parent.clientHeight||128:Number(this.options.height)}initHtml(){const t=document.createElement("div"),e=t.attachShadow({mode:"open"});return e.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: hidden;\n width: 100%;\n position: relative;\n }\n :host .noScrollbar {\n scrollbar-color: transparent;\n scrollbar-width: none;\n }\n :host .noScrollbar::-webkit-scrollbar {\n display: none;\n -webkit-appearance: none;\n }\n :host .wrapper {\n position: relative;\n overflow: visible;\n z-index: 2;\n }\n :host .canvases {\n min-height: ${this.getHeight()}px;\n }\n :host .canvases > div {\n position: relative;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n }\n :host .progress {\n pointer-events: none;\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n width: 0;\n height: 100%;\n overflow: hidden;\n }\n :host .progress > div {\n position: relative;\n }\n :host .cursor {\n pointer-events: none;\n position: absolute;\n z-index: 5;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n }\n </style>\n\n <div class="scroll" part="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress" part="progress"></div>\n <div class="cursor" part="cursor"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getWrapper(){return this.wrapper}getScroll(){return this.scrollContainer.scrollLeft}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}createDelay(t=10){const e={};return this.timeouts.push(e),i=>{e.timeout&&clearTimeout(e.timeout),e.timeout=setTimeout(i,t)}}convertColorValues(t){if(!Array.isArray(t))return t||"";if(t.length<2)return t[0]||"";const e=document.createElement("canvas"),i=e.getContext("2d").createLinearGradient(0,0,0,e.height),s=1/(t.length-1);return t.forEach(((t,e)=>{const r=e*s;i.addColorStop(r,t)})),i}renderBars(t,e,i){if(i.fillStyle=this.convertColorValues(e.waveColor),e.renderFunction)return void e.renderFunction(t,i);const s=t[0],r=t[1]||t[0],n=s.length,o=window.devicePixelRatio||1,{width:a,height:h}=i.canvas,l=h/2,c=e.barHeight||1,d=e.barWidth?e.barWidth*o:1,u=e.barGap?e.barGap*o:e.barWidth?d/2:0,p=e.barRadius||0,m=a/(d+u)/n;let g=1;if(e.normalize){g=0;for(let t=0;t<n;t++){const e=Math.abs(s[t]);e>g&&(g=e)}}const v=l/g*c;i.beginPath();let f=0,y=0,b=0;for(let t=0;t<=n;t++){const n=Math.round(t*m);if(n>f){const t=Math.round(y*v),s=t+Math.round(b*v)||1;let r=l-t;"top"===e.barAlign?r=0:"bottom"===e.barAlign&&(r=h-s),i.roundRect(f*(d+u),r,d,s,p),f=n,y=0,b=0}const o=Math.abs(s[t]||0),a=Math.abs(r[t]||0);o>y&&(y=o),a>b&&(b=a)}i.fill(),i.closePath()}renderSingleCanvas(t,e,i,s,r,n,o,a){const h=window.devicePixelRatio||1,l=document.createElement("canvas"),c=t[0].length;l.width=Math.round(i*(n-r)/c),l.height=s*h,l.style.width=`${Math.floor(l.width/h)}px`,l.style.height=`${s}px`,l.style.left=`${Math.floor(r*i/h/c)}px`,o.appendChild(l);const d=l.getContext("2d");this.renderBars(t.map((t=>t.slice(r,n))),e,d);const u=l.cloneNode();a.appendChild(u);const p=u.getContext("2d");l.width>0&&l.height>0&&p.drawImage(l,0,0),p.globalCompositeOperation="source-in",p.fillStyle=this.convertColorValues(e.progressColor),p.fillRect(0,0,l.width,l.height)}renderWaveform(t,e,i){const s=document.createElement("div"),r=this.getHeight();s.style.height=`${r}px`,this.canvasWrapper.style.minHeight=`${r}px`,this.canvasWrapper.appendChild(s);const n=s.cloneNode();this.progressWrapper.appendChild(n);const{scrollLeft:o,scrollWidth:h,clientWidth:l}=this.scrollContainer,c=t[0].length,d=c/h;let u=Math.min(a.MAX_CANVAS_WIDTH,l);if(e.barWidth||e.barGap){const t=e.barWidth||.5,i=t+(e.barGap||t/2);u%i!=0&&(u=Math.floor(u/i)*i)}const p=Math.floor(Math.abs(o)*d),m=Math.floor(p+u*d),g=m-p,v=(o,a)=>{this.renderSingleCanvas(t,e,i,r,Math.max(0,o),Math.min(a,c),s,n)},f=this.createDelay(),y=this.createDelay(),b=(t,e)=>{v(t,e),t>0&&f((()=>{b(t-g,e-g)}))},w=(t,e)=>{v(t,e),e<c&&y((()=>{w(t+g,e+g)}))};b(p,m),m<c&&w(m,m+g)}render(t){this.timeouts.forEach((t=>t.timeout&&clearTimeout(t.timeout))),this.timeouts=[];const e=window.devicePixelRatio||1,i=this.scrollContainer.clientWidth,s=Math.ceil(t.duration*(this.options.minPxPerSec||0));this.isScrolling=s>i;const r=this.options.fillParent&&!this.isScrolling,n=(r?i:s)*e;if(this.wrapper.style.width=r?"100%":`${s}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.cursor.style.backgroundColor=`${this.options.cursorColor||this.options.progressColor}`,this.cursor.style.width=`${this.options.cursorWidth}px`,this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="",this.options.splitChannels)for(let e=0;e<t.numberOfChannels;e++){const i={...this.options,...this.options.splitChannels[e]};this.renderWaveform([t.getChannelData(e)],i,n)}else{const e=[t.getChannelData(0)];t.numberOfChannels>1&&e.push(t.getChannelData(1)),this.renderWaveform(e,this.options,n)}this.audioData=t,this.emit("render")}reRender(){if(!this.audioData)return;const t=this.progressWrapper.clientWidth;this.render(this.audioData);const e=this.progressWrapper.clientWidth;this.scrollContainer.scrollLeft+=e-t}zoom(t){this.options.minPxPerSec=t,this.reRender()}scrollIntoView(t,e=!1){const{clientWidth:i,scrollLeft:s,scrollWidth:r}=this.scrollContainer,n=r*t,o=i/2;if(n>s+(e&&this.options.autoCenter&&!this.isDragging?o:i)||n<s)if(this.options.autoCenter&&!this.isDragging){const t=o/20;n-(s+o)>=t&&n<s+i?this.scrollContainer.scrollLeft+=t:this.scrollContainer.scrollLeft=n-o}else if(this.isDragging){const t=10;this.scrollContainer.scrollLeft=n<s?n-t:n-i+t}else this.scrollContainer.scrollLeft=n;{const{scrollLeft:t}=this.scrollContainer,e=t/r,s=(t+i)/r;this.emit("scroll",e,s)}}renderProgress(t,e){isNaN(t)||(this.progressWrapper.style.width=100*t+"%",this.cursor.style.left=100*t+"%",this.cursor.style.marginLeft=100===Math.round(100*t)?`-${this.options.cursorWidth}px`:"",this.isScrolling&&this.options.autoScroll&&this.scrollIntoView(t,e))}}a.MAX_CANVAS_WIDTH=4e3;const h=a,l=class extends i{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}},c={waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoScroll:!0,autoCenter:!0,sampleRate:8e3};class d extends o{static create(t){return new d(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.duration=null,this.subscriptions=[],this.options=Object.assign({},c,t),this.timer=new l,this.renderer=new h(this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initPlugins();const e=this.options.url||this.options.media?.currentSrc||this.options.media?.src;e&&this.load(e,this.options.peaks,this.options.duration)}setOptions(t){this.options=Object.assign({},this.options,t),this.renderer.setOptions(this.options),t.audioRate&&this.setPlaybackRate(t.audioRate)}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",t),this.emit("audioprocess",t)})))}initPlayerEvents(){this.subscriptions.push(this.onMediaEvent("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",t)})),this.onMediaEvent("play",(()=>{this.emit("play"),this.timer.start()})),this.onMediaEvent("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.onMediaEvent("ended",(()=>{this.emit("finish")})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(t=>{this.options.interact&&(this.seekTo(t),this.emit("interaction",this.getCurrentTime()),this.emit("click",t))})),this.renderer.on("scroll",((t,e)=>{const i=this.getDuration();this.emit("scroll",t*i,e*i)})),this.renderer.on("render",(()=>{this.emit("redraw")})));{let t;this.subscriptions.push(this.renderer.on("drag",(e=>{this.options.interact&&(this.renderer.renderProgress(e),clearTimeout(t),t=setTimeout((()=>{this.seekTo(e)}),this.isPlaying()?0:200),this.emit("interaction",e*this.getDuration()),this.emit("drag",e))})))}}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init(this),this.plugins.push(t),t}getWrapper(){return this.renderer.getWrapper()}getScroll(){return this.renderer.getScroll()}getActivePlugins(){return this.plugins}async load(t,e,i){this.decodedData=null,this.duration=null,this.emit("load",t);const s=e?void 0:await n(t);if(this.setSrc(t,s),this.duration=i||this.getDuration()||await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getDuration())))}))||0,e)this.decodedData=r.createBuffer(e,this.duration);else if(s){const t=await s.arrayBuffer();this.decodedData=await r.decode(t,this.options.sampleRate),0!==this.duration&&this.duration!==1/0||(this.duration=this.decodedData.duration)}this.emit("decode",this.duration),this.decodedData&&this.renderer.render(this.decodedData),this.emit("ready",this.duration)}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(t),this.emit("zoom",t)}getDecodedData(){return this.decodedData}getDuration(){return null!==this.duration?this.duration:super.getDuration()}toggleInteraction(t){this.options.interact=t}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}async playPause(){return this.isPlaying()?this.pause():this.play()}stop(){this.pause(),this.setTime(0)}skip(t){this.setTime(this.getCurrentTime()+t)}empty(){this.load("",[[0]],.001)}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const u=d,p={height:50,overlayColor:"rgba(100, 100, 100, 0.1)",insertPosition:"afterend"};class m extends s{constructor(t){super(t),this.miniWavesurfer=null,this.container=null,this.options=Object.assign({},p,t),this.minimapWrapper=this.initMinimapWrapper(),this.overlay=this.initOverlay()}static create(t){return new m(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.options.container?("string"==typeof this.options.container?this.container=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(this.container=this.options.container),this.container?.appendChild(this.minimapWrapper)):(this.container=this.wavesurfer.getWrapper().parentElement,this.container?.insertAdjacentElement(this.options.insertPosition,this.minimapWrapper)),this.initWaveSurferEvents()}initMinimapWrapper(){const t=document.createElement("div");return t.style.position="relative",t.setAttribute("part","minimap"),t}initOverlay(){const t=document.createElement("div");return t.setAttribute("style","position: absolute; z-index: 2; left: 0; top: 0; bottom: 0; transition: left 100ms ease-out; pointer-events: none;"),t.style.backgroundColor=this.options.overlayColor,this.minimapWrapper.appendChild(t),t}initMinimap(){if(this.miniWavesurfer&&(this.miniWavesurfer.destroy(),this.miniWavesurfer=null),!this.wavesurfer)return;const t=this.wavesurfer.getDecodedData(),e=this.wavesurfer.getMediaElement();t&&e&&(this.miniWavesurfer=u.create({...this.options,container:this.minimapWrapper,minPxPerSec:0,fillParent:!0,media:e,peaks:[t.getChannelData(0)],duration:t.duration}),this.subscriptions.push(this.miniWavesurfer.on("ready",(()=>{this.emit("ready")})),this.miniWavesurfer.on("interaction",(()=>{this.emit("interaction")}))))}getOverlayWidth(){const t=this.wavesurfer?.getWrapper().clientWidth||1;return Math.round(this.minimapWrapper.clientWidth/t*100)}onRedraw(){const t=this.getOverlayWidth();this.overlay.style.width=`${t}%`}onScroll(t){if(!this.wavesurfer)return;const e=this.wavesurfer.getDuration();this.overlay.style.left=t/e*100+"%"}initWaveSurferEvents(){this.wavesurfer&&this.subscriptions.push(this.wavesurfer.on("decode",(()=>{this.initMinimap()})),this.wavesurfer.on("scroll",(t=>{this.onScroll(t)})),this.wavesurfer.on("redraw",(()=>{this.onRedraw()})))}destroy(){this.miniWavesurfer?.destroy(),this.minimapWrapper.remove(),super.destroy()}}const g=m;return e.default})()));
@@ -3,8 +3,10 @@
3
3
  */
4
4
  import BasePlugin from '../base-plugin.js';
5
5
  export type RecordPluginOptions = {
6
- waveColor?: string;
6
+ realtimeWaveColor?: string;
7
7
  lineWidth?: number;
8
+ mimeType?: MediaRecorderOptions['mimeType'];
9
+ audioBitsPerSecond?: MediaRecorderOptions['audioBitsPerSecond'];
8
10
  };
9
11
  export type RecordPluginEvents = {
10
12
  startRecording: [];
@@ -2,6 +2,8 @@
2
2
  * Record audio from the microphone, render a waveform and download the audio.
3
3
  */
4
4
  import BasePlugin from '../base-plugin.js';
5
+ const MIME_TYPES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4', 'audio/mp3'];
6
+ const findSupportedMimeType = () => MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType));
5
7
  class RecordPlugin extends BasePlugin {
6
8
  constructor() {
7
9
  super(...arguments);
@@ -11,8 +13,8 @@ class RecordPlugin extends BasePlugin {
11
13
  static create(options) {
12
14
  return new RecordPlugin(options || {});
13
15
  }
14
- loadBlob(data) {
15
- const blob = new Blob(data, { type: 'audio/webm' });
16
+ loadBlob(data, type) {
17
+ const blob = new Blob(data, { type });
16
18
  this.recordedUrl = URL.createObjectURL(blob);
17
19
  this.wavesurfer?.load(this.recordedUrl);
18
20
  }
@@ -39,7 +41,8 @@ class RecordPlugin extends BasePlugin {
39
41
  const dataArray = new Uint8Array(bufferLength);
40
42
  analyser.getByteTimeDomainData(dataArray);
41
43
  canvasCtx.lineWidth = this.options.lineWidth || 2;
42
- canvasCtx.strokeStyle = this.options.waveColor || this.wavesurfer?.options.waveColor || '';
44
+ const color = this.options.realtimeWaveColor || this.wavesurfer?.options.waveColor || '';
45
+ canvasCtx.strokeStyle = Array.isArray(color) ? color[0] : color;
43
46
  canvasCtx.beginPath();
44
47
  const sliceWidth = (canvas.width * 1.0) / bufferLength;
45
48
  let x = 0;
@@ -91,7 +94,10 @@ class RecordPlugin extends BasePlugin {
91
94
  throw new Error('Error accessing the microphone: ' + err.message);
92
95
  }
93
96
  const onStop = this.render(stream);
94
- const mediaRecorder = new MediaRecorder(stream);
97
+ const mediaRecorder = new MediaRecorder(stream, {
98
+ mimeType: this.options.mimeType || findSupportedMimeType(),
99
+ audioBitsPerSecond: this.options.audioBitsPerSecond,
100
+ });
95
101
  const recordedChunks = [];
96
102
  mediaRecorder.addEventListener('dataavailable', (event) => {
97
103
  if (event.data.size > 0) {
@@ -100,7 +106,7 @@ class RecordPlugin extends BasePlugin {
100
106
  });
101
107
  mediaRecorder.addEventListener('stop', () => {
102
108
  onStop();
103
- this.loadBlob(recordedChunks);
109
+ this.loadBlob(recordedChunks, mediaRecorder.mimeType);
104
110
  this.emit('stopRecording');
105
111
  });
106
112
  mediaRecorder.start();
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Record=t():e.Record=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>n});const r=class{constructor(){this.listeners={}}on(e,t){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(t),()=>this.un(e,t)}once(e,t){const r=this.on(e,t),i=this.on(e,(()=>{r(),i()}));return r}un(e,t){this.listeners[e]&&(t?this.listeners[e].delete(t):delete this.listeners[e])}unAll(){this.listeners={}}emit(e,...t){this.listeners[e]&&this.listeners[e].forEach((e=>e(...t)))}},i=class extends r{constructor(e){super(),this.subscriptions=[],this.options=e}onInit(){}init(e){this.wavesurfer=e,this.onInit()}destroy(){this.subscriptions.forEach((e=>e()))}},s=["audio/webm","audio/wav","audio/mpeg","audio/mp4","audio/mp3"];class o extends i{constructor(){super(...arguments),this.mediaRecorder=null,this.recordedUrl=""}static create(e){return new o(e||{})}loadBlob(e,t){const r=new Blob(e,{type:t});this.recordedUrl=URL.createObjectURL(r),this.wavesurfer?.load(this.recordedUrl)}render(e){if(!this.wavesurfer)return()=>{};const t=this.wavesurfer.getWrapper(),r=document.createElement("canvas");r.width=t.clientWidth,r.height=t.clientHeight,r.style.zIndex="10",t.appendChild(r);const i=r.getContext("2d"),s=new AudioContext,o=s.createMediaStreamSource(e),n=s.createAnalyser();let a;o.connect(n);const d=()=>{if(!i)return;i.clearRect(0,0,r.width,r.height);const e=n.frequencyBinCount,t=new Uint8Array(e);n.getByteTimeDomainData(t),i.lineWidth=this.options.lineWidth||2;const s=this.options.realtimeWaveColor||this.wavesurfer?.options.waveColor||"";i.strokeStyle=Array.isArray(s)?s[0]:s,i.beginPath();const o=1*r.width/e;let c=0;for(let s=0;s<e;s++){const e=t[s]/128*r.height/2;0===s?i.moveTo(c,e):i.lineTo(c,e),c+=o}i.lineTo(r.width,r.height/2),i.stroke(),a=requestAnimationFrame(d)};return d(),()=>{a&&cancelAnimationFrame(a),o&&(o.disconnect(),o.mediaStream.getTracks().forEach((e=>e.stop()))),s&&s.close(),r?.remove()}}cleanUp(){this.stopRecording(),this.wavesurfer?.empty(),this.recordedUrl&&(URL.revokeObjectURL(this.recordedUrl),this.recordedUrl="")}async startRecording(){let e;this.cleanUp();try{e=await navigator.mediaDevices.getUserMedia({audio:!0})}catch(e){throw new Error("Error accessing the microphone: "+e.message)}const t=this.render(e),r=new MediaRecorder(e,{mimeType:this.options.mimeType||s.find((e=>MediaRecorder.isTypeSupported(e))),audioBitsPerSecond:this.options.audioBitsPerSecond}),i=[];r.addEventListener("dataavailable",(e=>{e.data.size>0&&i.push(e.data)})),r.addEventListener("stop",(()=>{t(),this.loadBlob(i,r.mimeType),this.emit("stopRecording")})),r.start(),this.emit("startRecording"),this.mediaRecorder=r}isRecording(){return"recording"===this.mediaRecorder?.state}stopRecording(){this.isRecording()&&this.mediaRecorder?.stop()}getRecordedUrl(){return this.recordedUrl}destroy(){super.destroy(),this.cleanUp()}}const n=o;return t.default})()));
@@ -10,6 +10,7 @@ export type RegionsPluginEvents = {
10
10
  'region-created': [region: Region];
11
11
  'region-updated': [region: Region];
12
12
  'region-clicked': [region: Region, e: MouseEvent];
13
+ 'region-double-clicked': [region: Region, e: MouseEvent];
13
14
  };
14
15
  export type RegionEvents = {
15
16
  remove: [];