wavesurfer.js 7.0.0-alpha.9 → 7.0.0-beta.10

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.
Files changed (55) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +137 -20
  3. package/dist/base-plugin.d.ts +6 -4
  4. package/dist/base-plugin.js +9 -3
  5. package/dist/decoder.d.ts +8 -7
  6. package/dist/decoder.js +43 -38
  7. package/dist/draggable.d.ts +1 -0
  8. package/dist/draggable.js +59 -0
  9. package/dist/event-emitter.d.ts +16 -10
  10. package/dist/event-emitter.js +38 -16
  11. package/dist/fetcher.d.ts +6 -3
  12. package/dist/fetcher.js +9 -15
  13. package/dist/player.d.ts +31 -11
  14. package/dist/player.js +58 -31
  15. package/dist/plugins/envelope.d.ts +71 -0
  16. package/dist/plugins/envelope.js +326 -0
  17. package/dist/plugins/envelope.min.cjs +1 -0
  18. package/dist/plugins/minimap.d.ts +39 -0
  19. package/dist/plugins/minimap.js +114 -0
  20. package/dist/plugins/minimap.min.cjs +1 -0
  21. package/dist/plugins/record.d.ts +28 -0
  22. package/dist/plugins/record.js +132 -0
  23. package/dist/plugins/record.min.cjs +1 -0
  24. package/dist/plugins/regions.d.ts +84 -43
  25. package/dist/plugins/regions.js +330 -195
  26. package/dist/plugins/regions.min.cjs +1 -0
  27. package/dist/plugins/spectrogram-fft.d.ts +9 -0
  28. package/dist/plugins/spectrogram-fft.js +150 -0
  29. package/dist/plugins/spectrogram.d.ts +72 -0
  30. package/dist/plugins/spectrogram.js +341 -0
  31. package/dist/plugins/spectrogram.min.cjs +1 -0
  32. package/dist/plugins/timeline.d.ts +25 -9
  33. package/dist/plugins/timeline.js +65 -24
  34. package/dist/plugins/timeline.min.cjs +1 -0
  35. package/dist/renderer.d.ts +32 -26
  36. package/dist/renderer.js +363 -167
  37. package/dist/timer.d.ts +1 -1
  38. package/dist/wavesurfer.d.ts +154 -0
  39. package/dist/wavesurfer.js +230 -0
  40. package/dist/wavesurfer.min.cjs +1 -0
  41. package/package.json +61 -28
  42. package/dist/index.d.ts +0 -117
  43. package/dist/index.js +0 -227
  44. package/dist/player-webaudio.d.ts +0 -8
  45. package/dist/player-webaudio.js +0 -32
  46. package/dist/plugins/multitrack.d.ts +0 -44
  47. package/dist/plugins/multitrack.js +0 -260
  48. package/dist/plugins/xmultitrack.d.ts +0 -44
  49. package/dist/plugins/xmultitrack.js +0 -260
  50. package/dist/react/useWavesurfer.d.ts +0 -5
  51. package/dist/react/useWavesurfer.js +0 -20
  52. package/dist/wavesurfer.Multitrack.min.js +0 -1
  53. package/dist/wavesurfer.Regions.min.js +0 -1
  54. package/dist/wavesurfer.Timeline.min.js +0 -1
  55. package/dist/wavesurfer.min.js +0 -1
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2012-2023, katspaugh and contributors
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,48 +1,165 @@
1
- # <img src="https://user-images.githubusercontent.com/381895/226091100-f5567a28-7736-4d37-8f84-e08f297b7e1a.png" alt="logo" height="60" valign="middle" /> wavesurfer.ts
1
+ # <img src="https://user-images.githubusercontent.com/381895/226091100-f5567a28-7736-4d37-8f84-e08f297b7e1a.png" alt="logo" height="60" valign="middle" /> wavesurfer.js
2
2
 
