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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,11 @@ An experimental rewrite of [wavesufer.js](https://github.com/wavesurfer-js/waves
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
+ Try it out:
8
+ ```
9
+ npm install --save wavesurfer.js@alpha
10
+ ```
11
+
7
12
  ## Goals
8
13
 
9
14
  * TypeScript API
@@ -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 unknown> extends EventEmitter<EventTypes> {
4
4
  protected wavesurfer: WaveSurfer;
5
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
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
@@ -2,10 +2,7 @@ declare class Decoder {
2
2
  audioCtx: AudioContext | null;
3
3
  private initAudioContext;
4
4
  constructor();
5
- decode(audioData: ArrayBuffer): Promise<{
6
- duration: number;
7
- channelData: Float32Array[];
8
- }>;
5
+ decode(audioData: ArrayBuffer): Promise<AudioBuffer>;
9
6
  destroy(): void;
10
7
  }
11
8
  export default Decoder;
package/dist/decoder.js CHANGED
@@ -31,15 +31,7 @@ class Decoder {
31
31
  if (!this.audioCtx) {
32
32
  throw new Error('AudioContext is not initialized');
33
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
- };
34
+ return this.audioCtx.decodeAudioData(audioData);
43
35
  });
44
36
  }
45
37
  destroy() {
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export type WaveSurferOptions = {
9
9
  waveColor?: string;
10
10
  /** The color of the progress mask */
11
11
  progressColor?: string;
12
+ /** The color of the playpack cursort */
13
+ cursorColor?: string;
12
14
  /** If set, the waveform will be rendered in bars like so: ▁ ▂ ▇ ▃ ▅ ▂ */
13
15
  barWidth?: number;
14
16
  /** Spacing between bars in pixels */
@@ -17,16 +19,20 @@ export type WaveSurferOptions = {
17
19
  barRadius?: number;
18
20
  /** Minimum pixels per second of audio (zoom) */
19
21
  minPxPerSec?: number;
22
+ /** Stretch the waveform to fill the container, true by default */
23
+ fillParent?: boolean;
20
24
  /** Audio URL */
21
25
  url?: string;
22
26
  /** Pre-computed audio data */
23
- channelData?: Float32Array[];
27
+ peaks?: Float32Array[];
24
28
  /** Pre-computed duration */
25
29
  duration?: number;
26
30
  /** Use an existing media element instead of creating one */
27
31
  media?: HTMLMediaElement;
28
32
  /** Play the audio on load */
29
33
  autoplay?: boolean;
34
+ /** Is the waveform clickable? */
35
+ interactive?: boolean;
30
36
  };
31
37
  export type WaveSurferEvents = {
32
38
  decode: {
@@ -37,12 +43,16 @@ export type WaveSurferEvents = {
37
43
  };
38
44
  play: void;
39
45
  pause: void;
40
- audioprocess: {
46
+ timeupdate: {
41
47
  currentTime: number;
42
48
  };
43
- seek: {
44
- time: number;
49
+ seeking: {
50
+ currentTime: number;
51
+ };
52
+ seekClick: {
53
+ currentTime: number;
45
54
  };
55
+ destroy: void;
46
56
  };
47
57
  export type WaveSurferPluginParams = {
48
58
  wavesurfer: WaveSurfer;
@@ -57,8 +67,7 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
57
67
  private timer;
58
68
  private plugins;
59
69
  private subscriptions;
60
- private channelData;
61
- private duration;
70
+ private decodedData;
62
71
  /** Create a new WaveSurfer instance */
63
72
  static create(options: WaveSurferOptions): WaveSurfer;
64
73
  /** Create a new WaveSurfer instance */
@@ -66,8 +75,6 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
66
75
  private initPlayerEvents;
67
76
  private initRendererEvents;
68
77
  private initTimerEvents;
69
- /** Unmount wavesurfer */
70
- destroy(): void;
71
78
  /** Load an audio file by URL, with optional pre-decoded audio data */
72
79
  load(url: string, channelData?: Float32Array[], duration?: number): Promise<void>;
73
80
  /** Zoom in or out */
@@ -97,8 +104,10 @@ export declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
97
104
  /** Set playback rate, with an optional parameter to NOT preserve the pitch if false */
98
105
  setPlaybackRate(rate: number, preservePitch?: boolean): void;
99
106
  /** Register and initialize a plugin */
100
- registerPlugin<T extends BasePlugin<GeneralEventTypes>>(CustomPlugin: new (params: WaveSurferPluginParams) => T): T;
107
+ registerPlugin<T extends BasePlugin<GeneralEventTypes, unknown>>(CustomPlugin: new (params: unknown, options: unknown) => T, options?: unknown): T;
101
108
  /** Get the decoded audio data */
102
- getAudioData(): Float32Array[] | null;
109
+ getDecodedData(): AudioBuffer | null;
110
+ /** Unmount wavesurfer */
111
+ destroy(): void;
103
112
  }
104
113
  export default WaveSurfer;
package/dist/index.js CHANGED
@@ -18,6 +18,8 @@ const defaultOptions = {
18
18
  waveColor: '#999',
19
19
  progressColor: '#555',
20
20
  minPxPerSec: 0,
21
+ fillParent: true,
22
+ interactive: true,
21
23
  };
22
24
  export class WaveSurfer extends EventEmitter {
23
25
  /** Create a new WaveSurfer instance */
@@ -30,8 +32,7 @@ export class WaveSurfer extends EventEmitter {
30
32
  super();
31
33
  this.plugins = [];
32
34
  this.subscriptions = [];
33
- this.channelData = null;
34
- this.duration = 0;
35
+ this.decodedData = null;
35
36
  this.options = Object.assign({}, defaultOptions, options);
36
37
  this.fetcher = new Fetcher();
37
38
  this.decoder = new Decoder();
@@ -45,7 +46,9 @@ export class WaveSurfer extends EventEmitter {
45
46
  height: this.options.height,
46
47
  waveColor: this.options.waveColor,
47
48
  progressColor: this.options.progressColor,
49
+ cursorColor: this.options.cursorColor,
48
50
  minPxPerSec: this.options.minPxPerSec,
51
+ fillParent: this.options.fillParent,
49
52
  barWidth: this.options.barWidth,
50
53
  barGap: this.options.barGap,
51
54
  barRadius: this.options.barRadius,
@@ -55,14 +58,14 @@ export class WaveSurfer extends EventEmitter {
55
58
  this.initTimerEvents();
56
59
  const url = this.options.url || ((_a = this.options.media) === null || _a === void 0 ? void 0 : _a.src);
57
60
  if (url) {
58
- this.load(url, this.options.channelData, this.options.duration);
61
+ this.load(url, this.options.peaks, this.options.duration);
59
62
  }
60
63
  }
61
64
  initPlayerEvents() {
62
65
  this.subscriptions.push(this.player.on('timeupdate', () => {
63
66
  const currentTime = this.getCurrentTime();
64
- this.renderer.renderProgress(currentTime / this.duration, this.isPlaying());
65
- this.emit('audioprocess', { currentTime });
67
+ this.renderer.renderProgress(currentTime / this.getDuration(), this.isPlaying());
68
+ this.emit('timeupdate', { currentTime });
66
69
  }), this.player.on('play', () => {
67
70
  this.emit('play');
68
71
  this.timer.start();
@@ -72,56 +75,61 @@ export class WaveSurfer extends EventEmitter {
72
75
  }), this.player.on('canplay', () => {
73
76
  this.emit('canplay', { duration: this.getDuration() });
74
77
  }), this.player.on('seeking', () => {
75
- this.emit('seek', { time: this.getCurrentTime() });
78
+ this.emit('seeking', { currentTime: this.getCurrentTime() });
76
79
  }));
77
80
  }
78
81
  initRendererEvents() {
79
82
  // Seek on click
80
83
  this.subscriptions.push(this.renderer.on('click', ({ relativeX }) => {
81
- const time = this.getDuration() * relativeX;
82
- this.seekTo(time);
84
+ if (this.options.interactive) {
85
+ const time = this.getDuration() * relativeX;
86
+ this.seekTo(time);
87
+ this.emit('seekClick', { currentTime: this.getCurrentTime() });
88
+ }
83
89
  }));
84
90
  }
85
91
  initTimerEvents() {
86
92
  // The timer fires every 16ms for a smooth progress animation
87
93
  this.subscriptions.push(this.timer.on('tick', () => {
88
94
  const currentTime = this.getCurrentTime();
89
- this.renderer.renderProgress(currentTime / this.duration, true);
90
- this.emit('audioprocess', { currentTime });
95
+ this.renderer.renderProgress(currentTime / this.getDuration(), true);
96
+ this.emit('timeupdate', { currentTime });
91
97
  }));
92
98
  }
93
- /** Unmount wavesurfer */
94
- destroy() {
95
- this.subscriptions.forEach((unsubscribe) => unsubscribe());
96
- this.plugins.forEach((plugin) => plugin.destroy());
97
- this.timer.destroy();
98
- this.player.destroy();
99
- this.decoder.destroy();
100
- this.renderer.destroy();
101
- }
102
99
  /** Load an audio file by URL, with optional pre-decoded audio data */
103
100
  load(url, channelData, duration) {
104
101
  return __awaiter(this, void 0, void 0, function* () {
105
102
  this.player.loadUrl(url);
106
103
  // Fetch and decode the audio of no pre-computed audio data is provided
107
- if (channelData == null || duration == null) {
104
+ if (channelData == null) {
108
105
  const audio = yield this.fetcher.load(url);
109
106
  const data = yield this.decoder.decode(audio);
110
- channelData = data.channelData;
111
- duration = data.duration;
107
+ this.decodedData = data;
108
+ }
109
+ else {
110
+ if (!duration) {
111
+ duration =
112
+ (yield new Promise((resolve) => {
113
+ this.player.on('canplay', () => resolve(this.getDuration()), { once: true });
114
+ })) || 0;
115
+ }
116
+ this.decodedData = {
117
+ duration,
118
+ numberOfChannels: channelData.length,
119
+ sampleRate: channelData[0].length / duration,
120
+ getChannelData: (i) => channelData[i],
121
+ };
112
122
  }
113
- this.channelData = channelData;
114
- this.duration = duration;
115
- this.renderer.render(this.channelData, this.duration);
116
- this.emit('decode', { duration: this.duration });
123
+ this.renderer.render(this.decodedData);
124
+ this.emit('decode', { duration: this.decodedData.duration });
117
125
  });
118
126
  }
119
127
  /** Zoom in or out */
120
128
  zoom(minPxPerSec) {
121
- if (this.channelData == null || this.duration == null) {
129
+ if (!this.decodedData) {
122
130
  throw new Error('No audio loaded');
123
131
  }
124
- this.renderer.zoom(this.channelData, this.duration, minPxPerSec);
132
+ this.renderer.zoom(this.decodedData, minPxPerSec);
125
133
  }
126
134
  /** Start playing the audio */
127
135
  play() {
@@ -141,7 +149,8 @@ export class WaveSurfer extends EventEmitter {
141
149
  }
142
150
  /** Get the duration of the audio in seconds */
143
151
  getDuration() {
144
- return this.duration;
152
+ var _a;
153
+ return this.player.getDuration() || ((_a = this.decodedData) === null || _a === void 0 ? void 0 : _a.duration) || 0;
145
154
  }
146
155
  /** Get the current audio position in seconds */
147
156
  getCurrentTime() {
@@ -172,17 +181,27 @@ export class WaveSurfer extends EventEmitter {
172
181
  this.player.setPlaybackRate(rate, preservePitch);
173
182
  }
174
183
  /** Register and initialize a plugin */
175
- registerPlugin(CustomPlugin) {
184
+ registerPlugin(CustomPlugin, options) {
176
185
  const plugin = new CustomPlugin({
177
186
  wavesurfer: this,
178
187
  container: this.renderer.getContainer(),
179
- });
188
+ }, options);
180
189
  this.plugins.push(plugin);
181
190
  return plugin;
182
191
  }
183
192
  /** Get the decoded audio data */
184
- getAudioData() {
185
- return this.channelData;
193
+ getDecodedData() {
194
+ return this.decodedData;
195
+ }
196
+ /** Unmount wavesurfer */
197
+ destroy() {
198
+ this.emit('destroy');
199
+ this.subscriptions.forEach((unsubscribe) => unsubscribe());
200
+ this.plugins.forEach((plugin) => plugin.destroy());
201
+ this.timer.destroy();
202
+ this.player.destroy();
203
+ this.decoder.destroy();
204
+ this.renderer.destroy();
186
205
  }
187
206
  }
188
207
  export default WaveSurfer;
package/dist/player.d.ts CHANGED
@@ -6,7 +6,7 @@ declare class Player {
6
6
  media?: HTMLMediaElement;
7
7
  autoplay?: boolean;
8
8
  });
9
- on(event: keyof HTMLMediaElementEventMap, callback: () => void): () => void;
9
+ on(event: keyof HTMLMediaElementEventMap, callback: () => void, options?: AddEventListenerOptions): () => void;
10
10
  destroy(): void;
11
11
  loadUrl(src: string): void;
12
12
  getCurrentTime(): number;
@@ -14,6 +14,7 @@ declare class Player {
14
14
  pause(): void;
15
15
  isPlaying(): boolean;
16
16
  seekTo(time: number): void;
17
+ getDuration(): number;
17
18
  getVolume(): number;
18
19
  setVolume(volume: number): void;
19
20
  getMuted(): boolean;
package/dist/player.js CHANGED
@@ -19,22 +19,18 @@ class Player {
19
19
  this.media.autoplay = true;
20
20
  }
21
21
  }
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); };
22
+ on(event, callback, options) {
23
+ this.media.addEventListener(event, callback, options);
24
+ return () => this.media.removeEventListener(event, callback);
26
25
  }
27
26
  destroy() {
28
- var _a, _b;
29
- (_a = this.media) === null || _a === void 0 ? void 0 : _a.pause();
27
+ this.media.pause();
30
28
  if (!this.isExternalMedia) {
31
- (_b = this.media) === null || _b === void 0 ? void 0 : _b.remove();
29
+ this.media.remove();
32
30
  }
33
31
  }
34
32
  loadUrl(src) {
35
- if (this.media) {
36
- this.media.src = src;
37
- }
33
+ this.media.src = src;
38
34
  }
39
35
  getCurrentTime() {
40
36
  return this.media.currentTime;
@@ -61,6 +57,9 @@ class Player {
61
57
  }
62
58
  }
63
59
  }
60
+ getDuration() {
61
+ return this.media.duration;
62
+ }
64
63
  getVolume() {
65
64
  return this.media.volume;
66
65
  }
@@ -1,5 +1,10 @@
1
1
  import BasePlugin from '../base-plugin.js';
2
2
  import { WaveSurferPluginParams } from '../index.js';
3
+ type RegionsPluginOptions = {
4
+ dragSelection?: boolean;
5
+ draggable?: boolean;
6
+ resizable?: boolean;
7
+ };
3
8
  type Region = {
4
9
  startTime: number;
5
10
  endTime: number;
@@ -19,7 +24,7 @@ type RegionsPluginEvents = {
19
24
  region: Region;
20
25
  };
21
26
  };
22
- declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
27
+ declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
23
28
  private dragStart;
24
29
  private wrapper;
25
30
  private regions;
@@ -28,7 +33,7 @@ declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents> {
28
33
  private isResizingLeft;
29
34
  private isMoving;
30
35
  /** Create an instance of RegionsPlugin */
31
- constructor(params: WaveSurferPluginParams);
36
+ constructor(params: WaveSurferPluginParams, options: RegionsPluginOptions);
32
37
  /** Unmounts the regions */
33
38
  destroy(): void;
34
39
  private initWrapper;
@@ -1,5 +1,10 @@
1
1
  import BasePlugin from '../base-plugin.js';
2
2
  const MIN_WIDTH = 10;
3
+ const defaultOptions = {
4
+ dragSelection: true,
5
+ draggable: true,
6
+ resizable: true,
7
+ };
3
8
  const style = (element, styles) => {
4
9
  for (const key in styles) {
5
10
  element.style[key] = styles[key] || '';
@@ -12,8 +17,8 @@ const el = (tagName, css) => {
12
17
  };
13
18
  class RegionsPlugin extends BasePlugin {
14
19
  /** Create an instance of RegionsPlugin */
15
- constructor(params) {
16
- super(params);
20
+ constructor(params, options) {
21
+ super(params, options);
17
22
  this.dragStart = NaN;
18
23
  this.regions = [];
19
24
  this.createdRegion = null;
@@ -21,7 +26,9 @@ class RegionsPlugin extends BasePlugin {
21
26
  this.isResizingLeft = false;
22
27
  this.isMoving = false;
23
28
  this.handleMouseDown = (e) => {
24
- this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
29
+ if (this.options.draggable || this.options.resizable || this.options.dragSelection) {
30
+ this.dragStart = e.clientX - this.container.getBoundingClientRect().left;
31
+ }
25
32
  };
26
33
  this.handleMouseMove = (e) => {
27
34
  const dragEnd = e.clientX - this.container.getBoundingClientRect().left;
@@ -57,6 +64,7 @@ class RegionsPlugin extends BasePlugin {
57
64
  this.dragStart = NaN;
58
65
  this.container.style.pointerEvents = '';
59
66
  };
67
+ this.options = Object.assign(Object.assign({}, defaultOptions), options);
60
68
  this.wrapper = this.initWrapper();
61
69
  const unsubscribe = this.wavesurfer.on('decode', () => {
62
70
  this.container.appendChild(this.wrapper);
@@ -87,25 +95,29 @@ class RegionsPlugin extends BasePlugin {
87
95
  });
88
96
  }
89
97
  createRegionElement(start, end, title = '') {
98
+ const noWidth = start === end;
90
99
  const div = el('div', {
91
100
  position: 'absolute',
92
101
  left: `${start}px`,
93
102
  width: `${end - start}px`,
94
103
  height: '100%',
95
- backgroundColor: 'rgba(0, 0, 0, 0.1)',
104
+ backgroundColor: noWidth ? '' : 'rgba(0, 0, 0, 0.1)',
105
+ borderRadius: '2px',
106
+ boxSizing: 'border-box',
107
+ borderLeft: '2px solid rgba(0, 0, 0, 0.5)',
108
+ borderRight: noWidth ? '' : '2px solid rgba(0, 0, 0, 0.5)',
96
109
  transition: 'background-color 0.2s ease',
97
- cursor: 'move',
110
+ cursor: this.options.draggable ? 'move' : '',
98
111
  pointerEvents: 'all',
112
+ padding: '0.2em',
99
113
  });
100
- div.title = title;
114
+ div.textContent = title;
101
115
  const leftHandle = el('div', {
102
116
  position: 'absolute',
103
117
  left: '0',
104
118
  width: '6px',
105
119
  height: '100%',
106
- borderLeft: '2px solid rgba(0, 0, 0, 0.5)',
107
- borderRadius: '2px 0 0 2px',
108
- cursor: 'ew-resize',
120
+ cursor: this.options.resizable ? 'ew-resize' : '',
109
121
  pointerEvents: 'all',
110
122
  });
111
123
  div.appendChild(leftHandle);
@@ -114,23 +126,27 @@ class RegionsPlugin extends BasePlugin {
114
126
  left: '',
115
127
  right: '0',
116
128
  borderLeft: '',
117
- borderRight: '2px solid rgba(0, 0, 0, 0.5)',
118
- borderRadius: '0 2px 2px 0',
119
129
  });
120
130
  div.appendChild(rightHandle);
121
131
  leftHandle.addEventListener('mousedown', (e) => {
132
+ if (!this.options.resizable)
133
+ return;
122
134
  e.stopPropagation();
123
135
  this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
124
136
  this.isResizingLeft = true;
125
137
  this.isMoving = false;
126
138
  });
127
139
  rightHandle.addEventListener('mousedown', (e) => {
140
+ if (!this.options.resizable)
141
+ return;
128
142
  e.stopPropagation();
129
143
  this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
130
144
  this.isResizingLeft = false;
131
145
  this.isMoving = false;
132
146
  });
133
147
  div.addEventListener('mousedown', () => {
148
+ if (!this.options.draggable)
149
+ return;
134
150
  this.modifiedRegion = this.regions.find((r) => r.element === div) || null;
135
151
  this.isMoving = true;
136
152
  });
@@ -146,6 +162,8 @@ class RegionsPlugin extends BasePlugin {
146
162
  createRegion(start, end, title = '') {
147
163
  const duration = this.wavesurfer.getDuration();
148
164
  const width = this.wrapper.clientWidth;
165
+ start = Math.max(0, Math.min(start, width - 1));
166
+ end = Math.max(0, Math.min(end, width - 1));
149
167
  return {
150
168
  element: this.createRegionElement(start, end, title),
151
169
  start,
@@ -160,12 +178,15 @@ class RegionsPlugin extends BasePlugin {
160
178
  this.emit('region-created', { region });
161
179
  }
162
180
  updateRegion(region, start, end) {
181
+ const width = this.wrapper.clientWidth;
163
182
  if (start != null) {
164
- region.start = start !== null && start !== void 0 ? start : region.start;
183
+ start = Math.max(0, Math.min(start, width));
184
+ region.start = start;
165
185
  region.element.style.left = `${region.start}px`;
166
186
  region.startTime = (start / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
167
187
  }
168
188
  if (end != null) {
189
+ end = Math.max(0, Math.min(end, width));
169
190
  region.end = end;
170
191
  region.element.style.width = `${region.end - region.start}px`;
171
192
  region.endTime = (end / this.wrapper.clientWidth) * this.wavesurfer.getDuration();
@@ -187,7 +208,7 @@ class RegionsPlugin extends BasePlugin {
187
208
  }
188
209
  /** Set the background color of a region */
189
210
  setRegionColor(region, color) {
190
- region.element.style.backgroundColor = color;
211
+ region.element.style[region.startTime === region.endTime ? 'borderColor' : 'backgroundColor'] = color;
191
212
  }
192
213
  }
193
214
  export default RegionsPlugin;
@@ -4,7 +4,9 @@ type RendererOptions = {
4
4
  height: number;
5
5
  waveColor: string;
6
6
  progressColor: string;
7
+ cursorColor?: string;
7
8
  minPxPerSec: number;
9
+ fillParent: boolean;
8
10
  barWidth?: number;
9
11
  barGap?: number;
10
12
  barRadius?: number;
@@ -29,8 +31,8 @@ declare class Renderer extends EventEmitter<RendererEvents> {
29
31
  private delay;
30
32
  private renderPeaks;
31
33
  private createProgressMask;
32
- render(channelData: Float32Array[], duration: number): void;
33
- zoom(channelData: Float32Array[], duration: number, minPxPerSec: number): void;
34
+ render(audioData: AudioBuffer): void;
35
+ zoom(audioData: AudioBuffer, minPxPerSec: number): void;
34
36
  renderProgress(progress: number, autoCenter?: boolean): void;
35
37
  }
36
38
  export default Renderer;
package/dist/renderer.js CHANGED
@@ -65,7 +65,7 @@ class Renderer extends EventEmitter {
65
65
  top: 0;
66
66
  left: 0;
67
67
  height: 100%;
68
- border-right: 1px solid ${this.options.progressColor};
68
+ border-right: 1px solid ${this.options.cursorColor || this.options.progressColor};
69
69
  margin-left: -1px;
70
70
  }
71
71
  </style>
@@ -197,24 +197,29 @@ class Renderer extends EventEmitter {
197
197
  // This rectangle acts as a mask thanks to the composition method
198
198
  progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
199
199
  }
200
- render(channelData, duration) {
200
+ render(audioData) {
201
201
  // Determine the width of the canvas
202
202
  const { devicePixelRatio } = window;
203
- const parentWidth = this.scrollContainer.clientWidth * devicePixelRatio;
204
- const scrollWidth = duration * this.options.minPxPerSec;
205
- const scrollParent = scrollWidth > parentWidth;
206
- const width = scrollParent ? scrollWidth : parentWidth;
203
+ const parentWidth = this.options.fillParent ? this.scrollContainer.clientWidth * devicePixelRatio : 0;
204
+ const scrollWidth = audioData.duration * this.options.minPxPerSec;
205
+ const isScrolling = scrollWidth > parentWidth;
206
+ const width = isScrolling ? scrollWidth : parentWidth;
207
207
  const { height } = this.options;
208
208
  this.mainCanvas.width = width;
209
209
  this.mainCanvas.height = height;
210
- this.mainCanvas.style.width = scrollParent ? Math.round(scrollWidth / devicePixelRatio) + 'px' : '100%';
210
+ this.mainCanvas.style.width = Math.floor(scrollWidth / devicePixelRatio) + 'px';
211
+ // First two channels are used
212
+ const channelData = [audioData.getChannelData(0)];
213
+ if (audioData.numberOfChannels > 1) {
214
+ channelData.push(audioData.getChannelData(1));
215
+ }
211
216
  this.renderPeaks(channelData, width, height);
212
217
  }
213
- zoom(channelData, duration, minPxPerSec) {
218
+ zoom(audioData, minPxPerSec) {
214
219
  // Remember the current cursor position
215
220
  const oldCursorPosition = this.cursor.getBoundingClientRect().left;
216
221
  this.options.minPxPerSec = minPxPerSec;
217
- this.render(channelData, duration);
222
+ this.render(audioData);
218
223
  // Adjust the scroll position so that the cursor stays in the same place
219
224
  const newCursortPosition = this.cursor.getBoundingClientRect().left;
220
225
  this.scrollContainer.scrollLeft += newCursortPosition - oldCursorPosition;
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>r});const i=class{constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t,i){const n=e=>{e instanceof CustomEvent&&t(e.detail)},s=String(e);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(e,t){return this.on(e,t,!0)}},n=class extends i{constructor(e){super(),this.subscriptions=[],this.wavesurfer=e.wavesurfer,this.container=e.container}destroy(){this.subscriptions.forEach((e=>e()))}},s=(e,t)=>{for(const i in t)e.style[i]=t[i]||""},o=(e,t)=>{const i=document.createElement(e);return s(i,t),i},r=class extends n{constructor(e){super(e),this.dragStart=NaN,this.regions=[],this.createdRegion=null,this.modifiedRegion=null,this.isResizingLeft=!1,this.isMoving=!1,this.handleMouseDown=e=>{this.dragStart=e.clientX-this.container.getBoundingClientRect().left},this.handleMouseMove=e=>{const t=e.clientX-this.container.getBoundingClientRect().left;if(this.modifiedRegion&&this.isMoving)return this.moveRegion(this.modifiedRegion,t-this.dragStart),void(this.dragStart=t);if(this.modifiedRegion)this.updateRegion(this.modifiedRegion,this.isResizingLeft?t:void 0,this.isResizingLeft?void 0:t);else if(!isNaN(this.dragStart)){const t=e.clientX-this.wrapper.getBoundingClientRect().left;t-this.dragStart>=10&&(this.createdRegion?this.updateRegion(this.createdRegion,this.dragStart,t):(this.container.style.pointerEvents="none",this.createdRegion=this.createRegion(this.dragStart,t)))}},this.handleMouseUp=()=>{this.createdRegion&&(this.addRegion(this.createdRegion),this.createdRegion=null),this.modifiedRegion=null,this.isMoving=!1,this.dragStart=NaN,this.container.style.pointerEvents=""},this.wrapper=this.initWrapper();const t=this.wavesurfer.on("decode",(()=>{this.container.appendChild(this.wrapper),t()}));this.subscriptions.push(t),this.container.addEventListener("mousedown",this.handleMouseDown),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}destroy(){this.container.removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp,!0),this.wrapper.remove(),super.destroy()}initWrapper(){return o("div",{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"3",pointerEvents:"none"})}createRegionElement(e,t,i=""){const n=o("div",{position:"absolute",left:`${e}px`,width:t-e+"px",height:"100%",backgroundColor:"rgba(0, 0, 0, 0.1)",transition:"background-color 0.2s ease",cursor:"move",pointerEvents:"all"});n.title=i;const r=o("div",{position:"absolute",left:"0",width:"6px",height:"100%",borderLeft:"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"2px 0 0 2px",cursor:"ew-resize",pointerEvents:"all"});n.appendChild(r);const d=r.cloneNode();return s(d,{left:"",right:"0",borderLeft:"",borderRight:"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"0 2px 2px 0"}),n.appendChild(d),r.addEventListener("mousedown",(e=>{e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===n))||null,this.isResizingLeft=!0,this.isMoving=!1})),d.addEventListener("mousedown",(e=>{e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===n))||null,this.isResizingLeft=!1,this.isMoving=!1})),n.addEventListener("mousedown",(()=>{this.modifiedRegion=this.regions.find((e=>e.element===n))||null,this.isMoving=!0})),n.addEventListener("click",(()=>{const e=this.regions.find((e=>e.element===n));e&&this.emit("region-clicked",{region:e})})),this.wrapper.appendChild(n),n}createRegion(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.wrapper.clientWidth;return{element:this.createRegionElement(e,t,i),start:e,end:t,startTime:e/s*n,endTime:t/s*n,title:i}}addRegion(e){this.regions.push(e),this.emit("region-created",{region:e})}updateRegion(e,t,i){null!=t&&(e.start=null!=t?t:e.start,e.element.style.left=`${e.start}px`,e.startTime=t/this.wrapper.clientWidth*this.wavesurfer.getDuration()),null!=i&&(e.end=i,e.element.style.width=e.end-e.start+"px",e.endTime=i/this.wrapper.clientWidth*this.wavesurfer.getDuration()),this.emit("region-updated",{region:e})}moveRegion(e,t){this.updateRegion(e,e.start+t,e.end+t)}add(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.wrapper.clientWidth,o=e/n*s,r=t/n*s,d=this.createRegion(o,r,i);return this.addRegion(d),d}setRegionColor(e,t){e.element.style.backgroundColor=t}};return t.default})()));
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Regions=t():e.Regions=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={d:(t,i)=>{for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>a});const i=class{constructor(){this.eventTarget=new EventTarget}emit(e,t){const i=new CustomEvent(String(e),{detail:t});this.eventTarget.dispatchEvent(i)}on(e,t,i){const n=e=>{e instanceof CustomEvent&&t(e.detail)},s=String(e);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(e,t){return this.on(e,t,!0)}},n=class extends i{constructor(e,t){super(),this.subscriptions=[],this.wavesurfer=e.wavesurfer,this.container=e.container,this.options=t}destroy(){this.subscriptions.forEach((e=>e()))}},s={dragSelection:!0,draggable:!0,resizable:!0},o=(e,t)=>{for(const i in t)e.style[i]=t[i]||""},r=(e,t)=>{const i=document.createElement(e);return o(i,t),i},a=class extends n{constructor(e,t){super(e,t),this.dragStart=NaN,this.regions=[],this.createdRegion=null,this.modifiedRegion=null,this.isResizingLeft=!1,this.isMoving=!1,this.handleMouseDown=e=>{(this.options.draggable||this.options.resizable||this.options.dragSelection)&&(this.dragStart=e.clientX-this.container.getBoundingClientRect().left)},this.handleMouseMove=e=>{const t=e.clientX-this.container.getBoundingClientRect().left;if(this.modifiedRegion&&this.isMoving)return this.moveRegion(this.modifiedRegion,t-this.dragStart),void(this.dragStart=t);if(this.modifiedRegion)this.updateRegion(this.modifiedRegion,this.isResizingLeft?t:void 0,this.isResizingLeft?void 0:t);else if(!isNaN(this.dragStart)){const t=e.clientX-this.wrapper.getBoundingClientRect().left;t-this.dragStart>=10&&(this.createdRegion?this.updateRegion(this.createdRegion,this.dragStart,t):(this.container.style.pointerEvents="none",this.createdRegion=this.createRegion(this.dragStart,t)))}},this.handleMouseUp=()=>{this.createdRegion&&(this.addRegion(this.createdRegion),this.createdRegion=null),this.modifiedRegion=null,this.isMoving=!1,this.dragStart=NaN,this.container.style.pointerEvents=""},this.options=Object.assign(Object.assign({},s),t),this.wrapper=this.initWrapper();const i=this.wavesurfer.on("decode",(()=>{this.container.appendChild(this.wrapper),i()}));this.subscriptions.push(i),this.container.addEventListener("mousedown",this.handleMouseDown),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}destroy(){this.container.removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp,!0),this.wrapper.remove(),super.destroy()}initWrapper(){return r("div",{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"3",pointerEvents:"none"})}createRegionElement(e,t,i=""){const n=e===t,s=r("div",{position:"absolute",left:`${e}px`,width:t-e+"px",height:"100%",backgroundColor:n?"":"rgba(0, 0, 0, 0.1)",borderRadius:"2px",boxSizing:"border-box",borderLeft:"2px solid rgba(0, 0, 0, 0.5)",borderRight:n?"":"2px solid rgba(0, 0, 0, 0.5)",transition:"background-color 0.2s ease",cursor:this.options.draggable?"move":"",pointerEvents:"all",padding:"0.2em"});s.textContent=i;const a=r("div",{position:"absolute",left:"0",width:"6px",height:"100%",cursor:this.options.resizable?"ew-resize":"",pointerEvents:"all"});s.appendChild(a);const d=a.cloneNode();return o(d,{left:"",right:"0",borderLeft:""}),s.appendChild(d),a.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===s))||null,this.isResizingLeft=!0,this.isMoving=!1)})),d.addEventListener("mousedown",(e=>{this.options.resizable&&(e.stopPropagation(),this.modifiedRegion=this.regions.find((e=>e.element===s))||null,this.isResizingLeft=!1,this.isMoving=!1)})),s.addEventListener("mousedown",(()=>{this.options.draggable&&(this.modifiedRegion=this.regions.find((e=>e.element===s))||null,this.isMoving=!0)})),s.addEventListener("click",(()=>{const e=this.regions.find((e=>e.element===s));e&&this.emit("region-clicked",{region:e})})),this.wrapper.appendChild(s),s}createRegion(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.wrapper.clientWidth;return e=Math.max(0,Math.min(e,s-1)),t=Math.max(0,Math.min(t,s-1)),{element:this.createRegionElement(e,t,i),start:e,end:t,startTime:e/s*n,endTime:t/s*n,title:i}}addRegion(e){this.regions.push(e),this.emit("region-created",{region:e})}updateRegion(e,t,i){const n=this.wrapper.clientWidth;null!=t&&(t=Math.max(0,Math.min(t,n)),e.start=t,e.element.style.left=`${e.start}px`,e.startTime=t/this.wrapper.clientWidth*this.wavesurfer.getDuration()),null!=i&&(i=Math.max(0,Math.min(i,n)),e.end=i,e.element.style.width=e.end-e.start+"px",e.endTime=i/this.wrapper.clientWidth*this.wavesurfer.getDuration()),this.emit("region-updated",{region:e})}moveRegion(e,t){this.updateRegion(e,e.start+t,e.end+t)}add(e,t,i=""){const n=this.wavesurfer.getDuration(),s=this.wrapper.clientWidth,o=e/n*s,r=t/n*s,a=this.createRegion(o,r,i);return this.addRegion(a),a}setRegionColor(e,t){e.element.style[e.startTime===e.endTime?"borderColor":"backgroundColor"]=t}};return t.default})()));
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>a});const i=class{constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e,i){const n=t=>{t instanceof CustomEvent&&e(t.detail)},s=String(t);return this.eventTarget.addEventListener(s,n,{once:i}),()=>this.eventTarget.removeEventListener(s,n)}once(t,e){return this.on(t,e,!0)}};const n=class extends i{constructor(t){super(),this.timeout=null,this.options=t;let e=null;if("string"==typeof this.options.container?e=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(e=this.options.container),!e)throw new Error("Container not found");const i=document.createElement("div"),n=i.attachShadow({mode:"open"});n.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: visible;\n width: 100%;\n height: ${this.options.height}px;\n position: relative;\n }\n :host .wrapper {\n position: relative;\n width: fit-content;\n min-width: 100%;\n height: 100%;\n z-index: 2;\n }\n :host canvas {\n display: block;\n height: 100%;\n min-width: 100%;\n image-rendering: pixelated;\n }\n :host .progress {\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n height: 100%;\n pointer-events: none;\n clip-path: inset(100%);\n }\n :host .cursor {\n position: absolute;\n z-index: 3;\n top: 0;\n left: 0;\n height: 100%;\n border-right: 1px solid ${this.options.progressColor};\n margin-left: -1px;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <canvas></canvas>\n <canvas class="progress"></canvas>\n <div class="cursor"></div>\n </div>\n </div>\n `,this.container=i,this.scrollContainer=n.querySelector(".scroll"),this.mainCanvas=n.querySelector("canvas"),this.ctx=this.mainCanvas.getContext("2d",{desynchronized:!0}),this.progressCanvas=n.querySelector(".progress"),this.cursor=n.querySelector(".cursor"),e.appendChild(i),this.mainCanvas.addEventListener("click",(t=>{const e=this.mainCanvas.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",{relativeX:i})}))}getContainer(){return this.scrollContainer.querySelector(".wrapper")}destroy(){this.container.remove()}delay(t,e=100){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}renderPeaks(t,e,i){var n,s,r,o,a;return s=this,r=void 0,a=function*(){const{devicePixelRatio:s}=window,{ctx:r}=this,o=null!=this.options.barWidth?this.options.barWidth*s:1,a=null!=this.options.barGap?this.options.barGap*s:this.options.barWidth?o/2:0,h=null!==(n=this.options.barRadius)&&void 0!==n?n:0,l=t[0],c=l.length,d=Math.floor(e/(o+a))/c,u=i/2,p=1===t.length,m=p?l:t[1],v=p&&m.some((t=>t<0)),y=(t,e)=>{let i=0,n=0,s=0;r.beginPath();for(let c=t;c<e;c++){const t=Math.round(c*d);if(t>i){const e=Math.round(n*u),l=Math.round(s*u);r.roundRect(i*(o+a),u-e,o,e+l||1,h),i=t,n=0,s=0}const e=v?l[c]:Math.abs(l[c]),p=v?m[c]:Math.abs(m[c]);e>n&&(n=e),(v?p<-s:p>s)&&(s=p<0?-p:p)}r.fillStyle=this.options.waveColor,r.fill(),r.closePath()};r.clearRect(0,0,e,i);const{scrollLeft:g,scrollWidth:f,clientWidth:C}=this.scrollContainer,w=c/f,b=Math.floor(g*w),x=Math.ceil((g+C)*w);y(b,x),this.createProgressMask(),b>0&&(yield this.delay((()=>{y(0,b)}))),x<c&&(yield this.delay((()=>{y(x,c)}))),this.delay((()=>{this.createProgressMask()}))},new((o=void 0)||(o=Promise))((function(t,e){function i(t){try{h(a.next(t))}catch(t){e(t)}}function n(t){try{h(a.throw(t))}catch(t){e(t)}}function h(e){var s;e.done?t(e.value):(s=e.value,s instanceof o?s:new o((function(t){t(s)}))).then(i,n)}h((a=a.apply(s,r||[])).next())}))}createProgressMask(){const t=this.progressCanvas.getContext("2d",{desynchronized:!0});this.progressCanvas.width=this.mainCanvas.width,this.progressCanvas.height=this.mainCanvas.height,this.progressCanvas.style.width=this.mainCanvas.style.width,this.progressCanvas.style.height=this.mainCanvas.style.height,t.drawImage(this.mainCanvas,0,0),t.globalCompositeOperation="source-in",t.fillStyle=this.options.progressColor,t.fillRect(0,0,this.progressCanvas.width,this.progressCanvas.height)}render(t,e){const{devicePixelRatio:i}=window,n=this.scrollContainer.clientWidth*i,s=e*this.options.minPxPerSec,r=s>n,o=r?s:n,{height:a}=this.options;this.mainCanvas.width=o,this.mainCanvas.height=a,this.mainCanvas.style.width=r?Math.round(s/i)+"px":"100%",this.renderPeaks(t,o,a)}zoom(t,e,i){const n=this.cursor.getBoundingClientRect().left;this.options.minPxPerSec=i,this.render(t,e);const s=this.cursor.getBoundingClientRect().left;this.scrollContainer.scrollLeft+=s-n}renderProgress(t,e=!1){if(this.progressCanvas.style.clipPath=`inset(0 ${100-100*t}% 0 0)`,this.cursor.style.left=100*t+"%",e){const e=this.container.clientWidth/2,i=this.mainCanvas.clientWidth;i*t>=e&&(this.scrollContainer.scrollLeft=i*t-e)}}},s=class extends i{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}};const r={height:128,waveColor:"#999",progressColor:"#555",minPxPerSec:0};class o extends i{static create(t){return new o(t)}constructor(t){var e;super(),this.plugins=[],this.subscriptions=[],this.channelData=null,this.duration=0,this.options=Object.assign({},r,t),this.fetcher=new class{load(t){return e=this,i=void 0,s=function*(){return fetch(t).then((t=>t.arrayBuffer()))},new((n=void 0)||(n=Promise))((function(t,r){function o(t){try{h(s.next(t))}catch(t){r(t)}}function a(t){try{h(s.throw(t))}catch(t){r(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof n?i:new n((function(t){t(i)}))).then(o,a)}h((s=s.apply(e,i||[])).next())}));var e,i,n,s}},this.decoder=new class{initAudioContext(t){this.audioCtx=new AudioContext({latencyHint:"playback",sampleRate:t})}constructor(){this.audioCtx=null;try{this.initAudioContext(3e3)}catch(t){this.initAudioContext(8e3)}}decode(t){return e=this,i=void 0,s=function*(){if(!this.audioCtx)throw new Error("AudioContext is not initialized");const e=yield this.audioCtx.decodeAudioData(t),i=[e.getChannelData(0)];return e.numberOfChannels>1&&i.push(e.getChannelData(1)),{duration:e.duration,channelData:i}},new((n=void 0)||(n=Promise))((function(t,r){function o(t){try{h(s.next(t))}catch(t){r(t)}}function a(t){try{h(s.throw(t))}catch(t){r(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof n?i:new n((function(t){t(i)}))).then(o,a)}h((s=s.apply(e,i||[])).next())}));var e,i,n,s}destroy(){var t;null===(t=this.audioCtx)||void 0===t||t.close(),this.audioCtx=null}},this.timer=new s,this.player=new class{constructor({media:t,autoplay:e}){this.isExternalMedia=!1,this.hasPlayedOnce=!1,t?(this.media=t,this.isExternalMedia=!0):this.media=document.createElement("audio");const i=this.on("play",(()=>{this.hasPlayedOnce=!0,i()}));e&&(this.media.autoplay=!0)}on(t,e){var i;return null===(i=this.media)||void 0===i||i.addEventListener(t,e),()=>{var i;return null===(i=this.media)||void 0===i?void 0:i.removeEventListener(t,e)}}destroy(){var t,e;null===(t=this.media)||void 0===t||t.pause(),this.isExternalMedia||null===(e=this.media)||void 0===e||e.remove()}loadUrl(t){this.media&&(this.media.src=t)}getCurrentTime(){return this.media.currentTime}play(){this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}seekTo(t){if(this.media){const{hasPlayedOnce:e}=this;e||this.media.play(),this.media.currentTime=t,e||this.media.pause()}}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}}({media:this.options.media,autoplay:this.options.autoplay}),this.renderer=new n({container:this.options.container,height:this.options.height,waveColor:this.options.waveColor,progressColor:this.options.progressColor,minPxPerSec:this.options.minPxPerSec,barWidth:this.options.barWidth,barGap:this.options.barGap,barRadius:this.options.barRadius}),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents();const i=this.options.url||(null===(e=this.options.media)||void 0===e?void 0:e.src);i&&this.load(i,this.options.channelData,this.options.duration)}initPlayerEvents(){this.subscriptions.push(this.player.on("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.duration,this.isPlaying()),this.emit("audioprocess",{currentTime:t})})),this.player.on("play",(()=>{this.emit("play"),this.timer.start()})),this.player.on("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.player.on("canplay",(()=>{this.emit("canplay",{duration:this.getDuration()})})),this.player.on("seeking",(()=>{this.emit("seek",{time:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{const e=this.getDuration()*t;this.seekTo(e)})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.duration,!0),this.emit("audioprocess",{currentTime:t})})))}destroy(){this.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.player.destroy(),this.decoder.destroy(),this.renderer.destroy()}load(t,e,i){return n=this,s=void 0,o=function*(){if(this.player.loadUrl(t),null==e||null==i){const n=yield this.fetcher.load(t),s=yield this.decoder.decode(n);e=s.channelData,i=s.duration}this.channelData=e,this.duration=i,this.renderer.render(this.channelData,this.duration),this.emit("decode",{duration:this.duration})},new((r=void 0)||(r=Promise))((function(t,e){function i(t){try{h(o.next(t))}catch(t){e(t)}}function a(t){try{h(o.throw(t))}catch(t){e(t)}}function h(e){var n;e.done?t(e.value):(n=e.value,n instanceof r?n:new r((function(t){t(n)}))).then(i,a)}h((o=o.apply(n,s||[])).next())}));var n,s,r,o}zoom(t){if(null==this.channelData||null==this.duration)throw new Error("No audio loaded");this.renderer.zoom(this.channelData,this.duration,t)}play(){this.player.play()}pause(){this.player.pause()}seekTo(t){this.player.seekTo(t)}isPlaying(){return this.player.isPlaying()}getDuration(){return this.duration}getCurrentTime(){return this.player.getCurrentTime()}getVolume(){return this.player.getVolume()}setVolume(t){this.player.setVolume(t)}getMuted(){return this.player.getMuted()}setMuted(t){this.player.setMuted(t)}getPlaybackRate(){return this.player.getPlaybackRate()}setPlaybackRate(t,e){this.player.setPlaybackRate(t,e)}registerPlugin(t){const e=new t({wavesurfer:this,container:this.renderer.getContainer()});return this.plugins.push(e),e}getAudioData(){return this.channelData}}const a=o;return e.default})()));
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.WaveSurfer=e():t.WaveSurfer=e()}(this,(()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>a});const i=class{constructor(){this.eventTarget=new EventTarget}emit(t,e){const i=new CustomEvent(String(t),{detail:e});this.eventTarget.dispatchEvent(i)}on(t,e,i){const s=t=>{t instanceof CustomEvent&&e(t.detail)},n=String(t);return this.eventTarget.addEventListener(n,s,{once:i}),()=>this.eventTarget.removeEventListener(n,s)}once(t,e){return this.on(t,e,!0)}};const s=class extends i{constructor(t){super(),this.timeout=null,this.options=t;let e=null;if("string"==typeof this.options.container?e=document.querySelector(this.options.container):this.options.container instanceof HTMLElement&&(e=this.options.container),!e)throw new Error("Container not found");const i=document.createElement("div"),s=i.attachShadow({mode:"open"});s.innerHTML=`\n <style>\n :host {\n user-select: none;\n }\n :host .scroll {\n overflow-x: auto;\n overflow-y: visible;\n width: 100%;\n height: ${this.options.height}px;\n position: relative;\n }\n :host .wrapper {\n position: relative;\n width: fit-content;\n min-width: 100%;\n height: 100%;\n z-index: 2;\n }\n :host canvas {\n display: block;\n height: 100%;\n min-width: 100%;\n image-rendering: pixelated;\n }\n :host .progress {\n position: absolute;\n z-index: 2;\n top: 0;\n left: 0;\n height: 100%;\n pointer-events: none;\n clip-path: inset(100%);\n }\n :host .cursor {\n position: absolute;\n z-index: 3;\n top: 0;\n left: 0;\n height: 100%;\n border-right: 1px solid ${this.options.cursorColor||this.options.progressColor};\n margin-left: -1px;\n }\n </style>\n\n <div class="scroll">\n <div class="wrapper">\n <canvas></canvas>\n <canvas class="progress"></canvas>\n <div class="cursor"></div>\n </div>\n </div>\n `,this.container=i,this.scrollContainer=s.querySelector(".scroll"),this.mainCanvas=s.querySelector("canvas"),this.ctx=this.mainCanvas.getContext("2d",{desynchronized:!0}),this.progressCanvas=s.querySelector(".progress"),this.cursor=s.querySelector(".cursor"),e.appendChild(i),this.mainCanvas.addEventListener("click",(t=>{const e=this.mainCanvas.getBoundingClientRect(),i=(t.clientX-e.left)/e.width;this.emit("click",{relativeX:i})}))}getContainer(){return this.scrollContainer.querySelector(".wrapper")}destroy(){this.container.remove()}delay(t,e=100){return this.timeout&&clearTimeout(this.timeout),new Promise((i=>{this.timeout=setTimeout((()=>{i(t())}),e)}))}renderPeaks(t,e,i){var s,n,r,o,a;return n=this,r=void 0,a=function*(){const{devicePixelRatio:n}=window,{ctx:r}=this,o=null!=this.options.barWidth?this.options.barWidth*n:1,a=null!=this.options.barGap?this.options.barGap*n:this.options.barWidth?o/2:0,h=null!==(s=this.options.barRadius)&&void 0!==s?s:0,l=t[0],c=l.length,d=Math.floor(e/(o+a))/c,u=i/2,p=1===t.length,m=p?l:t[1],y=p&&m.some((t=>t<0)),v=(t,e)=>{let i=0,s=0,n=0;r.beginPath();for(let c=t;c<e;c++){const t=Math.round(c*d);if(t>i){const e=Math.round(s*u),l=Math.round(n*u);r.roundRect(i*(o+a),u-e,o,e+l||1,h),i=t,s=0,n=0}const e=y?l[c]:Math.abs(l[c]),p=y?m[c]:Math.abs(m[c]);e>s&&(s=e),(y?p<-n:p>n)&&(n=p<0?-p:p)}r.fillStyle=this.options.waveColor,r.fill(),r.closePath()};r.clearRect(0,0,e,i);const{scrollLeft:g,scrollWidth:f,clientWidth:C}=this.scrollContainer,w=c/f,b=Math.floor(g*w),P=Math.ceil((g+C)*w);v(b,P),this.createProgressMask(),b>0&&(yield this.delay((()=>{v(0,b)}))),P<c&&(yield this.delay((()=>{v(P,c)}))),this.delay((()=>{this.createProgressMask()}))},new((o=void 0)||(o=Promise))((function(t,e){function i(t){try{h(a.next(t))}catch(t){e(t)}}function s(t){try{h(a.throw(t))}catch(t){e(t)}}function h(e){var n;e.done?t(e.value):(n=e.value,n instanceof o?n:new o((function(t){t(n)}))).then(i,s)}h((a=a.apply(n,r||[])).next())}))}createProgressMask(){const t=this.progressCanvas.getContext("2d",{desynchronized:!0});this.progressCanvas.width=this.mainCanvas.width,this.progressCanvas.height=this.mainCanvas.height,this.progressCanvas.style.width=this.mainCanvas.style.width,this.progressCanvas.style.height=this.mainCanvas.style.height,t.drawImage(this.mainCanvas,0,0),t.globalCompositeOperation="source-in",t.fillStyle=this.options.progressColor,t.fillRect(0,0,this.progressCanvas.width,this.progressCanvas.height)}render(t){const{devicePixelRatio:e}=window,i=this.options.fillParent?this.scrollContainer.clientWidth*e:0,s=t.duration*this.options.minPxPerSec,n=s>i?s:i,{height:r}=this.options;this.mainCanvas.width=n,this.mainCanvas.height=r,this.mainCanvas.style.width=Math.floor(s/e)+"px";const o=[t.getChannelData(0)];t.numberOfChannels>1&&o.push(t.getChannelData(1)),this.renderPeaks(o,n,r)}zoom(t,e){const i=this.cursor.getBoundingClientRect().left;this.options.minPxPerSec=e,this.render(t);const s=this.cursor.getBoundingClientRect().left;this.scrollContainer.scrollLeft+=s-i}renderProgress(t,e=!1){if(this.progressCanvas.style.clipPath=`inset(0 ${100-100*t}% 0 0)`,this.cursor.style.left=100*t+"%",e){const e=this.container.clientWidth/2,i=this.mainCanvas.clientWidth;i*t>=e&&(this.scrollContainer.scrollLeft=i*t-e)}}},n=class extends i{constructor(){super(...arguments),this.unsubscribe=()=>{}}start(){this.unsubscribe=this.on("tick",(()=>{requestAnimationFrame((()=>{this.emit("tick")}))})),this.emit("tick")}stop(){this.unsubscribe()}destroy(){this.unsubscribe()}};const r={height:128,waveColor:"#999",progressColor:"#555",minPxPerSec:0,fillParent:!0,interactive:!0};class o extends i{static create(t){return new o(t)}constructor(t){var e;super(),this.plugins=[],this.subscriptions=[],this.decodedData=null,this.options=Object.assign({},r,t),this.fetcher=new class{load(t){return e=this,i=void 0,n=function*(){return fetch(t).then((t=>t.arrayBuffer()))},new((s=void 0)||(s=Promise))((function(t,r){function o(t){try{h(n.next(t))}catch(t){r(t)}}function a(t){try{h(n.throw(t))}catch(t){r(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof s?i:new s((function(t){t(i)}))).then(o,a)}h((n=n.apply(e,i||[])).next())}));var e,i,s,n}},this.decoder=new class{initAudioContext(t){this.audioCtx=new AudioContext({latencyHint:"playback",sampleRate:t})}constructor(){this.audioCtx=null;try{this.initAudioContext(3e3)}catch(t){this.initAudioContext(8e3)}}decode(t){return e=this,i=void 0,n=function*(){if(!this.audioCtx)throw new Error("AudioContext is not initialized");return this.audioCtx.decodeAudioData(t)},new((s=void 0)||(s=Promise))((function(t,r){function o(t){try{h(n.next(t))}catch(t){r(t)}}function a(t){try{h(n.throw(t))}catch(t){r(t)}}function h(e){var i;e.done?t(e.value):(i=e.value,i instanceof s?i:new s((function(t){t(i)}))).then(o,a)}h((n=n.apply(e,i||[])).next())}));var e,i,s,n}destroy(){var t;null===(t=this.audioCtx)||void 0===t||t.close(),this.audioCtx=null}},this.timer=new n,this.player=new class{constructor({media:t,autoplay:e}){this.isExternalMedia=!1,this.hasPlayedOnce=!1,t?(this.media=t,this.isExternalMedia=!0):this.media=document.createElement("audio");const i=this.on("play",(()=>{this.hasPlayedOnce=!0,i()}));e&&(this.media.autoplay=!0)}on(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e)}destroy(){this.media.pause(),this.isExternalMedia||this.media.remove()}loadUrl(t){this.media.src=t}getCurrentTime(){return this.media.currentTime}play(){this.media.play()}pause(){this.media.pause()}isPlaying(){return this.media.currentTime>0&&!this.media.paused&&!this.media.ended}seekTo(t){if(this.media){const{hasPlayedOnce:e}=this;e||this.media.play(),this.media.currentTime=t,e||this.media.pause()}}getDuration(){return this.media.duration}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}}({media:this.options.media,autoplay:this.options.autoplay}),this.renderer=new s({container:this.options.container,height:this.options.height,waveColor:this.options.waveColor,progressColor:this.options.progressColor,cursorColor:this.options.cursorColor,minPxPerSec:this.options.minPxPerSec,fillParent:this.options.fillParent,barWidth:this.options.barWidth,barGap:this.options.barGap,barRadius:this.options.barRadius}),this.initPlayerEvents(),this.initRendererEvents(),this.initTimerEvents();const i=this.options.url||(null===(e=this.options.media)||void 0===e?void 0:e.src);i&&this.load(i,this.options.peaks,this.options.duration)}initPlayerEvents(){this.subscriptions.push(this.player.on("timeupdate",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),this.isPlaying()),this.emit("timeupdate",{currentTime:t})})),this.player.on("play",(()=>{this.emit("play"),this.timer.start()})),this.player.on("pause",(()=>{this.emit("pause"),this.timer.stop()})),this.player.on("canplay",(()=>{this.emit("canplay",{duration:this.getDuration()})})),this.player.on("seeking",(()=>{this.emit("seeking",{currentTime:this.getCurrentTime()})})))}initRendererEvents(){this.subscriptions.push(this.renderer.on("click",(({relativeX:t})=>{if(this.options.interactive){const e=this.getDuration()*t;this.seekTo(e),this.emit("seekClick",{currentTime:this.getCurrentTime()})}})))}initTimerEvents(){this.subscriptions.push(this.timer.on("tick",(()=>{const t=this.getCurrentTime();this.renderer.renderProgress(t/this.getDuration(),!0),this.emit("timeupdate",{currentTime:t})})))}load(t,e,i){return s=this,n=void 0,o=function*(){if(this.player.loadUrl(t),null==e){const e=yield this.fetcher.load(t),i=yield this.decoder.decode(e);this.decodedData=i}else i||(i=(yield new Promise((t=>{this.player.on("canplay",(()=>t(this.getDuration())),{once:!0})})))||0),this.decodedData={duration:i,numberOfChannels:e.length,sampleRate:e[0].length/i,getChannelData:t=>e[t]};this.renderer.render(this.decodedData),this.emit("decode",{duration:this.decodedData.duration})},new((r=void 0)||(r=Promise))((function(t,e){function i(t){try{h(o.next(t))}catch(t){e(t)}}function a(t){try{h(o.throw(t))}catch(t){e(t)}}function h(e){var s;e.done?t(e.value):(s=e.value,s instanceof r?s:new r((function(t){t(s)}))).then(i,a)}h((o=o.apply(s,n||[])).next())}));var s,n,r,o}zoom(t){if(!this.decodedData)throw new Error("No audio loaded");this.renderer.zoom(this.decodedData,t)}play(){this.player.play()}pause(){this.player.pause()}seekTo(t){this.player.seekTo(t)}isPlaying(){return this.player.isPlaying()}getDuration(){var t;return this.player.getDuration()||(null===(t=this.decodedData)||void 0===t?void 0:t.duration)||0}getCurrentTime(){return this.player.getCurrentTime()}getVolume(){return this.player.getVolume()}setVolume(t){this.player.setVolume(t)}getMuted(){return this.player.getMuted()}setMuted(t){this.player.setMuted(t)}getPlaybackRate(){return this.player.getPlaybackRate()}setPlaybackRate(t,e){this.player.setPlaybackRate(t,e)}registerPlugin(t,e){const i=new t({wavesurfer:this,container:this.renderer.getContainer()},e);return this.plugins.push(i),i}getDecodedData(){return this.decodedData}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.plugins.forEach((t=>t.destroy())),this.timer.destroy(),this.player.destroy(),this.decoder.destroy(),this.renderer.destroy()}}const a=o;return e.default})()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesurfer.js",
3
- "version": "7.0.0-alpha.4",
3
+ "version": "7.0.0-alpha.6",
4
4
  "description": "wavesurfer.js is an audio library that draws waveforms and plays audio in the browser.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",