source: gs3-extensions/mars-src/trunk/src/wavesurfer-renderer/wavesurfer.js.orig@ 36964

Last change on this file since 36964 was 34668, checked in by davidb, 3 years ago

Files to wavesurfer/src that need to be tweaked/augemented to

  • Property svn:executable set to *
File size: 53.0 KB
Line 
1import * as util from './util';
2import MultiCanvas from './drawer.multicanvas';
3import WebAudio from './webaudio';
4import MediaElement from './mediaelement';
5import PeakCache from './peakcache';
6import MediaElementWebAudio from './mediaelement-webaudio';
7
8/*
9 * This work is licensed under a BSD-3-Clause License.
10 */
11
12/** @external {HTMLElement} https://developer.mozilla.org/en/docs/Web/API/HTMLElement */
13/** @external {OfflineAudioContext} https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext */
14/** @external {File} https://developer.mozilla.org/en-US/docs/Web/API/File */
15/** @external {Blob} https://developer.mozilla.org/en-US/docs/Web/API/Blob */
16/** @external {CanvasRenderingContext2D} https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D */
17/** @external {MediaStreamConstraints} https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints */
18/** @external {AudioNode} https://developer.mozilla.org/de/docs/Web/API/AudioNode */
19
20/**
21 * @typedef {Object} WavesurferParams
22 * @property {AudioContext} audioContext=null Use your own previously
23 * initialized AudioContext or leave blank.
24 * @property {number} audioRate=1 Speed at which to play audio. Lower number is
25 * slower.
26 * @property {ScriptProcessorNode} audioScriptProcessor=null Use your own previously
27 * initialized ScriptProcessorNode or leave blank.
28 * @property {boolean} autoCenter=true If a scrollbar is present, center the
29 * waveform on current progress
30 * @property {number} autoCenterRate=5 If autoCenter is active, rate at which the
31 * waveform is centered
32 * @property {boolean} autoCenterImmediately=false If autoCenter is active, immediately
33 * center waveform on current progress
34 * @property {string} backend='WebAudio' `'WebAudio'|'MediaElement'|'MediaElementWebAudio'` In most cases
35 * you don't have to set this manually. MediaElement is a fallback for unsupported browsers.
36 * MediaElementWebAudio allows to use WebAudio API also with big audio files, loading audio like with
37 * MediaElement backend (HTML5 audio tag). You have to use the same methods of MediaElement backend for loading and
38 * playback, giving also peaks, so the audio data are not decoded. In this way you can use WebAudio features, like filters,
39 * also with audio with big duration. For example:
40 * ` wavesurfer.load(url | HTMLMediaElement, peaks, preload, duration);
41 * wavesurfer.play();
42 * wavesurfer.setFilter(customFilter);
43 * `
44 * @property {string} backgroundColor=null Change background color of the
45 * waveform container.
46 * @property {number} barHeight=1 The height of the wave bars.
47 * @property {number} barRadius=0 The radius of the wave bars. Makes bars rounded
48 * @property {number} barGap=null The optional spacing between bars of the wave,
49 * if not provided will be calculated in legacy format.
50 * @property {number} barWidth=null Draw the waveform using bars.
51 * @property {number} barMinHeight=null If specified, draw at least a bar of this height,
52 * eliminating waveform gaps
53 * @property {boolean} closeAudioContext=false Close and nullify all audio
54 * contexts when the destroy method is called.
55 * @property {!string|HTMLElement} container CSS selector or HTML element where
56 * the waveform should be drawn. This is the only required parameter.
57 * @property {string} cursorColor='#333' The fill color of the cursor indicating
58 * the playhead position.
59 * @property {number} cursorWidth=1 Measured in pixels.
60 * @property {object} drawingContextAttributes={desynchronized: false} Drawing context
61 * attributes.
62 * @property {number} duration=null Optional audio length so pre-rendered peaks
63 * can be display immediately for example.
64 * @property {boolean} fillParent=true Whether to fill the entire container or
65 * draw only according to `minPxPerSec`.
66 * @property {boolean} forceDecode=false Force decoding of audio using web audio
67 * when zooming to get a more detailed waveform.
68 * @property {number} height=128 The height of the waveform. Measured in
69 * pixels.
70 * @property {boolean} hideScrollbar=false Whether to hide the horizontal
71 * scrollbar when one would normally be shown.
72 * @property {boolean} interact=true Whether the mouse interaction will be
73 * enabled at initialization. You can switch this parameter at any time later
74 * on.
75 * @property {boolean} loopSelection=true (Use with regions plugin) Enable
76 * looping of selected regions
77 * @property {number} maxCanvasWidth=4000 Maximum width of a single canvas in
78 * pixels, excluding a small overlap (2 * `pixelRatio`, rounded up to the next
79 * even integer). If the waveform is longer than this value, additional canvases
80 * will be used to render the waveform, which is useful for very large waveforms
81 * that may be too wide for browsers to draw on a single canvas.
82 * @property {boolean} mediaControls=false (Use with backend `MediaElement` or `MediaElementWebAudio`)
83 * this enables the native controls for the media element
84 * @property {string} mediaType='audio' (Use with backend `MediaElement` or `MediaElementWebAudio`)
85 * `'audio'|'video'` ('video' only for `MediaElement`)
86 * @property {number} minPxPerSec=20 Minimum number of pixels per second of
87 * audio.
88 * @property {boolean} normalize=false If true, normalize by the maximum peak
89 * instead of 1.0.
90 * @property {boolean} partialRender=false Use the PeakCache to improve
91 * rendering speed of large waveforms
92 * @property {number} pixelRatio=window.devicePixelRatio The pixel ratio used to
93 * calculate display
94 * @property {PluginDefinition[]} plugins=[] An array of plugin definitions to
95 * register during instantiation, they will be directly initialised unless they
96 * are added with the `deferInit` property set to true.
97 * @property {string} progressColor='#555' The fill color of the part of the
98 * waveform behind the cursor. When `progressColor` and `waveColor` are the same
99 * the progress wave is not rendered at all.
100 * @property {boolean} removeMediaElementOnDestroy=true Set to false to keep the
101 * media element in the DOM when the player is destroyed. This is useful when
102 * reusing an existing media element via the `loadMediaElement` method.
103 * @property {Object} renderer=MultiCanvas Can be used to inject a custom
104 * renderer.
105 * @property {boolean|number} responsive=false If set to `true` resize the
106 * waveform, when the window is resized. This is debounced with a `100ms`
107 * timeout by default. If this parameter is a number it represents that timeout.
108 * @property {boolean} rtl=false If set to `true`, renders waveform from
109 * right-to-left.
110 * @property {boolean} scrollParent=false Whether to scroll the container with a
111 * lengthy waveform. Otherwise the waveform is shrunk to the container width
112 * (see fillParent).
113 * @property {number} skipLength=2 Number of seconds to skip with the
114 * skipForward() and skipBackward() methods.
115 * @property {boolean} splitChannels=false Render with separate waveforms for
116 * the channels of the audio
117 * @property {string} waveColor='#999' The fill color of the waveform after the
118 * cursor.
119 * @property {object} xhr={} XHR options. For example:
120 * `let xhr = {
121 * cache: 'default',
122 * mode: 'cors',
123 * method: 'GET',
124 * credentials: 'same-origin',
125 * redirect: 'follow',
126 * referrer: 'client',
127 * headers: [
128 * {
129 * key: 'Authorization',
130 * value: 'my-token'
131 * }
132 * ]
133 * };`
134 */
135
136/**
137 * @typedef {Object} PluginDefinition
138 * @desc The Object used to describe a plugin
139 * @example wavesurfer.addPlugin(pluginDefinition);
140 * @property {string} name The name of the plugin, the plugin instance will be
141 * added as a property to the wavesurfer instance under this name
142 * @property {?Object} staticProps The properties that should be added to the
143 * wavesurfer instance as static properties
144 * @property {?boolean} deferInit Don't initialise plugin
145 * automatically
146 * @property {Object} params={} The plugin parameters, they are the first parameter
147 * passed to the plugin class constructor function
148 * @property {PluginClass} instance The plugin instance factory, is called with
149 * the dependency specified in extends. Returns the plugin class.
150 */
151
152/**
153 * @interface PluginClass
154 *
155 * @desc This is the interface which is implemented by all plugin classes. Note
156 * that this only turns into an observer after being passed through
157 * `wavesurfer.addPlugin`.
158 *
159 * @extends {Observer}
160 */
161class PluginClass {
162 /**
163 * Plugin definition factory
164 *
165 * This function must be used to create a plugin definition which can be
166 * used by wavesurfer to correctly instantiate the plugin.
167 *
168 * It returns a `PluginDefinition` object representing the plugin.
169 *
170 * @param {Object} params={} The plugin params (specific to the plugin)
171 */
172 create(params) {}
173 /**
174 * Construct the plugin
175 *
176 * @param {Object} params={} The plugin params (specific to the plugin)
177 * @param {Object} ws The wavesurfer instance
178 */
179 constructor(params, ws) {}
180 /**
181 * Initialise the plugin
182 *
183 * Start doing something. This is called by
184 * `wavesurfer.initPlugin(pluginName)`
185 */
186 init() {}
187 /**
188 * Destroy the plugin instance
189 *
190 * Stop doing something. This is called by
191 * `wavesurfer.destroyPlugin(pluginName)`
192 */
193 destroy() {}
194}
195
196/**
197 * WaveSurfer core library class
198 *
199 * @extends {Observer}
200 * @example
201 * const params = {
202 * container: '#waveform',
203 * waveColor: 'violet',
204 * progressColor: 'purple'
205 * };
206 *
207 * // initialise like this
208 * const wavesurfer = WaveSurfer.create(params);
209 *
210 * // or like this ...
211 * const wavesurfer = new WaveSurfer(params);
212 * wavesurfer.init();
213 *
214 * // load audio file
215 * wavesurfer.load('example/media/demo.wav');
216 */
217export default class WaveSurfer extends util.Observer {
218 /** @private */
219 defaultParams = {
220 audioContext: null,
221 audioScriptProcessor: null,
222 audioRate: 1,
223 autoCenter: true,
224 autoCenterRate: 5,
225 autoCenterImmediately: false,
226 backend: 'WebAudio',
227 backgroundColor: null,
228 barHeight: 1,
229 barRadius: 0,
230 barGap: null,
231 barMinHeight: null,
232 container: null,
233 cursorColor: '#333',
234 cursorWidth: 1,
235 dragSelection: true,
236 drawingContextAttributes: {
237 // Boolean that hints the user agent to reduce the latency
238 // by desynchronizing the canvas paint cycle from the event
239 // loop
240 desynchronized: false
241 },
242 duration: null,
243 fillParent: true,
244 forceDecode: false,
245 height: 128,
246 hideScrollbar: false,
247 interact: true,
248 loopSelection: true,
249 maxCanvasWidth: 4000,
250 mediaContainer: null,
251 mediaControls: false,
252 mediaType: 'audio',
253 minPxPerSec: 20,
254 normalize: false,
255 partialRender: false,
256 pixelRatio:
257 window.devicePixelRatio || screen.deviceXDPI / screen.logicalXDPI,
258 plugins: [],
259 progressColor: '#555',
260 removeMediaElementOnDestroy: true,
261 renderer: MultiCanvas,
262 responsive: false,
263 rtl: false,
264 scrollParent: false,
265 skipLength: 2,
266 splitChannels: false,
267 splitChannelsOptions: {
268 overlay: false,
269 channelColors: {},
270 filterChannels: [],
271 },
272 waveColor: '#999',
273 xhr: {}
274 };
275
276 /** @private */
277 backends = {
278 MediaElement,
279 WebAudio,
280 MediaElementWebAudio
281 };
282
283 /**
284 * Instantiate this class, call its `init` function and returns it
285 *
286 * @param {WavesurferParams} params The wavesurfer parameters
287 * @return {Object} WaveSurfer instance
288 * @example const wavesurfer = WaveSurfer.create(params);
289 */
290 static create(params) {
291 const wavesurfer = new WaveSurfer(params);
292 return wavesurfer.init();
293 }
294
295 /**
296 * The library version number is available as a static property of the
297 * WaveSurfer class
298 *
299 * @type {String}
300 * @example
301 * console.log('Using wavesurfer.js ' + WaveSurfer.VERSION);
302 */
303 static VERSION = __VERSION__;
304
305 /**
306 * Functions in the `util` property are available as a prototype property to
307 * all instances
308 *
309 * @type {Object}
310 * @example
311 * const wavesurfer = WaveSurfer.create(params);
312 * wavesurfer.util.style(myElement, { background: 'blue' });
313 */
314 util = util;
315
316 /**
317 * Functions in the `util` property are available as a static property of the
318 * WaveSurfer class
319 *
320 * @type {Object}
321 * @example
322 * WaveSurfer.util.style(myElement, { background: 'blue' });
323 */
324 static util = util;
325
326 /**
327 * Initialise wavesurfer instance
328 *
329 * @param {WavesurferParams} params Instantiation options for wavesurfer
330 * @example
331 * const wavesurfer = new WaveSurfer(params);
332 * @returns {this} Wavesurfer instance
333 */
334 constructor(params) {
335 super();
336 /**
337 * Extract relevant parameters (or defaults)
338 * @private
339 */
340 this.params = Object.assign({}, this.defaultParams, params);
341
342 /** @private */
343 this.container =
344 'string' == typeof params.container
345 ? document.querySelector(this.params.container)
346 : this.params.container;
347
348 if (!this.container) {
349 throw new Error('Container element not found');
350 }
351
352 if (this.params.mediaContainer == null) {
353 /** @private */
354 this.mediaContainer = this.container;
355 } else if (typeof this.params.mediaContainer == 'string') {
356 /** @private */
357 this.mediaContainer = document.querySelector(
358 this.params.mediaContainer
359 );
360 } else {
361 /** @private */
362 this.mediaContainer = this.params.mediaContainer;
363 }
364
365 if (!this.mediaContainer) {
366 throw new Error('Media Container element not found');
367 }
368
369 if (this.params.maxCanvasWidth <= 1) {
370 throw new Error('maxCanvasWidth must be greater than 1');
371 } else if (this.params.maxCanvasWidth % 2 == 1) {
372 throw new Error('maxCanvasWidth must be an even number');
373 }
374
375 if (this.params.rtl === true) {
376 util.style(this.container, { transform: 'rotateY(180deg)' });
377 }
378
379 if (this.params.backgroundColor) {
380 this.setBackgroundColor(this.params.backgroundColor);
381 }
382
383 /**
384 * @private Used to save the current volume when muting so we can
385 * restore once unmuted
386 * @type {number}
387 */
388 this.savedVolume = 0;
389
390 /**
391 * @private The current muted state
392 * @type {boolean}
393 */
394 this.isMuted = false;
395
396 /**
397 * @private Will hold a list of event descriptors that need to be
398 * canceled on subsequent loads of audio
399 * @type {Object[]}
400 */
401 this.tmpEvents = [];
402
403 /**
404 * @private Holds any running audio downloads
405 * @type {Observer}
406 */
407 this.currentRequest = null;
408 /** @private */
409 this.arraybuffer = null;
410 /** @private */
411 this.drawer = null;
412 /** @private */
413 this.backend = null;
414 /** @private */
415 this.peakCache = null;
416
417 // cache constructor objects
418 if (typeof this.params.renderer !== 'function') {
419 throw new Error('Renderer parameter is invalid');
420 }
421 /**
422 * @private The uninitialised Drawer class
423 */
424 this.Drawer = this.params.renderer;
425 /**
426 * @private The uninitialised Backend class
427 */
428 // Back compat
429 if (this.params.backend == 'AudioElement') {
430 this.params.backend = 'MediaElement';
431 }
432
433 if (
434 (this.params.backend == 'WebAudio' ||
435 this.params.backend === 'MediaElementWebAudio') &&
436 !WebAudio.prototype.supportsWebAudio.call(null)
437 ) {
438 this.params.backend = 'MediaElement';
439 }
440 this.Backend = this.backends[this.params.backend];
441
442 /**
443 * @private map of plugin names that are currently initialised
444 */
445 this.initialisedPluginList = {};
446 /** @private */
447 this.isDestroyed = false;
448
449 /**
450 * Get the current ready status.
451 *
452 * @example const isReady = wavesurfer.isReady;
453 * @return {boolean}
454 */
455 this.isReady = false;
456
457 // responsive debounced event listener. If this.params.responsive is not
458 // set, this is never called. Use 100ms or this.params.responsive as
459 // timeout for the debounce function.
460 let prevWidth = 0;
461 this._onResize = util.debounce(
462 () => {
463 if (
464 prevWidth != this.drawer.wrapper.clientWidth &&
465 !this.params.scrollParent
466 ) {
467 prevWidth = this.drawer.wrapper.clientWidth;
468 this.drawer.fireEvent('redraw');
469 }
470 },
471 typeof this.params.responsive === 'number'
472 ? this.params.responsive
473 : 100
474 );
475
476 return this;
477 }
478
479 /**
480 * Initialise the wave
481 *
482 * @example
483 * var wavesurfer = new WaveSurfer(params);
484 * wavesurfer.init();
485 * @return {this} The wavesurfer instance
486 */
487 init() {
488 this.registerPlugins(this.params.plugins);
489 this.createDrawer();
490 this.createBackend();
491 this.createPeakCache();
492 return this;
493 }
494
495 /**
496 * Add and initialise array of plugins (if `plugin.deferInit` is falsey),
497 * this function is called in the init function of wavesurfer
498 *
499 * @param {PluginDefinition[]} plugins An array of plugin definitions
500 * @emits {WaveSurfer#plugins-registered} Called with the array of plugin definitions
501 * @return {this} The wavesurfer instance
502 */
503 registerPlugins(plugins) {
504 // first instantiate all the plugins
505 plugins.forEach(plugin => this.addPlugin(plugin));
506
507 // now run the init functions
508 plugins.forEach(plugin => {
509 // call init function of the plugin if deferInit is falsey
510 // in that case you would manually use initPlugins()
511 if (!plugin.deferInit) {
512 this.initPlugin(plugin.name);
513 }
514 });
515 this.fireEvent('plugins-registered', plugins);
516 return this;
517 }
518
519 /**
520 * Get a map of plugin names that are currently initialised
521 *
522 * @example wavesurfer.getPlugins();
523 * @return {Object} Object with plugin names
524 */
525 getActivePlugins() {
526 return this.initialisedPluginList;
527 }
528
529 /**
530 * Add a plugin object to wavesurfer
531 *
532 * @param {PluginDefinition} plugin A plugin definition
533 * @emits {WaveSurfer#plugin-added} Called with the name of the plugin that was added
534 * @example wavesurfer.addPlugin(WaveSurfer.minimap());
535 * @return {this} The wavesurfer instance
536 */
537 addPlugin(plugin) {
538 if (!plugin.name) {
539 throw new Error('Plugin does not have a name!');
540 }
541 if (!plugin.instance) {
542 throw new Error(
543 `Plugin ${plugin.name} does not have an instance property!`
544 );
545 }
546
547 // staticProps properties are applied to wavesurfer instance
548 if (plugin.staticProps) {
549 Object.keys(plugin.staticProps).forEach(pluginStaticProp => {
550 /**
551 * Properties defined in a plugin definition's `staticProps` property are added as
552 * staticProps properties of the WaveSurfer instance
553 */
554 this[pluginStaticProp] = plugin.staticProps[pluginStaticProp];
555 });
556 }
557
558 const Instance = plugin.instance;
559
560 // turn the plugin instance into an observer
561 const observerPrototypeKeys = Object.getOwnPropertyNames(
562 util.Observer.prototype
563 );
564 observerPrototypeKeys.forEach(key => {
565 Instance.prototype[key] = util.Observer.prototype[key];
566 });
567
568 /**
569 * Instantiated plugin classes are added as a property of the wavesurfer
570 * instance
571 * @type {Object}
572 */
573 this[plugin.name] = new Instance(plugin.params || {}, this);
574 this.fireEvent('plugin-added', plugin.name);
575 return this;
576 }
577
578 /**
579 * Initialise a plugin
580 *
581 * @param {string} name A plugin name
582 * @emits WaveSurfer#plugin-initialised
583 * @example wavesurfer.initPlugin('minimap');
584 * @return {this} The wavesurfer instance
585 */
586 initPlugin(name) {
587 if (!this[name]) {
588 throw new Error(`Plugin ${name} has not been added yet!`);
589 }
590 if (this.initialisedPluginList[name]) {
591 // destroy any already initialised plugins
592 this.destroyPlugin(name);
593 }
594 this[name].init();
595 this.initialisedPluginList[name] = true;
596 this.fireEvent('plugin-initialised', name);
597 return this;
598 }
599
600 /**
601 * Destroy a plugin
602 *
603 * @param {string} name A plugin name
604 * @emits WaveSurfer#plugin-destroyed
605 * @example wavesurfer.destroyPlugin('minimap');
606 * @returns {this} The wavesurfer instance
607 */
608 destroyPlugin(name) {
609 if (!this[name]) {
610 throw new Error(
611 `Plugin ${name} has not been added yet and cannot be destroyed!`
612 );
613 }
614 if (!this.initialisedPluginList[name]) {
615 throw new Error(
616 `Plugin ${name} is not active and cannot be destroyed!`
617 );
618 }
619 if (typeof this[name].destroy !== 'function') {
620 throw new Error(`Plugin ${name} does not have a destroy function!`);
621 }
622
623 this[name].destroy();
624 delete this.initialisedPluginList[name];
625 this.fireEvent('plugin-destroyed', name);
626 return this;
627 }
628
629 /**
630 * Destroy all initialised plugins. Convenience function to use when
631 * wavesurfer is removed
632 *
633 * @private
634 */
635 destroyAllPlugins() {
636 Object.keys(this.initialisedPluginList).forEach(name =>
637 this.destroyPlugin(name)
638 );
639 }
640
641 /**
642 * Create the drawer and draw the waveform
643 *
644 * @private
645 * @emits WaveSurfer#drawer-created
646 */
647 createDrawer() {
648 this.drawer = new this.Drawer(this.container, this.params);
649 this.drawer.init();
650 this.fireEvent('drawer-created', this.drawer);
651
652 if (this.params.responsive !== false) {
653 window.addEventListener('resize', this._onResize, true);
654 window.addEventListener('orientationchange', this._onResize, true);
655 }
656
657 this.drawer.on('redraw', () => {
658 this.drawBuffer();
659 this.drawer.progress(this.backend.getPlayedPercents());
660 });
661
662 // Click-to-seek
663 this.drawer.on('click', (e, progress) => {
664 setTimeout(() => this.seekTo(progress), 0);
665 });
666
667 // Relay the scroll event from the drawer
668 this.drawer.on('scroll', e => {
669 if (this.params.partialRender) {
670 this.drawBuffer();
671 }
672 this.fireEvent('scroll', e);
673 });
674 }
675
676 /**
677 * Create the backend
678 *
679 * @private
680 * @emits WaveSurfer#backend-created
681 */
682 createBackend() {
683 if (this.backend) {
684 this.backend.destroy();
685 }
686
687 this.backend = new this.Backend(this.params);
688 this.backend.init();
689 this.fireEvent('backend-created', this.backend);
690
691 this.backend.on('finish', () => {
692 this.drawer.progress(this.backend.getPlayedPercents());
693 this.fireEvent('finish');
694 });
695 this.backend.on('play', () => this.fireEvent('play'));
696 this.backend.on('pause', () => this.fireEvent('pause'));
697
698 this.backend.on('audioprocess', time => {
699 this.drawer.progress(this.backend.getPlayedPercents());
700 this.fireEvent('audioprocess', time);
701 });
702
703 // only needed for MediaElement and MediaElementWebAudio backend
704 if (
705 this.params.backend === 'MediaElement' ||
706 this.params.backend === 'MediaElementWebAudio'
707 ) {
708 this.backend.on('seek', () => {
709 this.drawer.progress(this.backend.getPlayedPercents());
710 });
711
712 this.backend.on('volume', () => {
713 let newVolume = this.getVolume();
714 this.fireEvent('volume', newVolume);
715
716 if (this.backend.isMuted !== this.isMuted) {
717 this.isMuted = this.backend.isMuted;
718 this.fireEvent('mute', this.isMuted);
719 }
720 });
721 }
722 }
723
724 /**
725 * Create the peak cache
726 *
727 * @private
728 */
729 createPeakCache() {
730 if (this.params.partialRender) {
731 this.peakCache = new PeakCache();
732 }
733 }
734
735 /**
736 * Get the duration of the audio clip
737 *
738 * @example const duration = wavesurfer.getDuration();
739 * @return {number} Duration in seconds
740 */
741 getDuration() {
742 return this.backend.getDuration();
743 }
744
745 /**
746 * Get the current playback position
747 *
748 * @example const currentTime = wavesurfer.getCurrentTime();
749 * @return {number} Playback position in seconds
750 */
751 getCurrentTime() {
752 return this.backend.getCurrentTime();
753 }
754
755 /**
756 * Set the current play time in seconds.
757 *
758 * @param {number} seconds A positive number in seconds. E.g. 10 means 10
759 * seconds, 60 means 1 minute
760 */
761 setCurrentTime(seconds) {
762 if (seconds >= this.getDuration()) {
763 this.seekTo(1);
764 } else {
765 this.seekTo(seconds / this.getDuration());
766 }
767 }
768
769 /**
770 * Starts playback from the current position. Optional start and end
771 * measured in seconds can be used to set the range of audio to play.
772 *
773 * @param {?number} start Position to start at
774 * @param {?number} end Position to end at
775 * @emits WaveSurfer#interaction
776 * @return {Promise} Result of the backend play method
777 * @example
778 * // play from second 1 to 5
779 * wavesurfer.play(1, 5);
780 */
781 play(start, end) {
782 this.fireEvent('interaction', () => this.play(start, end));
783 return this.backend.play(start, end);
784 }
785
786 /**
787 * Set a point in seconds for playback to stop at.
788 *
789 * @param {number} position Position (in seconds) to stop at
790 * @version 3.3.0
791 */
792 setPlayEnd(position) {
793 this.backend.setPlayEnd(position);
794 }
795
796 /**
797 * Stops and pauses playback
798 *
799 * @example wavesurfer.pause();
800 * @return {Promise} Result of the backend pause method
801 */
802 pause() {
803 if (!this.backend.isPaused()) {
804 return this.backend.pause();
805 }
806 }
807
808 /**
809 * Toggle playback
810 *
811 * @example wavesurfer.playPause();
812 * @return {Promise} Result of the backend play or pause method
813 */
814 playPause() {
815 return this.backend.isPaused() ? this.play() : this.pause();
816 }
817
818 /**
819 * Get the current playback state
820 *
821 * @example const isPlaying = wavesurfer.isPlaying();
822 * @return {boolean} False if paused, true if playing
823 */
824 isPlaying() {
825 return !this.backend.isPaused();
826 }
827
828 /**
829 * Skip backward
830 *
831 * @param {?number} seconds Amount to skip back, if not specified `skipLength`
832 * is used
833 * @example wavesurfer.skipBackward();
834 */
835 skipBackward(seconds) {
836 this.skip(-seconds || -this.params.skipLength);
837 }
838
839 /**
840 * Skip forward
841 *
842 * @param {?number} seconds Amount to skip back, if not specified `skipLength`
843 * is used
844 * @example wavesurfer.skipForward();
845 */
846 skipForward(seconds) {
847 this.skip(seconds || this.params.skipLength);
848 }
849
850 /**
851 * Skip a number of seconds from the current position (use a negative value
852 * to go backwards).
853 *
854 * @param {number} offset Amount to skip back or forwards
855 * @example
856 * // go back 2 seconds
857 * wavesurfer.skip(-2);
858 */
859 skip(offset) {
860 const duration = this.getDuration() || 1;
861 let position = this.getCurrentTime() || 0;
862 position = Math.max(0, Math.min(duration, position + (offset || 0)));
863 this.seekAndCenter(position / duration);
864 }
865
866 /**
867 * Seeks to a position and centers the view
868 *
869 * @param {number} progress Between 0 (=beginning) and 1 (=end)
870 * @example
871 * // seek and go to the middle of the audio
872 * wavesurfer.seekTo(0.5);
873 */
874 seekAndCenter(progress) {
875 this.seekTo(progress);
876 this.drawer.recenter(progress);
877 }
878
879 /**
880 * Seeks to a position
881 *
882 * @param {number} progress Between 0 (=beginning) and 1 (=end)
883 * @emits WaveSurfer#interaction
884 * @emits WaveSurfer#seek
885 * @example
886 * // seek to the middle of the audio
887 * wavesurfer.seekTo(0.5);
888 */
889 seekTo(progress) {
890 // return an error if progress is not a number between 0 and 1
891 if (
892 typeof progress !== 'number' ||
893 !isFinite(progress) ||
894 progress < 0 ||
895 progress > 1
896 ) {
897 throw new Error(
898 'Error calling wavesurfer.seekTo, parameter must be a number between 0 and 1!'
899 );
900 }
901 this.fireEvent('interaction', () => this.seekTo(progress));
902
903 const paused = this.backend.isPaused();
904 // avoid draw wrong position while playing backward seeking
905 if (!paused) {
906 this.backend.pause();
907 }
908 // avoid small scrolls while paused seeking
909 const oldScrollParent = this.params.scrollParent;
910 this.params.scrollParent = false;
911 this.backend.seekTo(progress * this.getDuration());
912 this.drawer.progress(progress);
913
914 if (!paused) {
915 this.backend.play();
916 }
917 this.params.scrollParent = oldScrollParent;
918 this.fireEvent('seek', progress);
919 }
920
921 /**
922 * Stops and goes to the beginning.
923 *
924 * @example wavesurfer.stop();
925 */
926 stop() {
927 this.pause();
928 this.seekTo(0);
929 this.drawer.progress(0);
930 }
931
932 /**
933 * Sets the ID of the audio device to use for output and returns a Promise.
934 *
935 * @param {string} deviceId String value representing underlying output
936 * device
937 * @returns {Promise} `Promise` that resolves to `undefined` when there are
938 * no errors detected.
939 */
940 setSinkId(deviceId) {
941 return this.backend.setSinkId(deviceId);
942 }
943
944 /**
945 * Set the playback volume.
946 *
947 * @param {number} newVolume A value between 0 and 1, 0 being no
948 * volume and 1 being full volume.
949 * @emits WaveSurfer#volume
950 */
951 setVolume(newVolume) {
952 this.backend.setVolume(newVolume);
953 this.fireEvent('volume', newVolume);
954 }
955
956 /**
957 * Get the playback volume.
958 *
959 * @return {number} A value between 0 and 1, 0 being no
960 * volume and 1 being full volume.
961 */
962 getVolume() {
963 return this.backend.getVolume();
964 }
965
966 /**
967 * Set the playback rate.
968 *
969 * @param {number} rate A positive number. E.g. 0.5 means half the normal
970 * speed, 2 means double speed and so on.
971 * @example wavesurfer.setPlaybackRate(2);
972 */
973 setPlaybackRate(rate) {
974 this.backend.setPlaybackRate(rate);
975 }
976
977 /**
978 * Get the playback rate.
979 *
980 * @return {number} The current playback rate.
981 */
982 getPlaybackRate() {
983 return this.backend.getPlaybackRate();
984 }
985
986 /**
987 * Toggle the volume on and off. If not currently muted it will save the
988 * current volume value and turn the volume off. If currently muted then it
989 * will restore the volume to the saved value, and then rest the saved
990 * value.
991 *
992 * @example wavesurfer.toggleMute();
993 */
994 toggleMute() {
995 this.setMute(!this.isMuted);
996 }
997
998 /**
999 * Enable or disable muted audio
1000 *
1001 * @param {boolean} mute Specify `true` to mute audio.
1002 * @emits WaveSurfer#volume
1003 * @emits WaveSurfer#mute
1004 * @example
1005 * // unmute
1006 * wavesurfer.setMute(false);
1007 * console.log(wavesurfer.getMute()) // logs false
1008 */
1009 setMute(mute) {
1010 // ignore all muting requests if the audio is already in that state
1011 if (mute === this.isMuted) {
1012 this.fireEvent('mute', this.isMuted);
1013 return;
1014 }
1015
1016 if (this.backend.setMute) {
1017 // Backends such as the MediaElement backend have their own handling
1018 // of mute, let them handle it.
1019 this.backend.setMute(mute);
1020 this.isMuted = mute;
1021 } else {
1022 if (mute) {
1023 // If currently not muted then save current volume,
1024 // turn off the volume and update the mute properties
1025 this.savedVolume = this.backend.getVolume();
1026 this.backend.setVolume(0);
1027 this.isMuted = true;
1028 this.fireEvent('volume', 0);
1029 } else {
1030 // If currently muted then restore to the saved volume
1031 // and update the mute properties
1032 this.backend.setVolume(this.savedVolume);
1033 this.isMuted = false;
1034 this.fireEvent('volume', this.savedVolume);
1035 }
1036 }
1037 this.fireEvent('mute', this.isMuted);
1038 }
1039
1040 /**
1041 * Get the current mute status.
1042 *
1043 * @example const isMuted = wavesurfer.getMute();
1044 * @return {boolean} Current mute status
1045 */
1046 getMute() {
1047 return this.isMuted;
1048 }
1049
1050 /**
1051 * Get the list of current set filters as an array.
1052 *
1053 * Filters must be set with setFilters method first
1054 *
1055 * @return {array} List of enabled filters
1056 */
1057 getFilters() {
1058 return this.backend.filters || [];
1059 }
1060
1061 /**
1062 * Toggles `scrollParent` and redraws
1063 *
1064 * @example wavesurfer.toggleScroll();
1065 */
1066 toggleScroll() {
1067 this.params.scrollParent = !this.params.scrollParent;
1068 this.drawBuffer();
1069 }
1070
1071 /**
1072 * Toggle mouse interaction
1073 *
1074 * @example wavesurfer.toggleInteraction();
1075 */
1076 toggleInteraction() {
1077 this.params.interact = !this.params.interact;
1078 }
1079
1080 /**
1081 * Get the fill color of the waveform after the cursor.
1082 *
1083 * @return {string} A CSS color string.
1084 */
1085 getWaveColor() {
1086 return this.params.waveColor;
1087 }
1088
1089 /**
1090 * Set the fill color of the waveform after the cursor.
1091 *
1092 * @param {string} color A CSS color string.
1093 * @example wavesurfer.setWaveColor('#ddd');
1094 */
1095 setWaveColor(color) {
1096 this.params.waveColor = color;
1097 this.drawBuffer();
1098 }
1099
1100 /**
1101 * Get the fill color of the waveform behind the cursor.
1102 *
1103 * @return {string} A CSS color string.
1104 */
1105 getProgressColor() {
1106 return this.params.progressColor;
1107 }
1108
1109 /**
1110 * Set the fill color of the waveform behind the cursor.
1111 *
1112 * @param {string} color A CSS color string.
1113 * @example wavesurfer.setProgressColor('#400');
1114 */
1115 setProgressColor(color) {
1116 this.params.progressColor = color;
1117 this.drawBuffer();
1118 }
1119
1120 /**
1121 * Get the background color of the waveform container.
1122 *
1123 * @return {string} A CSS color string.
1124 */
1125 getBackgroundColor() {
1126 return this.params.backgroundColor;
1127 }
1128
1129 /**
1130 * Set the background color of the waveform container.
1131 *
1132 * @param {string} color A CSS color string.
1133 * @example wavesurfer.setBackgroundColor('#FF00FF');
1134 */
1135 setBackgroundColor(color) {
1136 this.params.backgroundColor = color;
1137 util.style(this.container, { background: this.params.backgroundColor });
1138 }
1139
1140 /**
1141 * Get the fill color of the cursor indicating the playhead
1142 * position.
1143 *
1144 * @return {string} A CSS color string.
1145 */
1146 getCursorColor() {
1147 return this.params.cursorColor;
1148 }
1149
1150 /**
1151 * Set the fill color of the cursor indicating the playhead
1152 * position.
1153 *
1154 * @param {string} color A CSS color string.
1155 * @example wavesurfer.setCursorColor('#222');
1156 */
1157 setCursorColor(color) {
1158 this.params.cursorColor = color;
1159 this.drawer.updateCursor();
1160 }
1161
1162 /**
1163 * Get the height of the waveform.
1164 *
1165 * @return {number} Height measured in pixels.
1166 */
1167 getHeight() {
1168 return this.params.height;
1169 }
1170
1171 /**
1172 * Set the height of the waveform.
1173 *
1174 * @param {number} height Height measured in pixels.
1175 * @example wavesurfer.setHeight(200);
1176 */
1177 setHeight(height) {
1178 this.params.height = height;
1179 this.drawer.setHeight(height * this.params.pixelRatio);
1180 this.drawBuffer();
1181 }
1182
1183 /**
1184 * Hide channels from being drawn on the waveform if splitting channels.
1185 *
1186 * For example, if we want to draw only the peaks for the right stereo channel:
1187 *
1188 * const wavesurfer = new WaveSurfer.create({...splitChannels: true});
1189 * wavesurfer.load('stereo_audio.mp3');
1190 *
1191 * wavesurfer.setFilteredChannel([0]); <-- hide left channel peaks.
1192 *
1193 * @param {array} channelIndices Channels to be filtered out from drawing.
1194 * @version 4.0.0
1195 */
1196 setFilteredChannels(channelIndices) {
1197 this.params.splitChannelsOptions.filterChannels = channelIndices;
1198 this.drawBuffer();
1199 }
1200
1201 /**
1202 * Get the correct peaks for current wave view-port and render wave
1203 *
1204 * @private
1205 * @emits WaveSurfer#redraw
1206 */
1207 drawBuffer() {
1208 const nominalWidth = Math.round(
1209 this.getDuration() *
1210 this.params.minPxPerSec *
1211 this.params.pixelRatio
1212 );
1213 const parentWidth = this.drawer.getWidth();
1214 let width = nominalWidth;
1215 // always start at 0 after zooming for scrolling : issue redraw left part
1216 let start = 0;
1217 let end = Math.max(start + parentWidth, width);
1218 // Fill container
1219 if (
1220 this.params.fillParent &&
1221 (!this.params.scrollParent || nominalWidth < parentWidth)
1222 ) {
1223 width = parentWidth;
1224 start = 0;
1225 end = width;
1226 }
1227
1228 let peaks;
1229 if (this.params.partialRender) {
1230 const newRanges = this.peakCache.addRangeToPeakCache(
1231 width,
1232 start,
1233 end
1234 );
1235 let i;
1236 for (i = 0; i < newRanges.length; i++) {
1237 peaks = this.backend.getPeaks(
1238 width,
1239 newRanges[i][0],
1240 newRanges[i][1]
1241 );
1242 this.drawer.drawPeaks(
1243 peaks,
1244 width,
1245 newRanges[i][0],
1246 newRanges[i][1]
1247 );
1248 }
1249 } else {
1250 peaks = this.backend.getPeaks(width, start, end);
1251 this.drawer.drawPeaks(peaks, width, start, end);
1252 }
1253 this.fireEvent('redraw', peaks, width);
1254 }
1255
1256 /**
1257 * Horizontally zooms the waveform in and out. It also changes the parameter
1258 * `minPxPerSec` and enables the `scrollParent` option. Calling the function
1259 * with a falsey parameter will reset the zoom state.
1260 *
1261 * @param {?number} pxPerSec Number of horizontal pixels per second of
1262 * audio, if none is set the waveform returns to unzoomed state
1263 * @emits WaveSurfer#zoom
1264 * @example wavesurfer.zoom(20);
1265 */
1266 zoom(pxPerSec) {
1267 if (!pxPerSec) {
1268 this.params.minPxPerSec = this.defaultParams.minPxPerSec;
1269 this.params.scrollParent = false;
1270 } else {
1271 this.params.minPxPerSec = pxPerSec;
1272 this.params.scrollParent = true;
1273 }
1274
1275 this.drawBuffer();
1276 this.drawer.progress(this.backend.getPlayedPercents());
1277
1278 this.drawer.recenter(this.getCurrentTime() / this.getDuration());
1279 this.fireEvent('zoom', pxPerSec);
1280 }
1281
1282 /**
1283 * Decode buffer and load
1284 *
1285 * @private
1286 * @param {ArrayBuffer} arraybuffer Buffer to process
1287 */
1288 loadArrayBuffer(arraybuffer) {
1289 this.decodeArrayBuffer(arraybuffer, data => {
1290 if (!this.isDestroyed) {
1291 this.loadDecodedBuffer(data);
1292 }
1293 });
1294 }
1295
1296 /**
1297 * Directly load an externally decoded AudioBuffer
1298 *
1299 * @private
1300 * @param {AudioBuffer} buffer Buffer to process
1301 * @emits WaveSurfer#ready
1302 */
1303 loadDecodedBuffer(buffer) {
1304 this.backend.load(buffer);
1305 this.drawBuffer();
1306 this.isReady = true;
1307 this.fireEvent('ready');
1308 }
1309
1310 /**
1311 * Loads audio data from a Blob or File object
1312 *
1313 * @param {Blob|File} blob Audio data
1314 * @example
1315 */
1316 loadBlob(blob) {
1317 // Create file reader
1318 const reader = new FileReader();
1319 reader.addEventListener('progress', e => this.onProgress(e));
1320 reader.addEventListener('load', e =>
1321 this.loadArrayBuffer(e.target.result)
1322 );
1323 reader.addEventListener('error', () =>
1324 this.fireEvent('error', 'Error reading file')
1325 );
1326 reader.readAsArrayBuffer(blob);
1327 this.empty();
1328 }
1329
1330 /**
1331 * Loads audio and re-renders the waveform.
1332 *
1333 * @param {string|HTMLMediaElement} url The url of the audio file or the
1334 * audio element with the audio
1335 * @param {number[]|Number.<Array[]>} peaks Wavesurfer does not have to decode
1336 * the audio to render the waveform if this is specified
1337 * @param {?string} preload (Use with backend `MediaElement` and `MediaElementWebAudio`)
1338 * `'none'|'metadata'|'auto'` Preload attribute for the media element
1339 * @param {?number} duration The duration of the audio. This is used to
1340 * render the peaks data in the correct size for the audio duration (as
1341 * befits the current `minPxPerSec` and zoom value) without having to decode
1342 * the audio.
1343 * @returns {void}
1344 * @throws Will throw an error if the `url` argument is empty.
1345 * @example
1346 * // uses fetch or media element to load file (depending on backend)
1347 * wavesurfer.load('http://example.com/demo.wav');
1348 *
1349 * // setting preload attribute with media element backend and supplying
1350 * // peaks
1351 * wavesurfer.load(
1352 * 'http://example.com/demo.wav',
1353 * [0.0218, 0.0183, 0.0165, 0.0198, 0.2137, 0.2888],
1354 * true
1355 * );
1356 */
1357 load(url, peaks, preload, duration) {
1358 if (!url) {
1359 throw new Error('url parameter cannot be empty');
1360 }
1361 this.empty();
1362 if (preload) {
1363 // check whether the preload attribute will be usable and if not log
1364 // a warning listing the reasons why not and nullify the variable
1365 const preloadIgnoreReasons = {
1366 "Preload is not 'auto', 'none' or 'metadata'":
1367 ['auto', 'metadata', 'none'].indexOf(preload) === -1,
1368 'Peaks are not provided': !peaks,
1369 "Backend is not of type 'MediaElement' or 'MediaElementWebAudio'":
1370 ['MediaElement', 'MediaElementWebAudio'].indexOf(
1371 this.params.backend
1372 ) === -1,
1373 'Url is not of type string': typeof url !== 'string'
1374 };
1375 const activeReasons = Object.keys(preloadIgnoreReasons).filter(
1376 reason => preloadIgnoreReasons[reason]
1377 );
1378 if (activeReasons.length) {
1379 // eslint-disable-next-line no-console
1380 console.warn(
1381 'Preload parameter of wavesurfer.load will be ignored because:\n\t- ' +
1382 activeReasons.join('\n\t- ')
1383 );
1384 // stop invalid values from being used
1385 preload = null;
1386 }
1387 }
1388
1389 switch (this.params.backend) {
1390 case 'WebAudio':
1391 return this.loadBuffer(url, peaks, duration);
1392 case 'MediaElement':
1393 case 'MediaElementWebAudio':
1394 return this.loadMediaElement(url, peaks, preload, duration);
1395 }
1396 }
1397
1398 /**
1399 * Loads audio using Web Audio buffer backend.
1400 *
1401 * @private
1402 * @param {string} url URL of audio file
1403 * @param {number[]|Number.<Array[]>} peaks Peaks data
1404 * @param {?number} duration Optional duration of audio file
1405 * @returns {void}
1406 */
1407 loadBuffer(url, peaks, duration) {
1408 const load = action => {
1409 if (action) {
1410 this.tmpEvents.push(this.once('ready', action));
1411 }
1412 return this.getArrayBuffer(url, data => this.loadArrayBuffer(data));
1413 };
1414
1415 if (peaks) {
1416 this.backend.setPeaks(peaks, duration);
1417 this.drawBuffer();
1418 this.tmpEvents.push(this.once('interaction', load));
1419 } else {
1420 return load();
1421 }
1422 }
1423
1424 /**
1425 * Either create a media element, or load an existing media element.
1426 *
1427 * @private
1428 * @param {string|HTMLMediaElement} urlOrElt Either a path to a media file, or an
1429 * existing HTML5 Audio/Video Element
1430 * @param {number[]|Number.<Array[]>} peaks Array of peaks. Required to bypass web audio
1431 * dependency
1432 * @param {?boolean} preload Set to true if the preload attribute of the
1433 * audio element should be enabled
1434 * @param {?number} duration Optional duration of audio file
1435 */
1436 loadMediaElement(urlOrElt, peaks, preload, duration) {
1437 let url = urlOrElt;
1438
1439 if (typeof urlOrElt === 'string') {
1440 this.backend.load(url, this.mediaContainer, peaks, preload);
1441 } else {
1442 const elt = urlOrElt;
1443 this.backend.loadElt(elt, peaks);
1444
1445 // If peaks are not provided,
1446 // url = element.src so we can get peaks with web audio
1447 url = elt.src;
1448 }
1449
1450 this.tmpEvents.push(
1451 this.backend.once('canplay', () => {
1452 // ignore when backend was already destroyed
1453 if (!this.backend.destroyed) {
1454 this.drawBuffer();
1455 this.isReady = true;
1456 this.fireEvent('ready');
1457 }
1458 }),
1459 this.backend.once('error', err => this.fireEvent('error', err))
1460 );
1461
1462 // If no pre-decoded peaks provided or pre-decoded peaks are
1463 // provided with forceDecode flag, attempt to download the
1464 // audio file and decode it with Web Audio.
1465 if (peaks) {
1466 this.backend.setPeaks(peaks, duration);
1467 }
1468
1469 if (
1470 (!peaks || this.params.forceDecode) &&
1471 this.backend.supportsWebAudio()
1472 ) {
1473 this.getArrayBuffer(url, arraybuffer => {
1474 this.decodeArrayBuffer(arraybuffer, buffer => {
1475 this.backend.buffer = buffer;
1476 this.backend.setPeaks(null);
1477 this.drawBuffer();
1478 this.fireEvent('waveform-ready');
1479 });
1480 });
1481 }
1482 }
1483
1484 /**
1485 * Decode an array buffer and pass data to a callback
1486 *
1487 * @private
1488 * @param {Object} arraybuffer The array buffer to decode
1489 * @param {function} callback The function to call on complete
1490 */
1491 decodeArrayBuffer(arraybuffer, callback) {
1492 this.arraybuffer = arraybuffer;
1493 this.backend.decodeArrayBuffer(
1494 arraybuffer,
1495 data => {
1496 // Only use the decoded data if we haven't been destroyed or
1497 // another decode started in the meantime
1498 if (!this.isDestroyed && this.arraybuffer == arraybuffer) {
1499 callback(data);
1500 this.arraybuffer = null;
1501 }
1502 },
1503 () => this.fireEvent('error', 'Error decoding audiobuffer')
1504 );
1505 }
1506
1507 /**
1508 * Load an array buffer using fetch and pass the result to a callback
1509 *
1510 * @param {string} url The URL of the file object
1511 * @param {function} callback The function to call on complete
1512 * @returns {util.fetchFile} fetch call
1513 * @private
1514 */
1515 getArrayBuffer(url, callback) {
1516 let options = Object.assign(
1517 {
1518 url: url,
1519 responseType: 'arraybuffer'
1520 },
1521 this.params.xhr
1522 );
1523 const request = util.fetchFile(options);
1524
1525 this.currentRequest = request;
1526
1527 this.tmpEvents.push(
1528 request.on('progress', e => {
1529 this.onProgress(e);
1530 }),
1531 request.on('success', data => {
1532 callback(data);
1533 this.currentRequest = null;
1534 }),
1535 request.on('error', e => {
1536 this.fireEvent('error', e);
1537 this.currentRequest = null;
1538 })
1539 );
1540
1541 return request;
1542 }
1543
1544 /**
1545 * Called while the audio file is loading
1546 *
1547 * @private
1548 * @param {Event} e Progress event
1549 * @emits WaveSurfer#loading
1550 */
1551 onProgress(e) {
1552 let percentComplete;
1553 if (e.lengthComputable) {
1554 percentComplete = e.loaded / e.total;
1555 } else {
1556 // Approximate progress with an asymptotic
1557 // function, and assume downloads in the 1-3 MB range.
1558 percentComplete = e.loaded / (e.loaded + 1000000);
1559 }
1560 this.fireEvent('loading', Math.round(percentComplete * 100), e.target);
1561 }
1562
1563 /**
1564 * Exports PCM data into a JSON array and opens in a new window.
1565 *
1566 * @param {number} length=1024 The scale in which to export the peaks
1567 * @param {number} accuracy=10000
1568 * @param {?boolean} noWindow Set to true to disable opening a new
1569 * window with the JSON
1570 * @param {number} start Start index
1571 * @param {number} end End index
1572 * @return {Promise} Promise that resolves with array of peaks
1573 */
1574 exportPCM(length, accuracy, noWindow, start, end) {
1575 length = length || 1024;
1576 start = start || 0;
1577 accuracy = accuracy || 10000;
1578 noWindow = noWindow || false;
1579 const peaks = this.backend.getPeaks(length, start, end);
1580 const arr = [].map.call(
1581 peaks,
1582 val => Math.round(val * accuracy) / accuracy
1583 );
1584 return new Promise((resolve, reject) => {
1585 const json = JSON.stringify(arr);
1586
1587 if (!noWindow) {
1588 window.open(
1589 'data:application/json;charset=utf-8,' +
1590 encodeURIComponent(json)
1591 );
1592 }
1593 resolve(json);
1594 });
1595 }
1596
1597 /**
1598 * Save waveform image as data URI.
1599 *
1600 * The default format is `'image/png'`. Other supported types are
1601 * `'image/jpeg'` and `'image/webp'`.
1602 *
1603 * @param {string} format='image/png' A string indicating the image format.
1604 * The default format type is `'image/png'`.
1605 * @param {number} quality=1 A number between 0 and 1 indicating the image
1606 * quality to use for image formats that use lossy compression such as
1607 * `'image/jpeg'`` and `'image/webp'`.
1608 * @param {string} type Image data type to return. Either 'dataURL' (default)
1609 * or 'blob'.
1610 * @return {string|string[]|Promise} When using `'dataURL'` type this returns
1611 * a single data URL or an array of data URLs, one for each canvas. When using
1612 * `'blob'` type this returns a `Promise` resolving with an array of `Blob`
1613 * instances, one for each canvas.
1614 */
1615 exportImage(format, quality, type) {
1616 if (!format) {
1617 format = 'image/png';
1618 }
1619 if (!quality) {
1620 quality = 1;
1621 }
1622 if (!type) {
1623 type = 'dataURL';
1624 }
1625
1626 return this.drawer.getImage(format, quality, type);
1627 }
1628
1629 /**
1630 * Cancel any fetch request currently in progress
1631 */
1632 cancelAjax() {
1633 if (this.currentRequest && this.currentRequest.controller) {
1634 this.currentRequest.controller.abort();
1635 this.currentRequest = null;
1636 }
1637 }
1638
1639 /**
1640 * @private
1641 */
1642 clearTmpEvents() {
1643 this.tmpEvents.forEach(e => e.un());
1644 }
1645
1646 /**
1647 * Display empty waveform.
1648 */
1649 empty() {
1650 if (!this.backend.isPaused()) {
1651 this.stop();
1652 this.backend.disconnectSource();
1653 }
1654 this.isReady = false;
1655 this.cancelAjax();
1656 this.clearTmpEvents();
1657
1658 // empty drawer
1659 this.drawer.progress(0);
1660 this.drawer.setWidth(0);
1661 this.drawer.drawPeaks({ length: this.drawer.getWidth() }, 0);
1662 }
1663
1664 /**
1665 * Remove events, elements and disconnect WebAudio nodes.
1666 *
1667 * @emits WaveSurfer#destroy
1668 */
1669 destroy() {
1670 this.destroyAllPlugins();
1671 this.fireEvent('destroy');
1672 this.cancelAjax();
1673 this.clearTmpEvents();
1674 this.unAll();
1675 if (this.params.responsive !== false) {
1676 window.removeEventListener('resize', this._onResize, true);
1677 window.removeEventListener(
1678 'orientationchange',
1679 this._onResize,
1680 true
1681 );
1682 }
1683 if (this.backend) {
1684 this.backend.destroy();
1685 }
1686 if (this.drawer) {
1687 this.drawer.destroy();
1688 }
1689 this.isDestroyed = true;
1690 this.isReady = false;
1691 this.arraybuffer = null;
1692 }
1693}
Note: See TracBrowser for help on using the repository browser.