wavesurfer.js
Version:
Interactive navigable audio visualization using Web Audio and Canvas
1,539 lines (1,290 loc) • 161 kB
JavaScript
/*!
* wavesurfer.js 2.0.5 (Sun Mar 04 2018 20:09:50 GMT+0100 (CET))
* https://github.com/katspaugh/wavesurfer.js
* @license BSD-3-Clause
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("WaveSurfer", [], factory);
else if(typeof exports === 'object')
exports["WaveSurfer"] = factory();
else
root["WaveSurfer"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 4);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _ajax = __webpack_require__(5);
Object.defineProperty(exports, 'ajax', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_ajax).default;
}
});
var _getId = __webpack_require__(6);
Object.defineProperty(exports, 'getId', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_getId).default;
}
});
var _max = __webpack_require__(7);
Object.defineProperty(exports, 'max', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_max).default;
}
});
var _min = __webpack_require__(8);
Object.defineProperty(exports, 'min', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_min).default;
}
});
var _observer = __webpack_require__(1);
Object.defineProperty(exports, 'Observer', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_observer).default;
}
});
var _extend = __webpack_require__(9);
Object.defineProperty(exports, 'extend', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_extend).default;
}
});
var _style = __webpack_require__(10);
Object.defineProperty(exports, 'style', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_style).default;
}
});
var _requestAnimationFrame = __webpack_require__(2);
Object.defineProperty(exports, 'requestAnimationFrame', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_requestAnimationFrame).default;
}
});
var _frame = __webpack_require__(11);
Object.defineProperty(exports, 'frame', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_frame).default;
}
});
var _debounce = __webpack_require__(12);
Object.defineProperty(exports, 'debounce', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_debounce).default;
}
});
var _preventClick = __webpack_require__(13);
Object.defineProperty(exports, 'preventClick', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_preventClick).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* @typedef {Object} ListenerDescriptor
* @property {string} name The name of the event
* @property {function} callback The callback
* @property {function} un The function to call to remove the listener
*/
/**
* Observer class
*/
var Observer = function () {
/**
* Instantiate Observer
*/
function Observer() {
_classCallCheck(this, Observer);
/**
* @private
* @todo Initialise the handlers here already and remove the conditional
* assignment in `on()`
*/
this.handlers = null;
}
/**
* Attach a handler function for an event.
*
* @param {string} event Name of the event to listen to
* @param {function} fn The callback to trigger when the event is fired
* @return {ListenerDescriptor}
*/
_createClass(Observer, [{
key: "on",
value: function on(event, fn) {
var _this = this;
if (!this.handlers) {
this.handlers = {};
}
var handlers = this.handlers[event];
if (!handlers) {
handlers = this.handlers[event] = [];
}
handlers.push(fn);
// Return an event descriptor
return {
name: event,
callback: fn,
un: function un(e, fn) {
return _this.un(e, fn);
}
};
}
/**
* Remove an event handler.
*
* @param {string} event Name of the event the listener that should be
* removed listens to
* @param {function} fn The callback that should be removed
*/
}, {
key: "un",
value: function un(event, fn) {
if (!this.handlers) {
return;
}
var handlers = this.handlers[event];
var i = void 0;
if (handlers) {
if (fn) {
for (i = handlers.length - 1; i >= 0; i--) {
if (handlers[i] == fn) {
handlers.splice(i, 1);
}
}
} else {
handlers.length = 0;
}
}
}
/**
* Remove all event handlers.
*/
}, {
key: "unAll",
value: function unAll() {
this.handlers = null;
}
/**
* Attach a handler to an event. The handler is executed at most once per
* event type.
*
* @param {string} event The event to listen to
* @param {function} handler The callback that is only to be called once
* @return {ListenerDescriptor}
*/
}, {
key: "once",
value: function once(event, handler) {
var _this2 = this;
var fn = function fn() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
/* eslint-disable no-invalid-this */
handler.apply(_this2, args);
/* eslint-enable no-invalid-this */
setTimeout(function () {
_this2.un(event, fn);
}, 0);
};
return this.on(event, fn);
}
/**
* Manually fire an event
*
* @param {string} event The event to fire manually
* @param {...any} args The arguments with which to call the listeners
*/
}, {
key: "fireEvent",
value: function fireEvent(event) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
if (!this.handlers) {
return;
}
var handlers = this.handlers[event];
handlers && handlers.forEach(function (fn) {
fn.apply(undefined, args);
});
}
}]);
return Observer;
}();
exports.default = Observer;
module.exports = exports["default"];
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
/**
* Returns the requestAnimationFrame function for the browser, or a shim with
* setTimeout if none is found
*
* @return {function}
*/
exports.default = (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback, element) {
return setTimeout(callback, 1000 / 60);
}).bind(window);
module.exports = exports["default"];
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _util = __webpack_require__(0);
var util = _interopRequireWildcard(_util);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
// using consts to prevent someone writing the string wrong
var PLAYING = 'playing';
var PAUSED = 'paused';
var FINISHED = 'finished';
/**
* WebAudio backend
*
* @extends {Observer}
*/
var WebAudio = function (_util$Observer) {
_inherits(WebAudio, _util$Observer);
_createClass(WebAudio, [{
key: 'supportsWebAudio',
/**
* Does the browser support this backend
*
* @return {boolean}
*/
/** @private */
/** @private */
value: function supportsWebAudio() {
return !!(window.AudioContext || window.webkitAudioContext);
}
/**
* Get the audio context used by this backend or create one
*
* @return {AudioContext}
*/
/** @private */
/** @private */
}, {
key: 'getAudioContext',
value: function getAudioContext() {
if (!window.WaveSurferAudioContext) {
window.WaveSurferAudioContext = new (window.AudioContext || window.webkitAudioContext)();
}
return window.WaveSurferAudioContext;
}
/**
* Get the offline audio context used by this backend or create one
*
* @param {number} sampleRate
* @return {OfflineAudioContext}
*/
}, {
key: 'getOfflineAudioContext',
value: function getOfflineAudioContext(sampleRate) {
if (!window.WaveSurferOfflineAudioContext) {
window.WaveSurferOfflineAudioContext = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, 2, sampleRate);
}
return window.WaveSurferOfflineAudioContext;
}
/**
* Construct the backend
*
* @param {WavesurferParams} params
*/
}]);
function WebAudio(params) {
var _this$stateBehaviors, _this$states;
_classCallCheck(this, WebAudio);
/** @private */
var _this = _possibleConstructorReturn(this, (WebAudio.__proto__ || Object.getPrototypeOf(WebAudio)).call(this));
_this.audioContext = null;
_this.offlineAudioContext = null;
_this.stateBehaviors = (_this$stateBehaviors = {}, _defineProperty(_this$stateBehaviors, PLAYING, {
init: function init() {
this.addOnAudioProcess();
},
getPlayedPercents: function getPlayedPercents() {
var duration = this.getDuration();
return this.getCurrentTime() / duration || 0;
},
getCurrentTime: function getCurrentTime() {
return this.startPosition + this.getPlayedTime();
}
}), _defineProperty(_this$stateBehaviors, PAUSED, {
init: function init() {
this.removeOnAudioProcess();
},
getPlayedPercents: function getPlayedPercents() {
var duration = this.getDuration();
return this.getCurrentTime() / duration || 0;
},
getCurrentTime: function getCurrentTime() {
return this.startPosition;
}
}), _defineProperty(_this$stateBehaviors, FINISHED, {
init: function init() {
this.removeOnAudioProcess();
this.fireEvent('finish');
},
getPlayedPercents: function getPlayedPercents() {
return 1;
},
getCurrentTime: function getCurrentTime() {
return this.getDuration();
}
}), _this$stateBehaviors);
_this.params = params;
/** @private */
_this.ac = params.audioContext || _this.getAudioContext();
/**@private */
_this.lastPlay = _this.ac.currentTime;
/** @private */
_this.startPosition = 0;
/** @private */
_this.scheduledPause = null;
/** @private */
_this.states = (_this$states = {}, _defineProperty(_this$states, PLAYING, Object.create(_this.stateBehaviors[PLAYING])), _defineProperty(_this$states, PAUSED, Object.create(_this.stateBehaviors[PAUSED])), _defineProperty(_this$states, FINISHED, Object.create(_this.stateBehaviors[FINISHED])), _this$states);
/** @private */
_this.analyser = null;
/** @private */
_this.buffer = null;
/** @private */
_this.filters = [];
/** @private */
_this.gainNode = null;
/** @private */
_this.mergedPeaks = null;
/** @private */
_this.offlineAc = null;
/** @private */
_this.peaks = null;
/** @private */
_this.playbackRate = 1;
/** @private */
_this.analyser = null;
/** @private */
_this.scriptNode = null;
/** @private */
_this.source = null;
/** @private */
_this.splitPeaks = [];
/** @private */
_this.state = null;
/** @private */
_this.explicitDuration = null;
return _this;
}
/**
* Initialise the backend, called in `wavesurfer.createBackend()`
*/
_createClass(WebAudio, [{
key: 'init',
value: function init() {
this.createVolumeNode();
this.createScriptNode();
this.createAnalyserNode();
this.setState(PAUSED);
this.setPlaybackRate(this.params.audioRate);
this.setLength(0);
}
/** @private */
}, {
key: 'disconnectFilters',
value: function disconnectFilters() {
if (this.filters) {
this.filters.forEach(function (filter) {
filter && filter.disconnect();
});
this.filters = null;
// Reconnect direct path
this.analyser.connect(this.gainNode);
}
}
/** @private */
}, {
key: 'setState',
value: function setState(state) {
if (this.state !== this.states[state]) {
this.state = this.states[state];
this.state.init.call(this);
}
}
/**
* Unpacked `setFilters()`
*
* @param {...AudioNode} filters
*/
}, {
key: 'setFilter',
value: function setFilter() {
for (var _len = arguments.length, filters = Array(_len), _key = 0; _key < _len; _key++) {
filters[_key] = arguments[_key];
}
this.setFilters(filters);
}
/**
* Insert custom Web Audio nodes into the graph
*
* @param {AudioNode[]} filters Packed filters array
* @example
* const lowpass = wavesurfer.backend.ac.createBiquadFilter();
* wavesurfer.backend.setFilter(lowpass);
*/
}, {
key: 'setFilters',
value: function setFilters(filters) {
// Remove existing filters
this.disconnectFilters();
// Insert filters if filter array not empty
if (filters && filters.length) {
this.filters = filters;
// Disconnect direct path before inserting filters
this.analyser.disconnect();
// Connect each filter in turn
filters.reduce(function (prev, curr) {
prev.connect(curr);
return curr;
}, this.analyser).connect(this.gainNode);
}
}
/** @private */
}, {
key: 'createScriptNode',
value: function createScriptNode() {
if (this.ac.createScriptProcessor) {
this.scriptNode = this.ac.createScriptProcessor(WebAudio.scriptBufferSize);
} else {
this.scriptNode = this.ac.createJavaScriptNode(WebAudio.scriptBufferSize);
}
this.scriptNode.connect(this.ac.destination);
}
/** @private */
}, {
key: 'addOnAudioProcess',
value: function addOnAudioProcess() {
var _this2 = this;
this.scriptNode.onaudioprocess = function () {
var time = _this2.getCurrentTime();
if (time >= _this2.getDuration()) {
_this2.setState(FINISHED);
_this2.fireEvent('pause');
} else if (time >= _this2.scheduledPause) {
_this2.pause();
} else if (_this2.state === _this2.states[PLAYING]) {
_this2.fireEvent('audioprocess', time);
}
};
}
/** @private */
}, {
key: 'removeOnAudioProcess',
value: function removeOnAudioProcess() {
this.scriptNode.onaudioprocess = null;
}
/** @private */
}, {
key: 'createAnalyserNode',
value: function createAnalyserNode() {
this.analyser = this.ac.createAnalyser();
this.analyser.connect(this.gainNode);
}
/**
* Create the gain node needed to control the playback volume.
*
* @private
*/
}, {
key: 'createVolumeNode',
value: function createVolumeNode() {
// Create gain node using the AudioContext
if (this.ac.createGain) {
this.gainNode = this.ac.createGain();
} else {
this.gainNode = this.ac.createGainNode();
}
// Add the gain node to the graph
this.gainNode.connect(this.ac.destination);
}
/**
* Set the sink id for the media player
*
* @param {string} deviceId String value representing audio device id.
*/
}, {
key: 'setSinkId',
value: function setSinkId(deviceId) {
if (deviceId) {
/**
* The webaudio api doesn't currently support setting the device
* output. Here we create an HTMLAudioElement, connect the
* webaudio stream to that element and setSinkId there.
*/
var audio = new window.Audio();
if (!audio.setSinkId) {
return Promise.reject(new Error('setSinkId is not supported in your browser'));
}
audio.autoplay = true;
var dest = this.ac.createMediaStreamDestination();
this.gainNode.disconnect();
this.gainNode.connect(dest);
audio.src = URL.createObjectURL(dest.stream);
return audio.setSinkId(deviceId);
} else {
return Promise.reject(new Error('Invalid deviceId: ' + deviceId));
}
}
/**
* Set the audio volume
*
* @param {number} value A floating point value between 0 and 1.
*/
}, {
key: 'setVolume',
value: function setVolume(value) {
this.gainNode.gain.setValueAtTime(value, this.ac.currentTime);
}
/**
* Get the current volume
*
* @return {number} value A floating point value between 0 and 1.
*/
}, {
key: 'getVolume',
value: function getVolume() {
return this.gainNode.gain.value;
}
/** @private */
}, {
key: 'decodeArrayBuffer',
value: function decodeArrayBuffer(arraybuffer, callback, errback) {
if (!this.offlineAc) {
this.offlineAc = this.getOfflineAudioContext(this.ac ? this.ac.sampleRate : 44100);
}
this.offlineAc.decodeAudioData(arraybuffer, function (data) {
return callback(data);
}, errback);
}
/**
* Set pre-decoded peaks
*
* @param {number[]|number[][]} peaks
* @param {?number} duration
*/
}, {
key: 'setPeaks',
value: function setPeaks(peaks, duration) {
this.explicitDuration = duration;
this.peaks = peaks;
}
/**
* Set the rendered length (different from the length of the audio).
*
* @param {number} length
*/
}, {
key: 'setLength',
value: function setLength(length) {
// No resize, we can preserve the cached peaks.
if (this.mergedPeaks && length == 2 * this.mergedPeaks.length - 1 + 2) {
return;
}
this.splitPeaks = [];
this.mergedPeaks = [];
// Set the last element of the sparse array so the peak arrays are
// appropriately sized for other calculations.
var channels = this.buffer ? this.buffer.numberOfChannels : 1;
var c = void 0;
for (c = 0; c < channels; c++) {
this.splitPeaks[c] = [];
this.splitPeaks[c][2 * (length - 1)] = 0;
this.splitPeaks[c][2 * (length - 1) + 1] = 0;
}
this.mergedPeaks[2 * (length - 1)] = 0;
this.mergedPeaks[2 * (length - 1) + 1] = 0;
}
/**
* Compute the max and min value of the waveform when broken into <length> subranges.
*
* @param {number} length How many subranges to break the waveform into.
* @param {number} first First sample in the required range.
* @param {number} last Last sample in the required range.
* @return {number[]|number[][]} Array of 2*<length> peaks or array of arrays of
* peaks consisting of (max, min) values for each subrange.
*/
}, {
key: 'getPeaks',
value: function getPeaks(length, first, last) {
if (this.peaks) {
return this.peaks;
}
first = first || 0;
last = last || length - 1;
this.setLength(length);
/**
* The following snippet fixes a buffering data issue on the Safari
* browser which returned undefined It creates the missing buffer based
* on 1 channel, 4096 samples and the sampleRate from the current
* webaudio context 4096 samples seemed to be the best fit for rendering
* will review this code once a stable version of Safari TP is out
*/
if (!this.buffer.length) {
var newBuffer = this.createBuffer(1, 4096, this.sampleRate);
this.buffer = newBuffer.buffer;
}
var sampleSize = this.buffer.length / length;
var sampleStep = ~~(sampleSize / 10) || 1;
var channels = this.buffer.numberOfChannels;
var c = void 0;
for (c = 0; c < channels; c++) {
var peaks = this.splitPeaks[c];
var chan = this.buffer.getChannelData(c);
var i = void 0;
for (i = first; i <= last; i++) {
var start = ~~(i * sampleSize);
var end = ~~(start + sampleSize);
var min = 0;
var max = 0;
var j = void 0;
for (j = start; j < end; j += sampleStep) {
var value = chan[j];
if (value > max) {
max = value;
}
if (value < min) {
min = value;
}
}
peaks[2 * i] = max;
peaks[2 * i + 1] = min;
if (c == 0 || max > this.mergedPeaks[2 * i]) {
this.mergedPeaks[2 * i] = max;
}
if (c == 0 || min < this.mergedPeaks[2 * i + 1]) {
this.mergedPeaks[2 * i + 1] = min;
}
}
}
return this.params.splitChannels ? this.splitPeaks : this.mergedPeaks;
}
/**
* Get the position from 0 to 1
*
* @return {number}
*/
}, {
key: 'getPlayedPercents',
value: function getPlayedPercents() {
return this.state.getPlayedPercents.call(this);
}
/** @private */
}, {
key: 'disconnectSource',
value: function disconnectSource() {
if (this.source) {
this.source.disconnect();
}
}
/**
* This is called when wavesurfer is destroyed
*/
}, {
key: 'destroy',
value: function destroy() {
if (!this.isPaused()) {
this.pause();
}
this.unAll();
this.buffer = null;
this.disconnectFilters();
this.disconnectSource();
this.gainNode.disconnect();
this.scriptNode.disconnect();
this.analyser.disconnect();
// close the audioContext if closeAudioContext option is set to true
if (this.params.closeAudioContext) {
// check if browser supports AudioContext.close()
if (typeof this.ac.close === 'function' && this.ac.state != 'closed') {
this.ac.close();
}
// clear the reference to the audiocontext
this.ac = null;
// clear the actual audiocontext, either passed as param or the
// global singleton
if (!this.params.audioContext) {
window.WaveSurferAudioContext = null;
} else {
this.params.audioContext = null;
}
// clear the offlineAudioContext
window.WaveSurferOfflineAudioContext = null;
}
}
/**
* Loaded a decoded audio buffer
*
* @param {Object} buffer
*/
}, {
key: 'load',
value: function load(buffer) {
this.startPosition = 0;
this.lastPlay = this.ac.currentTime;
this.buffer = buffer;
this.createSource();
}
/** @private */
}, {
key: 'createSource',
value: function createSource() {
this.disconnectSource();
this.source = this.ac.createBufferSource();
// adjust for old browsers
this.source.start = this.source.start || this.source.noteGrainOn;
this.source.stop = this.source.stop || this.source.noteOff;
this.source.playbackRate.setValueAtTime(this.playbackRate, this.ac.currentTime);
this.source.buffer = this.buffer;
this.source.connect(this.analyser);
}
/**
* Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
*
* @return {boolean}
*/
}, {
key: 'isPaused',
value: function isPaused() {
return this.state !== this.states[PLAYING];
}
/**
* Used by `wavesurfer.getDuration()`
*
* @return {number}
*/
}, {
key: 'getDuration',
value: function getDuration() {
if (!this.buffer) {
if (this.explicitDuration) {
return this.explicitDuration;
}
return 0;
}
return this.buffer.duration;
}
/**
* Used by `wavesurfer.seekTo()`
*
* @param {number} start Position to start at in seconds
* @param {number} end Position to end at in seconds
* @return {{start: number, end: number}}
*/
}, {
key: 'seekTo',
value: function seekTo(start, end) {
if (!this.buffer) {
return;
}
this.scheduledPause = null;
if (start == null) {
start = this.getCurrentTime();
if (start >= this.getDuration()) {
start = 0;
}
}
if (end == null) {
end = this.getDuration();
}
this.startPosition = start;
this.lastPlay = this.ac.currentTime;
if (this.state === this.states[FINISHED]) {
this.setState(PAUSED);
}
return {
start: start,
end: end
};
}
/**
* Get the playback position in seconds
*
* @return {number}
*/
}, {
key: 'getPlayedTime',
value: function getPlayedTime() {
return (this.ac.currentTime - this.lastPlay) * this.playbackRate;
}
/**
* Plays the loaded audio region.
*
* @param {number} start Start offset in seconds, relative to the beginning
* of a clip.
* @param {number} end When to stop relative to the beginning of a clip.
*/
}, {
key: 'play',
value: function play(start, end) {
if (!this.buffer) {
return;
}
// need to re-create source on each playback
this.createSource();
var adjustedTime = this.seekTo(start, end);
start = adjustedTime.start;
end = adjustedTime.end;
this.scheduledPause = end;
this.source.start(0, start, end - start);
if (this.ac.state == 'suspended') {
this.ac.resume && this.ac.resume();
}
this.setState(PLAYING);
this.fireEvent('play');
}
/**
* Pauses the loaded audio.
*/
}, {
key: 'pause',
value: function pause() {
this.scheduledPause = null;
this.startPosition += this.getPlayedTime();
this.source && this.source.stop(0);
this.setState(PAUSED);
this.fireEvent('pause');
}
/**
* Returns the current time in seconds relative to the audioclip's
* duration.
*
* @return {number}
*/
}, {
key: 'getCurrentTime',
value: function getCurrentTime() {
return this.state.getCurrentTime.call(this);
}
/**
* Returns the current playback rate. (0=no playback, 1=normal playback)
*
* @return {number}
*/
}, {
key: 'getPlaybackRate',
value: function getPlaybackRate() {
return this.playbackRate;
}
/**
* Set the audio source playback rate.
*
* @param {number} value
*/
}, {
key: 'setPlaybackRate',
value: function setPlaybackRate(value) {
value = value || 1;
if (this.isPaused()) {
this.playbackRate = value;
} else {
this.pause();
this.playbackRate = value;
this.play();
}
}
}]);
return WebAudio;
}(util.Observer);
WebAudio.scriptBufferSize = 256;
exports.default = WebAudio;
module.exports = exports['default'];
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _util = __webpack_require__(0);
var util = _interopRequireWildcard(_util);
var _drawer = __webpack_require__(14);
var _drawer2 = _interopRequireDefault(_drawer);
var _webaudio = __webpack_require__(3);
var _webaudio2 = _interopRequireDefault(_webaudio);
var _mediaelement = __webpack_require__(16);
var _mediaelement2 = _interopRequireDefault(_mediaelement);
var _peakcache = __webpack_require__(17);
var _peakcache2 = _interopRequireDefault(_peakcache);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/*
* This work is licensed under a BSD-3-Clause License.
*/
/** @external {HTMLElement} https://developer.mozilla.org/en/docs/Web/API/HTMLElement */
/** @external {OfflineAudioContext} https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext */
/** @external {File} https://developer.mozilla.org/en-US/docs/Web/API/File */
/** @external {Blob} https://developer.mozilla.org/en-US/docs/Web/API/Blob */
/** @external {CanvasRenderingContext2D} https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D */
/** @external {MediaStreamConstraints} https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints */
/** @external {AudioNode} https://developer.mozilla.org/de/docs/Web/API/AudioNode */
/**
* @typedef {Object} WavesurferParams
* @property {AudioContext} audioContext=null Use your own previously
* initialized AudioContext or leave blank.
* @property {number} audioRate=1 Speed at which to play audio. Lower number is
* slower.
* @property {boolean} autoCenter=true If a scrollbar is present, center the
* waveform around the progress
* @property {string} backend='WebAudio' `'WebAudio'|'MediaElement'` In most cases
* you don't have to set this manually. MediaElement is a fallback for
* unsupported browsers.
* @property {number} barHeight=1 The height of the wave
* @property {number} barGap=null The optional spacing between bars of the wave,
* if not provided will be calculated in legacy format.
* @property {boolean} closeAudioContext=false Close and nullify all audio
* contexts when the destroy method is called.
* @property {!string|HTMLElement} container CSS selector or HTML element where
* the waveform should be drawn. This is the only required parameter.
* @property {string} cursorColor='#333' The fill color of the cursor indicating
* the playhead position.
* @property {number} cursorWidth=1 Measured in pixels.
* @property {boolean} fillParent=true Whether to fill the entire container or
* draw only according to `minPxPerSec`.
* @property {boolean} forceDecode=false Force decoding of audio using web audio
* when zooming to get a more detailed waveform.
* @property {number} height=128 The height of the waveform. Measured in
* pixels.
* @property {boolean} hideScrollbar=false Whether to hide the horizontal
* scrollbar when one would normally be shown.
* @property {boolean} interact=true Whether the mouse interaction will be
* enabled at initialization. You can switch this parameter at any time later
* on.
* @property {boolean} loopSelection=true (Use with regions plugin) Enable
* looping of selected regions
* @property {number} maxCanvasWidth=4000 Maximum width of a single canvas in
* pixels, excluding a small overlap (2 * `pixelRatio`, rounded up to the next
* even integer). If the waveform is longer than this value, additional canvases
* will be used to render the waveform, which is useful for very large waveforms
* that may be too wide for browsers to draw on a single canvas.
* @property {boolean} mediaControls=false (Use with backend `MediaElement`)
* this enables the native controls for the media element
* @property {string} mediaType='audio' (Use with backend `MediaElement`)
* `'audio'|'video'`
* @property {number} minPxPerSec=20 Minimum number of pixels per second of
* audio.
* @property {boolean} normalize=false If true, normalize by the maximum peak
* instead of 1.0.
* @property {boolean} partialRender=false Use the PeakCache to improve
* rendering speed of large waveforms
* @property {number} pixelRatio=window.devicePixelRatio The pixel ratio used to
* calculate display
* @property {PluginDefinition[]} plugins=[] An array of plugin definitions to
* register during instantiation, they will be directly initialised unless they
* are added with the `deferInit` property set to true.
* @property {string} progressColor='#555' The fill color of the part of the
* waveform behind the cursor.
* @property {boolean} removeMediaElementOnDestroy=true Set to false to keep the
* media element in the DOM when the player is destroyed. This is useful when
* reusing an existing media element via the `loadMediaElement` method.
* @property {Object} renderer=MultiCanvas Can be used to inject a custom
* renderer.
* @property {boolean|number} responsive=false If set to `true` resize the
* waveform, when the window is resized. This is debounced with a `100ms`
* timeout by default. If this parameter is a number it represents that timeout.
* @property {boolean} scrollParent=false Whether to scroll the container with a
* lengthy waveform. Otherwise the waveform is shrunk to the container width
* (see fillParent).
* @property {number} skipLength=2 Number of seconds to skip with the
* skipForward() and skipBackward() methods.
* @property {boolean} splitChannels=false Render with seperate waveforms for
* the channels of the audio
* @property {string} waveColor='#999' The fill color of the waveform after the
* cursor.
* @property {object} xhr={} XHR options.
*/
/**
* @typedef {Object} PluginDefinition
* @desc The Object used to describe a plugin
* @example wavesurfer.addPlugin(pluginDefinition);
* @property {string} name The name of the plugin, the plugin instance will be
* added as a property to the wavesurfer instance under this name
* @property {?Object} staticProps The properties that should be added to the
* wavesurfer instance as static properties
* @property {?boolean} deferInit Don't initialise plugin
* automatically
* @property {Object} params={} The plugin parameters, they are the first parameter
* passed to the plugin class constructor function
* @property {PluginClass} instance The plugin instance factory, is called with
* the dependency specified in extends. Returns the plugin class.
*/
/**
* @interface PluginClass
*
* @desc This is the interface which is implemented by all plugin classes. Note
* that this only turns into an observer after being passed through
* `wavesurfer.addPlugin`.
*
* @extends {Observer}
*/
var PluginClass = function () {
_createClass(PluginClass, [{
key: 'create',
/**
* Plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {Object} params={} The plugin params (specific to the plugin)
* @return {PluginDefinition} an object representing the plugin
*/
value: function create(params) {}
/**
* Construct the plugin
*
* @param {Object} ws The wavesurfer instance
* @param {Object} params={} The plugin params (specific to the plugin)
*/
}]);
function PluginClass(ws, params) {
_classCallCheck(this, PluginClass);
}
/**
* Initialise the plugin
*
* Start doing something. This is called by
* `wavesurfer.initPlugin(pluginName)`
*/
_createClass(PluginClass, [{
key: 'init',
value: function init() {}
/**
* Destroy the plugin instance
*
* Stop doing something. This is called by
* `wavesurfer.destroyPlugin(pluginName)`
*/
}, {
key: 'destroy',
value: function destroy() {}
}]);
return PluginClass;
}();
/**
* WaveSurfer core library class
*
* @extends {Observer}
* @example
* const params = {
* container: '#waveform',
* waveColor: 'violet',
* progressColor: 'purple'
* };
*
* // initialise like this
* const wavesurfer = WaveSurfer.create(params);
*
* // or like this ...
* const wavesurfer = new WaveSurfer(params);
* wavesurfer.init();
*
* // load audio file
* wavesurfer.load('example/media/demo.wav');
*/
var WaveSurfer = function (_util$Observer) {
_inherits(WaveSurfer, _util$Observer);
_createClass(WaveSurfer, null, [{
key: 'create',
/**
* Instantiate this class, call its `init` function and returns it
*
* @param {WavesurferParams} params
* @return {Object} WaveSurfer instance
* @example const wavesurfer = WaveSurfer.create(params);
*/
/** @private */
value: function create(params) {
var wavesurfer = new WaveSurfer(params);
return wavesurfer.init();
}
/**
* Functions in the `util` property are available as a prototype property to
* all instances
*
* @type {Object}
* @example
* const wavesurfer = WaveSurfer.create(params);
* wavesurfer.util.style(myElement, { background: 'blue' });
*/
/** @private */
/**
* Functions in the `util` property are available as a static property of the
* WaveSurfer class
*
* @type {Object}
* @example
* WaveSurfer.util.style(myElement, { background: 'blue' });
*/
}]);
/**
* Initialise wavesurfer instance
*
* @param {WavesurferParams} params Instantiation options for wavesurfer
* @example
* const wavesurfer = new WaveSurfer(params);
* @returns {this}
*/
function WaveSurfer(params) {
var _ret;
_classCallCheck(this, WaveSurfer);
/**
* Extract relevant parameters (or defaults)
* @private
*/
var _this = _possibleConstructorReturn(this, (WaveSurfer.__proto__ || Object.getPrototypeOf(WaveSurfer)).call(this));
_this.defaultParams = {
audioContext: null,
audioRate: 1,
autoCenter: true,
backend: 'WebAudio',
barHeight: 1,
barGap: null,
container: null,
cursorColor: '#333',
cursorWidth: 1,
dragSelection: true,
fillParent: true,
forceDecode: false,
height: 128,
hideScrollbar: false,
interact: true,
loopSelection: true,
maxCanvasWidth: 4000,
mediaContainer: null,
mediaControls: false,
mediaType: 'audio',
minPxPerSec: 20,
normalize: false,
partialRender: false,
pixelRatio: window.devicePixelRatio || screen.deviceXDPI / screen.logicalXDPI,
plugins: [],
progressColor: '#555',
removeMediaElementOnDestroy: true,
renderer: _drawer2.default,
responsive: false,
scrollParent: false,
skipLength: 2,
splitChannels: false,
waveColor: '#999',
xhr: {}
};
_this.backends = {
MediaElement: _mediaelement2.default,
WebAudio: _webaudio2.default