wavesurfer.js 7.0.0-alpha.9 → 7.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -18
- package/dist/base-plugin.d.ts +6 -4
- package/dist/base-plugin.js +9 -3
- package/dist/decoder.d.ts +8 -7
- package/dist/decoder.js +43 -38
- package/dist/event-emitter.d.ts +16 -10
- package/dist/event-emitter.js +38 -16
- package/dist/fetcher.d.ts +4 -3
- package/dist/fetcher.js +5 -15
- package/dist/player.d.ts +31 -11
- package/dist/player.js +58 -31
- package/dist/plugins/envelope.d.ts +71 -0
- package/dist/plugins/envelope.js +347 -0
- package/dist/plugins/envelope.min.js +1 -0
- package/dist/plugins/minimap.d.ts +39 -0
- package/dist/plugins/minimap.js +117 -0
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/multitrack.d.ts +85 -12
- package/dist/plugins/multitrack.js +331 -84
- package/dist/plugins/multitrack.min.js +1 -0
- package/dist/plugins/record.d.ts +26 -0
- package/dist/plugins/record.js +126 -0
- package/dist/plugins/record.min.js +1 -0
- package/dist/plugins/regions.d.ts +83 -43
- package/dist/plugins/regions.js +362 -192
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/spectrogram.d.ts +69 -0
- package/dist/plugins/spectrogram.js +340 -0
- package/dist/plugins/spectrogram.min.js +1 -0
- package/dist/plugins/timeline.d.ts +25 -9
- package/dist/plugins/timeline.js +65 -24
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/renderer.d.ts +29 -13
- package/dist/renderer.js +280 -161
- package/dist/timer.d.ts +1 -1
- package/dist/wavesurfer.d.ts +151 -0
- package/dist/wavesurfer.js +241 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +54 -27
- package/dist/index.d.ts +0 -117
- package/dist/index.js +0 -227
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -32
- package/dist/plugins/xmultitrack.d.ts +0 -44
- package/dist/plugins/xmultitrack.js +0 -260
- package/dist/react/useWavesurfer.d.ts +0 -5
- package/dist/react/useWavesurfer.js +0 -20
- package/dist/wavesurfer.Multitrack.min.js +0 -1
- package/dist/wavesurfer.Regions.min.js +0 -1
- package/dist/wavesurfer.Timeline.min.js +0 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { GenericPlugin } from './base-plugin.js';
|
|
2
|
+
import Player from './player.js';
|
|
3
|
+
import { type RendererStyleOptions } from './renderer.js';
|
|
4
|
+
export type WaveSurferOptions = {
|
|
5
|
+
/** HTML element or CSS selector */
|
|
6
|
+
container: HTMLElement | string;
|
|
7
|
+
/** The height of the waveform in pixels */
|
|
8
|
+
height?: number;
|
|
9
|
+
/** The color of the waveform */
|
|
10
|
+
waveColor?: string;
|
|
11
|
+
/** The color of the progress mask */
|
|
12
|
+
progressColor?: string;
|
|
13
|
+
/** The color of the playpack cursor */
|
|
14
|
+
cursorColor?: string;
|
|
15
|
+
/** The cursor width */
|
|
16
|
+
cursorWidth?: number;
|
|
17
|
+
/** Render the waveform with bars like this: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
18
|
+
barWidth?: number;
|
|
19
|
+
/** Spacing between bars in pixels */
|
|
20
|
+
barGap?: number;
|
|
21
|
+
/** Rounded borders for bars */
|
|
22
|
+
barRadius?: number;
|
|
23
|
+
/** A vertical scaling factor for the waveform */
|
|
24
|
+
barHeight?: number;
|
|
25
|
+
/** Minimum pixels per second of audio (i.e. zoom level) */
|
|
26
|
+
minPxPerSec?: number;
|
|
27
|
+
/** Stretch the waveform to fill the container, true by default */
|
|
28
|
+
fillParent?: boolean;
|
|
29
|
+
/** Audio URL */
|
|
30
|
+
url?: string;
|
|
31
|
+
/** Pre-computed audio data */
|
|
32
|
+
peaks?: Float32Array[] | Array<number[]>;
|
|
33
|
+
/** Pre-computed duration */
|
|
34
|
+
duration?: number;
|
|
35
|
+
/** Use an existing media element instead of creating one */
|
|
36
|
+
media?: HTMLMediaElement;
|
|
37
|
+
/** Play the audio on load */
|
|
38
|
+
autoplay?: boolean;
|
|
39
|
+
/** Pass false to disable clicks on the waveform */
|
|
40
|
+
interact?: boolean;
|
|
41
|
+
/** Hide the scrollbar */
|
|
42
|
+
hideScrollbar?: boolean;
|
|
43
|
+
/** Audio rate */
|
|
44
|
+
audioRate?: number;
|
|
45
|
+
/** Automatically scroll the container to keep the current position in viewport */
|
|
46
|
+
autoScroll?: boolean;
|
|
47
|
+
/** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
|
|
48
|
+
autoCenter?: boolean;
|
|
49
|
+
/** Decoding sample rate. Doesn't affect the playback. Defaults to 8000 */
|
|
50
|
+
sampleRate?: number;
|
|
51
|
+
/** The list of plugins to initialize on start */
|
|
52
|
+
plugins?: GenericPlugin[];
|
|
53
|
+
};
|
|
54
|
+
declare const defaultOptions: {
|
|
55
|
+
height: number;
|
|
56
|
+
waveColor: string;
|
|
57
|
+
progressColor: string;
|
|
58
|
+
cursorWidth: number;
|
|
59
|
+
minPxPerSec: number;
|
|
60
|
+
fillParent: boolean;
|
|
61
|
+
interact: boolean;
|
|
62
|
+
autoScroll: boolean;
|
|
63
|
+
autoCenter: boolean;
|
|
64
|
+
sampleRate: number;
|
|
65
|
+
};
|
|
66
|
+
export type WaveSurferEvents = {
|
|
67
|
+
/** When audio starts loading */
|
|
68
|
+
load: [url: string];
|
|
69
|
+
/** When the audio has been decoded */
|
|
70
|
+
decode: [duration: number];
|
|
71
|
+
/** When the media element has loaded enough to play */
|
|
72
|
+
canplay: [duration: number];
|
|
73
|
+
/** When the audio is both decoded and can play */
|
|
74
|
+
ready: [duration: number];
|
|
75
|
+
/** When a waveform is drawn */
|
|
76
|
+
redraw: [];
|
|
77
|
+
/** When the audio starts playing */
|
|
78
|
+
play: [];
|
|
79
|
+
/** When the audio pauses */
|
|
80
|
+
pause: [];
|
|
81
|
+
/** When the audio finishes playing */
|
|
82
|
+
finish: [];
|
|
83
|
+
/** On audio position change, fires continuously during playback */
|
|
84
|
+
timeupdate: [currentTime: number];
|
|
85
|
+
/** An alias of timeupdate but only when the audio is playing */
|
|
86
|
+
audioprocess: [currentTime: number];
|
|
87
|
+
/** When the user seeks to a new position */
|
|
88
|
+
seeking: [currentTime: number];
|
|
89
|
+
/** When the user interacts with the waveform (i.g. clicks or drags on it) */
|
|
90
|
+
interaction: [];
|
|
91
|
+
/** When the user clicks on the waveform */
|
|
92
|
+
click: [relativeX: number];
|
|
93
|
+
/** When the user drags the cursor */
|
|
94
|
+
drag: [relativeX: number];
|
|
95
|
+
/** When the waveform is scrolled (panned) */
|
|
96
|
+
scroll: [visibleStartTime: number, visibleEndTime: number];
|
|
97
|
+
/** When the zoom level changes */
|
|
98
|
+
zoom: [minPxPerSec: number];
|
|
99
|
+
/** Just before the waveform is destroyed so you can clean up your events */
|
|
100
|
+
destroy: [];
|
|
101
|
+
};
|
|
102
|
+
declare class WaveSurfer extends Player<WaveSurferEvents> {
|
|
103
|
+
options: WaveSurferOptions & typeof defaultOptions;
|
|
104
|
+
private renderer;
|
|
105
|
+
private timer;
|
|
106
|
+
private plugins;
|
|
107
|
+
private decodedData;
|
|
108
|
+
private canPlay;
|
|
109
|
+
protected subscriptions: Array<() => void>;
|
|
110
|
+
/** Create a new WaveSurfer instance */
|
|
111
|
+
static create(options: WaveSurferOptions): WaveSurfer;
|
|
112
|
+
/** Create a new WaveSurfer instance */
|
|
113
|
+
constructor(options: WaveSurferOptions);
|
|
114
|
+
setOptions(options: Partial<RendererStyleOptions> & Pick<WaveSurferOptions, 'interact' | 'audioRate'>): void;
|
|
115
|
+
private initPlayerEvents;
|
|
116
|
+
private initRendererEvents;
|
|
117
|
+
private initTimerEvents;
|
|
118
|
+
private initReadyEvent;
|
|
119
|
+
private initPlugins;
|
|
120
|
+
/** Register a wavesurfer.js plugin */
|
|
121
|
+
registerPlugin<T extends GenericPlugin>(plugin: T): T;
|
|
122
|
+
/** For plugins only: get the waveform wrapper div */
|
|
123
|
+
getWrapper(): HTMLElement;
|
|
124
|
+
/** Get the current scroll position in pixels */
|
|
125
|
+
getScroll(): number;
|
|
126
|
+
/** Get all registered plugins */
|
|
127
|
+
getActivePlugins(): GenericPlugin[];
|
|
128
|
+
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
129
|
+
load(url: string, channelData?: WaveSurferOptions['peaks'], duration?: number): Promise<void>;
|
|
130
|
+
/** Zoom in or out */
|
|
131
|
+
zoom(minPxPerSec: number): void;
|
|
132
|
+
/** Get the decoded audio data */
|
|
133
|
+
getDecodedData(): AudioBuffer | null;
|
|
134
|
+
/** Get the duration of the audio in seconds */
|
|
135
|
+
getDuration(): number;
|
|
136
|
+
/** Toggle if the waveform should react to clicks */
|
|
137
|
+
toggleInteraction(isInteractive: boolean): void;
|
|
138
|
+
/** Seeks to a percentage of audio as [0..1] (0 = beginning, 1 = end) */
|
|
139
|
+
seekTo(progress: number): void;
|
|
140
|
+
/** Play or pause the audio */
|
|
141
|
+
playPause(): Promise<void>;
|
|
142
|
+
/** Stop the audio and go to the beginning */
|
|
143
|
+
stop(): void;
|
|
144
|
+
/** Skip N or -N seconds from the current positions */
|
|
145
|
+
skip(seconds: number): void;
|
|
146
|
+
/** Empty the waveform by loading a tiny silent audio */
|
|
147
|
+
empty(): void;
|
|
148
|
+
/** Unmount wavesurfer */
|
|
149
|
+
destroy(): void;
|
|
150
|
+
}
|
|
151
|
+
export default WaveSurfer;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import Decoder from './decoder.js';
|
|
2
|
+
import Fetcher from './fetcher.js';
|
|
3
|
+
import Player from './player.js';
|
|
4
|
+
import Renderer from './renderer.js';
|
|
5
|
+
import Timer from './timer.js';
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
height: 128,
|
|
8
|
+
waveColor: '#999',
|
|
9
|
+
progressColor: '#555',
|
|
10
|
+
cursorWidth: 1,
|
|
11
|
+
minPxPerSec: 0,
|
|
12
|
+
fillParent: true,
|
|
13
|
+
interact: true,
|
|
14
|
+
autoScroll: true,
|
|
15
|
+
autoCenter: true,
|
|
16
|
+
sampleRate: 8000,
|
|
17
|
+
};
|
|
18
|
+
class WaveSurfer extends Player {
|
|
19
|
+
/** Create a new WaveSurfer instance */
|
|
20
|
+
static create(options) {
|
|
21
|
+
return new WaveSurfer(options);
|
|
22
|
+
}
|
|
23
|
+
/** Create a new WaveSurfer instance */
|
|
24
|
+
constructor(options) {
|
|
25
|
+
super({
|
|
26
|
+
media: options.media,
|
|
27
|
+
autoplay: options.autoplay,
|
|
28
|
+
playbackRate: options.audioRate,
|
|
29
|
+
});
|
|
30
|
+
this.plugins = [];
|
|
31
|
+
this.decodedData = null;
|
|
32
|
+
this.canPlay = false;
|
|
33
|
+
this.subscriptions = [];
|
|
34
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
35
|
+
this.timer = new Timer();
|
|
36
|
+
this.renderer = new Renderer(this.options.container, this.options);
|
|
37
|
+
this.initPlayerEvents();
|
|
38
|
+
this.initRendererEvents();
|
|
39
|
+
this.initTimerEvents();
|
|
40
|
+
this.initReadyEvent();
|
|
41
|
+
this.initPlugins();
|
|
42
|
+
const url = this.options.url || this.options.media?.currentSrc || this.options.media?.src;
|
|
43
|
+
if (url) {
|
|
44
|
+
this.load(url, this.options.peaks, this.options.duration);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
setOptions(options) {
|
|
48
|
+
this.options = { ...this.options, ...options };
|
|
49
|
+
this.renderer.setOptions(this.options);
|
|
50
|
+
if (options.audioRate) {
|
|
51
|
+
this.setPlaybackRate(options.audioRate);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
initPlayerEvents() {
|
|
55
|
+
this.subscriptions.push(this.onMediaEvent('timeupdate', () => {
|
|
56
|
+
const currentTime = this.getCurrentTime();
|
|
57
|
+
this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying());
|
|
58
|
+
this.emit('timeupdate', currentTime);
|
|
59
|
+
}), this.onMediaEvent('play', () => {
|
|
60
|
+
this.emit('play');
|
|
61
|
+
this.timer.start();
|
|
62
|
+
}), this.onMediaEvent('pause', () => {
|
|
63
|
+
this.emit('pause');
|
|
64
|
+
this.timer.stop();
|
|
65
|
+
}), this.onMediaEvent('ended', () => {
|
|
66
|
+
this.emit('finish');
|
|
67
|
+
}), this.onMediaEvent('canplay', () => {
|
|
68
|
+
this.canPlay = true;
|
|
69
|
+
this.emit('canplay', this.getDuration());
|
|
70
|
+
}), this.onMediaEvent('seeking', () => {
|
|
71
|
+
this.emit('seeking', this.getCurrentTime());
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
initRendererEvents() {
|
|
75
|
+
this.subscriptions.push(
|
|
76
|
+
// Seek on click
|
|
77
|
+
this.renderer.on('click', (relativeX) => {
|
|
78
|
+
if (this.options.interact) {
|
|
79
|
+
this.canPlay && this.seekTo(relativeX);
|
|
80
|
+
this.emit('interaction');
|
|
81
|
+
this.emit('click', relativeX);
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
// Scroll
|
|
85
|
+
this.renderer.on('scroll', (startX, endX) => {
|
|
86
|
+
const duration = this.getDuration();
|
|
87
|
+
this.emit('scroll', startX * duration, endX * duration);
|
|
88
|
+
}),
|
|
89
|
+
// Redraw
|
|
90
|
+
this.renderer.on('render', () => {
|
|
91
|
+
this.emit('redraw');
|
|
92
|
+
}));
|
|
93
|
+
// Drag
|
|
94
|
+
{
|
|
95
|
+
let debounce;
|
|
96
|
+
this.subscriptions.push(this.renderer.on('drag', (relativeX) => {
|
|
97
|
+
if (!this.options.interact)
|
|
98
|
+
return;
|
|
99
|
+
if (this.canPlay) {
|
|
100
|
+
// Update the visual position
|
|
101
|
+
this.renderer.renderProgress(relativeX);
|
|
102
|
+
// Set the audio position with a debounce
|
|
103
|
+
clearTimeout(debounce);
|
|
104
|
+
debounce = setTimeout(() => {
|
|
105
|
+
this.seekTo(relativeX);
|
|
106
|
+
}, this.isPlaying() ? 0 : 200);
|
|
107
|
+
}
|
|
108
|
+
this.emit('interaction');
|
|
109
|
+
this.emit('drag', relativeX);
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
initTimerEvents() {
|
|
114
|
+
// The timer fires every 16ms for a smooth progress animation
|
|
115
|
+
this.subscriptions.push(this.timer.on('tick', () => {
|
|
116
|
+
const currentTime = this.getCurrentTime();
|
|
117
|
+
this.renderer.renderProgress(currentTime / this.getDuration(), true);
|
|
118
|
+
this.emit('timeupdate', currentTime);
|
|
119
|
+
this.emit('audioprocess', currentTime);
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
initReadyEvent() {
|
|
123
|
+
const emitReady = () => {
|
|
124
|
+
if (this.decodedData && this.canPlay) {
|
|
125
|
+
this.emit('ready', this.getDuration());
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
this.subscriptions.push(this.on('decode', emitReady), this.on('canplay', emitReady));
|
|
129
|
+
}
|
|
130
|
+
initPlugins() {
|
|
131
|
+
if (!this.options.plugins?.length)
|
|
132
|
+
return;
|
|
133
|
+
this.options.plugins.forEach((plugin) => {
|
|
134
|
+
this.registerPlugin(plugin);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Register a wavesurfer.js plugin */
|
|
138
|
+
registerPlugin(plugin) {
|
|
139
|
+
plugin.init(this);
|
|
140
|
+
this.plugins.push(plugin);
|
|
141
|
+
return plugin;
|
|
142
|
+
}
|
|
143
|
+
/** For plugins only: get the waveform wrapper div */
|
|
144
|
+
getWrapper() {
|
|
145
|
+
return this.renderer.getWrapper();
|
|
146
|
+
}
|
|
147
|
+
/** Get the current scroll position in pixels */
|
|
148
|
+
getScroll() {
|
|
149
|
+
return this.renderer.getScroll();
|
|
150
|
+
}
|
|
151
|
+
/** Get all registered plugins */
|
|
152
|
+
getActivePlugins() {
|
|
153
|
+
return this.plugins;
|
|
154
|
+
}
|
|
155
|
+
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
156
|
+
async load(url, channelData, duration) {
|
|
157
|
+
this.decodedData = null;
|
|
158
|
+
this.canPlay = false;
|
|
159
|
+
this.emit('load', url);
|
|
160
|
+
if (channelData) {
|
|
161
|
+
// Set the mediaelement source to the URL
|
|
162
|
+
this.setSrc(url);
|
|
163
|
+
// Pre-decoded audio data
|
|
164
|
+
if (!duration) {
|
|
165
|
+
// Wait for the audio duration
|
|
166
|
+
duration =
|
|
167
|
+
(await new Promise((resolve) => {
|
|
168
|
+
this.onceMediaEvent('loadedmetadata', () => resolve(this.getMediaElement().duration));
|
|
169
|
+
})) || 0;
|
|
170
|
+
}
|
|
171
|
+
this.decodedData = Decoder.createBuffer(channelData, duration);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
175
|
+
const audio = await Fetcher.fetchArrayBuffer(url);
|
|
176
|
+
this.setSrc(url, audio);
|
|
177
|
+
this.decodedData = await Decoder.decode(audio, this.options.sampleRate);
|
|
178
|
+
}
|
|
179
|
+
this.emit('decode', this.getDuration());
|
|
180
|
+
this.renderer.render(this.decodedData);
|
|
181
|
+
}
|
|
182
|
+
/** Zoom in or out */
|
|
183
|
+
zoom(minPxPerSec) {
|
|
184
|
+
if (!this.decodedData) {
|
|
185
|
+
throw new Error('No audio loaded');
|
|
186
|
+
}
|
|
187
|
+
this.renderer.zoom(minPxPerSec);
|
|
188
|
+
this.emit('zoom', minPxPerSec);
|
|
189
|
+
}
|
|
190
|
+
/** Get the decoded audio data */
|
|
191
|
+
getDecodedData() {
|
|
192
|
+
return this.decodedData;
|
|
193
|
+
}
|
|
194
|
+
/** Get the duration of the audio in seconds */
|
|
195
|
+
getDuration() {
|
|
196
|
+
const audioDuration = super.getDuration();
|
|
197
|
+
return audioDuration > 0 && audioDuration < Infinity ? audioDuration : this.decodedData?.duration || 0;
|
|
198
|
+
}
|
|
199
|
+
/** Toggle if the waveform should react to clicks */
|
|
200
|
+
toggleInteraction(isInteractive) {
|
|
201
|
+
this.options.interact = isInteractive;
|
|
202
|
+
}
|
|
203
|
+
/** Seeks to a percentage of audio as [0..1] (0 = beginning, 1 = end) */
|
|
204
|
+
seekTo(progress) {
|
|
205
|
+
const time = this.getDuration() * progress;
|
|
206
|
+
this.setTime(time);
|
|
207
|
+
}
|
|
208
|
+
/** Play or pause the audio */
|
|
209
|
+
playPause() {
|
|
210
|
+
if (this.isPlaying()) {
|
|
211
|
+
this.pause();
|
|
212
|
+
return Promise.resolve();
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
return this.play();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Stop the audio and go to the beginning */
|
|
219
|
+
stop() {
|
|
220
|
+
this.pause();
|
|
221
|
+
this.setTime(0);
|
|
222
|
+
}
|
|
223
|
+
/** Skip N or -N seconds from the current positions */
|
|
224
|
+
skip(seconds) {
|
|
225
|
+
this.setTime(this.getCurrentTime() + seconds);
|
|
226
|
+
}
|
|
227
|
+
/** Empty the waveform by loading a tiny silent audio */
|
|
228
|
+
empty() {
|
|
229
|
+
this.load('', [[0]], 0.001);
|
|
230
|
+
}
|
|
231
|
+
/** Unmount wavesurfer */
|
|
232
|
+
destroy() {
|
|
233
|
+
this.emit('destroy');
|
|
234
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
235
|
+
this.plugins.forEach((plugin) => plugin.destroy());
|
|
236
|
+
this.timer.destroy();
|
|
237
|
+
this.renderer.destroy();
|
|
238
|
+
super.destroy();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
export default WaveSurfer;
|
package/dist/wavesurfer.min.js
CHANGED
|
@@ -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.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"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:()=>a});const i=class{constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e,i){const s=t=>{t instanceof CustomEvent&&e(t.detail)},n=String(t);return this.eventTarget.addEventListener(n,s,{once:i}),()=>this.eventTarget.removeEventListener(n,s)}once(t,e){return this.on(t,e,!0)}};const s=class extends i{constructor(t){super(),this.timeout=null,this.options=t;let e=null;if("string"==typeof this.options.container?e=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(e=this.options.container),!e)throw new Error("Container not found");const i=document.createElement("div"),s=i.attachShadow({mode:"open"});s.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 .wrapper {\n position: relative;\n width: fit-content;\n min-width: 100%;\n z-index: 2;\n }\n :host canvas {\n display: block;\n height: ${this.options.height}px;\n min-width: 100%;\n image-rendering: pixelated;\n }\n :host .progress {\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n pointer-events: none;\n clip-path: inset(100%);\n }\n :host .cursor {\n position: absolute;\n z-index: 3;\n top: 0;\n left: 0;\n height: 100%;\n border-right: 1px solid ${this.options.cursorColor||this.options.progressColor};\n margin-left: -1px;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <canvas></canvas>\n <canvas class="progress"></canvas>\n <div class="cursor"></div>\n </div>\n </div>\n `,this.container=i,this.scrollContainer=s.querySelector(".scroll"),this.mainCanvas=s.querySelector("canvas"),this.ctx=this.mainCanvas.getContext("2d",{desynchronized:!0}),this.progressCanvas=s.querySelector(".progress"),this.cursor=s.querySelector(".cursor"),e.appendChild(i),this.mainCanvas.addEventListener("click",(t=>{const e=this.mainCanvas.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",{relativeX:i})}))}getContainer(){return this.scrollContainer.querySelector(".wrapper")}destroy(){this.container.remove()}delay(t,e=100){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}renderPeaks(t,e,i){var s,n,o,r,a;return n=this,o=void 0,a=function*(){const{devicePixelRatio:n}=window,{ctx:o}=this,r=null!=this.options.barWidth?this.options.barWidth*n:1,a=null!=this.options.barGap?this.options.barGap*n:this.options.barWidth?r/2:0,h=null!==(s=this.options.barRadius)&&void 0!==s?s:0,l=t[0],d=l.length,c=Math.floor(e/(r+a))/d,u=i/2,p=1===t.length,m=p?l:t[1],y=p&&m.some((t=>t<0)),v=(t,e)=>{let i=0,s=0,n=0;o.beginPath(),o.fillStyle=this.options.waveColor,o.roundRect||(o.roundRect=o.fillRect);for(let d=t;d<e;d++){const t=Math.round(d*c);if(t>i){const e=Math.round(s*u),l=Math.round(n*u);o.roundRect(i*(r+a),u-e,r,e+l||1,h),i=t,s=0,n=0}const e=y?l[d]:Math.abs(l[d]),p=y?m[d]:Math.abs(m[d]);e>s&&(s=e),(y?p<-n:p>n)&&(n=p<0?-p:p)}o.fill(),o.closePath()};o.clearRect(0,0,e,i);const{scrollLeft:g,scrollWidth:f,clientWidth:C}=this.scrollContainer,w=d/f,b=Math.floor(g*w),P=Math.ceil((g+C)*w);v(b,P),this.createProgressMask(),b>0&&(yield this.delay((()=>{v(0,b)}))),P<d&&(yield this.delay((()=>{v(P,d)}))),this.delay((()=>{this.createProgressMask()}))},new((r=void 0)||(r=Promise))((function(t,e){function i(t){try{h(a.next(t))}catch(t){e(t)}}function s(t){try{h(a.throw(t))}catch(t){e(t)}}function h(e){var n;e.done?t(e.value):(n=e.value,n instanceof r?n:new r((function(t){t(n)}))).then(i,s)}h((a=a.apply(n,o||[])).next())}))}createProgressMask(){const t=this.progressCanvas.getContext("2d",{desynchronized:!0});this.progressCanvas.width=this.mainCanvas.width,this.progressCanvas.height=this.mainCanvas.height,this.progressCanvas.style.width=this.mainCanvas.style.width,this.progressCanvas.style.height=this.mainCanvas.style.height,t.drawImage(this.mainCanvas,0,0),t.globalCompositeOperation="source-in",t.fillStyle=this.options.progressColor,t.fillRect(0,0,this.progressCanvas.width,this.progressCanvas.height)}render(t){const{devicePixelRatio:e}=window,i=this.options.fillParent?this.scrollContainer.clientWidth*e:0,s=t.duration*this.options.minPxPerSec,n=s>i,o=Math.max(1,n?s:i),{height:r}=this.options;this.mainCanvas.width=o,this.mainCanvas.height=r,this.mainCanvas.style.width=Math.floor(s/e)+"px";const a=[t.getChannelData(0)];t.numberOfChannels>1&&a.push(t.getChannelData(1)),this.renderPeaks(a,o,r)}zoom(t,e){const i=this.cursor.getBoundingClientRect().left;this.options.minPxPerSec=e,this.render(t);const s=this.cursor.getBoundingClientRect().left;this.scrollContainer.scrollLeft+=s-i}renderProgress(t,e=!1){if(this.progressCanvas.style.clipPath=`inset(0 ${100-100*t}% 0 0)`,this.cursor.style.left=100*t+"%",e){const e=this.container.clientWidth/2,i=this.mainCanvas.clientWidth;i*t>=e&&(this.scrollContainer.scrollLeft=i*t-e)}}},n=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()}};const o={height:128,waveColor:"#999",progressColor:"#555",minPxPerSec:0,fillParent:!0,interactive:!0};class r extends i{static create(t){return new r(t)}constructor(t){var e;super(),this.plugins=[],this.subscriptions=[],this.decodedData=null,this.options=Object.assign({},o,t),this.fetcher=new class{load(t){return e=this,i=void 0,n=function*(){return fetch(t).then((t=>t.arrayBuffer()))},new((s=void 0)||(s=Promise))((function(t,o){function r(t){try{h(n.next(t))}catch(t){o(t)}}function a(t){try{h(n.throw(t))}catch(t){o(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof s?i:new s((function(t){t(i)}))).then(r,a)}h((n=n.apply(e,i||[])).next())}));var e,i,s,n}},this.decoder=new class{initAudioContext(t){this.audioCtx=new AudioContext({latencyHint:"playback",sampleRate:t})}constructor(){this.audioCtx=null;try{this.initAudioContext(3e3)}catch(t){this.initAudioContext(8e3)}}decode(t){return e=this,i=void 0,n=function*(){if(!this.audioCtx)throw new Error("AudioContext is not initialized");return this.audioCtx.decodeAudioData(t)},new((s=void 0)||(s=Promise))((function(t,o){function r(t){try{h(n.next(t))}catch(t){o(t)}}function a(t){try{h(n.throw(t))}catch(t){o(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof s?i:new s((function(t){t(i)}))).then(r,a)}h((n=n.apply(e,i||[])).next())}));var e,i,s,n}destroy(){var t;null===(t=this.audioCtx)||void 0===t||t.close(),this.audioCtx=null}},this.timer=new n,this.player=new class{constructor({media:t,autoplay:e}){this.isExternalMedia=!1,this.hasPlayedOnce=!1,t?(this.media=t,this.isExternalMedia=!0):this.media=document.createElement("audio");const i=this.on("play",(()=>{this.hasPlayedOnce=!0,i()}));e&&(this.media.autoplay=!0)}on(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}destroy(){this.media.pause(),this.isExternalMedia||this.media.remove()}loadUrl(t){this.media.src=t}getCurrentTime(){return this.media.currentTime}play(){this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}seekTo(t){if(this.media){const{hasPlayedOnce:e}=this;e||this.media.play(),this.media.currentTime=t,e||this.media.pause()}}getDuration(){return this.media.duration}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}}({media:this.options.media,autoplay:this.options.autoplay}),this.renderer=new s({container:this.options.container,height:this.options.height,waveColor:this.options.waveColor,progressColor:this.options.progressColor,cursorColor:this.options.cursorColor,minPxPerSec:this.options.minPxPerSec,fillParent:this.options.fillParent,barWidth:this.options.barWidth,barGap:this.options.barGap,barRadius:this.options.barRadius}),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent();const i=this.options.url||(null===(e=this.options.media)||void 0===e?void 0:e.src);i&&this.load(i,this.options.peaks,this.options.duration)}initPlayerEvents(){this.subscriptions.push(this.player.on("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",{currentTime:t})})),this.player.on("play",(()=>{this.emit("play"),this.timer.start()})),this.player.on("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.player.on("canplay",(()=>{this.emit("canplay",{duration:this.getDuration()})})),this.player.on("seeking",(()=>{this.emit("seeking",{currentTime:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{if(this.options.interactive){const e=this.getDuration()*t;this.seekTo(e),this.emit("seekClick",{currentTime:this.getCurrentTime()})}})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",{currentTime:t})})))}initReadyEvent(){let t=!1,e=!1;const i=()=>{t&&e&&this.emit("ready",{duration:this.getDuration()})};this.subscriptions.push(this.on("decode",(()=>{t=!0,i()})),this.on("canplay",(()=>{e=!0,i()})),this.player.on("waiting",(()=>{e=!1,t=!1})))}load(t,e,i){return s=this,n=void 0,r=function*(){if(this.player.loadUrl(t),null==e){const e=yield this.fetcher.load(t),i=yield this.decoder.decode(e);this.decodedData=i}else i||(i=(yield new Promise((t=>{this.player.on("canplay",(()=>t(this.getDuration())),{once:!0})})))||0),this.decodedData={duration:i,numberOfChannels:e.length,sampleRate:e[0].length/i,getChannelData:t=>e[t]};this.renderer.render(this.decodedData),this.emit("decode",{duration:this.decodedData.duration})},new((o=void 0)||(o=Promise))((function(t,e){function i(t){try{h(r.next(t))}catch(t){e(t)}}function a(t){try{h(r.throw(t))}catch(t){e(t)}}function h(e){var s;e.done?t(e.value):(s=e.value,s instanceof o?s:new o((function(t){t(s)}))).then(i,a)}h((r=r.apply(s,n||[])).next())}));var s,n,o,r}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(this.decodedData,t)}play(){this.player.play()}pause(){this.player.pause()}seekTo(t){this.player.seekTo(t)}isPlaying(){return this.player.isPlaying()}getDuration(){var t;return this.player.getDuration()||(null===(t=this.decodedData)||void 0===t?void 0:t.duration)||0}getCurrentTime(){return this.player.getCurrentTime()}getVolume(){return this.player.getVolume()}setVolume(t){this.player.setVolume(t)}getMuted(){return this.player.getMuted()}setMuted(t){this.player.setMuted(t)}getPlaybackRate(){return this.player.getPlaybackRate()}setPlaybackRate(t,e){this.player.setPlaybackRate(t,e)}registerPlugin(t,e){const i=new t({wavesurfer:this,container:this.renderer.getContainer()},e);return this.plugins.push(i),i}getDecodedData(){return this.decodedData}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.player.destroy(),this.decoder.destroy(),this.renderer.destroy()}}const a=r;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.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"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:()=>d});const i={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}}},s=async function(t){return fetch(t).then((t=>t.arrayBuffer()))},n=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)))}},r=class extends n{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?URL.createObjectURL(new Blob([e],{type:"audio/wav"})):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 o extends n{constructor(t,e){if(super(),this.options={height:0},this.timeout=null,this.isScrolling=!1,this.audioData=null,this.resizeObserver=null,this.isDragging=!1,this.options={...e},"string"==typeof t&&(t=document.querySelector(t)),!t)throw new Error("Container not found");const[i,s]=this.initHtml();t.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,n=(t+i)/e;this.emit("scroll",s,n)})),this.resizeObserver=new ResizeObserver((()=>{this.delay((()=>this.reRender()),100)})),this.resizeObserver.observe(this.scrollContainer)}initDrag(){this.wrapper.addEventListener("mousedown",(t=>{const e=t.clientX,i=t=>{if(Math.abs(t.clientX-e)>=5){this.isDragging=!0;const e=this.wrapper.getBoundingClientRect();this.emit("drag",Math.max(0,Math.min(1,(t.clientX-e.left)/e.width)))}},s=()=>{document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",s),this.isDragging=!1};document.addEventListener("mousemove",i),document.addEventListener("mouseup",s)}))}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 }\n :host .cursor {\n pointer-events: none;\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n width: ${this.options.cursorWidth}px;\n background-color: ${this.options.cursorColor||this.options.progressColor};\n }\n </style>\n\n <div class="scroll" part="scroll">\n <div class="wrapper">\n <div class="canvases"></div>\n <div class="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()}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||isNaN(this.options.barWidth)?1:this.options.barWidth*s,r=null==this.options.barGap||isNaN(this.options.barGap)?this.options.barWidth?n/2:0:this.options.barGap*s,a=this.options.barRadius||0,h=this.options.barHeight||1,l=t.getChannelData(0),c=l.length,d=Math.floor(e/(n+r))/c,u=i/2,p=1===t.numberOfChannels,m=p?l:t.getChannelData(1),g=p&&m.some((t=>t<0)),y=(t,i)=>{let o=0,p=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>o){const t=Math.round(p*u*h),e=Math.round(y*u*h);v.roundRect(o*(n+r),u-t,n,t+(e||1),a),o=i,p=0,y=0}const s=g?l[e]:Math.abs(l[e]),c=g?m[e]:Math.abs(m[e]);s>p&&(p=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(o.MAX_CANVAS_WIDTH,b);C-=C%((n+r)/s);const P=Math.floor(Math.abs(f)*w),E=Math.ceil(P+C*w);y(P,E);const M=E-P;for(let t=E;t<c;t+=M)await this.delay((()=>{y(t,Math.min(c,t+M))}));for(let t=P-1;t>=0;t-=M)await this.delay((()=>{y(Math.max(0,t-M),t)}))}render(t){const e=window.devicePixelRatio||1,i=this.scrollContainer.clientWidth,s=Math.ceil(t.duration*(this.options.minPxPerSec||0));this.isScrolling=s>i;const n=this.options.fillParent&&!this.isScrolling,r=(n?i:s)*e,{height:o}=this.options;this.wrapper.style.width=n?"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.style.height=`${this.options.height}px`,this.renderPeaks(t,r,o,e),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:n}=this.scrollContainer,r=n*t,o=i/2;if(r>s+(e&&this.options.autoCenter&&!this.isDragging?o:i)||r<s)if(this.options.autoCenter&&!this.isDragging){const t=o/20;r-(s+o)>=t&&r<s+i?this.scrollContainer.scrollLeft+=t:this.scrollContainer.scrollLeft=r-o}else if(this.isDragging){const t=10;this.scrollContainer.scrollLeft=r<s?r-t:r-i+t}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.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))}}o.MAX_CANVAS_WIDTH=4e3;const a=o,h=class extends n{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,sampleRate:8e3};class c extends r{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.subscriptions=[],this.options=Object.assign({},l,t),this.timer=new h,this.renderer=new a(this.options.container,this.options),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents(),this.initReadyEvent(),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={...this.options,...t},this.renderer.setOptions(this.options),t.audioRate&&this.setPlaybackRate(t.audioRate)}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("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.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.canPlay&&(this.renderer.renderProgress(e),clearTimeout(t),t=setTimeout((()=>{this.seekTo(e)}),this.isPlaying()?0:200)),this.emit("interaction"),this.emit("drag",e))})))}}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,n){if(this.decodedData=null,this.canPlay=!1,this.emit("load",t),e)this.setSrc(t),n||(n=await new Promise((t=>{this.onceMediaEvent("loadedmetadata",(()=>t(this.getMediaElement().duration)))}))||0),this.decodedData=i.createBuffer(e,n);else{const e=await s(t);this.setSrc(t,e),this.decodedData=await i.decode(e,this.options.sampleRate)}this.emit("decode",this.getDuration()),this.renderer.render(this.decodedData)}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.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.renderer.destroy(),super.destroy()}}const d=c;return e.default})()));
|
package/package.json
CHANGED
|
@@ -1,40 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wavesurfer.js",
|
|
3
|
-
"version": "7.0.0-
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "7.0.0-beta.1",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"author": "katspaugh",
|
|
6
|
+
"homepage": "https://wavesurfer-js.org",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"waveform",
|
|
9
|
+
"spectrogram",
|
|
10
|
+
"audio",
|
|
11
|
+
"player",
|
|
12
|
+
"music",
|
|
13
|
+
"linguistics"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
7
16
|
"files": [
|
|
8
|
-
"dist
|
|
17
|
+
"dist"
|
|
9
18
|
],
|
|
19
|
+
"types": "./dist/wavesurfer.d.ts",
|
|
20
|
+
"main": "./dist/wavesurfer.js",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/wavesurfer.js",
|
|
24
|
+
"require": "./dist/wavesurfer.min.js"
|
|
25
|
+
},
|
|
26
|
+
"./dist/plugins/*": {
|
|
27
|
+
"import": "./dist/plugins/*.js",
|
|
28
|
+
"require": "./dist/plugins/*.min.js"
|
|
29
|
+
},
|
|
30
|
+
"./dist/plugins/*.js": {
|
|
31
|
+
"import": "./dist/plugins/*.js",
|
|
32
|
+
"require": "./dist/plugins/*.min.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
10
35
|
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"build:
|
|
13
|
-
"build:umd": "
|
|
14
|
-
"
|
|
15
|
-
"
|
|
36
|
+
"build:dev": "tsc -w --target ESNext",
|
|
37
|
+
"build:umd": "webpack",
|
|
38
|
+
"build:umd:plugins": "webpack --config webpack.config.plugins.js",
|
|
39
|
+
"build": "tsc && npm run build:umd && npm run build:umd:plugins",
|
|
40
|
+
"deploy": "yarn build && yarn docs",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"docs": "typedoc src/wavesurfer.ts src/plugins/*.ts --out docs",
|
|
16
43
|
"lint": "eslint --ext .ts src --fix",
|
|
17
|
-
"prettier": "prettier -w
|
|
18
|
-
"
|
|
19
|
-
"
|
|
44
|
+
"prettier": "prettier -w '**/*.{js,ts,css}' --ignore-path .gitignore",
|
|
45
|
+
"cypress": "cypress open --e2e",
|
|
46
|
+
"cypress:canary": "cypress open --e2e -b chrome:canary",
|
|
47
|
+
"test": "cypress run",
|
|
48
|
+
"serve": "npx live-server --port=9090",
|
|
20
49
|
"start": "npm run build:dev & npm run serve"
|
|
21
50
|
},
|
|
22
|
-
"author": "katspaugh <katspaugh@gmail.com>",
|
|
23
|
-
"license": "ISC",
|
|
24
51
|
"devDependencies": {
|
|
25
|
-
"@
|
|
26
|
-
"@typescript-eslint/
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"eslint": "^8.
|
|
30
|
-
"eslint-config-prettier": "^8.
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
|
53
|
+
"@typescript-eslint/parser": "^5.57.0",
|
|
54
|
+
"cypress": "^12.9.0",
|
|
55
|
+
"cypress-image-snapshot": "^4.0.1",
|
|
56
|
+
"eslint": "^8.37.0",
|
|
57
|
+
"eslint-config-prettier": "^8.8.0",
|
|
31
58
|
"eslint-plugin-prettier": "^4.2.1",
|
|
32
|
-
"prettier": "^2.8.
|
|
59
|
+
"prettier": "^2.8.7",
|
|
33
60
|
"ts-loader": "^9.4.2",
|
|
34
|
-
"typedoc": "^0.
|
|
35
|
-
"
|
|
36
|
-
"
|
|
61
|
+
"typedoc": "^0.24.6",
|
|
62
|
+
"typedoc-plugin-rename-defaults": "^0.6.5",
|
|
63
|
+
"typescript": "^5.0.4",
|
|
64
|
+
"webpack": "^5.77.0",
|
|
37
65
|
"webpack-cli": "^5.0.1"
|
|
38
|
-
}
|
|
39
|
-
"dependencies": {}
|
|
66
|
+
}
|
|
40
67
|
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
-
import BasePlugin from './base-plugin.js';
|
|
3
|
-
export type WaveSurferOptions = {
|
|
4
|
-
/** HTML element or CSS selector */
|
|
5
|
-
container: HTMLElement | string | null;
|
|
6
|
-
/** Height of the waveform in pixels */
|
|
7
|
-
height?: number;
|
|
8
|
-
/** The color of the waveform */
|
|
9
|
-
waveColor?: string;
|
|
10
|
-
/** The color of the progress mask */
|
|
11
|
-
progressColor?: string;
|
|
12
|
-
/** The color of the playpack cursort */
|
|
13
|
-
cursorColor?: string;
|
|
14
|
-
/** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
15
|
-
barWidth?: number;
|
|
16
|
-
/** Spacing between bars in pixels */
|
|
17
|
-
barGap?: number;
|
|
18
|
-
/** Rounded borders for bars */
|
|
19
|
-
barRadius?: number;
|
|
20
|
-
/** Minimum pixels per second of audio (zoom) */
|
|
21
|
-
minPxPerSec?: number;
|
|
22
|
-
/** Stretch the waveform to fill the container, true by default */
|
|
23
|
-
fillParent?: boolean;
|
|
24
|
-
/** Audio URL */
|
|
25
|
-
url?: string;
|
|
26
|
-
/** Pre-computed audio data */
|
|
27
|
-
peaks?: Float32Array[];
|
|
28
|
-
/** Pre-computed duration */
|
|
29
|
-
duration?: number;
|
|
30
|
-
/** Use an existing media element instead of creating one */
|
|
31
|
-
media?: HTMLMediaElement;
|
|
32
|
-
/** Play the audio on load */
|
|
33
|
-
autoplay?: boolean;
|
|
34
|
-
/** Is the waveform clickable? */
|
|
35
|
-
interactive?: boolean;
|
|
36
|
-
};
|
|
37
|
-
export type WaveSurferEvents = {
|
|
38
|
-
decode: {
|
|
39
|
-
duration: number;
|
|
40
|
-
};
|
|
41
|
-
canplay: {
|
|
42
|
-
duration: number;
|
|
43
|
-
};
|
|
44
|
-
ready: {
|
|
45
|
-
duration: number;
|
|
46
|
-
};
|
|
47
|
-
play: void;
|
|
48
|
-
pause: void;
|
|
49
|
-
timeupdate: {
|
|
50
|
-
currentTime: number;
|
|
51
|
-
};
|
|
52
|
-
seeking: {
|
|
53
|
-
currentTime: number;
|
|
54
|
-
};
|
|
55
|
-
seekClick: {
|
|
56
|
-
currentTime: number;
|
|
57
|
-
};
|
|
58
|
-
destroy: void;
|
|
59
|
-
};
|
|
60
|
-
export type WaveSurferPluginParams = {
|
|
61
|
-
wavesurfer: WaveSurfer;
|
|
62
|
-
container: HTMLElement;
|
|
63
|
-
};
|
|
64
|
-
export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
65
|
-
private options;
|
|
66
|
-
private fetcher;
|
|
67
|
-
private decoder;
|
|
68
|
-
private renderer;
|
|
69
|
-
private player;
|
|
70
|
-
private timer;
|
|
71
|
-
private plugins;
|
|
72
|
-
private subscriptions;
|
|
73
|
-
private decodedData;
|
|
74
|
-
/** Create a new WaveSurfer instance */
|
|
75
|
-
static create(options: WaveSurferOptions): WaveSurfer;
|
|
76
|
-
/** Create a new WaveSurfer instance */
|
|
77
|
-
constructor(options: WaveSurferOptions);
|
|
78
|
-
private initPlayerEvents;
|
|
79
|
-
private initRendererEvents;
|
|
80
|
-
private initTimerEvents;
|
|
81
|
-
private initReadyEvent;
|
|
82
|
-
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
83
|
-
load(url: string, channelData?: Float32Array[], duration?: number): Promise<void>;
|
|
84
|
-
/** Zoom in or out */
|
|
85
|
-
zoom(minPxPerSec: number): void;
|
|
86
|
-
/** Start playing the audio */
|
|
87
|
-
play(): void;
|
|
88
|
-
/** Pause the audio */
|
|
89
|
-
pause(): void;
|
|
90
|
-
/** Skip to a time position in seconds */
|
|
91
|
-
seekTo(time: number): void;
|
|
92
|
-
/** Check if the audio is playing */
|
|
93
|
-
isPlaying(): boolean;
|
|
94
|
-
/** Get the duration of the audio in seconds */
|
|
95
|
-
getDuration(): number;
|
|
96
|
-
/** Get the current audio position in seconds */
|
|
97
|
-
getCurrentTime(): number;
|
|
98
|
-
/** Get the audio volume */
|
|
99
|
-
getVolume(): number;
|
|
100
|
-
/** Set the audio volume */
|
|
101
|
-
setVolume(volume: number): void;
|
|
102
|
-
/** Get the audio muted state */
|
|
103
|
-
getMuted(): boolean;
|
|
104
|
-
/** Mute or unmute the audio */
|
|
105
|
-
setMuted(muted: boolean): void;
|
|
106
|
-
/** Get playback rate */
|
|
107
|
-
getPlaybackRate(): number;
|
|
108
|
-
/** Set playback rate, with an optional parameter to NOT preserve the pitch if false */
|
|
109
|
-
setPlaybackRate(rate: number, preservePitch?: boolean): void;
|
|
110
|
-
/** Register and initialize a plugin */
|
|
111
|
-
registerPlugin<T extends BasePlugin<GeneralEventTypes, any>>(CustomPlugin: new (params: WaveSurferPluginParams, options: any) => T, options?: any): T;
|
|
112
|
-
/** Get the decoded audio data */
|
|
113
|
-
getDecodedData(): AudioBuffer | null;
|
|
114
|
-
/** Unmount wavesurfer */
|
|
115
|
-
destroy(): void;
|
|
116
|
-
}
|
|
117
|
-
export default WaveSurfer;
|