wavesurfer.js 7.0.0-alpha.4 → 7.0.0-alpha.41

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 (46) hide show
  1. package/README.md +64 -17
  2. package/dist/base-plugin.d.ts +14 -5
  3. package/dist/base-plugin.js +5 -1
  4. package/dist/decoder.d.ts +8 -10
  5. package/dist/decoder.js +37 -44
  6. package/dist/event-emitter.d.ts +16 -10
  7. package/dist/event-emitter.js +38 -16
  8. package/dist/fetcher.js +2 -13
  9. package/dist/legacy-adapter.d.ts +7 -0
  10. package/dist/legacy-adapter.js +15 -0
  11. package/dist/player.d.ts +32 -11
  12. package/dist/player.js +55 -39
  13. package/dist/plugins/envelope.d.ts +57 -0
  14. package/dist/plugins/envelope.js +305 -0
  15. package/dist/plugins/envelope.min.js +1 -0
  16. package/dist/plugins/minimap.d.ts +34 -0
  17. package/dist/plugins/minimap.js +99 -0
  18. package/dist/plugins/minimap.min.js +1 -0
  19. package/dist/plugins/multitrack.d.ts +117 -0
  20. package/dist/plugins/multitrack.js +506 -0
  21. package/dist/plugins/multitrack.min.js +1 -0
  22. package/dist/plugins/record.d.ts +26 -0
  23. package/dist/plugins/record.js +125 -0
  24. package/dist/plugins/record.min.js +1 -0
  25. package/dist/plugins/regions.d.ts +88 -41
  26. package/dist/plugins/regions.js +365 -170
  27. package/dist/plugins/regions.min.js +1 -0
  28. package/dist/plugins/spectrogram.d.ts +24 -0
  29. package/dist/plugins/spectrogram.js +165 -0
  30. package/dist/plugins/timeline.d.ts +41 -0
  31. package/dist/plugins/timeline.js +135 -0
  32. package/dist/plugins/timeline.min.js +1 -0
  33. package/dist/renderer.d.ts +26 -12
  34. package/dist/renderer.js +212 -157
  35. package/dist/timer.d.ts +1 -1
  36. package/dist/wavesurfer.d.ts +136 -0
  37. package/dist/wavesurfer.js +213 -0
  38. package/dist/wavesurfer.min.js +1 -1
  39. package/package.json +49 -24
  40. package/dist/index.d.ts +0 -104
  41. package/dist/index.js +0 -188
  42. package/dist/player-webaudio.d.ts +0 -8
  43. package/dist/player-webaudio.js +0 -32
  44. package/dist/react/useWavesurfer.d.ts +0 -5
  45. package/dist/react/useWavesurfer.js +0 -20
  46. package/dist/wavesurfer.Regions.min.js +0 -1