3
- An experimental rewrite of [wavesufer.js](https://github.com/wavesurfer-js/wavesurfer.js) to play around with new ideas.
3
+ [![npm](https://img.shields.io/npm/v/wavesurfer.js/beta)](https://www.npmjs.com/package/wavesurfer.js)
4
4
 
5
- <img alt="screenshot" src="https://user-images.githubusercontent.com/381895/225539680-fc724acd-8657-458e-a558-ff1c6758ba30.png" width="800" />
5
+ ## New TypeScript version
6
+
7
+ wavesurfer.js v7 beta is a TypeScript rewrite of wavesurfer.js that brings several improvements:
8
+
9
+ * Typed API for better development experience
10
+ * Enhanced decoding and rendering performance
11
+ * New and improved plugins
12
+
13
+ <img width="626" alt="waveform screenshot" src="https://github.com/katspaugh/wavesurfer.js/assets/381895/05f03bed-800e-4fa1-b09a-82a39a1c62ce">
14
+
15
+ ---
16
+
17
+ ℹ️ Looking for the old stable version? V6 is here: https://github.com/katspaugh/wavesurfer.js/tree/v6
18
+
19
+ ---
6
20
 
7
21
  Try it out:
22
+
23
+ ```bash
24
+ npm install --save wavesurfer.js@beta
25
+ ```
26
+ ```js
27
+ import WaveSurfer from 'wavesurfer.js'
28
+ ```
29
+
30
+ Alternatively, import it from a CDN as a ES6 module directly in the browser:
31
+
32
+ ```html
33
+ <script type="module">
34
+ import WaveSurfer from 'https://unpkg.com/wavesurfer.js@beta'
35
+
36
+ const wavesurfer = WaveSurfer.create({ ... })
37
+ </script>
8
38
  ```
9
- npm install --save wavesurfer.js@alpha
39
+
40
+ Or, as a UMD script tag which exports the library as a global `WaveSurfer` variable:
41
+ ```html
42
+ <script src="https://unpkg.com/wavesurfer.js@beta/dist/wavesurfer.min.cjs"></script>
43
+ ```
44
+
45
+ To import a plugin, e.g. the Timeline plugin:
46
+ ```js
47
+ import Timeline from 'https://unpkg.com/wavesurfer.js@beta/dist/plugins/timeline.js'
10
48
  ```
11
49
 
12
- ## Goals
50
+ TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
13
51
 
14
- * TypeScript API
15
- * Better architecture
16
- * Minimize the available options and provide sensible defaults
17
- * Improve the decoding and rendering performance
52
+ See more [examples](https://wavesurfer-js.org/examples).
18
53
 
19
- ## Non-goals
54
+ ## Documentation
20
55
 
21
- Keeping backwards compatibility with earlier versions of wavesurfer.js.
56
+ See the documentation on wavesurfer.js [methods](http://wavesurfer-js.org/docs/methods), [options](http://wavesurfer-js.org/docs/options) and [events](http://wavesurfer-js.org/docs/events) on our website.
22
57
 
23
- ## Architecture
58
+ ## Plugins
24
59
 
25
- Principles:
26
- * Modular and event-driven
27
- * Flexible (e.g. allow custom media elements and user-defined Web Audio graphs)
28
- * Extensible with plugins
60
+ The "official" plugins have been completely rewritten and enhanced:
61
+
62
+ * [Regions](https://wavesurfer-js.org/examples/#regions.js) visual overlays and markers for regions of audio
63
+ * [Timeline](https://wavesurfer-js.org/examples/#timeline.js) displays notches and time labels below the waveform
64
+ * [Minimap](https://wavesurfer-js.org/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
65
+ * [Envelope](https://wavesurfer-js.org/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
66
+ * [Record](https://wavesurfer-js.org/examples/#record.js) – records audio from the microphone and renders a waveform
67
+ * [Spectrogram](https://wavesurfer-js.org/examples/#spectrogram.js) – visualization of an audio frequency spectrum
68
+
69
+ ## CSS styling
70
+
71
+ wavesurfer.js v7 is rendered into a Shadow DOM tree. This isolates its CSS from the rest of the web page.
72
+ However, it's still possible to style various wavesurfer.js elements via CSS using the `::part()` pseudo-selector.
73
+ For example:
74
+
75
+ ```css
76
+ #waveform ::part(cursor):before {
77
+ content: '🏄';
78
+ }
79
+ #waveform ::part(region) {
80
+ font-family: fantasy;
81
+ }
82
+ ```
83
+
84
+ You can see which elements you can style in the DOM inspector – they will have a `part` attribute.
85
+ See [this example](https://wavesurfer-js.org/examples/#styling.js) for play around with styling.
86
+
87
+ ## Upgrading from v6
88
+
89
+ Most options, events, and methods are similar to those in previous versions.
90
+
91
+ ### Notable differences
92
+ * The `backend` option is removed – [HTML5 audio (or video) is the only playback mechanism](https://github.com/katspaugh/wavesurfer.js/discussions/2762#discussioncomment-5669347). However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer-js.org/examples/#webaudio.js).
93
+ * The Markers plugin is removed – use the Regions plugin with just a `startTime`.
94
+ * No Microphone plugn – superseded by the new Record plugin with more features.
95
+ * No Cursor and Playhead plugins yet – to be done.
96
+
97
+ ### Removed options
98
+ * `backend`, `audioContext`, `closeAudioContext', 'audioScriptProcessor` – there's no Web Audio backend, so no AudioContext
99
+ * `autoCenterImmediately` – `autoCenter` is now always immediate unless the audio is playing
100
+ * `backgroundColor`, `hideCursor` – this can be easily set via CSS
101
+ * `mediaType`, `mediaControls` – you should instead pass an entire media element in the `media` option. [Example](https://wavesurfer-js.org/examples/#video.js).
102
+ * `partialRender` – done by default
103
+ * `pixelRatio` – `window.devicePixelRatio` is used by default
104
+ * `renderer` – there's just one renderer for now, so no need for this option
105
+ * `responsive` – responsiveness is enabled by default
106
+ * `scrollParent` – the container will scroll if `minPxPerSec` is set to a higher value
107
+ * `skipLength` – there's no `skipForward` and `skipBackward` methods anymore
108
+ * `splitChannelsOptions` – you should now use `splitChannels` to pass the channel options. Pass `height: 0` to hide a channel. See [this example](https://wavesurfer-js.org/examples/#split-channels.js).
109
+ * `xhr`, `drawingContextAttributes`, `maxCanvasWidth`, `forceDecode` – removed to reduce code complexity
110
+
111
+ ### Removed methods
112
+ * `getFilters`, `setFilter` – as there's no Web Audio "backend"
113
+ * `drawBuffer` – to redraw the waveform, use `setOptions` instead and pass new rendering options
114
+ * `cancelAjax` – ajax is replaced by `fetch`
115
+ * `loadBlob` – use `URL.createObjectURL()` to convert a blob to a URL and call `load(url)` instead
116
+ * `skipForward`, `skipBackward`, `setPlayEnd` – can be implemented using `setTime(time)`
117
+ * `exportPCM` is renamed to `getDecodedData` and doesn't take any params
118
+ * `toggleMute` is now called `setMuted(true | false)`
119
+ * `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
120
+
121
+ See the complete [documentation of the new API](http://wavesurfer-js.org/docs/methods).
122
+
123
+ ## Questions
124
+
125
+ Have a question about integrating wavesurfer.js on your website? Feel free to ask in our [Discussions forum](https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/q-a).
126
+
127
+ ### FAQ
128
+
129
+ * **Q**: Does wavesurfer support large files?
130
+ * **A**: Since wavesurfer decodes audio entirely in the browser, large files may fail to decode due to memory constraints. We recommend using pre-decoded peaks for large files (see [this example](https://wavesurfer-js.org/examples/#predecoded.js)). You can use a tool like [bbc/audiowaveform](https://github.com/bbc/audiowaveform) to generate peaks.
29
131
 
30
132
  ## Development
31
133
 
32
- Install dev dependencies:
134
+ To get started with development, follow these steps:
135
+
136
+ 1. Install dev dependencies:
33
137
 
34
138
  ```
35
139
  yarn
36
140
  ```
37
141
 
38
- Start the TypeScript compiler in watch mode and an HTTP server:
142
+ 2. Start the TypeScript compiler in watch mode and launch an HTTP server:
39
143
 
40
144
  ```
41
145
  yarn start
42
146
  ```
43
147
 
44
- This will open http://localhost:9090/tutorial in your browser with live reload.
148
+ This command will open http://localhost:9090 in your browser with live reload, allowing you to see the changes as you develop.
149
+
150
+ ## Tests
151
+
152
+ The tests are written in the Cypress framework. They are a mix of e2e and visual regression tests.
153
+ To run the test suite locally:
154
+
155
+ ```
156
+ yarn cypress
157
+ ```
45
158
 
46
159
  ## Feedback
47
160
 
48
- Your feedback is very welcome here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
161
+ We appreciate your feedback and contributions! Join the conversation and share your thoughts here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2789
162
+
163
+ 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.
164
+
165
+ We hope you enjoy using wavesurfer.ts and look forward to hearing about your experiences with the library!
@@ -1,11 +1,13 @@
1
1
  import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
2
- import type { WaveSurfer, WaveSurferPluginParams } from './index.js';
2
+ import type WaveSurfer from './wavesurfer.js';
3
+ export type GenericPlugin = BasePlugin<GeneralEventTypes, unknown>;
3
4
  export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
4
- protected wavesurfer: WaveSurfer;
5
- protected container: HTMLElement;
5
+ protected wavesurfer?: WaveSurfer;
6
6
  protected subscriptions: (() => void)[];
7
7
  protected options: Options;
8
- constructor(params: WaveSurferPluginParams, options: Options);
8
+ constructor(options: Options);
9
+ onInit(): void;
10
+ init(wavesurfer: WaveSurfer): void;
9
11
  destroy(): void;
10
12
  }
11
13
  export default BasePlugin;
@@ -1,12 +1,18 @@
1
1
  import EventEmitter from './event-emitter.js';
2
2
  export class BasePlugin extends EventEmitter {
3
- constructor(params, options) {
3
+ constructor(options) {
4
4
  super();
5
5
  this.subscriptions = [];
6
- this.wavesurfer = params.wavesurfer;
7
- this.container = params.container;
8
6
  this.options = options;
9
7
  }
8
+ onInit() {
9
+ // Overridden in plugin definition
10
+ return;
11
+ }
12
+ init(wavesurfer) {
13
+ this.wavesurfer = wavesurfer;
14
+ this.onInit();
15
+ }
10
16
  destroy() {
11
17
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
12
18
  }
package/dist/decoder.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- declare class Decoder {
2
- audioCtx: AudioContext | null;
3
- private initAudioContext;
4
- constructor();
5
- decode(audioData: ArrayBuffer): Promise<AudioBuffer>;
6
- destroy(): void;
7
- }
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: Array<Float32Array | number[]>, duration: number): AudioBuffer;
5
+ declare const Decoder: {
6
+ decode: typeof decode;
7
+ createBuffer: typeof createBuffer;
8
+ };
8
9
  export default Decoder;
package/dist/decoder.js CHANGED
@@ -1,43 +1,48 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
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 Decoder {
11
- initAudioContext(sampleRate) {
12
- this.audioCtx = new AudioContext({
13
- latencyHint: 'playback',
14
- sampleRate,
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);
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;
27
18
  }
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');
19
+ for (const channel of channelData) {
20
+ for (let i = 0; i < length; i++) {
21
+ channel[i] /= max;
33
22
  }
34
- return this.audioCtx.decodeAudioData(audioData);
35
- });
36
- }
37
- destroy() {
38
- var _a;
39
- (_a = this.audioCtx) === null || _a === void 0 ? void 0 : _a.close();
40
- this.audioCtx = null;
23
+ }
41
24
  }
25
+ return channelData;
42
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
+ };
43
48
  export default Decoder;
@@ -0,0 +1 @@
1
+ export declare function makeDraggable(element: HTMLElement | null, onDrag: (dx: number, dy: number, x: number, y: number) => void, onStart?: (x: number, y: number) => void, onEnd?: () => void, threshold?: number): () => void;
@@ -0,0 +1,59 @@
1
+ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 5) {
2
+ let unsub = () => {
3
+ return;
4
+ };
5
+ if (!element)
6
+ return unsub;
7
+ const down = (e) => {
8
+ e.preventDefault();
9
+ e.stopPropagation();
10
+ let startX = e.clientX;
11
+ let startY = e.clientY;
12
+ let isDragging = false;
13
+ const move = (e) => {
14
+ e.preventDefault();
15
+ e.stopPropagation();
16
+ const x = e.clientX;
17
+ const y = e.clientY;
18
+ if (isDragging || Math.abs(x - startX) >= threshold || Math.abs(y - startY) >= threshold) {
19
+ if (!isDragging) {
20
+ isDragging = true;
21
+ onStart?.(startX, startY);
22
+ }
23
+ const { left, top } = element.getBoundingClientRect();
24
+ onDrag(x - startX, y - startY, x - left, y - top);
25
+ startX = x;
26
+ startY = y;
27
+ }
28
+ };
29
+ const click = (e) => {
30
+ if (isDragging) {
31
+ e.preventDefault();
32
+ e.stopPropagation();
33
+ }
34
+ };
35
+ const up = () => {
36
+ if (isDragging) {
37
+ onEnd?.();
38
+ }
39
+ unsub();
40
+ };
41
+ unsub = () => {
42
+ document.removeEventListener('pointermove', move);
43
+ document.removeEventListener('pointerup', up);
44
+ document.removeEventListener('pointerleave', up);
45
+ setTimeout(() => {
46
+ document.removeEventListener('click', click, true);
47
+ }, 10);
48
+ };
49
+ document.addEventListener('pointermove', move);
50
+ document.addEventListener('pointerup', up);
51
+ document.addEventListener('pointerleave', up);
52
+ document.addEventListener('click', click, true);
53
+ };
54
+ element.addEventListener('pointerdown', down);
55
+ return () => {
56
+ unsub();
57
+ element.removeEventListener('pointerdown', down);
58
+ };
59
+ }
@@ -1,13 +1,19 @@
1
- export interface GeneralEventTypes {
2
- [eventType: string]: unknown;
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 eventTarget;
6
- constructor();
7
- protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void;
8
- /** Subscribe to an event and return a function to unsubscribe */
9
- on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void, once?: boolean): () => void;
10
- /** Subscribe to an event once and return a function to unsubscribe */
11
- once<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void;
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;
@@ -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.eventTarget = new EventTarget();
4
+ this.listeners = {};
4
5
  }
5
- emit(eventType, detail) {
6
- const e = new CustomEvent(String(eventType), { detail });
7
- this.eventTarget.dispatchEvent(e);
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 and return a function to unsubscribe */
10
- on(eventType, callback, once) {
11
- const handler = (e) => {
12
- if (e instanceof CustomEvent) {
13
- callback(e.detail);
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
- const eventName = String(eventType);
17
- this.eventTarget.addEventListener(eventName, handler, { once });
18
- return () => this.eventTarget.removeEventListener(eventName, handler);
34
+ }
35
+ }
36
+ /** Clear all events */
37
+ unAll() {
38
+ this.listeners = {};
19
39
  }
20
- /** Subscribe to an event once and return a function to unsubscribe */
21
- once(eventType, callback) {
22
- return this.on(eventType, callback, true);
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
@@ -1,4 +1,7 @@
1
- declare class Fetcher {
2
- load(url: string): Promise<ArrayBuffer>;
3
- }
1
+ declare function fetchArrayBuffer(url: string): Promise<ArrayBuffer>;
2
+ declare function fetchBlob(url: string): Promise<Blob>;
3
+ declare const Fetcher: {
4
+ fetchArrayBuffer: typeof fetchArrayBuffer;
5
+ fetchBlob: typeof fetchBlob;
6
+ };
4
7
  export default Fetcher;
package/dist/fetcher.js CHANGED
@@ -1,17 +1,11 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
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());
3
+ }
4
+ async function fetchBlob(url) {
5
+ return fetch(url).then((response) => response.blob());
16
6
  }
7
+ const Fetcher = {
8
+ fetchArrayBuffer,
9
+ fetchBlob,
10
+ };
17
11
  export default Fetcher;
package/dist/player.d.ts CHANGED
@@ -1,25 +1,45 @@
1
- declare class Player {
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
- private hasPlayedOnce;
5
- constructor({ media, autoplay }: {
6
- media?: HTMLMediaElement;
7
- autoplay?: boolean;
8
- });
9
- on(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => 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, blob?: Blob): void;
10
15
  destroy(): void;
11
- loadUrl(src: string): void;
12
- getCurrentTime(): number;
13
- play(): void;
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
- seekTo(time: number): void;
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 */
17
25
  getDuration(): number;
26
+ /** Get the current audio position in seconds */
27
+ getCurrentTime(): number;
28
+ /** Get the audio volume */
18
29
  getVolume(): number;
30
+ /** Set the audio volume */
19
31
  setVolume(volume: number): void;
32
+ /** Get the audio muted state */
20
33
  getMuted(): boolean;
34
+ /** Mute or unmute the audio */
21
35
  setMuted(muted: boolean): void;
36
+ /** Get the playback speed */
22
37
  getPlaybackRate(): number;
38
+ /** Set the playback speed, pass an optional false to NOT preserve the pitch */
23
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>;
24
44
  }
25
45
  export default Player;