UNPKG

wavesurfer.js

Version:

Interactive navigable audio visualization using Web Audio and Canvas

1,539 lines (1,290 loc) 161 kB
/*! * 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