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.
- package/LICENSE +29 -0
- package/README.md +81 -36
- package/dist/decoder.d.ts +1 -1
- package/dist/draggable.d.ts +1 -0
- package/dist/draggable.js +59 -0
- package/dist/fetcher.d.ts +2 -0
- package/dist/fetcher.js +4 -0
- package/dist/player.d.ts +1 -1
- package/dist/player.js +2 -2
- package/dist/plugins/envelope.js +4 -25
- package/dist/plugins/envelope.min.cjs +1 -0
- package/dist/plugins/minimap.js +0 -3
- package/dist/plugins/minimap.min.cjs +1 -0
- package/dist/plugins/record.d.ts +3 -1
- package/dist/plugins/record.js +11 -5
- package/dist/plugins/record.min.cjs +1 -0
- package/dist/plugins/regions.d.ts +1 -0
- package/dist/plugins/regions.js +17 -52
- package/dist/plugins/regions.min.cjs +1 -0
- package/dist/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/plugins/spectrogram-fft.js +150 -0
- package/dist/plugins/spectrogram.d.ts +3 -0
- package/dist/plugins/spectrogram.js +3 -2
- package/dist/plugins/{spectrogram.min.js → spectrogram.min.cjs} +1 -1
- package/dist/plugins/timeline.min.cjs +1 -0
- package/dist/renderer.d.ts +11 -21
- package/dist/renderer.js +213 -136
- package/dist/wavesurfer.d.ts +17 -14
- package/dist/wavesurfer.js +54 -65
- package/dist/wavesurfer.min.cjs +1 -0
- package/package.json +17 -11
- package/dist/plugins/envelope.min.js +0 -1
- package/dist/plugins/minimap.min.js +0 -1
- package/dist/plugins/multitrack.d.ts +0 -117
- package/dist/plugins/multitrack.js +0 -507
- package/dist/plugins/multitrack.min.js +0 -1
- package/dist/plugins/record.min.js +0 -1
- package/dist/plugins/regions.min.js +0 -1
- package/dist/plugins/timeline.min.js +0 -1
- 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.
|
|
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
|
-

|
|
3
|
+
[](https://www.npmjs.com/package/wavesurfer.js)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## New TypeScript version
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
30
|
+
Alternatively, import it from a CDN as a ES6 module directly in the browser:
|
|
15
31
|
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
|
|
32
|
+
```html
|
|
33
|
+
<script type="module">
|
|
34
|
+
import WaveSurfer from 'https://unpkg.com/wavesurfer.js@beta'
|
|
19
35
|
|
|
20
|
-
|
|
36
|
+
const wavesurfer = WaveSurfer.create({ ... })
|
|
37
|
+
</script>
|
|
21
38
|
```
|
|
22
|
-
|
|
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
|
-
|
|
52
|
+
See more [examples](https://wavesurfer-js.org/examples).
|
|
33
53
|
|
|
34
|
-
|
|
54
|
+
## Documentation
|
|
35
55
|
|
|
36
|
-
|
|
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.
|
|
44
|
-
* [Timeline](https://wavesurfer.
|
|
45
|
-
* [Minimap](https://wavesurfer.
|
|
46
|
-
* [Envelope](https://wavesurfer.
|
|
47
|
-
* [Record](https://wavesurfer.
|
|
48
|
-
* [Spectrogram](https://wavesurfer.
|
|
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
|
-
|
|
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.
|
|
80
|
-
* The Markers plugin is removed – use the Regions plugin with
|
|
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](
|
|
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
|
|
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/
|
|
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
|
|
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
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,
|
|
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,
|
|
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 =
|
|
40
|
+
const newSrc = blob instanceof Blob ? URL.createObjectURL(blob) : url;
|
|
41
41
|
this.media.src = newSrc;
|
|
42
42
|
}
|
|
43
43
|
destroy() {
|
package/dist/plugins/envelope.js
CHANGED
|
@@ -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, (_,
|
|
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, (
|
|
83
|
+
this.makeDraggable(draggable, (x) => onDragX(index + 1, x));
|
|
83
84
|
});
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
makeDraggable(draggable, onDrag) {
|
|
87
|
-
draggable
|
|
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})()));
|
package/dist/plugins/minimap.js
CHANGED
|
@@ -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})()));
|
package/dist/plugins/record.d.ts
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import BasePlugin from '../base-plugin.js';
|
|
5
5
|
export type RecordPluginOptions = {
|
|
6
|
-
|
|
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: [];
|
package/dist/plugins/record.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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: [];
|