wavesurfer.js 7.0.0-alpha.4 → 7.0.0-alpha.6
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 +5 -0
- package/dist/base-plugin.d.ts +3 -2
- package/dist/base-plugin.js +2 -1
- package/dist/decoder.d.ts +1 -4
- package/dist/decoder.js +1 -9
- package/dist/index.d.ts +19 -10
- package/dist/index.js +52 -33
- package/dist/player.d.ts +2 -1
- package/dist/player.js +9 -10
- package/dist/plugins/regions.d.ts +7 -2
- package/dist/plugins/regions.js +34 -13
- package/dist/renderer.d.ts +4 -2
- package/dist/renderer.js +14 -9
- package/dist/wavesurfer.Regions.min.js +1 -1
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,11 @@ An experimental rewrite of [wavesufer.js](https://github.com/wavesurfer-js/waves
|
|
|
4
4
|
|
|
5
5
|
<img alt="screenshot" src="https://user-images.githubusercontent.com/381895/225539680-fc724acd-8657-458e-a558-ff1c6758ba30.png" width="800" />
|
|
6
6
|
|
|
7
|
+
Try it out:
|
|
8
|
+
```
|
|
9
|
+
npm install --save wavesurfer.js@alpha
|
|
10
|
+
```
|
|
11
|
+
|
|
7
12
|
## Goals
|
|
8
13
|
|
|
9
14
|
* TypeScript API
|
package/dist/base-plugin.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
2
|
import type { WaveSurfer, WaveSurferPluginParams } from './index.js';
|
|
3
|
-
export declare class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
|
|
3
|
+
export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options extends unknown> extends EventEmitter<EventTypes> {
|
|
4
4
|
protected wavesurfer: WaveSurfer;
|
|
5
5
|
protected container: HTMLElement;
|
|
6
6
|
protected subscriptions: (() => void)[];
|
|
7
|
-
|
|
7
|
+
protected options: Options;
|
|
8
|
+
constructor(params: WaveSurferPluginParams, options: Options);
|
|
8
9
|
destroy(): void;
|
|
9
10
|
}
|
|
10
11
|
export default BasePlugin;
|
package/dist/base-plugin.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
export class BasePlugin extends EventEmitter {
|
|
3
|
-
constructor(params) {
|
|
3
|
+
constructor(params, options) {
|
|
4
4
|
super();
|
|
5
5
|
this.subscriptions = [];
|
|
6
6
|
this.wavesurfer = params.wavesurfer;
|
|
7
7
|
this.container = params.container;
|
|
8
|
+
this.options = options;
|
|
8
9
|
}
|
|
9
10
|
destroy() {
|
|
10
11
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
package/dist/decoder.d.ts
CHANGED
|
@@ -2,10 +2,7 @@ declare class Decoder {
|
|
|
2
2
|
audioCtx: AudioContext | null;
|
|
3
3
|
private initAudioContext;
|
|
4
4
|
constructor();
|
|
5
|
-
decode(audioData: ArrayBuffer): Promise<
|
|
6
|
-
duration: number;
|
|
7
|
-
channelData: Float32Array[];
|
|
8
|
-
}>;
|
|
5
|
+
decode(audioData: ArrayBuffer): Promise<AudioBuffer>;
|
|
9
6
|
destroy(): void;
|
|
10
7
|
}
|
|
11
8
|
export default Decoder;
|
package/dist/decoder.js
CHANGED
|
@@ -31,15 +31,7 @@ class Decoder {
|
|
|
31
31
|
if (!this.audioCtx) {
|
|
32
32
|
throw new Error('AudioContext is not initialized');
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
const channelData = [buffer.getChannelData(0)];
|
|
36
|
-
if (buffer.numberOfChannels > 1) {
|
|
37
|
-
channelData.push(buffer.getChannelData(1));
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
duration: buffer.duration,
|
|
41
|
-
channelData,
|
|
42
|
-
};
|
|
34
|
+
return this.audioCtx.decodeAudioData(audioData);
|
|
43
35
|
});
|
|
44
36
|
}
|
|
45
37
|
destroy() {
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type WaveSurferOptions = {
|
|
|
9
9
|
waveColor?: string;
|
|
10
10
|
/** The color of the progress mask */
|
|
11
11
|
progressColor?: string;
|
|
12
|
+
/** The color of the playpack cursort */
|
|
13
|
+
cursorColor?: string;
|
|
12
14
|
/** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
|
|
13
15
|
barWidth?: number;
|
|
14
16
|
/** Spacing between bars in pixels */
|
|
@@ -17,16 +19,20 @@ export type WaveSurferOptions = {
|
|
|
17
19
|
barRadius?: number;
|
|
18
20
|
/** Minimum pixels per second of audio (zoom) */
|
|
19
21
|
minPxPerSec?: number;
|
|
22
|
+
/** Stretch the waveform to fill the container, true by default */
|
|
23
|
+
fillParent?: boolean;
|
|
20
24
|
/** Audio URL */
|
|
21
25
|
url?: string;
|
|
22
26
|
/** Pre-computed audio data */
|
|
23
|
-
|
|
27
|
+
peaks?: Float32Array[];
|
|
24
28
|
/** Pre-computed duration */
|
|
25
29
|
duration?: number;
|
|
26
30
|
/** Use an existing media element instead of creating one */
|
|
27
31
|
media?: HTMLMediaElement;
|
|
28
32
|
/** Play the audio on load */
|
|
29
33
|
autoplay?: boolean;
|
|
34
|
+
/** Is the waveform clickable? */
|
|
35
|
+
interactive?: boolean;
|
|
30
36
|
};
|
|
31
37
|
export type WaveSurferEvents = {
|
|
32
38
|
decode: {
|
|
@@ -37,12 +43,16 @@ export type WaveSurferEvents = {
|
|
|
37
43
|
};
|
|
38
44
|
play: void;
|
|
39
45
|
pause: void;
|
|
40
|
-
|
|
46
|
+
timeupdate: {
|
|
41
47
|
currentTime: number;
|
|
42
48
|
};
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
seeking: {
|
|
50
|
+
currentTime: number;
|
|
51
|
+
};
|
|
52
|
+
seekClick: {
|
|
53
|
+
currentTime: number;
|
|
45
54
|
};
|
|
55
|
+
destroy: void;
|
|
46
56
|
};
|
|
47
57
|
export type WaveSurferPluginParams = {
|
|
48
58
|
wavesurfer: WaveSurfer;
|
|
@@ -57,8 +67,7 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
|
57
67
|
private timer;
|
|
58
68
|
private plugins;
|
|
59
69
|
private subscriptions;
|
|
60
|
-
private
|
|
61
|
-
private duration;
|
|
70
|
+
private decodedData;
|
|
62
71
|
/** Create a new WaveSurfer instance */
|
|
63
72
|
static create(options: WaveSurferOptions): WaveSurfer;
|
|
64
73
|
/** Create a new WaveSurfer instance */
|
|
@@ -66,8 +75,6 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
|
66
75
|
private initPlayerEvents;
|
|
67
76
|
private initRendererEvents;
|
|
68
77
|
private initTimerEvents;
|
|
69
|
-
/** Unmount wavesurfer */
|
|
70
|
-
destroy(): void;
|
|
71
78
|
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
72
79
|
load(url: string, channelData?: Float32Array[], duration?: number): Promise<void>;
|
|
73
80
|
/** Zoom in or out */
|
|
@@ -97,8 +104,10 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
|
97
104
|
/** Set playback rate, with an optional parameter to NOT preserve the pitch if false */
|
|
98
105
|
setPlaybackRate(rate: number, preservePitch?: boolean): void;
|
|
99
106
|
/** Register and initialize a plugin */
|
|
100
|
-
registerPlugin<T extends BasePlugin<GeneralEventTypes>>(CustomPlugin: new (params:
|
|
107
|
+
registerPlugin<T extends BasePlugin<GeneralEventTypes, unknown>>(CustomPlugin: new (params: unknown, options: unknown) => T, options?: unknown): T;
|
|
101
108
|
/** Get the decoded audio data */
|
|
102
|
-
|
|
109
|
+
getDecodedData(): AudioBuffer | null;
|
|
110
|
+
/** Unmount wavesurfer */
|
|
111
|
+
destroy(): void;
|
|
103
112
|
}
|
|
104
113
|
export default WaveSurfer;
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,8 @@ const defaultOptions = {
|
|
|
18
18
|
waveColor: '#999',
|
|
19
19
|
progressColor: '#555',
|
|
20
20
|
minPxPerSec: 0,
|
|
21
|
+
fillParent: true,
|
|
22
|
+
interactive: true,
|
|
21
23
|
};
|
|
22
24
|
export class WaveSurfer extends EventEmitter {
|
|
23
25
|
/** Create a new WaveSurfer instance */
|
|
@@ -30,8 +32,7 @@ export class WaveSurfer extends EventEmitter {
|
|
|
30
32
|
super();
|
|
31
33
|
this.plugins = [];
|
|
32
34
|
this.subscriptions = [];
|
|
33
|
-
this.
|
|
34
|
-
this.duration = 0;
|
|
35
|
+
this.decodedData = null;
|
|
35
36
|
this.options = Object.assign({}, defaultOptions, options);
|
|
36
37
|
this.fetcher = new Fetcher();
|
|
37
38
|
this.decoder = new Decoder();
|
|
@@ -45,7 +46,9 @@ export class WaveSurfer extends EventEmitter {
|
|
|
45
46
|
height: this.options.height,
|
|
46
47
|
waveColor: this.options.waveColor,
|
|
47
48
|
progressColor: this.options.progressColor,
|
|
49
|
+
cursorColor: this.options.cursorColor,
|
|
48
50
|
minPxPerSec: this.options.minPxPerSec,
|
|
51
|
+
fillParent: this.options.fillParent,
|
|
49
52
|
barWidth: this.options.barWidth,
|
|
50
53
|
barGap: this.options.barGap,
|
|
51
54
|
barRadius: this.options.barRadius,
|
|
@@ -55,14 +58,14 @@ export class WaveSurfer extends EventEmitter {
|
|
|
55
58
|
this.initTimerEvents();
|
|
56
59
|
const url = this.options.url || ((_a = this.options.media) === null || _a === void 0 ? void 0 : _a.src);
|
|
57
60
|
if (url) {
|
|
58
|
-
this.load(url, this.options.
|
|
61
|
+
this.load(url, this.options.peaks, this.options.duration);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
initPlayerEvents() {
|
|
62
65
|
this.subscriptions.push(this.player.on('timeupdate', () => {
|
|
63
66
|
const currentTime = this.getCurrentTime();
|
|
64
|
-
this.renderer.renderProgress(currentTime / this.
|
|
65
|
-
this.emit('
|
|
67
|
+
this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying());
|
|
68
|
+
this.emit('timeupdate', { currentTime });
|
|
66
69
|
}), this.player.on('play', () => {
|
|
67
70
|
this.emit('play');
|
|
68
71
|
this.timer.start();
|
|
@@ -72,56 +75,61 @@ export class WaveSurfer extends EventEmitter {
|
|
|
72
75
|
}), this.player.on('canplay', () => {
|
|
73
76
|
this.emit('canplay', { duration: this.getDuration() });
|
|
74
77
|
}), this.player.on('seeking', () => {
|
|
75
|
-
this.emit('
|
|
78
|
+
this.emit('seeking', { currentTime: this.getCurrentTime() });
|
|
76
79
|
}));
|
|
77
80
|
}
|
|
78
81
|
initRendererEvents() {
|
|
79
82
|
// Seek on click
|
|
80
83
|
this.subscriptions.push(this.renderer.on('click', ({ relativeX }) => {
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
if (this.options.interactive) {
|
|
85
|
+
const time = this.getDuration() * relativeX;
|
|
86
|
+
this.seekTo(time);
|
|
87
|
+
this.emit('seekClick', { currentTime: this.getCurrentTime() });
|
|
88
|
+
}
|
|
83
89
|
}));
|
|
84
90
|
}
|
|
85
91
|
initTimerEvents() {
|
|
86
92
|
// The timer fires every 16ms for a smooth progress animation
|
|
87
93
|
this.subscriptions.push(this.timer.on('tick', () => {
|
|
88
94
|
const currentTime = this.getCurrentTime();
|
|
89
|
-
this.renderer.renderProgress(currentTime / this.
|
|
90
|
-
this.emit('
|
|
95
|
+
this.renderer.renderProgress(currentTime / this.getDuration(), true);
|
|
96
|
+
this.emit('timeupdate', { currentTime });
|
|
91
97
|
}));
|
|
92
98
|
}
|
|
93
|
-
/** Unmount wavesurfer */
|
|
94
|
-
destroy() {
|
|
95
|
-
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
96
|
-
this.plugins.forEach((plugin) => plugin.destroy());
|
|
97
|
-
this.timer.destroy();
|
|
98
|
-
this.player.destroy();
|
|
99
|
-
this.decoder.destroy();
|
|
100
|
-
this.renderer.destroy();
|
|
101
|
-
}
|
|
102
99
|
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
103
100
|
load(url, channelData, duration) {
|
|
104
101
|
return __awaiter(this, void 0, void 0, function* () {
|
|
105
102
|
this.player.loadUrl(url);
|
|
106
103
|
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
107
|
-
if (channelData == null
|
|
104
|
+
if (channelData == null) {
|
|
108
105
|
const audio = yield this.fetcher.load(url);
|
|
109
106
|
const data = yield this.decoder.decode(audio);
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
this.decodedData = data;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (!duration) {
|
|
111
|
+
duration =
|
|
112
|
+
(yield new Promise((resolve) => {
|
|
113
|
+
this.player.on('canplay', () => resolve(this.getDuration()), { once: true });
|
|
114
|
+
})) || 0;
|
|
115
|
+
}
|
|
116
|
+
this.decodedData = {
|
|
117
|
+
duration,
|
|
118
|
+
numberOfChannels: channelData.length,
|
|
119
|
+
sampleRate: channelData[0].length / duration,
|
|
120
|
+
getChannelData: (i) => channelData[i],
|
|
121
|
+
};
|
|
112
122
|
}
|
|
113
|
-
this.
|
|
114
|
-
this.duration
|
|
115
|
-
this.renderer.render(this.channelData, this.duration);
|
|
116
|
-
this.emit('decode', { duration: this.duration });
|
|
123
|
+
this.renderer.render(this.decodedData);
|
|
124
|
+
this.emit('decode', { duration: this.decodedData.duration });
|
|
117
125
|
});
|
|
118
126
|
}
|
|
119
127
|
/** Zoom in or out */
|
|
120
128
|
zoom(minPxPerSec) {
|
|
121
|
-
if (this.
|
|
129
|
+
if (!this.decodedData) {
|
|
122
130
|
throw new Error('No audio loaded');
|
|
123
131
|
}
|
|
124
|
-
this.renderer.zoom(this.
|
|
132
|
+
this.renderer.zoom(this.decodedData, minPxPerSec);
|
|
125
133
|
}
|
|
126
134
|
/** Start playing the audio */
|
|
127
135
|
play() {
|
|
@@ -141,7 +149,8 @@ export class WaveSurfer extends EventEmitter {
|
|
|
141
149
|
}
|
|
142
150
|
/** Get the duration of the audio in seconds */
|
|
143
151
|
getDuration() {
|
|
144
|
-
|
|
152
|
+
var _a;
|
|
153
|
+
return this.player.getDuration() || ((_a = this.decodedData) === null || _a === void 0 ? void 0 : _a.duration) || 0;
|
|
145
154
|
}
|
|
146
155
|
/** Get the current audio position in seconds */
|
|
147
156
|
getCurrentTime() {
|
|
@@ -172,17 +181,27 @@ export class WaveSurfer extends EventEmitter {
|
|
|
172
181
|
this.player.setPlaybackRate(rate, preservePitch);
|
|
173
182
|
}
|
|
174
183
|
/** Register and initialize a plugin */
|
|
175
|
-
registerPlugin(CustomPlugin) {
|
|
184
|
+
registerPlugin(CustomPlugin, options) {
|
|
176
185
|
const plugin = new CustomPlugin({
|
|
177
186
|
wavesurfer: this,
|
|
178
187
|
container: this.renderer.getContainer(),
|
|
179
|
-
});
|
|
188
|
+
}, options);
|
|
180
189
|
this.plugins.push(plugin);
|
|
181
190
|
return plugin;
|
|
182
191
|
}
|
|
183
192
|
/** Get the decoded audio data */
|
|
184
|
-
|
|
185
|
-
return this.
|
|
193
|
+
getDecodedData() {
|
|
194
|
+
return this.decodedData;
|
|
195
|
+
}
|
|
196
|
+
/** Unmount wavesurfer */
|
|
197
|
+
destroy() {
|
|
198
|
+
this.emit('destroy');
|
|
199
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
200
|
+
this.plugins.forEach((plugin) => plugin.destroy());
|
|
201
|
+
this.timer.destroy();
|
|
202
|
+
this.player.destroy();
|
|
203
|
+
this.decoder.destroy();
|
|
204
|
+
this.renderer.destroy();
|
|
186
205
|
}
|
|
187
206
|
}
|
|
188
207
|
export default WaveSurfer;
|
package/dist/player.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ declare class Player {
|
|
|
6
6
|
media?: HTMLMediaElement;
|
|
7
7
|
autoplay?: boolean;
|
|
8
8
|
});
|
|
9
|
-
on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
|
|
9
|
+
on(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
|
|
10
10
|
destroy(): void;
|
|
11
11
|
loadUrl(src: string): void;
|
|
12
12
|
getCurrentTime(): number;
|
|
@@ -14,6 +14,7 @@ declare class Player {
|
|
|
14
14
|
pause(): void;
|
|
15
15
|
isPlaying(): boolean;
|
|
16
16
|
seekTo(time: number): void;
|
|
17
|
+
getDuration(): number;
|
|
17
18
|
getVolume(): number;
|
|
18
19
|
setVolume(volume: number): void;
|
|
19
20
|
getMuted(): boolean;
|
package/dist/player.js
CHANGED
|
@@ -19,22 +19,18 @@ class Player {
|
|
|
19
19
|
this.media.autoplay = true;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
on(event, callback) {
|
|
23
|
-
|
|
24
|
-
(
|
|
25
|
-
return () => { var _a; return (_a = this.media) === null || _a === void 0 ? void 0 : _a.removeEventListener(event, callback); };
|
|
22
|
+
on(event, callback, options) {
|
|
23
|
+
this.media.addEventListener(event, callback, options);
|
|
24
|
+
return () => this.media.removeEventListener(event, callback);
|
|
26
25
|
}
|
|
27
26
|
destroy() {
|
|
28
|
-
|
|
29
|
-
(_a = this.media) === null || _a === void 0 ? void 0 : _a.pause();
|
|
27
|
+
this.media.pause();
|
|
30
28
|
if (!this.isExternalMedia) {
|
|
31
|
-
|
|
29
|
+
this.media.remove();
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
loadUrl(src) {
|
|
35
|
-
|
|
36
|
-
this.media.src = src;
|
|
37
|
-
}
|
|
33
|
+
this.media.src = src;
|
|
38
34
|
}
|
|
39
35
|
getCurrentTime() {
|
|
40
36
|
return this.media.currentTime;
|
|
@@ -61,6 +57,9 @@ class Player {
|
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
59
|
}
|
|
60
|
+
getDuration() {
|
|
61
|
+
return this.media.duration;
|
|
62
|
+
}
|
|
64
63
|
getVolume() {
|
|
65
64
|
return this.media.volume;
|
|
66
65
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
2
|
import { WaveSurferPluginParams } from '../index.js';
|
|
3
|
+
type RegionsPluginOptions = {
|
|
4
|
+
dragSelection?: boolean;
|
|
5
|
+
draggable?: boolean;
|
|
6
|
+
resizable?: boolean;
|
|
7
|
+
};
|
|
3
8
|
type Region = {
|
|
4
9
|
startTime: number;
|
|
5
10
|
endTime: number;
|
|
@@ -19,7 +24,7 @@ type RegionsPluginEvents = {
|
|
|
19
24
|
region: Region;
|
|
20
25
|
};
|
|
21
26
|
};
|
|
22
|
-
declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
|
|
27
|
+
declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
|
|
23
28
|
private dragStart;
|
|
24
29
|
private wrapper;
|
|
25
30
|
private regions;
|
|
@@ -28,7 +33,7 @@ declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
|
|
|
28
33
|
private isResizingLeft;
|
|
29
34
|
private isMoving;
|
|
30
35
|
/** Create an instance of RegionsPlugin */
|
|
31
|
-
constructor(params: WaveSurferPluginParams);
|
|
36
|
+
constructor(params: WaveSurferPluginParams, options: RegionsPluginOptions);
|
|
32
37
|
/** Unmounts the regions */
|
|
33
38
|
destroy(): void;
|
|
34
39
|
private initWrapper;
|
package/dist/plugins/regions.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
2
|
const MIN_WIDTH = 10;
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
dragSelection: true,
|
|
5
|
+
draggable: true,
|
|
6
|
+
resizable: true,
|
|
7
|
+
};
|
|
3
8
|
const style = (element, styles) => {
|
|
4
9
|
for (const key in styles) {
|
|
5
10
|
element.style[key] = styles[key] || '';
|
|
@@ -12,8 +17,8 @@ const el = (tagName, css) => {
|
|
|
12
17
|
};
|
|
13
18
|
class RegionsPlugin extends BasePlugin {
|
|
14
19
|
/** Create an instance of RegionsPlugin */
|
|
15
|
-
constructor(params) {
|
|
16
|
-
super(params);
|
|
20
|
+
constructor(params, options) {
|
|
21
|
+
super(params, options);
|
|
17
22
|
this.dragStart = NaN;
|
|
18
23
|
this.regions = [];
|
|
19
24
|
this.createdRegion = null;
|
|
@@ -21,7 +26,9 @@ class RegionsPlugin extends BasePlugin {
|
|
|
21
26
|
this.isResizingLeft = false;
|
|
22
27
|
this.isMoving = false;
|
|
23
28
|
this.handleMouseDown = (e) => {
|
|
24
|
-
this.
|
|
29
|
+
if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
|
|
30
|
+
this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
|
|
31
|
+
}
|
|
25
32
|
};
|
|
26
33
|
this.handleMouseMove = (e) => {
|
|
27
34
|
const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
|
|
@@ -57,6 +64,7 @@ class RegionsPlugin extends BasePlugin {
|
|
|
57
64
|
this.dragStart = NaN;
|
|
58
65
|
this.container.style.pointerEvents = '';
|
|
59
66
|
};
|
|
67
|
+
this.options = Object.assign(Object.assign({}, defaultOptions), options);
|
|
60
68
|
this.wrapper = this.initWrapper();
|
|
61
69
|
const unsubscribe = this.wavesurfer.on('decode', () => {
|
|
62
70
|
this.container.appendChild(this.wrapper);
|
|
@@ -87,25 +95,29 @@ class RegionsPlugin extends BasePlugin {
|
|
|
87
95
|
});
|
|
88
96
|
}
|
|
89
97
|
createRegionElement(start, end, title = '') {
|
|
98
|
+
const noWidth = start === end;
|
|
90
99
|
const div = el('div', {
|
|
91
100
|
position: 'absolute',
|
|
92
101
|
left: `${start}px`,
|
|
93
102
|
width: `${end - start}px`,
|
|
94
103
|
height: '100%',
|
|
95
|
-
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
|
104
|
+
backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
|
|
105
|
+
borderRadius: '2px',
|
|
106
|
+
boxSizing: 'border-box',
|
|
107
|
+
borderLeft: '2px solid rgba(0, 0, 0, 0.5)',
|
|
108
|
+
borderRight: noWidth ? '' : '2px solid rgba(0, 0, 0, 0.5)',
|
|
96
109
|
transition: 'background-color 0.2s ease',
|
|
97
|
-
cursor: 'move',
|
|
110
|
+
cursor: this.options.draggable ? 'move' : '',
|
|
98
111
|
pointerEvents: 'all',
|
|
112
|
+
padding: '0.2em',
|
|
99
113
|
});
|
|
100
|
-
div.
|
|
114
|
+
div.textContent = title;
|
|
101
115
|
const leftHandle = el('div', {
|
|
102
116
|
position: 'absolute',
|
|
103
117
|
left: '0',
|
|
104
118
|
width: '6px',
|
|
105
119
|
height: '100%',
|
|
106
|
-
|
|
107
|
-
borderRadius: '2px 0 0 2px',
|
|
108
|
-
cursor: 'ew-resize',
|
|
120
|
+
cursor: this.options.resizable ? 'ew-resize' : '',
|
|
109
121
|
pointerEvents: 'all',
|
|
110
122
|
});
|
|
111
123
|
div.appendChild(leftHandle);
|
|
@@ -114,23 +126,27 @@ class RegionsPlugin extends BasePlugin {
|
|
|
114
126
|
left: '',
|
|
115
127
|
right: '0',
|
|
116
128
|
borderLeft: '',
|
|
117
|
-
borderRight: '2px solid rgba(0, 0, 0, 0.5)',
|
|
118
|
-
borderRadius: '0 2px 2px 0',
|
|
119
129
|
});
|
|
120
130
|
div.appendChild(rightHandle);
|
|
121
131
|
leftHandle.addEventListener('mousedown', (e) => {
|
|
132
|
+
if (!this.options.resizable)
|
|
133
|
+
return;
|
|
122
134
|
e.stopPropagation();
|
|
123
135
|
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
124
136
|
this.isResizingLeft = true;
|
|
125
137
|
this.isMoving = false;
|
|
126
138
|
});
|
|
127
139
|
rightHandle.addEventListener('mousedown', (e) => {
|
|
140
|
+
if (!this.options.resizable)
|
|
141
|
+
return;
|
|
128
142
|
e.stopPropagation();
|
|
129
143
|
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
130
144
|
this.isResizingLeft = false;
|
|
131
145
|
this.isMoving = false;
|
|
132
146
|
});
|
|
133
147
|
div.addEventListener('mousedown', () => {
|
|
148
|
+
if (!this.options.draggable)
|
|
149
|
+
return;
|
|
134
150
|
this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
|
|
135
151
|
this.isMoving = true;
|
|
136
152
|
});
|
|
@@ -146,6 +162,8 @@ class RegionsPlugin extends BasePlugin {
|
|
|
146
162
|
createRegion(start, end, title = '') {
|
|
147
163
|
const duration = this.wavesurfer.getDuration();
|
|
148
164
|
const width = this.wrapper.clientWidth;
|
|
165
|
+
start = Math.max(0, Math.min(start, width - 1));
|
|
166
|
+
end = Math.max(0, Math.min(end, width - 1));
|
|
149
167
|
return {
|
|
150
168
|
element: this.createRegionElement(start, end, title),
|
|
151
169
|
start,
|
|
@@ -160,12 +178,15 @@ class RegionsPlugin extends BasePlugin {
|
|
|
160
178
|
this.emit('region-created', { region });
|
|
161
179
|
}
|
|
162
180
|
updateRegion(region, start, end) {
|
|
181
|
+
const width = this.wrapper.clientWidth;
|
|
163
182
|
if (start != null) {
|
|
164
|
-
|
|
183
|
+
start = Math.max(0, Math.min(start, width));
|
|
184
|
+
region.start = start;
|
|
165
185
|
region.element.style.left = `${region.start}px`;
|
|
166
186
|
region.startTime = (start / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
|
|
167
187
|
}
|
|
168
188
|
if (end != null) {
|
|
189
|
+
end = Math.max(0, Math.min(end, width));
|
|
169
190
|
region.end = end;
|
|
170
191
|
region.element.style.width = `${region.end - region.start}px`;
|
|
171
192
|
region.endTime = (end / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
|
|
@@ -187,7 +208,7 @@ class RegionsPlugin extends BasePlugin {
|
|
|
187
208
|
}
|
|
188
209
|
/** Set the background color of a region */
|
|
189
210
|
setRegionColor(region, color) {
|
|
190
|
-
region.element.style.backgroundColor = color;
|
|
211
|
+
region.element.style[region.startTime === region.endTime ? 'borderColor' : 'backgroundColor'] = color;
|
|
191
212
|
}
|
|
192
213
|
}
|
|
193
214
|
export default RegionsPlugin;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ type RendererOptions = {
|
|
|
4
4
|
height: number;
|
|
5
5
|
waveColor: string;
|
|
6
6
|
progressColor: string;
|
|
7
|
+
cursorColor?: string;
|
|
7
8
|
minPxPerSec: number;
|
|
9
|
+
fillParent: boolean;
|
|
8
10
|
barWidth?: number;
|
|
9
11
|
barGap?: number;
|
|
10
12
|
barRadius?: number;
|
|
@@ -29,8 +31,8 @@ declare class Renderer extends EventEmitter<RendererEvents> {
|
|
|
29
31
|
private delay;
|
|
30
32
|
private renderPeaks;
|
|
31
33
|
private createProgressMask;
|
|
32
|
-
render(
|
|
33
|
-
zoom(
|
|
34
|
+
render(audioData: AudioBuffer): void;
|
|
35
|
+
zoom(audioData: AudioBuffer, minPxPerSec: number): void;
|
|
34
36
|
renderProgress(progress: number, autoCenter?: boolean): void;
|
|
35
37
|
}
|
|
36
38
|
export default Renderer;
|
package/dist/renderer.js
CHANGED
|
@@ -65,7 +65,7 @@ class Renderer extends EventEmitter {
|
|
|
65
65
|
top: 0;
|
|
66
66
|
left: 0;
|
|
67
67
|
height: 100%;
|
|
68
|
-
border-right: 1px solid ${this.options.progressColor};
|
|
68
|
+
border-right: 1px solid ${this.options.cursorColor || this.options.progressColor};
|
|
69
69
|
margin-left: -1px;
|
|
70
70
|
}
|
|
71
71
|
</style>
|
|
@@ -197,24 +197,29 @@ class Renderer extends EventEmitter {
|
|
|
197
197
|
// This rectangle acts as a mask thanks to the composition method
|
|
198
198
|
progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
|
|
199
199
|
}
|
|
200
|
-
render(
|
|
200
|
+
render(audioData) {
|
|
201
201
|
// Determine the width of the canvas
|
|
202
202
|
const { devicePixelRatio } = window;
|
|
203
|
-
const parentWidth = this.scrollContainer.clientWidth * devicePixelRatio;
|
|
204
|
-
const scrollWidth = duration * this.options.minPxPerSec;
|
|
205
|
-
const
|
|
206
|
-
const width =
|
|
203
|
+
const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth * devicePixelRatio : 0;
|
|
204
|
+
const scrollWidth = audioData.duration * this.options.minPxPerSec;
|
|
205
|
+
const isScrolling = scrollWidth > parentWidth;
|
|
206
|
+
const width = isScrolling ? scrollWidth : parentWidth;
|
|
207
207
|
const { height } = this.options;
|
|
208
208
|
this.mainCanvas.width = width;
|
|
209
209
|
this.mainCanvas.height = height;
|
|
210
|
-
this.mainCanvas.style.width =
|
|
210
|
+
this.mainCanvas.style.width = Math.floor(scrollWidth / devicePixelRatio) + 'px';
|
|
211
|
+
// First two channels are used
|
|
212
|
+
const channelData = [audioData.getChannelData(0)];
|
|
213
|
+
if (audioData.numberOfChannels > 1) {
|
|
214
|
+
channelData.push(audioData.getChannelData(1));
|
|
215
|
+
}
|
|
211
216
|
this.renderPeaks(channelData, width, height);
|
|
212
217
|
}
|
|
213
|
-
zoom(
|
|
218
|
+
zoom(audioData, minPxPerSec) {
|
|
214
219
|
// Remember the current cursor position
|
|
215
220
|
const oldCursorPosition = this.cursor.getBoundingClientRect().left;
|
|
216
221
|
this.options.minPxPerSec = minPxPerSec;
|
|
217
|
-
this.render(
|
|
222
|
+
this.render(audioData);
|
|
218
223
|
// Adjust the scroll position so that the cursor stays in the same place
|
|
219
224
|
const newCursortPosition = this.cursor.getBoundingClientRect().left;
|
|
220
225
|
this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>a});const i=class{constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t,i){const n=e=>{e instanceof CustomEvent&&t(e.detail)},s=String(e);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(e,t){return this.on(e,t,!0)}},n=class extends i{constructor(e,t){super(),this.subscriptions=[],this.wavesurfer=e.wavesurfer,this.container=e.container,this.options=t}destroy(){this.subscriptions.forEach((e=>e()))}},s={dragSelection:!0,draggable:!0,resizable:!0},o=(e,t)=>{for(const i in t)e.style[i]=t[i]||""},r=(e,t)=>{const i=document.createElement(e);return o(i,t),i},a=class extends n{constructor(e,t){super(e,t),this.dragStart=NaN,this.regions=[],this.createdRegion=null,this.modifiedRegion=null,this.isResizingLeft=!1,this.isMoving=!1,this.handleMouseDown=e=>{(this.options.draggable||this.options.resizable||this.options.dragSelection)&&(this.dragStart=e.clientX-this.container.getBoundingClientRect().left)},this.handleMouseMove=e=>{const t=e.clientX-this.container.getBoundingClientRect().left;if(this.modifiedRegion&&this.isMoving)return this.moveRegion(this.modifiedRegion,t-this.dragStart),void(this.dragStart=t);if(this.modifiedRegion)this.updateRegion(this.modifiedRegion,this.isResizingLeft?t:void 0,this.isResizingLeft?void 0:t);else if(!isNaN(this.dragStart)){const t=e.clientX-this.wrapper.getBoundingClientRect().left;t-this.dragStart>=10&&(this.createdRegion?this.updateRegion(this.createdRegion,this.dragStart,t):(this.container.style.pointerEvents="none",this.createdRegion=this.createRegion(this.dragStart,t)))}},this.handleMouseUp=()=>{this.createdRegion&&(this.addRegion(this.createdRegion),this.createdRegion=null),this.modifiedRegion=null,this.isMoving=!1,this.dragStart=NaN,this.container.style.pointerEvents=""},this.options=Object.assign(Object.assign({},s),t),this.wrapper=this.initWrapper();const i=this.wavesurfer.on("decode",(()=>{this.container.appendChild(this.wrapper),i()}));this.subscriptions.push(i),this.container.addEventListener("mousedown",this.handleMouseDown),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}destroy(){this.container.removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp,!0),this.wrapper.remove(),super.destroy()}initWrapper(){return r("div",{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"3",pointerEvents:"none"})}createRegionElement(e,t,i=""){const n=e===t,s=r("div",{position:"absolute",left:`${e}px`,width:t-e+"px",height:"100%",backgroundColor:n?"":"rgba(0, 0, 0, 0.1)",borderRadius:"2px",boxSizing:"border-box",borderLeft:"2px solid rgba(0, 0, 0, 0.5)",borderRight:n?"":"2px solid rgba(0, 0, 0, 0.5)",transition:"background-color 0.2s ease",cursor:this.options.draggable?"move":"",pointerEvents:"all",padding:"0.2em"});s.textContent=i;const a=r("div",{position:"absolute",left:"0",width:"6px",height:"100%",cursor:this.options.resizable?"ew-resize":"",pointerEvents:"all"});s.appendChild(a);const d=a.cloneNode();return o(d,{left:"",right:"0",borderLeft:""}),s.appendChild(d),a.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===s))||null,this.isResizingLeft=!0,this.isMoving=!1)})),d.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===s))||null,this.isResizingLeft=!1,this.isMoving=!1)})),s.addEventListener("mousedown",(()=>{this.options.draggable&&(this.modifiedRegion=this.regions.find((e=>e.element===s))||null,this.isMoving=!0)})),s.addEventListener("click",(()=>{const e=this.regions.find((e=>e.element===s));e&&this.emit("region-clicked",{region:e})})),this.wrapper.appendChild(s),s}createRegion(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.wrapper.clientWidth;return e=Math.max(0,Math.min(e,s-1)),t=Math.max(0,Math.min(t,s-1)),{element:this.createRegionElement(e,t,i),start:e,end:t,startTime:e/s*n,endTime:t/s*n,title:i}}addRegion(e){this.regions.push(e),this.emit("region-created",{region:e})}updateRegion(e,t,i){const n=this.wrapper.clientWidth;null!=t&&(t=Math.max(0,Math.min(t,n)),e.start=t,e.element.style.left=`${e.start}px`,e.startTime=t/this.wrapper.clientWidth*this.wavesurfer.getDuration()),null!=i&&(i=Math.max(0,Math.min(i,n)),e.end=i,e.element.style.width=e.end-e.start+"px",e.endTime=i/this.wrapper.clientWidth*this.wavesurfer.getDuration()),this.emit("region-updated",{region:e})}moveRegion(e,t){this.updateRegion(e,e.start+t,e.end+t)}add(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.wrapper.clientWidth,o=e/n*s,r=t/n*s,a=this.createRegion(o,r,i);return this.addRegion(a),a}setRegionColor(e,t){e.element.style[e.startTime===e.endTime?"borderColor":"backgroundColor"]=t}};return t.default})()));
|
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 n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>a});const i=class{constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e,i){const n=t=>{t instanceof CustomEvent&&e(t.detail)},s=String(t);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(t,e){return this.on(t,e,!0)}};const n=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"),n=i.attachShadow({mode:"open"});n.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: visible;\n width: 100%;\n height: ${this.options.height}px;\n position: relative;\n }\n :host .wrapper {\n position: relative;\n width: fit-content;\n min-width: 100%;\n height: 100%;\n z-index: 2;\n }\n :host canvas {\n display: block;\n height: 100%;\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 height: 100%;\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.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=n.querySelector(".scroll"),this.mainCanvas=n.querySelector("canvas"),this.ctx=this.mainCanvas.getContext("2d",{desynchronized:!0}),this.progressCanvas=n.querySelector(".progress"),this.cursor=n.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 n,s,r,o,a;return s=this,r=void 0,a=function*(){const{devicePixelRatio:s}=window,{ctx:r}=this,o=null!=this.options.barWidth?this.options.barWidth*s:1,a=null!=this.options.barGap?this.options.barGap*s:this.options.barWidth?o/2:0,h=null!==(n=this.options.barRadius)&&void 0!==n?n:0,l=t[0],c=l.length,d=Math.floor(e/(o+a))/c,u=i/2,p=1===t.length,m=p?l:t[1],v=p&&m.some((t=>t<0)),y=(t,e)=>{let i=0,n=0,s=0;r.beginPath();for(let c=t;c<e;c++){const t=Math.round(c*d);if(t>i){const e=Math.round(n*u),l=Math.round(s*u);r.roundRect(i*(o+a),u-e,o,e+l||1,h),i=t,n=0,s=0}const e=v?l[c]:Math.abs(l[c]),p=v?m[c]:Math.abs(m[c]);e>n&&(n=e),(v?p<-s:p>s)&&(s=p<0?-p:p)}r.fillStyle=this.options.waveColor,r.fill(),r.closePath()};r.clearRect(0,0,e,i);const{scrollLeft:g,scrollWidth:f,clientWidth:C}=this.scrollContainer,w=c/f,b=Math.floor(g*w),x=Math.ceil((g+C)*w);y(b,x),this.createProgressMask(),b>0&&(yield this.delay((()=>{y(0,b)}))),x<c&&(yield this.delay((()=>{y(x,c)}))),this.delay((()=>{this.createProgressMask()}))},new((o=void 0)||(o=Promise))((function(t,e){function i(t){try{h(a.next(t))}catch(t){e(t)}}function n(t){try{h(a.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,n)}h((a=a.apply(s,r||[])).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,e){const{devicePixelRatio:i}=window,n=this.scrollContainer.clientWidth*i,s=e*this.options.minPxPerSec,r=s>n,o=r?s:n,{height:a}=this.options;this.mainCanvas.width=o,this.mainCanvas.height=a,this.mainCanvas.style.width=r?Math.round(s/i)+"px":"100%",this.renderPeaks(t,o,a)}zoom(t,e,i){const n=this.cursor.getBoundingClientRect().left;this.options.minPxPerSec=i,this.render(t,e);const s=this.cursor.getBoundingClientRect().left;this.scrollContainer.scrollLeft+=s-n}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)}}},s=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 r={height:128,waveColor:"#999",progressColor:"#555",minPxPerSec:0};class o extends i{static create(t){return new o(t)}constructor(t){var e;super(),this.plugins=[],this.subscriptions=[],this.channelData=null,this.duration=0,this.options=Object.assign({},r,t),this.fetcher=new class{load(t){return e=this,i=void 0,s=function*(){return fetch(t).then((t=>t.arrayBuffer()))},new((n=void 0)||(n=Promise))((function(t,r){function o(t){try{h(s.next(t))}catch(t){r(t)}}function a(t){try{h(s.throw(t))}catch(t){r(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof n?i:new n((function(t){t(i)}))).then(o,a)}h((s=s.apply(e,i||[])).next())}));var e,i,n,s}},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,s=function*(){if(!this.audioCtx)throw new Error("AudioContext is not initialized");const e=yield this.audioCtx.decodeAudioData(t),i=[e.getChannelData(0)];return e.numberOfChannels>1&&i.push(e.getChannelData(1)),{duration:e.duration,channelData:i}},new((n=void 0)||(n=Promise))((function(t,r){function o(t){try{h(s.next(t))}catch(t){r(t)}}function a(t){try{h(s.throw(t))}catch(t){r(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof n?i:new n((function(t){t(i)}))).then(o,a)}h((s=s.apply(e,i||[])).next())}));var e,i,n,s}destroy(){var t;null===(t=this.audioCtx)||void 0===t||t.close(),this.audioCtx=null}},this.timer=new s,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){var i;return null===(i=this.media)||void 0===i||i.addEventListener(t,e),()=>{var i;return null===(i=this.media)||void 0===i?void 0:i.removeEventListener(t,e)}}destroy(){var t,e;null===(t=this.media)||void 0===t||t.pause(),this.isExternalMedia||null===(e=this.media)||void 0===e||e.remove()}loadUrl(t){this.media&&(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()}}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 n({container:this.options.container,height:this.options.height,waveColor:this.options.waveColor,progressColor:this.options.progressColor,minPxPerSec:this.options.minPxPerSec,barWidth:this.options.barWidth,barGap:this.options.barGap,barRadius:this.options.barRadius}),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents();const i=this.options.url||(null===(e=this.options.media)||void 0===e?void 0:e.src);i&&this.load(i,this.options.channelData,this.options.duration)}initPlayerEvents(){this.subscriptions.push(this.player.on("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.duration,this.isPlaying()),this.emit("audioprocess",{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("seek",{time:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{const e=this.getDuration()*t;this.seekTo(e)})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.duration,!0),this.emit("audioprocess",{currentTime:t})})))}destroy(){this.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.player.destroy(),this.decoder.destroy(),this.renderer.destroy()}load(t,e,i){return n=this,s=void 0,o=function*(){if(this.player.loadUrl(t),null==e||null==i){const n=yield this.fetcher.load(t),s=yield this.decoder.decode(n);e=s.channelData,i=s.duration}this.channelData=e,this.duration=i,this.renderer.render(this.channelData,this.duration),this.emit("decode",{duration:this.duration})},new((r=void 0)||(r=Promise))((function(t,e){function i(t){try{h(o.next(t))}catch(t){e(t)}}function a(t){try{h(o.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,a)}h((o=o.apply(n,s||[])).next())}));var n,s,r,o}zoom(t){if(null==this.channelData||null==this.duration)throw new Error("No audio loaded");this.renderer.zoom(this.channelData,this.duration,t)}play(){this.player.play()}pause(){this.player.pause()}seekTo(t){this.player.seekTo(t)}isPlaying(){return this.player.isPlaying()}getDuration(){return this.duration}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){const e=new t({wavesurfer:this,container:this.renderer.getContainer()});return this.plugins.push(e),e}getAudioData(){return this.channelData}}const a=o;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:()=>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: visible;\n width: 100%;\n height: ${this.options.height}px;\n position: relative;\n }\n :host .wrapper {\n position: relative;\n width: fit-content;\n min-width: 100%;\n height: 100%;\n z-index: 2;\n }\n :host canvas {\n display: block;\n height: 100%;\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 height: 100%;\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,r,o,a;return n=this,r=void 0,a=function*(){const{devicePixelRatio:n}=window,{ctx:r}=this,o=null!=this.options.barWidth?this.options.barWidth*n:1,a=null!=this.options.barGap?this.options.barGap*n:this.options.barWidth?o/2:0,h=null!==(s=this.options.barRadius)&&void 0!==s?s:0,l=t[0],c=l.length,d=Math.floor(e/(o+a))/c,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;r.beginPath();for(let c=t;c<e;c++){const t=Math.round(c*d);if(t>i){const e=Math.round(s*u),l=Math.round(n*u);r.roundRect(i*(o+a),u-e,o,e+l||1,h),i=t,s=0,n=0}const e=y?l[c]:Math.abs(l[c]),p=y?m[c]:Math.abs(m[c]);e>s&&(s=e),(y?p<-n:p>n)&&(n=p<0?-p:p)}r.fillStyle=this.options.waveColor,r.fill(),r.closePath()};r.clearRect(0,0,e,i);const{scrollLeft:g,scrollWidth:f,clientWidth:C}=this.scrollContainer,w=c/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<c&&(yield this.delay((()=>{v(P,c)}))),this.delay((()=>{this.createProgressMask()}))},new((o=void 0)||(o=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 o?n:new o((function(t){t(n)}))).then(i,s)}h((a=a.apply(n,r||[])).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?s:i,{height:r}=this.options;this.mainCanvas.width=n,this.mainCanvas.height=r,this.mainCanvas.style.width=Math.floor(s/e)+"px";const o=[t.getChannelData(0)];t.numberOfChannels>1&&o.push(t.getChannelData(1)),this.renderPeaks(o,n,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 r={height:128,waveColor:"#999",progressColor:"#555",minPxPerSec:0,fillParent:!0,interactive:!0};class o extends i{static create(t){return new o(t)}constructor(t){var e;super(),this.plugins=[],this.subscriptions=[],this.decodedData=null,this.options=Object.assign({},r,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,r){function o(t){try{h(n.next(t))}catch(t){r(t)}}function a(t){try{h(n.throw(t))}catch(t){r(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(o,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,r){function o(t){try{h(n.next(t))}catch(t){r(t)}}function a(t){try{h(n.throw(t))}catch(t){r(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(o,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();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})})))}load(t,e,i){return s=this,n=void 0,o=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((r=void 0)||(r=Promise))((function(t,e){function i(t){try{h(o.next(t))}catch(t){e(t)}}function a(t){try{h(o.throw(t))}catch(t){e(t)}}function h(e){var s;e.done?t(e.value):(s=e.value,s instanceof r?s:new r((function(t){t(s)}))).then(i,a)}h((o=o.apply(s,n||[])).next())}));var s,n,r,o}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=o;return e.default})()));
|
package/package.json
CHANGED