wavesurfer.js 7.0.0-alpha.43 → 7.0.0-alpha.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/base-plugin.d.ts +2 -8
- package/dist/base-plugin.js +7 -4
- package/dist/decoder.d.ts +1 -1
- package/dist/decoder.js +2 -4
- package/dist/plugins/envelope.d.ts +13 -5
- package/dist/plugins/envelope.js +59 -30
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/minimap.d.ts +3 -2
- package/dist/plugins/minimap.js +8 -8
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/multitrack.min.js +1 -1
- package/dist/plugins/record.js +5 -4
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.d.ts +2 -2
- package/dist/plugins/regions.js +9 -7
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/spectrogram-fft.d.mts +22 -0
- package/dist/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/plugins/spectrogram-fft.js +150 -0
- package/dist/plugins/spectrogram-fft.mjs +175 -0
- package/dist/plugins/spectrogram.d.mts +177 -0
- package/dist/plugins/spectrogram.d.ts +58 -16
- package/dist/plugins/spectrogram.js +312 -140
- package/dist/plugins/spectrogram.min.js +1 -0
- package/dist/plugins/spectrogram.mjs +407 -0
- package/dist/plugins/timeline.d.ts +2 -2
- package/dist/plugins/timeline.js +3 -4
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +3 -3
- package/dist/wavesurfer.d.ts +6 -0
- package/dist/wavesurfer.js +10 -6
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +1 -1
- package/dist/legacy-adapter.d.ts +0 -7
- package/dist/legacy-adapter.js +0 -15
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ The "official" plugins have been completely rewritten and enhanced:
|
|
|
38
38
|
* [Minimap](https://wavesurfer-ts.pages.dev/tutorial/#/examples/minimap.js) – a small waveform that serves as a scrollbar for the main waveform
|
|
39
39
|
* [Envelope](https://wavesurfer-ts.pages.dev/tutorial/#/examples/envelope.js) – a graphical interface to add fade-in and -out effects and control volume
|
|
40
40
|
* [Record](https://wavesurfer-ts.pages.dev/tutorial/#/examples/record.js) – records audio from the microphone and renders a waveform
|
|
41
|
+
* [Spectrogram](https://wavesurfer-ts.pages.dev/tutorial/#/examples/spectrogram.js) – visualization of an audio frequency spectrum
|
|
41
42
|
|
|
42
43
|
## Documentation
|
|
43
44
|
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.
|
|
@@ -50,7 +51,7 @@ Most options, events, and methods are similar to those in previous versions.
|
|
|
50
51
|
* The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer-ts.pages.dev/tutorial/#/examples/webaudio.js).
|
|
51
52
|
* The Markers plugin is removed – use the Regions plugin with `startTime` equal to `endTime`.
|
|
52
53
|
* No Microphone plugn – superseded by the new Record plugin with more features.
|
|
53
|
-
* No
|
|
54
|
+
* No Cursor and Playhead plugins yet – to be done.
|
|
54
55
|
|
|
55
56
|
### Removed methods
|
|
56
57
|
* `getFilters`, `setFilter` – as there's no Web Audio "backend"
|
package/dist/base-plugin.d.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
2
|
import type WaveSurfer from './wavesurfer.js';
|
|
3
3
|
export type GenericPlugin = BasePlugin<GeneralEventTypes, unknown>;
|
|
4
|
-
export type WaveSurferPluginParams = {
|
|
5
|
-
wavesurfer: WaveSurfer;
|
|
6
|
-
container: HTMLElement;
|
|
7
|
-
wrapper: HTMLElement;
|
|
8
|
-
};
|
|
9
4
|
export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
|
|
10
5
|
protected wavesurfer?: WaveSurfer;
|
|
11
|
-
protected container?: HTMLElement;
|
|
12
|
-
protected wrapper?: HTMLElement;
|
|
13
6
|
protected subscriptions: (() => void)[];
|
|
14
7
|
protected options: Options;
|
|
15
8
|
constructor(options: Options);
|
|
16
|
-
|
|
9
|
+
onInit(): void;
|
|
10
|
+
init(wavesurfer: WaveSurfer): void;
|
|
17
11
|
destroy(): void;
|
|
18
12
|
}
|
|
19
13
|
export default BasePlugin;
|
package/dist/base-plugin.js
CHANGED
|
@@ -5,10 +5,13 @@ export class BasePlugin extends EventEmitter {
|
|
|
5
5
|
this.subscriptions = [];
|
|
6
6
|
this.options = options;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
onInit() {
|
|
9
|
+
// Overridden in plugin definition
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
init(wavesurfer) {
|
|
13
|
+
this.wavesurfer = wavesurfer;
|
|
14
|
+
this.onInit();
|
|
12
15
|
}
|
|
13
16
|
destroy() {
|
|
14
17
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
package/dist/decoder.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Decode an array buffer into an audio buffer */
|
|
2
|
-
declare function decode(audioData: ArrayBuffer): Promise<AudioBuffer>;
|
|
2
|
+
declare function decode(audioData: ArrayBuffer, sampleRate?: number): Promise<AudioBuffer>;
|
|
3
3
|
/** Create an audio buffer from pre-decoded audio data */
|
|
4
4
|
declare function createBuffer(channelData: Float32Array[] | Array<number[]>, duration: number): AudioBuffer;
|
|
5
5
|
declare const Decoder: {
|
package/dist/decoder.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/** Decode an array buffer into an audio buffer */
|
|
2
|
-
async function decode(audioData) {
|
|
3
|
-
const audioCtx = new AudioContext({
|
|
4
|
-
sampleRate: 8000, // the lowsest sample rate of all browsers
|
|
5
|
-
});
|
|
2
|
+
async function decode(audioData, sampleRate = 8000) {
|
|
3
|
+
const audioCtx = new AudioContext({ sampleRate });
|
|
6
4
|
const decode = audioCtx.decodeAudioData(audioData);
|
|
7
5
|
decode.finally(() => audioCtx.close());
|
|
8
6
|
return decode;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
|
|
3
3
|
*/
|
|
4
|
-
import BasePlugin
|
|
4
|
+
import BasePlugin from '../base-plugin.js';
|
|
5
5
|
export type EnvelopePluginOptions = {
|
|
6
6
|
fadeInStart?: number;
|
|
7
7
|
fadeInEnd?: number;
|
|
@@ -38,20 +38,28 @@ declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePl
|
|
|
38
38
|
private volume;
|
|
39
39
|
private isFadingIn;
|
|
40
40
|
private isFadingOut;
|
|
41
|
+
private readonly naturalVolumeExponent;
|
|
41
42
|
constructor(options: EnvelopePluginOptions);
|
|
42
43
|
static create(options: EnvelopePluginOptions): EnvelopePlugin;
|
|
43
44
|
/** Called by wavesurfer, don't call manually */
|
|
44
|
-
|
|
45
|
+
onInit(): void;
|
|
45
46
|
private makeDraggable;
|
|
46
47
|
private renderPolyline;
|
|
47
48
|
private initSvg;
|
|
48
49
|
destroy(): void;
|
|
49
50
|
private initWebAudio;
|
|
51
|
+
private invertNaturalVolume;
|
|
50
52
|
private naturalVolume;
|
|
51
|
-
private
|
|
53
|
+
private setGainValue;
|
|
54
|
+
private onVolumeDrag;
|
|
52
55
|
private initFadeEffects;
|
|
56
|
+
/** Get the current audio volume */
|
|
53
57
|
getCurrentVolume(): number;
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
/** Set the fade-in start time */
|
|
59
|
+
setStartTime(time: number, moveDragPoint?: boolean): void;
|
|
60
|
+
/** Set the fade-out end time */
|
|
61
|
+
setEndTime(time: number, moveDragPoint?: boolean): void;
|
|
62
|
+
/** Set the volume of the audio */
|
|
63
|
+
setVolume(volume: number): void;
|
|
56
64
|
}
|
|
57
65
|
export default EnvelopePlugin;
|
package/dist/plugins/envelope.js
CHANGED
|
@@ -22,6 +22,8 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
22
22
|
this.volume = 1;
|
|
23
23
|
this.isFadingIn = false;
|
|
24
24
|
this.isFadingOut = false;
|
|
25
|
+
// Adjust the exponent to change the curve of the volume control
|
|
26
|
+
this.naturalVolumeExponent = 1.5;
|
|
25
27
|
this.options = Object.assign({}, defaultOptions, options);
|
|
26
28
|
this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
|
|
27
29
|
this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
|
|
@@ -32,8 +34,7 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
32
34
|
return new EnvelopePlugin(options);
|
|
33
35
|
}
|
|
34
36
|
/** Called by wavesurfer, don't call manually */
|
|
35
|
-
|
|
36
|
-
super.init(params);
|
|
37
|
+
onInit() {
|
|
37
38
|
if (!this.wavesurfer) {
|
|
38
39
|
throw Error('WaveSurfer is not initialized');
|
|
39
40
|
}
|
|
@@ -88,13 +89,17 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
renderPolyline() {
|
|
91
|
-
if (!this.svg || !this.
|
|
92
|
+
if (!this.svg || !this.wavesurfer)
|
|
92
93
|
return;
|
|
93
94
|
const polyline = this.svg.querySelector('polyline');
|
|
94
95
|
const points = polyline.points;
|
|
95
|
-
const
|
|
96
|
-
const
|
|
96
|
+
const width = this.svg.clientWidth;
|
|
97
|
+
const height = this.svg.clientHeight;
|
|
97
98
|
const duration = this.wavesurfer.getDuration();
|
|
99
|
+
const offset = this.options.dragPointSize / 2;
|
|
100
|
+
const top = height - this.invertNaturalVolume(this.volume) * height + offset;
|
|
101
|
+
points.getItem(1).y = top;
|
|
102
|
+
points.getItem(2).y = top;
|
|
98
103
|
points.getItem(0).x = (this.options.fadeInStart / duration) * width;
|
|
99
104
|
points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
|
|
100
105
|
const line = this.svg.querySelector('line');
|
|
@@ -111,10 +116,11 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
initSvg() {
|
|
114
|
-
if (!this.
|
|
119
|
+
if (!this.wavesurfer)
|
|
115
120
|
return;
|
|
116
|
-
const
|
|
117
|
-
const
|
|
121
|
+
const wrapper = this.wavesurfer.getWrapper();
|
|
122
|
+
const width = wrapper.clientWidth;
|
|
123
|
+
const height = wrapper.clientHeight;
|
|
118
124
|
const duration = this.wavesurfer.getDuration();
|
|
119
125
|
// SVG element
|
|
120
126
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
@@ -140,13 +146,12 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
140
146
|
svg.appendChild(line);
|
|
141
147
|
const points = polyline.points;
|
|
142
148
|
const offset = this.options.dragPointSize / 2;
|
|
143
|
-
const top = height - this.volume * height + offset;
|
|
144
149
|
points.getItem(0).x = (this.options.fadeInStart / duration) * width;
|
|
145
150
|
points.getItem(0).y = height;
|
|
146
151
|
points.getItem(1).x = (this.options.fadeInEnd / duration) * width;
|
|
147
|
-
points.getItem(1).y =
|
|
152
|
+
points.getItem(1).y = 0;
|
|
148
153
|
points.getItem(2).x = (this.options.fadeOutStart / duration) * width;
|
|
149
|
-
points.getItem(2).y =
|
|
154
|
+
points.getItem(2).y = 0;
|
|
150
155
|
points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
|
|
151
156
|
points.getItem(3).y = height;
|
|
152
157
|
// Drag points
|
|
@@ -160,7 +165,7 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
160
165
|
circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
|
|
161
166
|
svg.appendChild(circle);
|
|
162
167
|
});
|
|
163
|
-
this.
|
|
168
|
+
this.wavesurfer.getWrapper().appendChild(svg);
|
|
164
169
|
// Initial polyline
|
|
165
170
|
this.renderPolyline();
|
|
166
171
|
// On top line drag
|
|
@@ -168,12 +173,8 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
168
173
|
const newTop = points.getItem(1).y + dy - offset;
|
|
169
174
|
if (newTop < -0.5 || newTop > height)
|
|
170
175
|
return;
|
|
171
|
-
points.getItem(1).y = newTop + offset;
|
|
172
|
-
points.getItem(2).y = newTop + offset;
|
|
173
|
-
this.renderPolyline();
|
|
174
176
|
const newVolume = Math.min(1, Math.max(0, (height - newTop) / height));
|
|
175
|
-
this.
|
|
176
|
-
this.renderPolyline();
|
|
177
|
+
this.onVolumeDrag(newVolume);
|
|
177
178
|
};
|
|
178
179
|
// On points drag
|
|
179
180
|
const onDragX = (dx, dy, index) => {
|
|
@@ -223,7 +224,7 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
223
224
|
const audioContext = new window.AudioContext();
|
|
224
225
|
// Create a GainNode for controlling the volume
|
|
225
226
|
this.gainNode = audioContext.createGain();
|
|
226
|
-
this.
|
|
227
|
+
this.setGainValue();
|
|
227
228
|
// Create a MediaElementAudioSourceNode using the audio element
|
|
228
229
|
const source = audioContext.createMediaElementSource(audio);
|
|
229
230
|
// Connect the source to the GainNode, and the GainNode to the destination (speakers)
|
|
@@ -231,20 +232,25 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
231
232
|
this.gainNode.connect(audioContext.destination);
|
|
232
233
|
this.audioContext = audioContext;
|
|
233
234
|
}
|
|
235
|
+
invertNaturalVolume(value) {
|
|
236
|
+
const minValue = 0.0001;
|
|
237
|
+
const maxValue = 1;
|
|
238
|
+
const interpolatedValue = Math.pow((value - minValue) / (maxValue - minValue), 1 / this.naturalVolumeExponent);
|
|
239
|
+
return interpolatedValue;
|
|
240
|
+
}
|
|
234
241
|
naturalVolume(value) {
|
|
235
242
|
const minValue = 0.0001;
|
|
236
243
|
const maxValue = 1;
|
|
237
|
-
const
|
|
238
|
-
const interpolatedValue = minValue + (maxValue - minValue) * Math.pow(value, exponent);
|
|
244
|
+
const interpolatedValue = minValue + (maxValue - minValue) * Math.pow(value, this.naturalVolumeExponent);
|
|
239
245
|
return interpolatedValue;
|
|
240
246
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.
|
|
247
|
+
setGainValue() {
|
|
248
|
+
if (this.gainNode) {
|
|
249
|
+
this.gainNode.gain.value = this.volume;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
onVolumeDrag(volume) {
|
|
253
|
+
this.setVolume(this.naturalVolume(volume));
|
|
248
254
|
}
|
|
249
255
|
initFadeEffects() {
|
|
250
256
|
if (!this.audioContext || !this.wavesurfer)
|
|
@@ -269,6 +275,11 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
269
275
|
// Fade out
|
|
270
276
|
if (!this.isFadingOut && currentTime >= this.options.fadeOutStart && currentTime <= this.options.fadeOutEnd) {
|
|
271
277
|
this.isFadingOut = true;
|
|
278
|
+
/**
|
|
279
|
+
* Set the gain at this point in time to the current volume, otherwise
|
|
280
|
+
* the audio will start fading out from the fade-in point.
|
|
281
|
+
*/
|
|
282
|
+
this.gainNode.gain.setValueAtTime(this.volume, this.audioContext.currentTime);
|
|
272
283
|
// Set the target gain (volume) to 0 (silent) over N seconds
|
|
273
284
|
this.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + (this.options.fadeOutEnd - currentTime));
|
|
274
285
|
return;
|
|
@@ -285,21 +296,39 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
285
296
|
}
|
|
286
297
|
if (cancelRamp) {
|
|
287
298
|
this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);
|
|
288
|
-
this.
|
|
299
|
+
this.setGainValue();
|
|
289
300
|
}
|
|
290
301
|
});
|
|
291
302
|
this.subscriptions.push(unsub);
|
|
292
303
|
}
|
|
304
|
+
/** Get the current audio volume */
|
|
293
305
|
getCurrentVolume() {
|
|
294
306
|
return this.gainNode ? this.gainNode.gain.value : this.volume;
|
|
295
307
|
}
|
|
296
|
-
|
|
308
|
+
/** Set the fade-in start time */
|
|
309
|
+
setStartTime(time, moveDragPoint) {
|
|
310
|
+
const rampLength = this.options.fadeInEnd - this.options.fadeInStart;
|
|
297
311
|
this.options.fadeInStart = time;
|
|
312
|
+
if (moveDragPoint) {
|
|
313
|
+
this.options.fadeInEnd = time + rampLength;
|
|
314
|
+
}
|
|
298
315
|
this.renderPolyline();
|
|
299
316
|
}
|
|
300
|
-
|
|
317
|
+
/** Set the fade-out end time */
|
|
318
|
+
setEndTime(time, moveDragPoint) {
|
|
319
|
+
const rampLength = this.options.fadeOutEnd - this.options.fadeOutStart;
|
|
301
320
|
this.options.fadeOutEnd = time;
|
|
321
|
+
if (moveDragPoint) {
|
|
322
|
+
this.options.fadeOutStart = time - rampLength;
|
|
323
|
+
}
|
|
302
324
|
this.renderPolyline();
|
|
303
325
|
}
|
|
326
|
+
/** Set the volume of the audio */
|
|
327
|
+
setVolume(volume) {
|
|
328
|
+
this.volume = volume;
|
|
329
|
+
this.setGainValue();
|
|
330
|
+
this.renderPolyline();
|
|
331
|
+
this.emit('volume-change', volume);
|
|
332
|
+
}
|
|
304
333
|
}
|
|
305
334
|
export default EnvelopePlugin;
|
|
@@ -1 +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={284:(t,e,i)=>{i.d(e,{Z:()=>o});var s=i(139);class n extends s.Z{constructor(t){super(),this.subscriptions=[],this.options=t}init(t){this.wavesurfer=t
|
|
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={284:(t,e,i)=>{i.d(e,{Z:()=>o});var s=i(139);class n extends s.Z{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}}const o=n},139:(t,e,i)=>{i.d(e,{Z:()=>s});const s=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)))}}}},e={};function i(s){var n=e[s];if(void 0!==n)return n.exports;var o=e[s]={exports:{}};return t[s](o,o.exports,i),o.exports}i.d=(t,e)=>{for(var s in e)i.o(e,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var s={};return(()=>{i.d(s,{default:()=>o});var t=i(284);const e={fadeInStart:0,fadeOutEnd:0,fadeInEnd:0,fadeOutStart:0,lineWidth:4,lineColor:"rgba(0, 0, 255, 0.5)",dragPointSize:10,dragPointFill:"rgba(255, 255, 255, 0.8)",dragPointStroke:"rgba(255, 255, 255, 0.8)"};class n extends t.Z{constructor(t){super(t),this.svg=null,this.audioContext=null,this.gainNode=null,this.volume=1,this.isFadingIn=!1,this.isFadingOut=!1,this.naturalVolumeExponent=1.5,this.options=Object.assign({},e,t),this.options.lineColor=this.options.lineColor||e.lineColor,this.options.dragPointFill=this.options.dragPointFill||e.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||e.dragPointStroke,this.volume=this.options.volume??1}static create(t){return new n(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");let t;this.subscriptions.push(this.wavesurfer.once("decode",(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.initWebAudio(),this.initSvg(),this.initFadeEffects()}))),this.subscriptions.push(this.wavesurfer.on("zoom",(()=>{t&&clearTimeout(t),t=setTimeout((()=>{this.svg?.remove(),this.initSvg()}),100)})))}makeDraggable(t,e){t.addEventListener("mousedown",(t=>{let i=t.clientX,s=t.clientY;const n=this.wavesurfer?.options.interact||!0;let o;this.wavesurfer?.toggleInteraction(!1);const r=t=>{const n=t.clientX-i,o=t.clientY-s;i=t.clientX,s=t.clientY,e(n,o)},a=()=>{document.removeEventListener("mousemove",r),document.removeEventListener("mouseup",a),o&&clearTimeout(o),o=setTimeout((()=>{this.wavesurfer?.toggleInteraction(n)}),100)};document.addEventListener("mousemove",r),document.addEventListener("mouseup",a),t.preventDefault(),t.stopPropagation()}))}renderPolyline(){if(!this.svg||!this.wavesurfer)return;const t=this.svg.querySelector("polyline"),e=t.points,i=this.svg.clientWidth,s=this.svg.clientHeight,n=this.wavesurfer.getDuration(),o=this.options.dragPointSize/2,r=s-this.invertNaturalVolume(this.volume)*s+o;e.getItem(1).y=r,e.getItem(2).y=r,e.getItem(0).x=this.options.fadeInStart/n*i,e.getItem(3).x=this.options.fadeOutEnd/n*i;const a=this.svg.querySelector("line");a.setAttribute("x1",e.getItem(1).x.toString()),a.setAttribute("x2",e.getItem(2).x.toString()),a.setAttribute("y1",r.toString()),a.setAttribute("y2",r.toString());const u=this.svg.querySelectorAll("circle");for(let e=0;e<u.length;e++){const i=u[e],s=t.points.getItem(e+1);i.setAttribute("cx",s.x.toString()),i.setAttribute("cy",r.toString())}}initSvg(){if(!this.wavesurfer)return;const t=this.wavesurfer.getWrapper(),e=t.clientWidth,i=t.clientHeight,s=this.wavesurfer.getDuration(),n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","100%"),n.setAttribute("height","100%"),n.setAttribute("viewBox",`0 0 ${e} ${i}`),n.setAttribute("preserveAspectRatio","none"),n.setAttribute("style","position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;"),this.svg=n;const o=document.createElementNS("http://www.w3.org/2000/svg","polyline");o.setAttribute("points","0,0 0,0 0,0 0,0"),o.setAttribute("stroke",this.options.lineColor),o.setAttribute("stroke-width",this.options.lineWidth),o.setAttribute("fill","none"),o.setAttribute("style","pointer-events: none"),n.appendChild(o);const r=document.createElementNS("http://www.w3.org/2000/svg","line");r.setAttribute("stroke","none"),r.setAttribute("stroke-width",(3*this.options.lineWidth).toString()),r.setAttribute("style","cursor: ns-resize; pointer-events: all;"),n.appendChild(r);const a=o.points,u=this.options.dragPointSize/2;a.getItem(0).x=this.options.fadeInStart/s*e,a.getItem(0).y=i,a.getItem(1).x=this.options.fadeInEnd/s*e,a.getItem(1).y=0,a.getItem(2).x=this.options.fadeOutStart/s*e,a.getItem(2).y=0,a.getItem(3).x=this.options.fadeOutEnd/s*e,a.getItem(3).y=i,[1,2].forEach((()=>{const t=document.createElementNS("http://www.w3.org/2000/svg","circle");t.setAttribute("r",(this.options.dragPointSize/2).toString()),t.setAttribute("fill",this.options.dragPointFill),t.setAttribute("stroke",this.options.dragPointStroke||this.options.dragPointFill),t.setAttribute("stroke-width","2"),t.setAttribute("style","cursor: ew-resize; pointer-events: all;"),n.appendChild(t)})),this.wavesurfer.getWrapper().appendChild(n),this.renderPolyline();const h=t=>{const e=a.getItem(1).y+t-u;if(e<-.5||e>i)return;const s=Math.min(1,Math.max(0,(i-e)/i));this.onVolumeDrag(s)},d=(t,i,n)=>{const r=o.points.getItem(n),a=r.x+t,u=a/e*s;1===n&&u>this.options.fadeOutStart||u<this.options.fadeInStart||2===n&&u<this.options.fadeInEnd||u>this.options.fadeOutEnd||(r.x=a,1===n?(this.options.fadeInEnd=u,this.emit("fade-in-change",u)):2===n&&(this.options.fadeOutStart=u,this.emit("fade-out-change",u)),i>1||i<-1?h(i):this.renderPolyline())};this.makeDraggable(r,((t,e)=>h(e)));const l=n.querySelectorAll("circle");for(let t=0;t<l.length;t++){const e=t+1;this.makeDraggable(l[t],((t,i)=>d(t,i,e)))}}destroy(){this.svg?.remove(),super.destroy()}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)}onVolumeDrag(t){this.setVolume(this.naturalVolume(t))}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){const i=this.options.fadeInEnd-this.options.fadeInStart;this.options.fadeInStart=t,e&&(this.options.fadeInEnd=t+i),this.renderPolyline()}setEndTime(t,e){const i=this.options.fadeOutEnd-this.options.fadeOutStart;this.options.fadeOutEnd=t,e&&(this.options.fadeOutStart=t-i),this.renderPolyline()}setVolume(t){this.volume=t,this.setGainValue(),this.renderPolyline(),this.emit("volume-change",t)}}const o=n})(),s.default})()));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimap is a tiny copy of the main waveform serving as a navigation tool.
|
|
3
3
|
*/
|
|
4
|
-
import BasePlugin
|
|
4
|
+
import BasePlugin from '../base-plugin.js';
|
|
5
5
|
import { type WaveSurferOptions } from '../wavesurfer.js';
|
|
6
6
|
export type MinimapPluginOptions = {
|
|
7
7
|
overlayColor?: string;
|
|
@@ -21,10 +21,11 @@ declare class MinimapPlugin extends BasePlugin<MinimapPluginEvents, MinimapPlugi
|
|
|
21
21
|
private minimapWrapper;
|
|
22
22
|
private miniWavesurfer;
|
|
23
23
|
private overlay;
|
|
24
|
+
private container;
|
|
24
25
|
constructor(options: MinimapPluginOptions);
|
|
25
26
|
static create(options: MinimapPluginOptions): MinimapPlugin;
|
|
26
27
|
/** Called by wavesurfer, don't call manually */
|
|
27
|
-
|
|
28
|
+
onInit(): void;
|
|
28
29
|
private initMinimapWrapper;
|
|
29
30
|
private initOverlay;
|
|
30
31
|
private initMinimap;
|
package/dist/plugins/minimap.js
CHANGED
|
@@ -12,6 +12,7 @@ class MinimapPlugin extends BasePlugin {
|
|
|
12
12
|
constructor(options) {
|
|
13
13
|
super(options);
|
|
14
14
|
this.miniWavesurfer = null;
|
|
15
|
+
this.container = null;
|
|
15
16
|
this.options = Object.assign({}, defaultOptions, options);
|
|
16
17
|
this.minimapWrapper = this.initMinimapWrapper();
|
|
17
18
|
this.overlay = this.initOverlay();
|
|
@@ -20,22 +21,21 @@ class MinimapPlugin extends BasePlugin {
|
|
|
20
21
|
return new MinimapPlugin(options);
|
|
21
22
|
}
|
|
22
23
|
/** Called by wavesurfer, don't call manually */
|
|
23
|
-
|
|
24
|
-
super.init(params);
|
|
24
|
+
onInit() {
|
|
25
25
|
if (!this.wavesurfer) {
|
|
26
26
|
throw Error('WaveSurfer is not initialized');
|
|
27
27
|
}
|
|
28
28
|
if (this.options.container) {
|
|
29
|
-
let container = null;
|
|
30
29
|
if (typeof this.options.container === 'string') {
|
|
31
|
-
container = document.querySelector(this.options.container);
|
|
30
|
+
this.container = document.querySelector(this.options.container);
|
|
32
31
|
}
|
|
33
32
|
else if (this.options.container instanceof HTMLElement) {
|
|
34
|
-
container = this.options.container;
|
|
33
|
+
this.container = this.options.container;
|
|
35
34
|
}
|
|
36
|
-
container?.appendChild(this.minimapWrapper);
|
|
35
|
+
this.container?.appendChild(this.minimapWrapper);
|
|
37
36
|
}
|
|
38
37
|
else {
|
|
38
|
+
this.container = this.wavesurfer.getWrapper().parentElement;
|
|
39
39
|
this.container?.insertAdjacentElement(this.options.insertPosition, this.minimapWrapper);
|
|
40
40
|
}
|
|
41
41
|
this.subscriptions.push(this.wavesurfer.on('decode', () => {
|
|
@@ -55,7 +55,7 @@ class MinimapPlugin extends BasePlugin {
|
|
|
55
55
|
return div;
|
|
56
56
|
}
|
|
57
57
|
initMinimap() {
|
|
58
|
-
if (!this.wavesurfer
|
|
58
|
+
if (!this.wavesurfer)
|
|
59
59
|
return;
|
|
60
60
|
const data = this.wavesurfer.getDecodedData();
|
|
61
61
|
const media = this.wavesurfer.getMediaElement();
|
|
@@ -70,7 +70,7 @@ class MinimapPlugin extends BasePlugin {
|
|
|
70
70
|
peaks: [data.getChannelData(0)],
|
|
71
71
|
duration: data.duration,
|
|
72
72
|
});
|
|
73
|
-
const overlayWidth = Math.round((this.minimapWrapper.clientWidth / this.
|
|
73
|
+
const overlayWidth = Math.round((this.minimapWrapper.clientWidth / this.wavesurfer.getWrapper().clientWidth) * 100);
|
|
74
74
|
this.overlay.style.width = `${overlayWidth}%`;
|
|
75
75
|
this.subscriptions.push(this.wavesurfer.on('timeupdate', (currentTime) => {
|
|
76
76
|
const offset = Math.max(0, Math.min((currentTime / data.duration) * 100 - overlayWidth / 2, 100 - overlayWidth)).toFixed(2);
|
|
@@ -1 +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:()=>m});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}init(t){this.wavesurfer=t.wavesurfer,this.container=t.container,this.wrapper=t.wrapper}destroy(){this.subscriptions.forEach((t=>t()))}},n={decode:async function(t){const e=new AudioContext({sampleRate:8e3}),i=e.decodeAudioData(t);return i.finally((()=>e.close())),i},createBuffer:function(t,e){if("number"==typeof t[0]&&(t=[t]),t[0].some((t=>t>1||t<-1))){const e=Math.max(...t[0]);t=t.map((t=>t.map((t=>t/e))))}return{length:t[0].length,duration:e,numberOfChannels:t.length,sampleRate:t[0].length/e,getChannelData:e=>t?.[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}};class r extends i{constructor(t,e){super(),this.options={height:0},this.timeout=null,this.isScrolling=!1,this.channelData=null,this.duration=null,this.resizeObserver=null,this.options={...e};let i=null;if("string"==typeof t.container?i=document.querySelector(t.container):t.container instanceof HTMLElement&&(i=t.container),!i)throw new Error("Container not found");const[s,n]=this.initHtml();i.appendChild(s),this.container=s,this.scrollContainer=n.querySelector(".scroll"),this.wrapper=n.querySelector(".wrapper"),this.canvasWrapper=n.querySelector(".canvases"),this.progressWrapper=n.querySelector(".progress"),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.scrollContainer.addEventListener("scroll",(()=>{const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer,s=t/e,n=(t+i)/e;this.emit("scroll",s,n)})),this.resizeObserver=new ResizeObserver((()=>{this.delay((()=>this.reRender()),100)})),this.resizeObserver.observe(this.scrollContainer)}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 position: relative;\n height: ${this.options.height}px;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n height: ${this.options.height}px;\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 box-sizing: border-box;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></div>\n </div>\n </div>\n `,[t,e]}setOptions(t){this.options=t,this.reRender()}getContainer(){return this.scrollContainer}getWrapper(){return this.wrapper}destroy(){this.container.remove(),this.resizeObserver?.disconnect()}delay(t,e=10){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}async renderPeaks(t,e,i,s){const n=null!=this.options.barWidth?this.options.barWidth*s:1,o=null!=this.options.barGap?this.options.barGap*s:this.options.barWidth?n/2:0,a=this.options.barRadius??0,h=this.options.barHeight??1,l=t[0],c=l.length,d=Math.floor(e/(n+o))/c,p=i/2,u=1===t.length,m=u?l:t[1],g=u&&m.some((t=>t<0)),y=(t,i)=>{let r=0,u=0,y=0;const f=document.createElement("canvas");f.width=Math.round(e*(i-t)/c),f.height=this.options.height,f.style.width=`${Math.floor(f.width/s)}px`,f.style.height=`${this.options.height}px`,f.style.left=`${Math.floor(t*e/s/c)}px`,this.canvasWrapper.appendChild(f);const v=f.getContext("2d",{desynchronized:!0});v.beginPath(),v.fillStyle=this.options.waveColor??"",v.roundRect||(v.roundRect=v.fillRect);for(let e=t;e<i;e++){const i=Math.round((e-t)*d);if(i>r){const t=Math.round(u*p*h),e=Math.round(y*p*h);v.roundRect(r*(n+o),p-t,n,t+(e||1),a),r=i,u=0,y=0}const s=g?l[e]:Math.abs(l[e]),c=g?m[e]:Math.abs(m[e]);s>u&&(u=s),(g?c<-y:c>y)&&(y=c<0?-c:c)}v.fill(),v.closePath();const b=f.cloneNode();this.progressWrapper.appendChild(b);const w=b.getContext("2d",{desynchronized:!0});f.width>0&&f.height>0&&w.drawImage(f,0,0),w.globalCompositeOperation="source-in",w.fillStyle=this.options.progressColor??"",w.fillRect(0,0,f.width,f.height)};this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="";const{scrollLeft:f,scrollWidth:v,clientWidth:b}=this.scrollContainer,w=c/v;let C=Math.min(r.MAX_CANVAS_WIDTH,b);C-=C%((n+o)/s);const W=Math.floor(Math.abs(f)*w),M=Math.ceil(W+C*w);y(W,M);const E=M-W;for(let t=M;t<c;t+=E)await this.delay((()=>{y(t,Math.min(c,t+E))}));for(let t=W-1;t>=0;t-=E)await this.delay((()=>{y(Math.max(0,t-E),t)}))}render(t,e){const i=window.devicePixelRatio||1,s=this.scrollContainer.clientWidth,n=Math.ceil(e*(this.options.minPxPerSec||0));this.isScrolling=n>s;const r=this.options.fillParent&&!this.isScrolling,o=(r?s:n)*i,{height:a}=this.options;this.wrapper.style.width=r?"100%":`${n}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.progressWrapper.style.borderRightStyle="solid",this.progressWrapper.style.borderRightColor=`${this.options.cursorColor||this.options.progressColor}`,this.progressWrapper.style.borderRightWidth=`${this.options.cursorWidth}px`,this.canvasWrapper.style.height=`${this.options.height}px`,this.renderPeaks(t,o,a,i),this.channelData=t,this.duration=e}reRender(){if(!this.channelData||!this.duration)return;const t=this.progressWrapper.clientWidth;this.render(this.channelData,this.duration);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:n}=this.scrollContainer,r=n*t,o=i/2;if(r>s+(e&&this.options.autoCenter?o:i)||r<s)if(this.options.autoCenter){const t=o/20;r-(s+o)>=t&&r<s+i?this.scrollContainer.scrollLeft+=t:this.scrollContainer.scrollLeft=r-o}else this.scrollContainer.scrollLeft=r;{const{scrollLeft:t}=this.scrollContainer,e=t/n,s=(t+i)/n;this.emit("scroll",e,s)}}renderProgress(t,e){isNaN(t)||(this.progressWrapper.style.width=100*t+"%",this.isScrolling&&this.options.autoScroll&&this.scrollIntoView(t,e))}}r.MAX_CANVAS_WIDTH=4e3;const o=r,a=class extends i{constructor(t){super(),this.subscriptions=[],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})}loadUrl(t){this.media.src=t}destroy(){this.media.pause(),this.subscriptions.forEach((t=>t())),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)}},h=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()}},l={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoScroll:!0,autoCenter:!0};class c extends a{static create(t){return new c(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.canPlay=!1,this.options=Object.assign({},l,t),this.fetcher=new class{async load(t){return fetch(t).then((t=>t.arrayBuffer()))}},this.timer=new h,this.renderer=new o({container:this.options.container},this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent(),this.initPlugins();const e=this.options.url||this.options.media?.src;e&&this.load(e,this.options.peaks,this.options.duration)}setOptions(t){this.options={...this.options,...t},this.renderer.setOptions(this.options)}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.getCurrentTime()>=this.getDuration()&&this.emit("finish")})),this.onMediaEvent("canplay",(()=>{this.canPlay=!0,this.emit("canplay",this.getDuration())})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(t=>{this.options.interact&&(this.canPlay&&this.seekTo(t),this.emit("interaction"))})),this.renderer.on("scroll",((t,e)=>{const i=this.getDuration();this.emit("scroll",t*i,e*i)})))}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)})))}initReadyEvent(){const t=()=>{this.decodedData&&this.canPlay&&this.emit("ready",this.getDuration())};this.subscriptions.push(this.on("decode",t),this.on("canplay",t))}initPlugins(){this.options.plugins?.length&&this.options.plugins.forEach((t=>{this.registerPlugin(t)}))}registerPlugin(t){return t.init({wavesurfer:this,container:this.renderer.getContainer(),wrapper:this.renderer.getWrapper()}),this.plugins.push(t),t}getActivePlugins(){return this.plugins}async load(t,e,i){if(this.decodedData=null,this.canPlay=!1,this.loadUrl(t),this.emit("load",t),e)i||(i=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getMediaElement().duration)))}))||0),this.decodedData=n.createBuffer(e,i);else{const e=await this.fetcher.load(t);this.decodedData=await n.decode(e)}this.renderAudio(),this.emit("decode",this.getDuration()),this.emit("redraw")}renderAudio(){if(!this.decodedData)return;const t=[this.decodedData.getChannelData(0)];this.decodedData.numberOfChannels>1&&t.push(this.decodedData.getChannelData(1)),this.renderer.render(t,this.decodedData.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(){const t=super.getDuration();return t>0&&t<1/0?t:this.decodedData?.duration||0}toggleInteraction(t){this.options.interact=t}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}playPause(){return this.isPlaying()?(this.pause(),Promise.resolve()):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.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const d=c,p={height:50,overlayColor:"rgba(100, 100, 100, 0.1)",insertPosition:"afterend"};class u extends s{constructor(t){super(t),this.miniWavesurfer=null,this.options=Object.assign({},p,t),this.minimapWrapper=this.initMinimapWrapper(),this.overlay=this.initOverlay()}static create(t){return new u(t)}init(t){if(super.init(t),!this.wavesurfer)throw Error("WaveSurfer is not initialized");if(this.options.container){let t=null;"string"==typeof this.options.container?t=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(t=this.options.container),t?.appendChild(this.minimapWrapper)}else this.container?.insertAdjacentElement(this.options.insertPosition,this.minimapWrapper);this.subscriptions.push(this.wavesurfer.on("decode",(()=>{this.initMinimap()})))}initMinimapWrapper(){const t=document.createElement("div");return t.style.position="relative",t}initOverlay(){const t=document.createElement("div");return t.setAttribute("style","position: absolute; z-index: 2; left: 0; top: 0; bottom: 0;"),t.style.backgroundColor=this.options.overlayColor,this.minimapWrapper.appendChild(t),t}initMinimap(){if(!this.wavesurfer||!this.wrapper)return;const t=this.wavesurfer.getDecodedData(),e=this.wavesurfer.getMediaElement();if(!t||!e)return;this.miniWavesurfer=d.create({...this.options,container:this.minimapWrapper,minPxPerSec:0,fillParent:!0,media:e,peaks:[t.getChannelData(0)],duration:t.duration});const i=Math.round(this.minimapWrapper.clientWidth/this.wrapper.clientWidth*100);this.overlay.style.width=`${i}%`,this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{const s=Math.max(0,Math.min(e/t.duration*100-i/2,100-i)).toFixed(2);this.overlay.style.left=`${s}%`}))),this.subscriptions.push(this.miniWavesurfer.on("interaction",(()=>{this.wavesurfer&&this.miniWavesurfer&&this.wavesurfer.setTime(this.miniWavesurfer.getCurrentTime()),this.container&&(this.container.scrollLeft=this.minimapWrapper.scrollLeft/this.minimapWrapper.scrollWidth*this.container.scrollWidth),this.emit("interaction")})),this.miniWavesurfer.on("ready",(()=>{this.emit("ready")})))}destroy(){this.miniWavesurfer?.destroy(),this.minimapWrapper.remove(),super.destroy()}}const m=u;return e.default})()));
|
|
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:()=>m});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()))}},n={decode:async function(t,e=8e3){const i=new AudioContext({sampleRate:e}),s=i.decodeAudioData(t);return s.finally((()=>i.close())),s},createBuffer:function(t,e){if("number"==typeof t[0]&&(t=[t]),t[0].some((t=>t>1||t<-1))){const e=Math.max(...t[0]);t=t.map((t=>t.map((t=>t/e))))}return{length:t[0].length,duration:e,numberOfChannels:t.length,sampleRate:t[0].length/e,getChannelData:e=>t?.[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}};class r extends i{constructor(t,e){super(),this.options={height:0},this.timeout=null,this.isScrolling=!1,this.channelData=null,this.duration=null,this.resizeObserver=null,this.options={...e};let i=null;if("string"==typeof t.container?i=document.querySelector(t.container):t.container instanceof HTMLElement&&(i=t.container),!i)throw new Error("Container not found");const[s,n]=this.initHtml();i.appendChild(s),this.container=s,this.scrollContainer=n.querySelector(".scroll"),this.wrapper=n.querySelector(".wrapper"),this.canvasWrapper=n.querySelector(".canvases"),this.progressWrapper=n.querySelector(".progress"),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.scrollContainer.addEventListener("scroll",(()=>{const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer,s=t/e,n=(t+i)/e;this.emit("scroll",s,n)})),this.resizeObserver=new ResizeObserver((()=>{this.delay((()=>this.reRender()),100)})),this.resizeObserver.observe(this.scrollContainer)}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 position: relative;\n height: ${this.options.height}px;\n }\n :host canvas {\n display: block;\n position: absolute;\n top: 0;\n image-rendering: pixelated;\n height: ${this.options.height}px;\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 box-sizing: border-box;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="progress"></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()}delay(t,e=10){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}async renderPeaks(t,e,i,s){const n=null!=this.options.barWidth?this.options.barWidth*s:1,o=null!=this.options.barGap?this.options.barGap*s:this.options.barWidth?n/2:0,a=this.options.barRadius??0,h=this.options.barHeight??1,l=t[0],c=l.length,d=Math.floor(e/(n+o))/c,p=i/2,u=1===t.length,m=u?l:t[1],g=u&&m.some((t=>t<0)),y=(t,i)=>{let r=0,u=0,y=0;const f=document.createElement("canvas");f.width=Math.round(e*(i-t)/c),f.height=this.options.height,f.style.width=`${Math.floor(f.width/s)}px`,f.style.height=`${this.options.height}px`,f.style.left=`${Math.floor(t*e/s/c)}px`,this.canvasWrapper.appendChild(f);const v=f.getContext("2d",{desynchronized:!0});v.beginPath(),v.fillStyle=this.options.waveColor??"",v.roundRect||(v.roundRect=v.fillRect);for(let e=t;e<i;e++){const i=Math.round((e-t)*d);if(i>r){const t=Math.round(u*p*h),e=Math.round(y*p*h);v.roundRect(r*(n+o),p-t,n,t+(e||1),a),r=i,u=0,y=0}const s=g?l[e]:Math.abs(l[e]),c=g?m[e]:Math.abs(m[e]);s>u&&(u=s),(g?c<-y:c>y)&&(y=c<0?-c:c)}v.fill(),v.closePath();const b=f.cloneNode();this.progressWrapper.appendChild(b);const w=b.getContext("2d",{desynchronized:!0});f.width>0&&f.height>0&&w.drawImage(f,0,0),w.globalCompositeOperation="source-in",w.fillStyle=this.options.progressColor??"",w.fillRect(0,0,f.width,f.height)};this.canvasWrapper.innerHTML="",this.progressWrapper.innerHTML="";const{scrollLeft:f,scrollWidth:v,clientWidth:b}=this.scrollContainer,w=c/v;let W=Math.min(r.MAX_CANVAS_WIDTH,b);W-=W%((n+o)/s);const C=Math.floor(Math.abs(f)*w),M=Math.ceil(C+W*w);y(C,M);const E=M-C;for(let t=M;t<c;t+=E)await this.delay((()=>{y(t,Math.min(c,t+E))}));for(let t=C-1;t>=0;t-=E)await this.delay((()=>{y(Math.max(0,t-E),t)}))}render(t,e){const i=window.devicePixelRatio||1,s=this.scrollContainer.clientWidth,n=Math.ceil(e*(this.options.minPxPerSec||0));this.isScrolling=n>s;const r=this.options.fillParent&&!this.isScrolling,o=(r?s:n)*i,{height:a}=this.options;this.wrapper.style.width=r?"100%":`${n}px`,this.scrollContainer.style.overflowX=this.isScrolling?"auto":"hidden",this.scrollContainer.classList.toggle("noScrollbar",!!this.options.hideScrollbar),this.progressWrapper.style.borderRightStyle="solid",this.progressWrapper.style.borderRightColor=`${this.options.cursorColor||this.options.progressColor}`,this.progressWrapper.style.borderRightWidth=`${this.options.cursorWidth}px`,this.canvasWrapper.style.height=`${this.options.height}px`,this.renderPeaks(t,o,a,i),this.channelData=t,this.duration=e}reRender(){if(!this.channelData||!this.duration)return;const t=this.progressWrapper.clientWidth;this.render(this.channelData,this.duration);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:n}=this.scrollContainer,r=n*t,o=i/2;if(r>s+(e&&this.options.autoCenter?o:i)||r<s)if(this.options.autoCenter){const t=o/20;r-(s+o)>=t&&r<s+i?this.scrollContainer.scrollLeft+=t:this.scrollContainer.scrollLeft=r-o}else this.scrollContainer.scrollLeft=r;{const{scrollLeft:t}=this.scrollContainer,e=t/n,s=(t+i)/n;this.emit("scroll",e,s)}}renderProgress(t,e){isNaN(t)||(this.progressWrapper.style.width=100*t+"%",this.isScrolling&&this.options.autoScroll&&this.scrollIntoView(t,e))}}r.MAX_CANVAS_WIDTH=4e3;const o=r,a=class extends i{constructor(t){super(),this.subscriptions=[],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})}loadUrl(t){this.media.src=t}destroy(){this.media.pause(),this.subscriptions.forEach((t=>t())),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)}},h=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()}},l={height:128,waveColor:"#999",progressColor:"#555",cursorWidth:1,minPxPerSec:0,fillParent:!0,interact:!0,autoScroll:!0,autoCenter:!0};class c extends a{static create(t){return new c(t)}constructor(t){super({media:t.media,autoplay:t.autoplay,playbackRate:t.audioRate}),this.plugins=[],this.decodedData=null,this.canPlay=!1,this.options=Object.assign({},l,t),this.fetcher=new class{async load(t){return fetch(t).then((t=>t.arrayBuffer()))}},this.timer=new h,this.renderer=new o({container:this.options.container},this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent(),this.initPlugins();const e=this.options.url||this.options.media?.src;e&&this.load(e,this.options.peaks,this.options.duration)}setOptions(t){this.options={...this.options,...t},this.renderer.setOptions(this.options)}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.getCurrentTime()>=this.getDuration()&&this.emit("finish")})),this.onMediaEvent("canplay",(()=>{this.canPlay=!0,this.emit("canplay",this.getDuration())})),this.onMediaEvent("seeking",(()=>{this.emit("seeking",this.getCurrentTime())})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(t=>{this.options.interact&&(this.canPlay&&this.seekTo(t),this.emit("interaction"))})),this.renderer.on("scroll",((t,e)=>{const i=this.getDuration();this.emit("scroll",t*i,e*i)})))}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)})))}initReadyEvent(){const t=()=>{this.decodedData&&this.canPlay&&this.emit("ready",this.getDuration())};this.subscriptions.push(this.on("decode",t),this.on("canplay",t))}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){if(this.decodedData=null,this.canPlay=!1,this.loadUrl(t),this.emit("load",t),e)i||(i=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getMediaElement().duration)))}))||0),this.decodedData=n.createBuffer(e,i);else{const e=await this.fetcher.load(t);this.decodedData=await n.decode(e,this.options.sampleRate)}this.renderAudio(),this.emit("decode",this.getDuration()),this.emit("redraw")}renderAudio(){if(!this.decodedData)return;const t=[this.decodedData.getChannelData(0)];this.decodedData.numberOfChannels>1&&t.push(this.decodedData.getChannelData(1)),this.renderer.render(t,this.decodedData.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(){const t=super.getDuration();return t>0&&t<1/0?t:this.decodedData?.duration||0}toggleInteraction(t){this.options.interact=t}seekTo(t){const e=this.getDuration()*t;this.setTime(e)}playPause(){return this.isPlaying()?(this.pause(),Promise.resolve()):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.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const d=c,p={height:50,overlayColor:"rgba(100, 100, 100, 0.1)",insertPosition:"afterend"};class u 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 u(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.subscriptions.push(this.wavesurfer.on("decode",(()=>{this.initMinimap()})))}initMinimapWrapper(){const t=document.createElement("div");return t.style.position="relative",t}initOverlay(){const t=document.createElement("div");return t.setAttribute("style","position: absolute; z-index: 2; left: 0; top: 0; bottom: 0;"),t.style.backgroundColor=this.options.overlayColor,this.minimapWrapper.appendChild(t),t}initMinimap(){if(!this.wavesurfer)return;const t=this.wavesurfer.getDecodedData(),e=this.wavesurfer.getMediaElement();if(!t||!e)return;this.miniWavesurfer=d.create({...this.options,container:this.minimapWrapper,minPxPerSec:0,fillParent:!0,media:e,peaks:[t.getChannelData(0)],duration:t.duration});const i=Math.round(this.minimapWrapper.clientWidth/this.wavesurfer.getWrapper().clientWidth*100);this.overlay.style.width=`${i}%`,this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{const s=Math.max(0,Math.min(e/t.duration*100-i/2,100-i)).toFixed(2);this.overlay.style.left=`${s}%`}))),this.subscriptions.push(this.miniWavesurfer.on("interaction",(()=>{this.wavesurfer&&this.miniWavesurfer&&this.wavesurfer.setTime(this.miniWavesurfer.getCurrentTime()),this.container&&(this.container.scrollLeft=this.minimapWrapper.scrollLeft/this.minimapWrapper.scrollWidth*this.container.scrollWidth),this.emit("interaction")})),this.miniWavesurfer.on("ready",(()=>{this.emit("ready")})))}destroy(){this.miniWavesurfer?.destroy(),this.minimapWrapper.remove(),super.destroy()}}const m=u;return e.default})()));
|