wavesurfer.js 7.0.0-beta.5 → 7.0.0-beta.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 +1 -5
- package/dist/cypress/support/commands.d.ts +1 -0
- package/dist/cypress/support/commands.js +39 -0
- package/dist/cypress/support/e2e.d.ts +1 -0
- package/dist/cypress/support/e2e.js +18 -0
- package/dist/cypress.config.d.ts +2 -0
- package/dist/cypress.config.js +10 -0
- package/dist/decoder.d.ts +1 -1
- package/dist/draggable.d.ts +1 -0
- package/dist/draggable.js +57 -0
- package/dist/plugins/envelope.js +4 -25
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/regions.js +13 -51
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/renderer.d.ts +1 -0
- package/dist/renderer.js +64 -66
- package/dist/src/base-plugin.d.ts +13 -0
- package/dist/src/base-plugin.js +22 -0
- package/dist/src/decoder.d.ts +9 -0
- package/dist/src/decoder.js +48 -0
- package/dist/src/event-emitter.d.ts +19 -0
- package/dist/src/event-emitter.js +45 -0
- package/dist/src/fetcher.d.ts +5 -0
- package/dist/src/fetcher.js +7 -0
- package/dist/src/player.d.ts +45 -0
- package/dist/src/player.js +114 -0
- package/dist/src/plugins/envelope.d.ts +71 -0
- package/dist/src/plugins/envelope.js +350 -0
- package/dist/src/plugins/minimap.d.ts +39 -0
- package/dist/src/plugins/minimap.js +117 -0
- package/dist/src/plugins/record.d.ts +26 -0
- package/dist/src/plugins/record.js +124 -0
- package/dist/src/plugins/regions.d.ts +93 -0
- package/dist/src/plugins/regions.js +395 -0
- package/dist/src/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/src/plugins/spectrogram-fft.js +150 -0
- package/dist/src/plugins/spectrogram.d.ts +69 -0
- package/dist/src/plugins/spectrogram.js +340 -0
- package/dist/src/plugins/timeline.d.ts +45 -0
- package/dist/src/plugins/timeline.js +162 -0
- package/dist/src/renderer.d.ts +41 -0
- package/dist/src/renderer.js +420 -0
- package/dist/src/timer.d.ts +11 -0
- package/dist/src/timer.js +19 -0
- package/dist/src/wavesurfer.d.ts +149 -0
- package/dist/src/wavesurfer.js +229 -0
- package/dist/wavesurfer.d.ts +5 -1
- package/dist/wavesurfer.js +2 -8
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +4 -1
package/dist/renderer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { makeDraggable } from './draggable.js';
|
|
1
2
|
import EventEmitter from './event-emitter.js';
|
|
2
3
|
class Renderer extends EventEmitter {
|
|
3
4
|
constructor(options) {
|
|
@@ -53,25 +54,15 @@ class Renderer extends EventEmitter {
|
|
|
53
54
|
this.resizeObserver.observe(this.scrollContainer);
|
|
54
55
|
}
|
|
55
56
|
initDrag() {
|
|
56
|
-
this.wrapper
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const up = () => {
|
|
68
|
-
document.removeEventListener('mousemove', move);
|
|
69
|
-
document.removeEventListener('mouseup', up);
|
|
70
|
-
this.isDragging = false;
|
|
71
|
-
};
|
|
72
|
-
document.addEventListener('mousemove', move);
|
|
73
|
-
document.addEventListener('mouseup', up);
|
|
74
|
-
});
|
|
57
|
+
makeDraggable(this.wrapper,
|
|
58
|
+
// On drag
|
|
59
|
+
(_, __, x) => {
|
|
60
|
+
this.emit('drag', Math.max(0, Math.min(1, x / this.wrapper.clientWidth)));
|
|
61
|
+
},
|
|
62
|
+
// On start drag
|
|
63
|
+
() => (this.isDragging = true),
|
|
64
|
+
// On end drag
|
|
65
|
+
() => (this.isDragging = false));
|
|
75
66
|
}
|
|
76
67
|
initHtml() {
|
|
77
68
|
const div = document.createElement('div');
|
|
@@ -185,29 +176,63 @@ class Renderer extends EventEmitter {
|
|
|
185
176
|
});
|
|
186
177
|
return gradient;
|
|
187
178
|
}
|
|
188
|
-
|
|
179
|
+
renderBars(channelData, options, ctx) {
|
|
180
|
+
ctx.fillStyle = this.convertColorValues(options.waveColor);
|
|
181
|
+
// Custom rendering function
|
|
182
|
+
if (options.renderFunction) {
|
|
183
|
+
options.renderFunction(channelData, ctx);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const topChannel = channelData[0];
|
|
187
|
+
const bottomChannel = channelData[1] || channelData[0];
|
|
188
|
+
const length = topChannel.length;
|
|
189
189
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
190
|
-
const height =
|
|
191
|
-
const barWidth = options.barWidth != null && !isNaN(options.barWidth) ? options.barWidth * pixelRatio : 1;
|
|
192
|
-
const barGap = options.barGap != null && !isNaN(options.barGap)
|
|
193
|
-
? options.barGap * pixelRatio
|
|
194
|
-
: options.barWidth
|
|
195
|
-
? barWidth / 2
|
|
196
|
-
: 0;
|
|
197
|
-
const barRadius = options.barRadius || 0;
|
|
198
|
-
const barScale = options.barHeight || 1;
|
|
199
|
-
const isMono = channelData.length === 1;
|
|
200
|
-
const leftChannel = channelData[0];
|
|
201
|
-
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
202
|
-
const useNegative = isMono && rightChannel.some((v) => v < 0);
|
|
203
|
-
const length = leftChannel.length;
|
|
204
|
-
const barCount = Math.floor(width / (barWidth + barGap));
|
|
205
|
-
const barIndexScale = barCount / length;
|
|
190
|
+
const { width, height } = ctx.canvas;
|
|
206
191
|
const halfHeight = height / 2;
|
|
192
|
+
const barHeight = options.barHeight || 1;
|
|
193
|
+
const barWidth = options.barWidth ? options.barWidth * pixelRatio : 1;
|
|
194
|
+
const barGap = options.barGap ? options.barGap * pixelRatio : options.barWidth ? barWidth / 2 : 0;
|
|
195
|
+
const barRadius = options.barRadius || 0;
|
|
196
|
+
const barIndexScale = width / (barWidth + barGap) / length;
|
|
197
|
+
let max = 1;
|
|
198
|
+
if (options.normalize) {
|
|
199
|
+
max = 0;
|
|
200
|
+
for (let i = 0; i < length; i++) {
|
|
201
|
+
const value = Math.abs(topChannel[i]);
|
|
202
|
+
if (value > max)
|
|
203
|
+
max = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const vScale = (halfHeight / max) * barHeight;
|
|
207
|
+
ctx.beginPath();
|
|
207
208
|
let prevX = 0;
|
|
208
|
-
let
|
|
209
|
-
let
|
|
209
|
+
let maxTop = 0;
|
|
210
|
+
let maxBottom = 0;
|
|
211
|
+
for (let i = 0; i <= length; i++) {
|
|
212
|
+
const x = Math.round(i * barIndexScale);
|
|
213
|
+
if (x > prevX) {
|
|
214
|
+
const leftBarHeight = Math.round(maxTop * vScale);
|
|
215
|
+
const rightBarHeight = Math.round(maxBottom * vScale);
|
|
216
|
+
ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + rightBarHeight || 1, barRadius);
|
|
217
|
+
prevX = x;
|
|
218
|
+
maxTop = 0;
|
|
219
|
+
maxBottom = 0;
|
|
220
|
+
}
|
|
221
|
+
const magnitudeTop = Math.abs(topChannel[i] || 0);
|
|
222
|
+
const magnitudeBottom = Math.abs(bottomChannel[i] || 0);
|
|
223
|
+
if (magnitudeTop > maxTop)
|
|
224
|
+
maxTop = magnitudeTop;
|
|
225
|
+
if (magnitudeBottom > maxBottom)
|
|
226
|
+
maxBottom = magnitudeBottom;
|
|
227
|
+
}
|
|
228
|
+
ctx.fill();
|
|
229
|
+
ctx.closePath();
|
|
230
|
+
}
|
|
231
|
+
renderSingleCanvas(channelData, options, width, start, end, canvasContainer, progressContainer) {
|
|
232
|
+
const pixelRatio = window.devicePixelRatio || 1;
|
|
233
|
+
const height = options.height || 0;
|
|
210
234
|
const canvas = document.createElement('canvas');
|
|
235
|
+
const length = channelData[0].length;
|
|
211
236
|
canvas.width = Math.round((width * (end - start)) / length);
|
|
212
237
|
canvas.height = height;
|
|
213
238
|
canvas.style.width = `${Math.floor(canvas.width / pixelRatio)}px`;
|
|
@@ -217,34 +242,7 @@ class Renderer extends EventEmitter {
|
|
|
217
242
|
const ctx = canvas.getContext('2d', {
|
|
218
243
|
desynchronized: true,
|
|
219
244
|
});
|
|
220
|
-
|
|
221
|
-
ctx.fillStyle = this.convertColorValues(options.waveColor);
|
|
222
|
-
// Firefox shim until 2023.04.11
|
|
223
|
-
if (!ctx.roundRect)
|
|
224
|
-
ctx.roundRect = ctx.fillRect;
|
|
225
|
-
for (let i = start; i < end; i++) {
|
|
226
|
-
const barIndex = Math.round((i - start) * barIndexScale);
|
|
227
|
-
if (barIndex > prevX) {
|
|
228
|
-
const leftBarHeight = Math.round(prevLeft * halfHeight * barScale);
|
|
229
|
-
const rightBarHeight = Math.round(prevRight * halfHeight * barScale);
|
|
230
|
-
ctx.roundRect(prevX * (barWidth + barGap), halfHeight - leftBarHeight, barWidth, leftBarHeight + (rightBarHeight || 1), barRadius);
|
|
231
|
-
prevX = barIndex;
|
|
232
|
-
prevLeft = 0;
|
|
233
|
-
prevRight = 0;
|
|
234
|
-
}
|
|
235
|
-
const leftValue = useNegative ? leftChannel[i] : Math.abs(leftChannel[i]);
|
|
236
|
-
const rightValue = useNegative ? rightChannel[i] : Math.abs(rightChannel[i]);
|
|
237
|
-
if (leftValue > prevLeft) {
|
|
238
|
-
prevLeft = leftValue;
|
|
239
|
-
}
|
|
240
|
-
// If stereo, both channels are drawn as max values
|
|
241
|
-
// If mono with negative values, the bottom channel will be the min negative values
|
|
242
|
-
if (useNegative ? rightValue < -prevRight : rightValue > prevRight) {
|
|
243
|
-
prevRight = rightValue < 0 ? -rightValue : rightValue;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
ctx.fill();
|
|
247
|
-
ctx.closePath();
|
|
245
|
+
this.renderBars(channelData.map((channel) => channel.slice(start, end)), options, ctx);
|
|
248
246
|
// Draw a progress canvas
|
|
249
247
|
const progressCanvas = canvas.cloneNode();
|
|
250
248
|
progressContainer.appendChild(progressCanvas);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
+
import type WaveSurfer from './wavesurfer.js';
|
|
3
|
+
export type GenericPlugin = BasePlugin<GeneralEventTypes, unknown>;
|
|
4
|
+
export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
|
|
5
|
+
protected wavesurfer?: WaveSurfer;
|
|
6
|
+
protected subscriptions: (() => void)[];
|
|
7
|
+
protected options: Options;
|
|
8
|
+
constructor(options: Options);
|
|
9
|
+
onInit(): void;
|
|
10
|
+
init(wavesurfer: WaveSurfer): void;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
}
|
|
13
|
+
export default BasePlugin;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
export class BasePlugin extends EventEmitter {
|
|
3
|
+
wavesurfer;
|
|
4
|
+
subscriptions = [];
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super();
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
onInit() {
|
|
11
|
+
// Overridden in plugin definition
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
init(wavesurfer) {
|
|
15
|
+
this.wavesurfer = wavesurfer;
|
|
16
|
+
this.onInit();
|
|
17
|
+
}
|
|
18
|
+
destroy() {
|
|
19
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export default BasePlugin;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Decode an array buffer into an audio buffer */
|
|
2
|
+
declare function decode(audioData: ArrayBuffer, sampleRate: number): Promise<AudioBuffer>;
|
|
3
|
+
/** Create an audio buffer from pre-decoded audio data */
|
|
4
|
+
declare function createBuffer(channelData: Float32Array[] | Array<number[]>, duration: number): AudioBuffer;
|
|
5
|
+
declare const Decoder: {
|
|
6
|
+
decode: typeof decode;
|
|
7
|
+
createBuffer: typeof createBuffer;
|
|
8
|
+
};
|
|
9
|
+
export default Decoder;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Decode an array buffer into an audio buffer */
|
|
2
|
+
async function decode(audioData, sampleRate) {
|
|
3
|
+
const audioCtx = new AudioContext({ sampleRate });
|
|
4
|
+
const decode = audioCtx.decodeAudioData(audioData);
|
|
5
|
+
decode.finally(() => audioCtx.close());
|
|
6
|
+
return decode;
|
|
7
|
+
}
|
|
8
|
+
/** Normalize peaks to -1..1 */
|
|
9
|
+
function normalize(channelData) {
|
|
10
|
+
const firstChannel = channelData[0];
|
|
11
|
+
if (firstChannel.some((n) => n > 1 || n < -1)) {
|
|
12
|
+
const length = firstChannel.length;
|
|
13
|
+
let max = 0;
|
|
14
|
+
for (let i = 0; i < length; i++) {
|
|
15
|
+
const absN = Math.abs(firstChannel[i]);
|
|
16
|
+
if (absN > max)
|
|
17
|
+
max = absN;
|
|
18
|
+
}
|
|
19
|
+
for (const channel of channelData) {
|
|
20
|
+
for (let i = 0; i < length; i++) {
|
|
21
|
+
channel[i] /= max;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return channelData;
|
|
26
|
+
}
|
|
27
|
+
/** Create an audio buffer from pre-decoded audio data */
|
|
28
|
+
function createBuffer(channelData, duration) {
|
|
29
|
+
// If a single array of numbers is passed, make it an array of arrays
|
|
30
|
+
if (typeof channelData[0] === 'number')
|
|
31
|
+
channelData = [channelData];
|
|
32
|
+
// Normalize to -1..1
|
|
33
|
+
normalize(channelData);
|
|
34
|
+
return {
|
|
35
|
+
duration,
|
|
36
|
+
length: channelData[0].length,
|
|
37
|
+
sampleRate: channelData[0].length / duration,
|
|
38
|
+
numberOfChannels: channelData.length,
|
|
39
|
+
getChannelData: (i) => channelData?.[i],
|
|
40
|
+
copyFromChannel: AudioBuffer.prototype.copyFromChannel,
|
|
41
|
+
copyToChannel: AudioBuffer.prototype.copyToChannel,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const Decoder = {
|
|
45
|
+
decode,
|
|
46
|
+
createBuffer,
|
|
47
|
+
};
|
|
48
|
+
export default Decoder;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type GeneralEventTypes = {
|
|
2
|
+
[EventName: string]: any[];
|
|
3
|
+
};
|
|
4
|
+
type EventListener<EventTypes extends GeneralEventTypes, EventName extends keyof EventTypes> = (...args: EventTypes[EventName]) => void;
|
|
5
|
+
/** A simple event emitter that can be used to listen to and emit events. */
|
|
6
|
+
declare class EventEmitter<EventTypes extends GeneralEventTypes> {
|
|
7
|
+
private listeners;
|
|
8
|
+
/** Subscribe to an event. Returns an unsubscribe function. */
|
|
9
|
+
on<EventName extends keyof EventTypes>(eventName: EventName, listener: EventListener<EventTypes, EventName>): () => void;
|
|
10
|
+
/** Subscribe to an event only once */
|
|
11
|
+
once<EventName extends keyof EventTypes>(eventName: EventName, listener: EventListener<EventTypes, EventName>): () => void;
|
|
12
|
+
/** Unsubscribe from an event */
|
|
13
|
+
un<EventName extends keyof EventTypes>(eventName: EventName, listener: EventListener<EventTypes, EventName>): void;
|
|
14
|
+
/** Clear all events */
|
|
15
|
+
unAll(): void;
|
|
16
|
+
/** Emit an event */
|
|
17
|
+
protected emit<EventName extends keyof EventTypes>(eventName: EventName, ...args: EventTypes[EventName]): void;
|
|
18
|
+
}
|
|
19
|
+
export default EventEmitter;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** A simple event emitter that can be used to listen to and emit events. */
|
|
2
|
+
class EventEmitter {
|
|
3
|
+
listeners = {};
|
|
4
|
+
/** Subscribe to an event. Returns an unsubscribe function. */
|
|
5
|
+
on(eventName, listener) {
|
|
6
|
+
if (!this.listeners[eventName]) {
|
|
7
|
+
this.listeners[eventName] = new Set();
|
|
8
|
+
}
|
|
9
|
+
this.listeners[eventName].add(listener);
|
|
10
|
+
return () => this.un(eventName, listener);
|
|
11
|
+
}
|
|
12
|
+
/** Subscribe to an event only once */
|
|
13
|
+
once(eventName, listener) {
|
|
14
|
+
// The actual subscription
|
|
15
|
+
const unsubscribe = this.on(eventName, listener);
|
|
16
|
+
// Another subscription that will unsubscribe the actual subscription and itself after the first event
|
|
17
|
+
const unsubscribeOnce = this.on(eventName, () => {
|
|
18
|
+
unsubscribe();
|
|
19
|
+
unsubscribeOnce();
|
|
20
|
+
});
|
|
21
|
+
return unsubscribe;
|
|
22
|
+
}
|
|
23
|
+
/** Unsubscribe from an event */
|
|
24
|
+
un(eventName, listener) {
|
|
25
|
+
if (this.listeners[eventName]) {
|
|
26
|
+
if (listener) {
|
|
27
|
+
this.listeners[eventName].delete(listener);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
delete this.listeners[eventName];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Clear all events */
|
|
35
|
+
unAll() {
|
|
36
|
+
this.listeners = {};
|
|
37
|
+
}
|
|
38
|
+
/** Emit an event */
|
|
39
|
+
emit(eventName, ...args) {
|
|
40
|
+
if (this.listeners[eventName]) {
|
|
41
|
+
this.listeners[eventName].forEach((listener) => listener(...args));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export default EventEmitter;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
+
type PlayerOptions = {
|
|
3
|
+
media?: HTMLMediaElement;
|
|
4
|
+
autoplay?: boolean;
|
|
5
|
+
playbackRate?: number;
|
|
6
|
+
};
|
|
7
|
+
declare class Player<T extends GeneralEventTypes> extends EventEmitter<T> {
|
|
8
|
+
protected media: HTMLMediaElement;
|
|
9
|
+
private isExternalMedia;
|
|
10
|
+
constructor(options: PlayerOptions);
|
|
11
|
+
protected onMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
|
|
12
|
+
protected onceMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
|
|
13
|
+
private revokeSrc;
|
|
14
|
+
protected setSrc(url: string, arrayBuffer?: ArrayBuffer): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
/** Start playing the audio */
|
|
17
|
+
play(): Promise<void>;
|
|
18
|
+
/** Pause the audio */
|
|
19
|
+
pause(): void;
|
|
20
|
+
/** Check if the audio is playing */
|
|
21
|
+
isPlaying(): boolean;
|
|
22
|
+
/** Jumpt to a specific time in the audio (in seconds) */
|
|
23
|
+
setTime(time: number): void;
|
|
24
|
+
/** Get the duration of the audio in seconds */
|
|
25
|
+
getDuration(): number;
|
|
26
|
+
/** Get the current audio position in seconds */
|
|
27
|
+
getCurrentTime(): number;
|
|
28
|
+
/** Get the audio volume */
|
|
29
|
+
getVolume(): number;
|
|
30
|
+
/** Set the audio volume */
|
|
31
|
+
setVolume(volume: number): void;
|
|
32
|
+
/** Get the audio muted state */
|
|
33
|
+
getMuted(): boolean;
|
|
34
|
+
/** Mute or unmute the audio */
|
|
35
|
+
setMuted(muted: boolean): void;
|
|
36
|
+
/** Get the playback speed */
|
|
37
|
+
getPlaybackRate(): number;
|
|
38
|
+
/** Set the playback speed, pass an optional false to NOT preserve the pitch */
|
|
39
|
+
setPlaybackRate(rate: number, preservePitch?: boolean): void;
|
|
40
|
+
/** Get the HTML media element */
|
|
41
|
+
getMediaElement(): HTMLMediaElement;
|
|
42
|
+
/** Set a sink id to change the audio output device */
|
|
43
|
+
setSinkId(sinkId: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export default Player;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
class Player extends EventEmitter {
|
|
3
|
+
media;
|
|
4
|
+
isExternalMedia = false;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
super();
|
|
7
|
+
if (options.media) {
|
|
8
|
+
this.media = options.media;
|
|
9
|
+
this.isExternalMedia = true;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
this.media = document.createElement('audio');
|
|
13
|
+
}
|
|
14
|
+
// Autoplay
|
|
15
|
+
if (options.autoplay) {
|
|
16
|
+
this.media.autoplay = true;
|
|
17
|
+
}
|
|
18
|
+
// Speed
|
|
19
|
+
if (options.playbackRate != null) {
|
|
20
|
+
this.media.playbackRate = options.playbackRate;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
onMediaEvent(event, callback, options) {
|
|
24
|
+
this.media.addEventListener(event, callback, options);
|
|
25
|
+
return () => this.media.removeEventListener(event, callback);
|
|
26
|
+
}
|
|
27
|
+
onceMediaEvent(event, callback) {
|
|
28
|
+
return this.onMediaEvent(event, callback, { once: true });
|
|
29
|
+
}
|
|
30
|
+
revokeSrc() {
|
|
31
|
+
const src = this.media.currentSrc || this.media.src || '';
|
|
32
|
+
if (src.startsWith('blob:')) {
|
|
33
|
+
URL.revokeObjectURL(this.media.currentSrc);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
setSrc(url, arrayBuffer) {
|
|
37
|
+
const src = this.media.currentSrc || this.media.src || '';
|
|
38
|
+
if (src === url)
|
|
39
|
+
return;
|
|
40
|
+
this.revokeSrc();
|
|
41
|
+
const newSrc = arrayBuffer ? URL.createObjectURL(new Blob([arrayBuffer], { type: 'audio/wav' })) : url;
|
|
42
|
+
this.media.src = newSrc;
|
|
43
|
+
}
|
|
44
|
+
destroy() {
|
|
45
|
+
this.media.pause();
|
|
46
|
+
this.revokeSrc();
|
|
47
|
+
if (!this.isExternalMedia) {
|
|
48
|
+
this.media.remove();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Start playing the audio */
|
|
52
|
+
play() {
|
|
53
|
+
return this.media.play();
|
|
54
|
+
}
|
|
55
|
+
/** Pause the audio */
|
|
56
|
+
pause() {
|
|
57
|
+
this.media.pause();
|
|
58
|
+
}
|
|
59
|
+
/** Check if the audio is playing */
|
|
60
|
+
isPlaying() {
|
|
61
|
+
return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
|
|
62
|
+
}
|
|
63
|
+
/** Jumpt to a specific time in the audio (in seconds) */
|
|
64
|
+
setTime(time) {
|
|
65
|
+
this.media.currentTime = time;
|
|
66
|
+
}
|
|
67
|
+
/** Get the duration of the audio in seconds */
|
|
68
|
+
getDuration() {
|
|
69
|
+
return this.media.duration;
|
|
70
|
+
}
|
|
71
|
+
/** Get the current audio position in seconds */
|
|
72
|
+
getCurrentTime() {
|
|
73
|
+
return this.media.currentTime;
|
|
74
|
+
}
|
|
75
|
+
/** Get the audio volume */
|
|
76
|
+
getVolume() {
|
|
77
|
+
return this.media.volume;
|
|
78
|
+
}
|
|
79
|
+
/** Set the audio volume */
|
|
80
|
+
setVolume(volume) {
|
|
81
|
+
this.media.volume = volume;
|
|
82
|
+
}
|
|
83
|
+
/** Get the audio muted state */
|
|
84
|
+
getMuted() {
|
|
85
|
+
return this.media.muted;
|
|
86
|
+
}
|
|
87
|
+
/** Mute or unmute the audio */
|
|
88
|
+
setMuted(muted) {
|
|
89
|
+
this.media.muted = muted;
|
|
90
|
+
}
|
|
91
|
+
/** Get the playback speed */
|
|
92
|
+
getPlaybackRate() {
|
|
93
|
+
return this.media.playbackRate;
|
|
94
|
+
}
|
|
95
|
+
/** Set the playback speed, pass an optional false to NOT preserve the pitch */
|
|
96
|
+
setPlaybackRate(rate, preservePitch) {
|
|
97
|
+
// preservePitch is true by default in most browsers
|
|
98
|
+
if (preservePitch != null) {
|
|
99
|
+
this.media.preservesPitch = preservePitch;
|
|
100
|
+
}
|
|
101
|
+
this.media.playbackRate = rate;
|
|
102
|
+
}
|
|
103
|
+
/** Get the HTML media element */
|
|
104
|
+
getMediaElement() {
|
|
105
|
+
return this.media;
|
|
106
|
+
}
|
|
107
|
+
/** Set a sink id to change the audio output device */
|
|
108
|
+
setSinkId(sinkId) {
|
|
109
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
|
|
110
|
+
const media = this.media;
|
|
111
|
+
return media.setSinkId(sinkId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export default Player;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin from '../base-plugin.js';
|
|
5
|
+
export type EnvelopePluginOptions = {
|
|
6
|
+
fadeInStart?: number;
|
|
7
|
+
fadeInEnd?: number;
|
|
8
|
+
fadeOutStart?: number;
|
|
9
|
+
fadeOutEnd?: number;
|
|
10
|
+
volume?: number;
|
|
11
|
+
lineWidth?: string;
|
|
12
|
+
lineColor?: string;
|
|
13
|
+
dragPointSize?: number;
|
|
14
|
+
dragPointFill?: string;
|
|
15
|
+
dragPointStroke?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const defaultOptions: {
|
|
18
|
+
fadeInStart: number;
|
|
19
|
+
fadeOutEnd: number;
|
|
20
|
+
fadeInEnd: number;
|
|
21
|
+
fadeOutStart: number;
|
|
22
|
+
lineWidth: number;
|
|
23
|
+
lineColor: string;
|
|
24
|
+
dragPointSize: number;
|
|
25
|
+
dragPointFill: string;
|
|
26
|
+
dragPointStroke: string;
|
|
27
|
+
};
|
|
28
|
+
type Options = EnvelopePluginOptions & typeof defaultOptions;
|
|
29
|
+
export type EnvelopePluginEvents = {
|
|
30
|
+
'fade-in-change': [time: number];
|
|
31
|
+
'fade-out-change': [time: number];
|
|
32
|
+
'volume-change': [volume: number];
|
|
33
|
+
};
|
|
34
|
+
declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePluginOptions> {
|
|
35
|
+
protected options: Options;
|
|
36
|
+
private polyline;
|
|
37
|
+
private audioContext;
|
|
38
|
+
private gainNode;
|
|
39
|
+
private volume;
|
|
40
|
+
private isFadingIn;
|
|
41
|
+
private isFadingOut;
|
|
42
|
+
private readonly naturalVolumeExponent;
|
|
43
|
+
constructor(options: EnvelopePluginOptions);
|
|
44
|
+
static create(options: EnvelopePluginOptions): EnvelopePlugin;
|
|
45
|
+
destroy(): void;
|
|
46
|
+
/** Called by wavesurfer, don't call manually */
|
|
47
|
+
onInit(): void;
|
|
48
|
+
private initSvg;
|
|
49
|
+
private renderPolyline;
|
|
50
|
+
private initWebAudio;
|
|
51
|
+
private invertNaturalVolume;
|
|
52
|
+
private naturalVolume;
|
|
53
|
+
private setGainValue;
|
|
54
|
+
private initFadeEffects;
|
|
55
|
+
/** Get the current audio volume */
|
|
56
|
+
getCurrentVolume(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Set the fade-in start time.
|
|
59
|
+
* @param time The time (in seconds) to set the fade-in start time to
|
|
60
|
+
* @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
|
|
61
|
+
*/
|
|
62
|
+
setStartTime(time: number, moveFadeInEnd?: boolean): void;
|
|
63
|
+
/** Set the fade-in end time.
|
|
64
|
+
* @param time The time (in seconds) to set the fade-in end time to
|
|
65
|
+
* @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
|
|
66
|
+
*/
|
|
67
|
+
setEndTime(time: number, moveFadeOutStart?: boolean): void;
|
|
68
|
+
/** Set the volume of the audio */
|
|
69
|
+
setVolume(volume: number): void;
|
|
70
|
+
}
|
|
71
|
+
export default EnvelopePlugin;
|