wavesurfer.js 7.0.0-alpha.0 → 7.0.0-alpha.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 (57) hide show
  1. package/README.md +13 -15
  2. package/dist/base-plugin.d.ts +4 -3
  3. package/dist/base-plugin.js +3 -2
  4. package/dist/decoder.d.ts +2 -4
  5. package/dist/decoder.js +16 -16
  6. package/dist/event-emitter.d.ts +3 -1
  7. package/dist/event-emitter.js +6 -2
  8. package/dist/index.d.ts +43 -18
  9. package/dist/index.js +105 -44
  10. package/dist/player-webaudio.js +5 -4
  11. package/dist/player.d.ts +11 -2
  12. package/dist/player.js +50 -4
  13. package/dist/plugins/multitrack.d.ts +46 -0
  14. package/dist/plugins/multitrack.js +274 -0
  15. package/dist/plugins/regions.d.ts +11 -6
  16. package/dist/plugins/regions.js +106 -74
  17. package/dist/plugins/timeline.d.ts +29 -0
  18. package/dist/plugins/timeline.js +119 -0
  19. package/dist/plugins/xmultitrack.d.ts +44 -0
  20. package/dist/plugins/xmultitrack.js +260 -0
  21. package/dist/react/useWavesurfer.d.ts +5 -0
  22. package/dist/react/useWavesurfer.js +20 -0
  23. package/dist/renderer.d.ts +9 -5
  24. package/dist/renderer.js +137 -91
  25. package/dist/timer.d.ts +2 -1
  26. package/dist/timer.js +6 -1
  27. package/dist/wavesurfer.Multitrack.min.js +1 -0
  28. package/dist/wavesurfer.Regions.min.js +1 -0
  29. package/dist/wavesurfer.Timeline.min.js +1 -0
  30. package/dist/wavesurfer.min.js +1 -0
  31. package/package.json +19 -5
  32. package/.eslintrc.json +0 -24
  33. package/.prettierrc +0 -8
  34. package/examples/audio.ogg +0 -0
  35. package/examples/bars.js +0 -19
  36. package/examples/basic.js +0 -8
  37. package/examples/gradient.js +0 -28
  38. package/examples/regions.js +0 -63
  39. package/examples/video.js +0 -19
  40. package/examples/webaudio.js +0 -14
  41. package/src/base-plugin.ts +0 -20
  42. package/src/decoder.ts +0 -41
  43. package/src/event-emitter.ts +0 -35
  44. package/src/fetcher.ts +0 -7
  45. package/src/index.ts +0 -252
  46. package/src/player-webaudio.ts +0 -34
  47. package/src/player.ts +0 -50
  48. package/src/plugins/regions.ts +0 -240
  49. package/src/renderer.ts +0 -250
  50. package/src/timer.ts +0 -27
  51. package/tsconfig.json +0 -105
  52. package/tutorial/index.html +0 -47
  53. package/tutorial/src/editor.js +0 -70
  54. package/tutorial/src/init.js +0 -66
  55. package/tutorial/src/url.js +0 -25
  56. package/tutorial/style.css +0 -211
  57. package/yarn-error.log +0 -1049
package/README.md CHANGED
@@ -1,7 +1,14 @@
1
- # 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.ts
2
2
 