package/README.md CHANGED
@@ -1,43 +1,90 @@
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
- An experimental rewrite of [wavesufer.js](https://github.com/wavesurfer-js/wavesurfer.js) to play around with new ideas.
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
- ## Goals
7
+ Try it out:
8
+ ```
9
+ npm install --save wavesurfer.js@alpha
10
+ ```
11
+
12
+ Import like this:
13
+
14
+ ```
15
+ import WaveSurfer from 'wavesurfer.js'
16
+ ```
17
+
18
+ To import a plugin, e.g. the Timeline plugin:
19
+ ```
20
+ import Timeline from 'wavesurfer.js/dist/plugins/timeline'
21
+ ```
22
+
23
+ TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
8
24
 
9
- * TypeScript API
10
- * Better architecture
11
- * Minimize the available options and provide sensible defaults
12
- * Improve the decoding and rendering performance
25
+ ## Why upgrade to wavesurfer.ts?
13
26
 
14
- ## Non-goals
27
+ wavesurfer.js v7 (aka wavesurfer.ts) brings several improvements:
15
28
 
16
- Keeping backwards compatibility with earlier versions of wavesurfer.js.
29
+ * Typed API for better development experience
30
+ * Enhanced decoding and rendering performance
31
+ * New and improved plugins
17
32
 
18
- ## Architecture
33
+ ## Plugins
34
+ The "official" plugins have been completely rewritten and enhanced:
19
35
 
20
- Principles:
21
- * Modular and event-driven
22
- * Flexible (e.g. allow custom media elements and user-defined Web Audio graphs)
23
- * Extensible with plugins
36
+ * [Regions](https://wavesurfer-ts.pages.dev/tutorial/#/examples/regions.js) – now also replaces the old Markers plugin
37
+ * [Timeline](https://wavesurfer-ts.pages.dev/tutorial/#/examples/timeline.js) – displays notches and time labels below the waveform
38
+ * [Minimap](https://wavesurfer-ts.pages.dev/tutorial/#/examples/minimap.js) a small waveform that serves as a scrollbar for the main waveform
39
+ * [Envelope](https://wavesurfer-ts.pages.dev/tutorial/#/examples/envelope.js) a graphical interface to add fade-in and -out effects and control volume
40
+ * [Record](https://wavesurfer-ts.pages.dev/tutorial/#/examples/record.js) – records audio from the microphone and renders a waveform
41
+
42
+ ## Documentation
43
+ 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.
44
+
45
+ ## Migrating from v6 and lower
46
+
47
+ Most options, events, and methods are similar to those in previous versions.
48
+
49
+ ### Notable differences
50
+ * 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-ts.pages.dev/tutorial/#/examples/webaudio.js).
51
+ * The Markers plugin is removed – use the Regions plugin with `startTime` equal to `endTime`.
52
+ * No Microphone plugn – superseded by the new Record plugin with more features.
53
+ * No Spectrogram, Cursor and Playhead plugins yet – to be done.
54
+
55
+ ### Removed methods
56
+ * `getFilters`, `setFilter` – as there's no Web Audio "backend"
57
+ * `cancelAjax` – ajax is replaced by `fetch`
58
+ * `loadBlob` – use `URL.createObjectURL()` to convert a blob to a URL and call `load(url)` instead
59
+ * `skipForward`, `skipBackward`, `setPlayEnd` – can be implemented using `setTime(time)`
60
+ * `exportPCM` is renamed to `getDecodedData` and doesn't take any params
61
+ * `toggleMute` is now called `setMuted(true | false)`
62
+ * `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
63
+
64
+ See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/classes/wavesurfer.WaveSurfer).
24
65
 
25
66
  ## Development
26
67
 
27
- Install dev dependencies:
68
+ To get started with development, follow these steps:
69
+
70
+ 1. Install dev dependencies:
28
71
 
29
72
  ```
30
73
  yarn
31
74
  ```
32
75
 
33
- Start the TypeScript compiler in watch mode and an HTTP server:
76
+ 2. Start the TypeScript compiler in watch mode and launch an HTTP server:
34
77
 
35
78
  ```
36
79
  yarn start
37
80
  ```
38
81
 
39
- This will open http://localhost:9090/tutorial in your browser with live reload.
82
+ This command will open http://localhost:9090/tutorial in your browser with live reload, allowing you to see the changes as you develop.
40
83
 
41
84
  ## Feedback
42
85
 
43
- Your feedback is very welcome here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
86
+ We appreciate your feedback and contributions! Join the conversation and share your thoughts here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
87
+
88
+ 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.
89
+
90
+ We hope you enjoy using wavesurfer.ts and look forward to hearing about your experiences with the library!
@@ -1,10 +1,19 @@
1
1
  import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
2
- import type { WaveSurfer, WaveSurferPluginParams } from './index.js';
3
- export declare class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
4
- protected wavesurfer: WaveSurfer;
5
- protected container: HTMLElement;
2
+ import type WaveSurfer from './wavesurfer.js';
3
+ export type GenericPlugin = BasePlugin<GeneralEventTypes, unknown>;
4
+ export type WaveSurferPluginParams = {
5
+ wavesurfer: WaveSurfer;
6
+ container: HTMLElement;
7
+ wrapper: HTMLElement;
8
+ };
9
+ export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
10
+ protected wavesurfer?: WaveSurfer;
11
+ protected container?: HTMLElement;
12
+ protected wrapper?: HTMLElement;
6
13
  protected subscriptions: (() => void)[];
7
- constructor(params: WaveSurferPluginParams);
14
+ protected options: Options;
15
+ constructor(options: Options);
16
+ init(params: WaveSurferPluginParams): void;
8
17
  destroy(): void;
9
18
  }
10
19
  export default BasePlugin;
@@ -1,10 +1,14 @@
1
1
  import EventEmitter from './event-emitter.js';
2
2
  export class BasePlugin extends EventEmitter {
3
- constructor(params) {
3
+ constructor(options) {
4
4
  super();
5
5
  this.subscriptions = [];
6
+ this.options = options;
7
+ }
8
+ init(params) {
6
9
  this.wavesurfer = params.wavesurfer;
7
10
  this.container = params.container;
11
+ this.wrapper = params.wrapper;
8
12
  }
9
13
  destroy() {
10
14
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
package/dist/decoder.d.ts CHANGED
@@ -1,11 +1,9 @@
1
- declare class Decoder {
2
- audioCtx: AudioContext | null;
3
- private initAudioContext;
4
- constructor();
5
- decode(audioData: ArrayBuffer): Promise<{
6
- duration: number;
7
- channelData: Float32Array[];
8
- }>;
9
- destroy(): void;
10
- }
1
+ /** Decode an array buffer into an audio buffer */
2
+ declare function decode(audioData: ArrayBuffer): 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,44 @@
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,
1
+ /** Decode an array buffer into an audio buffer */
2
+ async function decode(audioData) {
3
+ const DEFAULT_SAMPLE_RATE = 8000; // All user agents are required to support a range of 8000Hz to 96000Hz
4
+ const MIN_SAMPLE_RATE = 3000; // Chrome, Safari can do 3kHz
5
+ let audioCtx;
6
+ try {
7
+ audioCtx = new AudioContext({
8
+ sampleRate: MIN_SAMPLE_RATE,
15
9
  });
16
10
  }
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
- };
11
+ catch (_) {
12
+ audioCtx = new AudioContext({
13
+ sampleRate: DEFAULT_SAMPLE_RATE,
43
14
  });
44
15
  }
45
- destroy() {
46
- var _a;
47
- (_a = this.audioCtx) === null || _a === void 0 ? void 0 : _a.close();
48
- this.audioCtx = null;
16
+ const decode = audioCtx.decodeAudioData(audioData);
17
+ decode.finally(() => audioCtx.close());
18
+ return decode;
19
+ }
20
+ /** Create an audio buffer from pre-decoded audio data */
21
+ function createBuffer(channelData, duration) {
22
+ // If a single array of numbers is passed, make it an array of arrays
23
+ if (typeof channelData[0] === 'number')
24
+ channelData = [channelData];
25
+ // Normalize to -1..1
26
+ if (channelData[0].some((n) => n > 1 || n < -1)) {
27
+ const max = Math.max(...channelData[0]);
28
+ channelData = channelData.map((channel) => channel.map((n) => n / max));
49
29
  }
30
+ return {
31
+ length: channelData[0].length,
32
+ duration,
33
+ numberOfChannels: channelData.length,
34
+ sampleRate: channelData[0].length / duration,
35
+ getChannelData: (i) => channelData?.[i],
36
+ copyFromChannel: AudioBuffer.prototype.copyFromChannel,
37
+ copyToChannel: AudioBuffer.prototype.copyToChannel,
38
+ };
50
39
  }
40
+ const Decoder = {
41
+ decode,
42
+ createBuffer,
43
+ };
51
44
  export default Decoder;
@@ -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.js CHANGED
@@ -1,17 +1,6 @@
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
1
  class Fetcher {
11
- load(url) {
12
- return __awaiter(this, void 0, void 0, function* () {
13
- return fetch(url).then((response) => response.arrayBuffer());
14
- });
2
+ async load(url) {
3
+ return fetch(url).then((response) => response.arrayBuffer());
15
4
  }
16
5
  }
17
6
  export default Fetcher;
@@ -0,0 +1,7 @@
1
+ import type WaveSurfer from "./wavesurfer";
2
+ type LegacyWavesurferMethodsType = Partial<WaveSurfer> & {
3
+ skip(offset: number): void;
4
+ stop(): void;
5
+ };
6
+ declare const LegacyWavesurferMethods: LegacyWavesurferMethodsType;
7
+ export default LegacyWavesurferMethods;
@@ -0,0 +1,15 @@
1
+ const LegacyWavesurferMethods = {
2
+ /** Skip a number of seconds from the current position (use a negative value to go backwards) */
3
+ skip(seconds) {
4
+ console.warn('Deprecated, please use `wavesurfer.setTime(wavesurfer.getCurrentTime() + seconds)` instead');
5
+ const time = this.getCurrentTime() + seconds;
6
+ this.setTime(time);
7
+ },
8
+ /** Stop the audio and go to the beginning */
9
+ stop() {
10
+ console.warn('Deprecated, please use `wavesurfer.pause(); wavesurfer.setTime(0)` instead');
11
+ this.pause();
12
+ this.setTime(0);
13
+ }
14
+ };
15
+ export default LegacyWavesurferMethods;
package/dist/player.d.ts CHANGED
@@ -1,24 +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;
9
+ protected subscriptions: Array<() => void>;
3
10
  private isExternalMedia;
4
- private hasPlayedOnce;
5
- constructor({ media, autoplay }: {
6
- media?: HTMLMediaElement;
7
- autoplay?: boolean;
8
- });
9
- on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
11
+ constructor(options: PlayerOptions);
12
+ protected onMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
13
+ protected onceMediaEvent(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
14
+ protected loadUrl(src: string): 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 */
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,87 @@
1
- class Player {
2
- constructor({ media, autoplay }) {
1
+ import EventEmitter from './event-emitter.js';
2
+ class Player extends EventEmitter {
3
+ constructor(options) {
4
+ super();
5
+ this.subscriptions = [];
3
6
  this.isExternalMedia = false;
4
- this.hasPlayedOnce = false;
5
- if (media) {
6
- this.media = media;
7
+ if (options.media) {
8
+ this.media = options.media;
7
9
  this.isExternalMedia = true;
8
10
  }
9
11
  else {
10
12
  this.media = document.createElement('audio');
11
13
  }
12
- // Track the first play() call
13
- const unsubscribe = this.on('play', () => {
14
- this.hasPlayedOnce = true;
15
- unsubscribe();
16
- });
17
14
  // Autoplay
18
- if (autoplay) {
15
+ if (options.autoplay) {
19
16
  this.media.autoplay = true;
20
17
  }
18
+ // Speed
19
+ if (options.playbackRate != null) {
20
+ this.media.playbackRate = options.playbackRate;
21
+ }
21
22
  }
22
- on(event, callback) {
23
- var _a;
24
- (_a = this.media) === null || _a === void 0 ? void 0 : _a.addEventListener(event, callback);
25
- return () => { var _a; return (_a = this.media) === null || _a === void 0 ? void 0 : _a.removeEventListener(event, callback); };
23
+ onMediaEvent(event, callback, options) {
24
+ this.media.addEventListener(event, callback, options);
25
+ return () => this.media.removeEventListener(event, callback);
26
26
  }
27
- destroy() {
28
- var _a, _b;
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
- }
27
+ onceMediaEvent(event, callback) {
28
+ return this.onMediaEvent(event, callback, { once: true });
33
29
  }
34
30
  loadUrl(src) {
35
- if (this.media) {
36
- this.media.src = src;
37
- }
31
+ this.media.src = src;
38
32
  }
39
- getCurrentTime() {
40
- return this.media.currentTime;
33
+ destroy() {
34
+ this.media.pause();
35
+ this.subscriptions.forEach((unsubscribe) => unsubscribe());
36
+ if (!this.isExternalMedia) {
37
+ this.media.remove();
38
+ }
41
39
  }
40
+ /** Start playing the audio */
42
41
  play() {
43
- this.media.play();
42
+ return this.media.play();
44
43
  }
44
+ /** Pause the audio */
45
45
  pause() {
46
46
  this.media.pause();
47
47
  }
48
+ /** Check if the audio is playing */
48
49
  isPlaying() {
49
50
  return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
50
51
  }
51
- seekTo(time) {
52
- if (this.media) {
53
- // iOS Safari requires a play() call before seeking
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
- }
52
+ /** Jumpt to a specific time in the audio (in seconds) */
53
+ setTime(time) {
54
+ this.media.currentTime = time;
63
55
  }
56
+ /** Get the duration of the audio in seconds */
57
+ getDuration() {
58
+ return this.media.duration;
59
+ }
60
+ /** Get the current audio position in seconds */
61
+ getCurrentTime() {
62
+ return this.media.currentTime;
63
+ }
64
+ /** Get the audio volume */
64
65
  getVolume() {
65
66
  return this.media.volume;
66
67
  }
68
+ /** Set the audio volume */
67
69
  setVolume(volume) {
68
70
  this.media.volume = volume;
69
71
  }
72
+ /** Get the audio muted state */
70
73
  getMuted() {
71
74
  return this.media.muted;
72
75
  }
76
+ /** Mute or unmute the audio */
73
77
  setMuted(muted) {
74
78
  this.media.muted = muted;
75
79
  }
80
+ /** Get the playback speed */
76
81
  getPlaybackRate() {
77
82
  return this.media.playbackRate;
78
83
  }
84
+ /** Set the playback speed, pass an optional false to NOT preserve the pitch */
79
85
  setPlaybackRate(rate, preservePitch) {
80
86
  // preservePitch is true by default in most browsers
81
87
  if (preservePitch != null) {
@@ -83,5 +89,15 @@ class Player {
83
89
  }
84
90
  this.media.playbackRate = rate;
85
91
  }
92
+ /** Get the HTML media element */
93
+ getMediaElement() {
94
+ return this.media;
95
+ }
96
+ /** Set a sink id to change the audio output device */
97
+ setSinkId(sinkId) {
98
+ // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId
99
+ const media = this.media;
100
+ return media.setSinkId(sinkId);
101
+ }
86
102
  }
87
103
  export default Player;