wavesurfer.js 7.0.0-alpha.5 → 7.0.0-alpha.51
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 +92 -17
- package/dist/base-plugin.d.ts +8 -5
- package/dist/base-plugin.js +10 -3
- package/dist/decoder.d.ts +8 -10
- package/dist/decoder.js +29 -48
- 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 +32 -11
- package/dist/player.js +63 -39
- 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 +35 -0
- package/dist/plugins/minimap.js +100 -0
- package/dist/plugins/minimap.min.js +1 -0
- package/dist/plugins/multitrack.d.ts +117 -0
- package/dist/plugins/multitrack.js +507 -0
- 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 +85 -40
- package/dist/plugins/regions.js +366 -170
- package/dist/plugins/regions.min.js +1 -0
- package/dist/plugins/spectrogram-fft.d.mts +22 -0
- package/dist/plugins/spectrogram-fft.d.ts +9 -0
- package/dist/plugins/spectrogram-fft.js +150 -0
- package/dist/plugins/spectrogram-fft.mjs +175 -0
- package/dist/plugins/spectrogram.d.mts +177 -0
- package/dist/plugins/spectrogram.d.ts +69 -0
- package/dist/plugins/spectrogram.js +340 -0
- package/dist/plugins/spectrogram.min.js +1 -0
- package/dist/plugins/spectrogram.mjs +407 -0
- package/dist/plugins/timeline.d.ts +45 -0
- package/dist/plugins/timeline.js +160 -0
- package/dist/plugins/timeline.min.js +1 -0
- package/dist/renderer.d.ts +34 -14
- package/dist/renderer.js +276 -156
- package/dist/timer.d.ts +1 -1
- package/dist/wavesurfer.d.ts +151 -0
- package/dist/wavesurfer.js +229 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +52 -25
- package/dist/index.d.ts +0 -104
- package/dist/index.js +0 -188
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -32
- package/dist/react/useWavesurfer.d.ts +0 -5
- package/dist/react/useWavesurfer.js +0 -20
- package/dist/wavesurfer.Regions.min.js +0 -1
package/README.md
CHANGED
|
@@ -1,43 +1,118 @@
|
|
|
1
1
|
# <img src="https://user-images.githubusercontent.com/381895/226091100-f5567a28-7736-4d37-8f84-e08f297b7e1a.png" alt="logo" height="60" valign="middle" /> wavesurfer.ts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A rewrite of [wavesufer.js](https://github.com/wavesurfer-js/wavesurfer.js) in TypeScript with better performance.
|
|
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
|
-
|
|
7
|
+
Try it out:
|
|
8
|
+
```
|
|
9
|
+
npm install --save wavesurfer.js@alpha
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Import from a CDN or a local file like this:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
import WaveSurfer from 'https://unpkg.com/wavesurfer.js@alpha'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or, as a script tag which exposes `WaveSurfer` as a global variable:
|
|
19
|
+
```
|
|
20
|
+
<script src="https://unpkg.com/wavesurfer.js@alpha"></script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To import a plugin, e.g. the Timeline plugin:
|
|
24
|
+
```
|
|
25
|
+
import Timeline from 'https://unpkg.com/wavesurfer.js@alpha/dist/plugins/timeline.js'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
|
|
29
|
+
|
|
30
|
+
## Why upgrade to wavesurfer.ts?
|
|
31
|
+
|
|
32
|
+
wavesurfer.js v7 (aka wavesurfer.ts) brings several improvements:
|
|
8
33
|
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Improve the decoding and rendering performance
|
|
34
|
+
* Typed API for better development experience
|
|
35
|
+
* Enhanced decoding and rendering performance
|
|
36
|
+
* New and improved plugins
|
|
13
37
|
|
|
14
|
-
##
|
|
38
|
+
## Plugins
|
|
39
|
+
The "official" plugins have been completely rewritten and enhanced:
|
|
15
40
|
|
|
16
|
-
|
|
41
|
+
* [Regions](https://wavesurfer.pages.dev/examples/#regions.js) – now also replaces the old Markers plugin
|
|
42
|
+
* [Timeline](https://wavesurfer.pages.dev/examples/#timeline.js) – displays notches and time labels below the waveform
|
|
43
|
+
* [Minimap](https://wavesurfer.pages.dev/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
|
|
44
|
+
* [Envelope](https://wavesurfer.pages.dev/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
|
|
45
|
+
* [Record](https://wavesurfer.pages.dev/examples/#record.js) – records audio from the microphone and renders a waveform
|
|
46
|
+
* [Spectrogram](https://wavesurfer.pages.dev/examples/#spectrogram.js) – visualization of an audio frequency spectrum
|
|
17
47
|
|
|
18
|
-
##
|
|
48
|
+
## CSS styling
|
|
19
49
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
wavesurfer.js v7 is rendered into a Shadow DOM tree. This isolates its CSS from the rest of the web page.
|
|
51
|
+
However, it's still possible to style various wavesurfer.js elements via CSS using the `::part()` pseudo-selector.
|
|
52
|
+
For example:
|
|
53
|
+
|
|
54
|
+
```css
|
|
55
|
+
#waveform ::part(cursor):before {
|
|
56
|
+
content: '🏄';
|
|
57
|
+
}
|
|
58
|
+
#waveform ::part(region) {
|
|
59
|
+
font-family: fantasy;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can see which elements you can style in the DOM inspector – they will have a `part` attribute.
|
|
64
|
+
|
|
65
|
+
<img width="466" alt="DOM inspector screenshot" src="https://github.com/katspaugh/wavesurfer.ts/assets/381895/fcfb4e4d-9572-4931-811f-9615b7e3aa85">
|
|
66
|
+
|
|
67
|
+
See [this example](https://wavesurfer.pages.dev/examples/#styling.js) for play around with styling.
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
See the documentation on wavesurfer.js [methods](https://wavesurfer-ts.pages.dev/docs/classes/wavesurfer.WaveSurfer), [options](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferOptions) and [events](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferEvents) on our website.
|
|
71
|
+
|
|
72
|
+
## Migrating from v6 and lower
|
|
73
|
+
|
|
74
|
+
Most options, events, and methods are similar to those in previous versions.
|
|
75
|
+
|
|
76
|
+
### Notable differences
|
|
77
|
+
* The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer.pages.dev/examples/#webaudio.js).
|
|
78
|
+
* The Markers plugin is removed – use the Regions plugin with `startTime` equal to `endTime`.
|
|
79
|
+
* No Microphone plugn – superseded by the new Record plugin with more features.
|
|
80
|
+
* No Cursor and Playhead plugins yet – to be done.
|
|
81
|
+
|
|
82
|
+
### Removed methods
|
|
83
|
+
* `getFilters`, `setFilter` – as there's no Web Audio "backend"
|
|
84
|
+
* `drawBuffer` – to redraw the waveform, use `setOptions` instead and pass new rendering options
|
|
85
|
+
* `cancelAjax` – ajax is replaced by `fetch`
|
|
86
|
+
* `loadBlob` – use `URL.createObjectURL()` to convert a blob to a URL and call `load(url)` instead
|
|
87
|
+
* `skipForward`, `skipBackward`, `setPlayEnd` – can be implemented using `setTime(time)`
|
|
88
|
+
* `exportPCM` is renamed to `getDecodedData` and doesn't take any params
|
|
89
|
+
* `toggleMute` is now called `setMuted(true | false)`
|
|
90
|
+
* `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
|
|
91
|
+
|
|
92
|
+
See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/modules/wavesurfer).
|
|
24
93
|
|
|
25
94
|
## Development
|
|
26
95
|
|
|
27
|
-
|
|
96
|
+
To get started with development, follow these steps:
|
|
97
|
+
|
|
98
|
+
1. Install dev dependencies:
|
|
28
99
|
|
|
29
100
|
```
|
|
30
101
|
yarn
|
|
31
102
|
```
|
|
32
103
|
|
|
33
|
-
Start the TypeScript compiler in watch mode and an HTTP server:
|
|
104
|
+
2. Start the TypeScript compiler in watch mode and launch an HTTP server:
|
|
34
105
|
|
|
35
106
|
```
|
|
36
107
|
yarn start
|
|
37
108
|
```
|
|
38
109
|
|
|
39
|
-
This will open http://localhost:9090/tutorial in your browser with live reload.
|
|
110
|
+
This command will open http://localhost:9090/tutorial in your browser with live reload, allowing you to see the changes as you develop.
|
|
40
111
|
|
|
41
112
|
## Feedback
|
|
42
113
|
|
|
43
|
-
|
|
114
|
+
We appreciate your feedback and contributions! Join the conversation and share your thoughts here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
|
|
115
|
+
|
|
116
|
+
If you encounter any issues or have suggestions for improvements, please don't hesitate to open an issue or submit a pull request on the GitHub repository.
|
|
117
|
+
|
|
118
|
+
We hope you enjoy using wavesurfer.ts and look forward to hearing about your experiences with the library!
|
package/dist/base-plugin.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
-
import type
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
protected
|
|
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
6
|
protected subscriptions: (() => void)[];
|
|
7
|
-
|
|
7
|
+
protected options: Options;
|
|
8
|
+
constructor(options: Options);
|
|
9
|
+
onInit(): void;
|
|
10
|
+
init(wavesurfer: WaveSurfer): void;
|
|
8
11
|
destroy(): void;
|
|
9
12
|
}
|
|
10
13
|
export default BasePlugin;
|
package/dist/base-plugin.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
export class BasePlugin extends EventEmitter {
|
|
3
|
-
constructor(
|
|
3
|
+
constructor(options) {
|
|
4
4
|
super();
|
|
5
5
|
this.subscriptions = [];
|
|
6
|
-
this.
|
|
7
|
-
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
onInit() {
|
|
9
|
+
// Overridden in plugin definition
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
init(wavesurfer) {
|
|
13
|
+
this.wavesurfer = wavesurfer;
|
|
14
|
+
this.onInit();
|
|
8
15
|
}
|
|
9
16
|
destroy() {
|
|
10
17
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
package/dist/decoder.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
destroy(): void;
|
|
10
|
-
}
|
|
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
|
+
};
|
|
11
9
|
export default Decoder;
|
package/dist/decoder.js
CHANGED
|
@@ -1,51 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
constructor() {
|
|
18
|
-
this.audioCtx = null;
|
|
19
|
-
// Minimum sample rate supported by Web Audio API
|
|
20
|
-
const DEFAULT_SAMPLE_RATE = 3000; // Chrome, Safari
|
|
21
|
-
const FALLBACK_SAMPLE_RATE = 8000; // Firefox
|
|
22
|
-
try {
|
|
23
|
-
this.initAudioContext(DEFAULT_SAMPLE_RATE);
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
this.initAudioContext(FALLBACK_SAMPLE_RATE);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
decode(audioData) {
|
|
30
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
-
if (!this.audioCtx) {
|
|
32
|
-
throw new Error('AudioContext is not initialized');
|
|
33
|
-
}
|
|
34
|
-
const buffer = yield this.audioCtx.decodeAudioData(audioData);
|
|
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
|
-
};
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
destroy() {
|
|
46
|
-
var _a;
|
|
47
|
-
(_a = this.audioCtx) === null || _a === void 0 ? void 0 : _a.close();
|
|
48
|
-
this.audioCtx = null;
|
|
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
|
+
/** Create an audio buffer from pre-decoded audio data */
|
|
9
|
+
function createBuffer(channelData, duration) {
|
|
10
|
+
// If a single array of numbers is passed, make it an array of arrays
|
|
11
|
+
if (typeof channelData[0] === 'number')
|
|
12
|
+
channelData = [channelData];
|
|
13
|
+
// Normalize to -1..1
|
|
14
|
+
if (channelData[0].some((n) => n > 1 || n < -1)) {
|
|
15
|
+
const max = Math.max(...channelData[0]);
|
|
16
|
+
channelData = channelData.map((channel) => channel.map((n) => n / max));
|
|
49
17
|
}
|
|
18
|
+
return {
|
|
19
|
+
length: channelData[0].length,
|
|
20
|
+
duration,
|
|
21
|
+
numberOfChannels: channelData.length,
|
|
22
|
+
sampleRate: channelData[0].length / duration,
|
|
23
|
+
getChannelData: (i) => channelData?.[i],
|
|
24
|
+
copyFromChannel: AudioBuffer.prototype.copyFromChannel,
|
|
25
|
+
copyToChannel: AudioBuffer.prototype.copyToChannel,
|
|
26
|
+
};
|
|
50
27
|
}
|
|
28
|
+
const Decoder = {
|
|
29
|
+
decode,
|
|
30
|
+
createBuffer,
|
|
31
|
+
};
|
|
51
32
|
export default Decoder;
|
package/dist/event-emitter.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
export
|
|
2
|
-
[
|
|
3
|
-
}
|
|
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. */
|
|
4
6
|
declare class EventEmitter<EventTypes extends GeneralEventTypes> {
|
|
5
|
-
private
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Subscribe to an event
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
|
|
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;
|
|
12
18
|
}
|
|
13
19
|
export default EventEmitter;
|
package/dist/event-emitter.js
CHANGED
|
@@ -1,25 +1,47 @@
|
|
|
1
|
+
/** A simple event emitter that can be used to listen to and emit events. */
|
|
1
2
|
class EventEmitter {
|
|
2
3
|
constructor() {
|
|
3
|
-
this.
|
|
4
|
+
this.listeners = {};
|
|
4
5
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.
|
|
6
|
+
/** Subscribe to an event. Returns an unsubscribe function. */
|
|
7
|
+
on(eventName, listener) {
|
|
8
|
+
if (!this.listeners[eventName]) {
|
|
9
|
+
this.listeners[eventName] = new Set();
|
|
10
|
+
}
|
|
11
|
+
this.listeners[eventName].add(listener);
|
|
12
|
+
return () => this.un(eventName, listener);
|
|
8
13
|
}
|
|
9
|
-
/** Subscribe to an event
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/** Subscribe to an event only once */
|
|
15
|
+
once(eventName, listener) {
|
|
16
|
+
// The actual subscription
|
|
17
|
+
const unsubscribe = this.on(eventName, listener);
|
|
18
|
+
// Another subscription that will unsubscribe the actual subscription and itself after the first event
|
|
19
|
+
const unsubscribeOnce = this.on(eventName, () => {
|
|
20
|
+
unsubscribe();
|
|
21
|
+
unsubscribeOnce();
|
|
22
|
+
});
|
|
23
|
+
return unsubscribe;
|
|
24
|
+
}
|
|
25
|
+
/** Unsubscribe from an event */
|
|
26
|
+
un(eventName, listener) {
|
|
27
|
+
if (this.listeners[eventName]) {
|
|
28
|
+
if (listener) {
|
|
29
|
+
this.listeners[eventName].delete(listener);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
delete this.listeners[eventName];
|
|
14
33
|
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Clear all events */
|
|
37
|
+
unAll() {
|
|
38
|
+
this.listeners = {};
|
|
19
39
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
/** Emit an event */
|
|
41
|
+
emit(eventName, ...args) {
|
|
42
|
+
if (this.listeners[eventName]) {
|
|
43
|
+
this.listeners[eventName].forEach((listener) => listener(...args));
|
|
44
|
+
}
|
|
23
45
|
}
|
|
24
46
|
}
|
|
25
47
|
export default EventEmitter;
|
package/dist/fetcher.d.ts
CHANGED
package/dist/fetcher.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
class Fetcher {
|
|
11
|
-
load(url) {
|
|
12
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
13
|
-
return fetch(url).then((response) => response.arrayBuffer());
|
|
14
|
-
});
|
|
15
|
-
}
|
|
1
|
+
async function fetchArrayBuffer(url) {
|
|
2
|
+
return fetch(url).then((response) => response.arrayBuffer());
|
|
16
3
|
}
|
|
4
|
+
const Fetcher = {
|
|
5
|
+
fetchArrayBuffer,
|
|
6
|
+
};
|
|
17
7
|
export default Fetcher;
|
package/dist/player.d.ts
CHANGED
|
@@ -1,24 +1,45 @@
|
|
|
1
|
-
|
|
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> {
|
|
2
8
|
protected media: HTMLMediaElement;
|
|
3
9
|
private isExternalMedia;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
|
|
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;
|
|
10
15
|
destroy(): void;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
/** Start playing the audio */
|
|
17
|
+
play(): Promise<void>;
|
|
18
|
+
/** Pause the audio */
|
|
14
19
|
pause(): void;
|
|
20
|
+
/** Check if the audio is playing */
|
|
15
21
|
isPlaying(): boolean;
|
|
16
|
-
|
|
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 */
|
|
17
29
|
getVolume(): number;
|
|
30
|
+
/** Set the audio volume */
|
|
18
31
|
setVolume(volume: number): void;
|
|
32
|
+
/** Get the audio muted state */
|
|
19
33
|
getMuted(): boolean;
|
|
34
|
+
/** Mute or unmute the audio */
|
|
20
35
|
setMuted(muted: boolean): void;
|
|
36
|
+
/** Get the playback speed */
|
|
21
37
|
getPlaybackRate(): number;
|
|
38
|
+
/** Set the playback speed, pass an optional false to NOT preserve the pitch */
|
|
22
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>;
|
|
23
44
|
}
|
|
24
45
|
export default Player;
|
package/dist/player.js
CHANGED
|
@@ -1,81 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import EventEmitter from './event-emitter.js';
|
|
2
|
+
class Player extends EventEmitter {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super();
|
|
3
5
|
this.isExternalMedia = false;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this.media = media;
|
|
6
|
+
if (options.media) {
|
|
7
|
+
this.media = options.media;
|
|
7
8
|
this.isExternalMedia = true;
|
|
8
9
|
}
|
|
9
10
|
else {
|
|
10
11
|
this.media = document.createElement('audio');
|
|
11
12
|
}
|
|
12
|
-
// Track the first play() call
|
|
13
|
-
const unsubscribe = this.on('play', () => {
|
|
14
|
-
this.hasPlayedOnce = true;
|
|
15
|
-
unsubscribe();
|
|
16
|
-
});
|
|
17
13
|
// Autoplay
|
|
18
|
-
if (autoplay) {
|
|
14
|
+
if (options.autoplay) {
|
|
19
15
|
this.media.autoplay = true;
|
|
20
16
|
}
|
|
17
|
+
// Speed
|
|
18
|
+
if (options.playbackRate != null) {
|
|
19
|
+
this.media.playbackRate = options.playbackRate;
|
|
20
|
+
}
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
(
|
|
25
|
-
return () => { var _a; return (_a = this.media) === null || _a === void 0 ? void 0 : _a.removeEventListener(event, callback); };
|
|
22
|
+
onMediaEvent(event, callback, options) {
|
|
23
|
+
this.media.addEventListener(event, callback, options);
|
|
24
|
+
return () => this.media.removeEventListener(event, callback);
|
|
26
25
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(_a = this.media) === null || _a === void 0 ? void 0 : _a.pause();
|
|
30
|
-
if (!this.isExternalMedia) {
|
|
31
|
-
(_b = this.media) === null || _b === void 0 ? void 0 : _b.remove();
|
|
32
|
-
}
|
|
26
|
+
onceMediaEvent(event, callback) {
|
|
27
|
+
return this.onMediaEvent(event, callback, { once: true });
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
if (this.media) {
|
|
36
|
-
this.media.src
|
|
29
|
+
revokeSrc() {
|
|
30
|
+
if (this.media.src?.startsWith('blob:')) {
|
|
31
|
+
URL.revokeObjectURL(this.media.src);
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
setSrc(url, arrayBuffer) {
|
|
35
|
+
if (this.media.src === url)
|
|
36
|
+
return;
|
|
37
|
+
this.revokeSrc();
|
|
38
|
+
const src = arrayBuffer ? URL.createObjectURL(new Blob([arrayBuffer], { type: 'audio/wav' })) : url;
|
|
39
|
+
this.media.src = src;
|
|
40
|
+
}
|
|
41
|
+
destroy() {
|
|
42
|
+
this.media.pause();
|
|
43
|
+
this.revokeSrc();
|
|
44
|
+
if (!this.isExternalMedia) {
|
|
45
|
+
this.media.remove();
|
|
46
|
+
}
|
|
41
47
|
}
|
|
48
|
+
/** Start playing the audio */
|
|
42
49
|
play() {
|
|
43
|
-
this.media.play();
|
|
50
|
+
return this.media.play();
|
|
44
51
|
}
|
|
52
|
+
/** Pause the audio */
|
|
45
53
|
pause() {
|
|
46
54
|
this.media.pause();
|
|
47
55
|
}
|
|
56
|
+
/** Check if the audio is playing */
|
|
48
57
|
isPlaying() {
|
|
49
58
|
return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
|
|
50
59
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const { hasPlayedOnce } = this;
|
|
55
|
-
if (!hasPlayedOnce) {
|
|
56
|
-
this.media.play();
|
|
57
|
-
}
|
|
58
|
-
this.media.currentTime = time;
|
|
59
|
-
if (!hasPlayedOnce) {
|
|
60
|
-
this.media.pause();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
60
|
+
/** Jumpt to a specific time in the audio (in seconds) */
|
|
61
|
+
setTime(time) {
|
|
62
|
+
this.media.currentTime = time;
|
|
63
63
|
}
|
|
64
|
+
/** Get the duration of the audio in seconds */
|
|
65
|
+
getDuration() {
|
|
66
|
+
return this.media.duration;
|
|
67
|
+
}
|
|
68
|
+
/** Get the current audio position in seconds */
|
|
69
|
+
getCurrentTime() {
|
|
70
|
+
return this.media.currentTime;
|
|
71
|
+
}
|
|
72
|
+
/** Get the audio volume */
|
|
64
73
|
getVolume() {
|
|
65
74
|
return this.media.volume;
|
|
66
75
|
}
|
|
76
|
+
/** Set the audio volume */
|
|
67
77
|
setVolume(volume) {
|
|
68
78
|
this.media.volume = volume;
|
|
69
79
|
}
|
|
80
|
+
/** Get the audio muted state */
|
|
70
81
|
getMuted() {
|
|
71
82
|
return this.media.muted;
|
|
72
83
|
}
|
|
84
|
+
/** Mute or unmute the audio */
|
|
73
85
|
setMuted(muted) {
|
|
74
86
|
this.media.muted = muted;
|
|
75
87
|
}
|
|
88
|
+
/** Get the playback speed */
|
|
76
89
|
getPlaybackRate() {
|
|
77
90
|
return this.media.playbackRate;
|
|
78
91
|
}
|
|
92
|
+
/** Set the playback speed, pass an optional false to NOT preserve the pitch */
|
|
79
93
|
setPlaybackRate(rate, preservePitch) {
|
|
80
94
|
// preservePitch is true by default in most browsers
|
|
81
95
|
if (preservePitch != null) {
|
|
@@ -83,5 +97,15 @@ class Player {
|
|
|
83
97
|
}
|
|
84
98
|
this.media.playbackRate = rate;
|
|
85
99
|
}
|
|
100
|
+
/** Get the HTML media element */
|
|
101
|
+
getMediaElement() {
|
|
102
|
+
return this.media;
|
|
103
|
+
}
|
|
104
|
+
/** Set a sink id to change the audio output device */
|
|
105
|
+
setSinkId(sinkId) {
|
|
106
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
|
|
107
|
+
const media = this.media;
|
|
108
|
+
return media.setSinkId(sinkId);
|
|
109
|
+
}
|
|
86
110
|
}
|
|
87
111
|
export default Player;
|