3
3
  An experimental rewrite of [wavesufer.js](https://github.com/wavesurfer-js/wavesurfer.js) to play around with new ideas.
4
4
 
5
+ <img alt="screenshot" src="https://user-images.githubusercontent.com/381895/225539680-fc724acd-8657-458e-a558-ff1c6758ba30.png" width="800" />
6
+
7
+ Try it out:
8
+ ```
9
+ npm install --save wavesurfer.js@alpha
10
+ ```
11
+
5
12
  ## Goals
6
13
 
7
14
  * TypeScript API
@@ -17,11 +24,9 @@ Keeping backwards compatibility with earlier versions of wavesurfer.js.
17
24
 
18
25
  Principles:
19
26
  * Modular and event-driven
20
- * Allow swapping the decoding, rendering and playback mechanisms
27
+ * Flexible (e.g. allow custom media elements and user-defined Web Audio graphs)
21
28
  * Extensible with plugins
22
29
 
23
- ![diagram](https://user-images.githubusercontent.com/381895/222349436-38b550e5-24dc-4143-9cdb-efbe00540213.png)
24
-
25
30
  ## Development
26
31
 
27
32
  Install dev dependencies:
@@ -30,21 +35,14 @@ Install dev dependencies:
30
35
  yarn
31
36
  ```
32
37
 
33
- Start the TypeScript compiler in watch mode:
34
-
35
- ```
36
- yarn build -w
37
- ```
38
-
39
- Run an HTTP server to view the examples:
38
+ Start the TypeScript compiler in watch mode and an HTTP server:
40
39
 
41
40
  ```
42
- python3 -m http.server --cgi 8080
41
+ yarn start
43
42
  ```
44
43
 
45
- Open http://localhost:8080/examples/basic/ in your browser.
46
- There's no hot reload yet, so you'll need to reload the page manually on every change.
44
+ This will open http://localhost:9090/tutorial in your browser with live reload.
47
45
 
48
46
  ## Feedback
49
47
 
50
- See https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
48
+ Your feedback is very welcome here: https://github.com/wavesurfer-js/wavesurfer.js/discussions/2684
@@ -1,10 +1,11 @@
1
1
  import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
2
2
  import type { WaveSurfer, WaveSurferPluginParams } from './index.js';
3
- export declare class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
3
+ export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
4
4
  protected wavesurfer: WaveSurfer;
5
- protected renderer: WaveSurfer['renderer'];
5
+ protected container: HTMLElement;
6
6
  protected subscriptions: (() => void)[];
7
- constructor(params: WaveSurferPluginParams);
7
+ protected options: Options;
8
+ constructor(params: WaveSurferPluginParams, options: Options);
8
9
  destroy(): void;
9
10
  }
10
11
  export default BasePlugin;
@@ -1,10 +1,11 @@
1
1
  import EventEmitter from './event-emitter.js';
2
2
  export class BasePlugin extends EventEmitter {
3
- constructor(params) {
3
+ constructor(params, options) {
4
4
  super();
5
5
  this.subscriptions = [];
6
6
  this.wavesurfer = params.wavesurfer;
7
- this.renderer = params.renderer;
7
+ this.container = params.container;
8
+ this.options = options;
8
9
  }
9
10
  destroy() {
10
11
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
package/dist/decoder.d.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  declare class Decoder {
2
2
  audioCtx: AudioContext | null;
3
+ private initAudioContext;
3
4
  constructor();
4
- decode(audioData: ArrayBuffer): Promise<{
5
- duration: number;
6
- channelData: Float32Array[];
7
- }>;
5
+ decode(audioData: ArrayBuffer): Promise<AudioBuffer>;
8
6
  destroy(): void;
9
7
  }
10
8
  export default Decoder;
package/dist/decoder.js CHANGED
@@ -7,31 +7,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- // Web Audio decodeAudioData with a minimum allowed sample rate
11
- const SAMPLE_RATE = 3000;
12
10
  class Decoder {
13
- constructor() {
14
- this.audioCtx = null;
15
- this.audioCtx = new (window.AudioContext ||
16
- window.webkitAudioContext)({
11
+ initAudioContext(sampleRate) {
12
+ this.audioCtx = new AudioContext({
17
13
  latencyHint: 'playback',
18
- sampleRate: SAMPLE_RATE,
14
+ sampleRate,
19
15
  });
20
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
+ }
21
29
  decode(audioData) {
22
30
  return __awaiter(this, void 0, void 0, function* () {
23
31
  if (!this.audioCtx) {
24
32
  throw new Error('AudioContext is not initialized');
25
33
  }
26
- const buffer = yield this.audioCtx.decodeAudioData(audioData);
27
- const channelData = [buffer.getChannelData(0)];
28
- if (buffer.numberOfChannels > 1) {
29
- channelData.push(buffer.getChannelData(1));
30
- }
31
- return {
32
- duration: buffer.duration,
33
- channelData,
34
- };
34
+ return this.audioCtx.decodeAudioData(audioData);
35
35
  });
36
36
  }
37
37
  destroy() {
@@ -6,6 +6,8 @@ declare class EventEmitter<EventTypes extends GeneralEventTypes> {
6
6
  constructor();
7
7
  protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void;
8
8
  /** Subscribe to an event and return a function to unsubscribe */
9
- on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void;
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;
10
12
  }
11
13
  export default EventEmitter;
@@ -7,15 +7,19 @@ class EventEmitter {
7
7
  this.eventTarget.dispatchEvent(e);
8
8
  }
9
9
  /** Subscribe to an event and return a function to unsubscribe */
10
- on(eventType, callback) {
10
+ on(eventType, callback, once) {
11
11
  const handler = (e) => {
12
12
  if (e instanceof CustomEvent) {
13
13
  callback(e.detail);
14
14
  }
15
15
  };
16
16
  const eventName = String(eventType);
17
- this.eventTarget.addEventListener(eventName, handler);
17
+ this.eventTarget.addEventListener(eventName, handler, { once });
18
18
  return () => this.eventTarget.removeEventListener(eventName, handler);
19
19
  }
20
+ /** Subscribe to an event once and return a function to unsubscribe */
21
+ once(eventType, callback) {
22
+ return this.on(eventType, callback, true);
23
+ }
20
24
  }
21
25
  export default EventEmitter;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
2
2
  import BasePlugin from './base-plugin.js';
3
- export declare enum PlayerType {
4
- WebAudio = "WebAudio",
5
- MediaElement = "MediaElement"
6
- }
7
3
  export type WaveSurferOptions = {
8
4
  /** HTML element or CSS selector */
9
5
  container: HTMLElement | string | null;
@@ -13,6 +9,8 @@ export type WaveSurferOptions = {
13
9
  waveColor?: string;
14
10
  /** The color of the progress mask */
15
11
  progressColor?: string;
12
+ /** The color of the playpack cursort */
13
+ cursorColor?: string;
16
14
  /** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
17
15
  barWidth?: number;
18
16
  /** Spacing between bars in pixels */
@@ -21,34 +19,47 @@ export type WaveSurferOptions = {
21
19
  barRadius?: number;
22
20
  /** Minimum pixels per second of audio (zoom) */
23
21
  minPxPerSec?: number;
22
+ /** Stretch the waveform to fill the container, true by default */
23
+ fillParent?: boolean;
24
24
  /** Audio URL */
25
25
  url?: string;
26
26
  /** Pre-computed audio data */
27
- channelData?: Float32Array[];
27
+ peaks?: Float32Array[];
28
28
  /** Pre-computed duration */
29
29
  duration?: number;
30
- /** Player "backend", the default is MediaElement */
31
- backend?: PlayerType;
32
30
  /** Use an existing media element instead of creating one */
33
31
  media?: HTMLMediaElement;
32
+ /** Play the audio on load */
33
+ autoplay?: boolean;
34
+ /** Is the waveform clickable? */
35
+ interactive?: boolean;
34
36
  };
35
37
  export type WaveSurferEvents = {
38
+ decode: {
39
+ duration: number;
40
+ };
41
+ canplay: {
42
+ duration: number;
43
+ };
36
44
  ready: {
37
45
  duration: number;
38
46
  };
39
- canplay: void;
40
47
  play: void;
41
48
  pause: void;
42
- audioprocess: {
49
+ timeupdate: {
50
+ currentTime: number;
51
+ };
52
+ seeking: {
43
53
  currentTime: number;
44
54
  };
45
- seek: {
46
- time: number;
55
+ seekClick: {
56
+ currentTime: number;
47
57
  };
58
+ destroy: void;
48
59
  };
49
60
  export type WaveSurferPluginParams = {
50
61
  wavesurfer: WaveSurfer;
51
- renderer: WaveSurfer['renderer'];
62
+ container: HTMLElement;
52
63
  };
53
64
  export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
54
65
  private options;
@@ -59,8 +70,7 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
59
70
  private timer;
60
71
  private plugins;
61
72
  private subscriptions;
62
- private channelData;
63
- private duration;
73
+ private decodedData;
64
74
  /** Create a new WaveSurfer instance */
65
75
  static create(options: WaveSurferOptions): WaveSurfer;
66
76
  /** Create a new WaveSurfer instance */
@@ -68,9 +78,8 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
68
78
  private initPlayerEvents;
69
79
  private initRendererEvents;
70
80
  private initTimerEvents;
71
- /** Unmount wavesurfer */
72
- destroy(): void;
73
- /** Load an audio file by URL */
81
+ private initReadyEvent;
82
+ /** Load an audio file by URL, with optional pre-decoded audio data */
74
83
  load(url: string, channelData?: Float32Array[], duration?: number): Promise<void>;
75
84
  /** Zoom in or out */
76
85
  zoom(minPxPerSec: number): void;
@@ -86,7 +95,23 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
86
95
  getDuration(): number;
87
96
  /** Get the current audio position in seconds */
88
97
  getCurrentTime(): number;
98
+ /** Get the audio volume */
99
+ getVolume(): number;
100
+ /** Set the audio volume */
101
+ setVolume(volume: number): void;
102
+ /** Get the audio muted state */
103
+ getMuted(): boolean;
104
+ /** Mute or unmute the audio */
105
+ setMuted(muted: boolean): void;
106
+ /** Get playback rate */
107
+ getPlaybackRate(): number;
108
+ /** Set playback rate, with an optional parameter to NOT preserve the pitch if false */
109
+ setPlaybackRate(rate: number, preservePitch?: boolean): void;
89
110
  /** Register and initialize a plugin */
90
- registerPlugin<T extends BasePlugin<GeneralEventTypes>>(CustomPlugin: new (params: WaveSurferPluginParams) => T): T;
111
+ registerPlugin<T extends BasePlugin<GeneralEventTypes, any>>(CustomPlugin: new (params: WaveSurferPluginParams, options: any) => T, options?: any): T;
112
+ /** Get the decoded audio data */
113
+ getDecodedData(): AudioBuffer | null;
114
+ /** Unmount wavesurfer */
115
+ destroy(): void;
91
116
  }
92
117
  export default WaveSurfer;
package/dist/index.js CHANGED
@@ -11,20 +11,15 @@ import Fetcher from './fetcher.js';
11
11
  import Decoder from './decoder.js';
12
12
  import Renderer from './renderer.js';
13
13
  import Player from './player.js';
14
- import WebAudioPlayer from './player-webaudio.js';
15
14
  import EventEmitter from './event-emitter.js';
16
15
  import Timer from './timer.js';
17
- export var PlayerType;
18
- (function (PlayerType) {
19
- PlayerType["WebAudio"] = "WebAudio";
20
- PlayerType["MediaElement"] = "MediaElement";
21
- })(PlayerType || (PlayerType = {}));
22
16
  const defaultOptions = {
23
17
  height: 128,
24
18
  waveColor: '#999',
25
19
  progressColor: '#555',
26
20
  minPxPerSec: 0,
27
- backend: 'MediaElement',
21
+ fillParent: true,
22
+ interactive: true,
28
23
  };
29
24
  export class WaveSurfer extends EventEmitter {
30
25
  /** Create a new WaveSurfer instance */
@@ -37,21 +32,23 @@ export class WaveSurfer extends EventEmitter {
37
32
  super();
38
33
  this.plugins = [];
39
34
  this.subscriptions = [];
40
- this.channelData = null;
41
- this.duration = 0;
35
+ this.decodedData = null;
42
36
  this.options = Object.assign({}, defaultOptions, options);
43
37
  this.fetcher = new Fetcher();
44
38
  this.decoder = new Decoder();
45
39
  this.timer = new Timer();
46
- this.player = new (this.options.backend === PlayerType.WebAudio ? WebAudioPlayer : Player)({
40
+ this.player = new Player({
47
41
  media: this.options.media,
42
+ autoplay: this.options.autoplay,
48
43
  });
49
44
  this.renderer = new Renderer({
50
45
  container: this.options.container,
51
46
  height: this.options.height,
52
47
  waveColor: this.options.waveColor,
53
48
  progressColor: this.options.progressColor,
49
+ cursorColor: this.options.cursorColor,
54
50
  minPxPerSec: this.options.minPxPerSec,
51
+ fillParent: this.options.fillParent,
55
52
  barWidth: this.options.barWidth,
56
53
  barGap: this.options.barGap,
57
54
  barRadius: this.options.barRadius,
@@ -59,75 +56,100 @@ export class WaveSurfer extends EventEmitter {
59
56
  this.initPlayerEvents();
60
57
  this.initRendererEvents();
61
58
  this.initTimerEvents();
59
+ this.initReadyEvent();
62
60
  const url = this.options.url || ((_a = this.options.media) === null || _a === void 0 ? void 0 : _a.src);
63
61
  if (url) {
64
- this.load(url, this.options.channelData, this.options.duration);
62
+ this.load(url, this.options.peaks, this.options.duration);
65
63
  }
66
64
  }
67
65
  initPlayerEvents() {
68
66
  this.subscriptions.push(this.player.on('timeupdate', () => {
69
67
  const currentTime = this.getCurrentTime();
70
- this.renderer.renderProgress(currentTime / this.duration, this.isPlaying());
71
- this.emit('audioprocess', { currentTime });
68
+ this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying());
69
+ this.emit('timeupdate', { currentTime });
72
70
  }), this.player.on('play', () => {
73
71
  this.emit('play');
72
+ this.timer.start();
74
73
  }), this.player.on('pause', () => {
75
74
  this.emit('pause');
75
+ this.timer.stop();
76
76
  }), this.player.on('canplay', () => {
77
- this.emit('canplay');
77
+ this.emit('canplay', { duration: this.getDuration() });
78
78
  }), this.player.on('seeking', () => {
79
- this.emit('seek', { time: this.getCurrentTime() });
79
+ this.emit('seeking', { currentTime: this.getCurrentTime() });
80
80
  }));
81
81
  }
82
82
  initRendererEvents() {
83
83
  // Seek on click
84
84
  this.subscriptions.push(this.renderer.on('click', ({ relativeX }) => {
85
- const time = this.getDuration() * relativeX;
86
- this.seekTo(time);
85
+ if (this.options.interactive) {
86
+ const time = this.getDuration() * relativeX;
87
+ this.seekTo(time);
88
+ this.emit('seekClick', { currentTime: this.getCurrentTime() });
89
+ }
87
90
  }));
88
91
  }
89
92
  initTimerEvents() {
90
93
  // The timer fires every 16ms for a smooth progress animation
91
94
  this.subscriptions.push(this.timer.on('tick', () => {
92
- if (this.isPlaying()) {
93
- const currentTime = this.getCurrentTime();
94
- this.renderer.renderProgress(currentTime / this.duration, true);
95
- this.emit('audioprocess', { currentTime });
96
- }
95
+ const currentTime = this.getCurrentTime();
96
+ this.renderer.renderProgress(currentTime / this.getDuration(), true);
97
+ this.emit('timeupdate', { currentTime });
97
98
  }));
98
99
  }
99
- /** Unmount wavesurfer */
100
- destroy() {
101
- this.subscriptions.forEach((unsubscribe) => unsubscribe());
102
- this.plugins.forEach((plugin) => plugin.destroy());
103
- this.timer.destroy();
104
- this.player.destroy();
105
- this.decoder.destroy();
106
- this.renderer.destroy();
100
+ initReadyEvent() {
101
+ let isDecoded = false;
102
+ let isPlayable = false;
103
+ const emitReady = () => {
104
+ if (isDecoded && isPlayable) {
105
+ this.emit('ready', { duration: this.getDuration() });
106
+ }
107
+ };
108
+ this.subscriptions.push(this.on('decode', () => {
109
+ isDecoded = true;
110
+ emitReady();
111
+ }), this.on('canplay', () => {
112
+ isPlayable = true;
113
+ emitReady();
114
+ }), this.player.on('waiting', () => {
115
+ isPlayable = false;
116
+ isDecoded = false;
117
+ }));
107
118
  }
108
- /** Load an audio file by URL */
119
+ /** Load an audio file by URL, with optional pre-decoded audio data */
109
120
  load(url, channelData, duration) {
110
121
  return __awaiter(this, void 0, void 0, function* () {
111
122
  this.player.loadUrl(url);
112
123
  // Fetch and decode the audio of no pre-computed audio data is provided
113
- if (channelData == null || duration == null) {
124
+ if (channelData == null) {
114
125
  const audio = yield this.fetcher.load(url);
115
126
  const data = yield this.decoder.decode(audio);
116
- channelData = data.channelData;
117
- duration = data.duration;
127
+ this.decodedData = data;
128
+ }
129
+ else {
130
+ if (!duration) {
131
+ duration =
132
+ (yield new Promise((resolve) => {
133
+ this.player.on('canplay', () => resolve(this.getDuration()), { once: true });
134
+ })) || 0;
135
+ }
136
+ this.decodedData = {
137
+ duration,
138
+ numberOfChannels: channelData.length,
139
+ sampleRate: channelData[0].length / duration,
140
+ getChannelData: (i) => channelData[i],
141
+ };
118
142
  }
119
- this.channelData = channelData;
120
- this.duration = duration;
121
- this.renderer.render(this.channelData, this.duration);
122
- this.emit('ready', { duration: this.duration });
143
+ this.renderer.render(this.decodedData);
144
+ this.emit('decode', { duration: this.decodedData.duration });
123
145
  });
124
146
  }
125
147
  /** Zoom in or out */
126
148
  zoom(minPxPerSec) {
127
- if (this.channelData == null || this.duration == null) {
149
+ if (!this.decodedData) {
128
150
  throw new Error('No audio loaded');
129
151
  }
130
- this.renderer.render(this.channelData, this.duration, minPxPerSec);
152
+ this.renderer.zoom(this.decodedData, minPxPerSec);
131
153
  }
132
154
  /** Start playing the audio */
133
155
  play() {
@@ -147,20 +169,59 @@ export class WaveSurfer extends EventEmitter {
147
169
  }
148
170
  /** Get the duration of the audio in seconds */
149
171
  getDuration() {
150
- return this.duration;
172
+ var _a;
173
+ return this.player.getDuration() || ((_a = this.decodedData) === null || _a === void 0 ? void 0 : _a.duration) || 0;
151
174
  }
152
175
  /** Get the current audio position in seconds */
153
176
  getCurrentTime() {
154
177
  return this.player.getCurrentTime();
155
178
  }
179
+ /** Get the audio volume */
180
+ getVolume() {
181
+ return this.player.getVolume();
182
+ }
183
+ /** Set the audio volume */
184
+ setVolume(volume) {
185
+ this.player.setVolume(volume);
186
+ }
187
+ /** Get the audio muted state */
188
+ getMuted() {
189
+ return this.player.getMuted();
190
+ }
191
+ /** Mute or unmute the audio */
192
+ setMuted(muted) {
193
+ this.player.setMuted(muted);
194
+ }
195
+ /** Get playback rate */
196
+ getPlaybackRate() {
197
+ return this.player.getPlaybackRate();
198
+ }
199
+ /** Set playback rate, with an optional parameter to NOT preserve the pitch if false */
200
+ setPlaybackRate(rate, preservePitch) {
201
+ this.player.setPlaybackRate(rate, preservePitch);
202
+ }
156
203
  /** Register and initialize a plugin */
157
- registerPlugin(CustomPlugin) {
204
+ registerPlugin(CustomPlugin, options) {
158
205
  const plugin = new CustomPlugin({
159
206
  wavesurfer: this,
160
- renderer: this.renderer,
161
- });
207
+ container: this.renderer.getContainer(),
208
+ }, options);
162
209
  this.plugins.push(plugin);
163
210
  return plugin;
164
211
  }
212
+ /** Get the decoded audio data */
213
+ getDecodedData() {
214
+ return this.decodedData;
215
+ }
216
+ /** Unmount wavesurfer */
217
+ destroy() {
218
+ this.emit('destroy');
219
+ this.subscriptions.forEach((unsubscribe) => unsubscribe());
220
+ this.plugins.forEach((plugin) => plugin.destroy());
221
+ this.timer.destroy();
222
+ this.player.destroy();
223
+ this.decoder.destroy();
224
+ this.renderer.destroy();
225
+ }
165
226
  }
166
227
  export default WaveSurfer;
@@ -16,16 +16,17 @@ class WebAudioPlayer extends Player {
16
16
  loadUrl(url) {
17
17
  super.loadUrl(url);
18
18
  if (!this.audioCtx) {
19
- this.audioCtx = new (window.AudioContext ||
20
- window.webkitAudioContext)({
19
+ this.audioCtx = new AudioContext({
21
20
  latencyHint: 'playback',
22
21
  });
23
22
  }
24
23
  if (this.sourceNode) {
25
24
  this.sourceNode.disconnect();
26
25
  }
27
- this.sourceNode = this.audioCtx.createMediaElementSource(this.media);
28
- this.sourceNode.connect(this.audioCtx.destination);
26
+ if (this.media) {
27
+ this.sourceNode = this.audioCtx.createMediaElementSource(this.media);
28
+ this.sourceNode.connect(this.audioCtx.destination);
29
+ }
29
30
  }
30
31
  }
31
32
  export default WebAudioPlayer;
package/dist/player.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  declare class Player {
2
2
  protected media: HTMLMediaElement;
3
3
  private isExternalMedia;
4
- constructor({ media }: {
4
+ private hasPlayedOnce;
5
+ constructor({ media, autoplay }: {
5
6
  media?: HTMLMediaElement;
7
+ autoplay?: boolean;
6
8
  });
7
- on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
9
+ on(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
8
10
  destroy(): void;
9
11
  loadUrl(src: string): void;
10
12
  getCurrentTime(): number;
@@ -12,5 +14,12 @@ declare class Player {
12
14
  pause(): void;
13
15
  isPlaying(): boolean;
14
16
  seekTo(time: number): void;
17
+ getDuration(): number;
18
+ getVolume(): number;
19
+ setVolume(volume: number): void;
20
+ getMuted(): boolean;
21
+ setMuted(muted: boolean): void;
22
+ getPlaybackRate(): number;
23
+ setPlaybackRate(rate: number, preservePitch?: boolean): void;
15
24
  }
16
25
  export default Player;
package/dist/player.js CHANGED
@@ -1,6 +1,7 @@
1
1
  class Player {
2
- constructor({ media }) {
2
+ constructor({ media, autoplay }) {
3
3
  this.isExternalMedia = false;
4
+ this.hasPlayedOnce = false;
4
5
  if (media) {
5
6
  this.media = media;
6
7
  this.isExternalMedia = true;
@@ -8,12 +9,22 @@ class Player {
8
9
  else {
9
10
  this.media = document.createElement('audio');
10
11
  }
12
+ // Track the first play() call
13
+ const unsubscribe = this.on('play', () => {
14
+ this.hasPlayedOnce = true;
15
+ unsubscribe();
16
+ });
17
+ // Autoplay
18
+ if (autoplay) {
19
+ this.media.autoplay = true;
20
+ }
11
21
  }
12
- on(event, callback) {
13
- this.media.addEventListener(event, callback);
22
+ on(event, callback, options) {
23
+ this.media.addEventListener(event, callback, options);
14
24
  return () => this.media.removeEventListener(event, callback);
15
25
  }
16
26
  destroy() {
27
+ this.media.pause();
17
28
  if (!this.isExternalMedia) {
18
29
  this.media.remove();
19
30
  }
@@ -34,7 +45,42 @@ class Player {
34
45
  return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
35
46
  }
36
47
  seekTo(time) {
37
- this.media.currentTime = time;
48
+ if (this.media) {
49
+ // iOS Safari requires a play() call before seeking
50
+ const { hasPlayedOnce } = this;
51
+ if (!hasPlayedOnce) {
52
+ this.media.play();
53
+ }
54
+ this.media.currentTime = time;
55
+ if (!hasPlayedOnce) {
56
+ this.media.pause();
57
+ }
58
+ }
59
+ }
60
+ getDuration() {
61
+ return this.media.duration;
62
+ }
63
+ getVolume() {
64
+ return this.media.volume;
65
+ }
66
+ setVolume(volume) {
67
+ this.media.volume = volume;
68
+ }
69
+ getMuted() {
70
+ return this.media.muted;
71
+ }
72
+ setMuted(muted) {
73
+ this.media.muted = muted;
74
+ }
75
+ getPlaybackRate() {
76
+ return this.media.playbackRate;
77
+ }
78
+ setPlaybackRate(rate, preservePitch) {
79
+ // preservePitch is true by default in most browsers
80
+ if (preservePitch != null) {
81
+ this.media.preservesPitch = preservePitch;
82
+ }
83
+ this.media.playbackRate = rate;
38
84
  }
39
85
  }
40
86
  export default Player;