gswz/SFHY/static/js/wavesurfer.js

5131 lines
230 KiB
JavaScript

/*!
* wavesurfer.js 2.1.1 (2018-11-19)
* 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();
})(window, 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, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // 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 = "./src/wavesurfer.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./node_modules/debounce/index.js":
/*!****************************************!*\
!*** ./node_modules/debounce/index.js ***!
\****************************************/
/*! no static exports found */
/***/ (function(module, exports) {
/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing. The function also has a property 'clear'
* that is a function which will clear the timer to prevent previously scheduled executions.
*
* @source underscore.js
* @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
* @param {Function} function to wrap
* @param {Number} timeout in ms (`100`)
* @param {Boolean} whether to execute at the beginning (`false`)
* @api public
*/
function debounce(func, wait, immediate){
var timeout, args, context, timestamp, result;
if (null == wait) wait = 100;
function later() {
var last = Date.now() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
context = args = null;
}
}
};
var debounced = function(){
context = this;
args = arguments;
timestamp = Date.now();
var callNow = immediate && !timeout;
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
debounced.clear = function() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
debounced.flush = function() {
if (timeout) {
result = func.apply(context, args);
context = args = null;
clearTimeout(timeout);
timeout = null;
}
};
return debounced;
};
// Adds compatibility for ES modules
debounce.debounce = debounce;
module.exports = debounce;
/***/ }),
/***/ "./src/drawer.js":
/*!***********************!*\
!*** ./src/drawer.js ***!
\***********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var util = _interopRequireWildcard(__webpack_require__(/*! ./util */ "./src/util/index.js"));
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
/**
* Parent class for renderers
*
* @extends {Observer}
*/
var Drawer =
/*#__PURE__*/
function (_util$Observer) {
_inherits(Drawer, _util$Observer);
/**
* @param {HTMLElement} container The container node of the wavesurfer instance
* @param {WavesurferParams} params The wavesurfer initialisation options
*/
function Drawer(container, params) {
var _this;
_classCallCheck(this, Drawer);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Drawer).call(this));
/** @private */
_this.container = container;
/**
* @type {WavesurferParams}
* @private
*/
_this.params = params;
/**
* The width of the renderer
* @type {number}
*/
_this.width = 0;
/**
* The height of the renderer
* @type {number}
*/
_this.height = params.height * _this.params.pixelRatio;
/** @private */
_this.lastPos = 0;
/**
* The `<wave>` element which is added to the container
* @type {HTMLElement}
*/
_this.wrapper = null;
return _this;
}
/**
* Alias of `util.style`
*
* @param {HTMLElement} el The element that the styles will be applied to
* @param {Object} styles The map of propName: attribute, both are used as-is
* @return {HTMLElement} el
*/
_createClass(Drawer, [{
key: "style",
value: function style(el, styles) {
return util.style(el, styles);
}
/**
* Create the wrapper `<wave>` element, style it and set up the events for
* interaction
*/
}, {
key: "createWrapper",
value: function createWrapper() {
this.wrapper = this.container.appendChild(document.createElement('wave'));
this.style(this.wrapper, {
display: 'block',
position: 'relative',
userSelect: 'none',
webkitUserSelect: 'none',
height: this.params.height + 'px'
});
if (this.params.fillParent || this.params.scrollParent) {
this.style(this.wrapper, {
width: '100%',
overflowX: this.params.hideScrollbar ? 'hidden' : 'auto',
overflowY: 'hidden'
});
}
this.setupWrapperEvents();
}
/**
* Handle click event
*
* @param {Event} e Click event
* @param {?boolean} noPrevent Set to true to not call `e.preventDefault()`
* @return {number} Playback position from 0 to 1
*/
}, {
key: "handleEvent",
value: function handleEvent(e, noPrevent) {
!noPrevent && e.preventDefault();
var clientX = e.targetTouches ? e.targetTouches[0].clientX : e.clientX;
var bbox = this.wrapper.getBoundingClientRect();
var nominalWidth = this.width;
var parentWidth = this.getWidth();
var progress;
if (!this.params.fillParent && nominalWidth < parentWidth) {
progress = (clientX - bbox.left) * (this.params.pixelRatio / nominalWidth) || 0;
if (progress > 1) {
progress = 1;
}
} else {
progress = (clientX - bbox.left + this.wrapper.scrollLeft) / this.wrapper.scrollWidth || 0;
}
return progress;
}
/**
* @private
*/
}, {
key: "setupWrapperEvents",
value: function setupWrapperEvents() {
var _this2 = this;
this.wrapper.addEventListener('click', function (e) {
var scrollbarHeight = _this2.wrapper.offsetHeight - _this2.wrapper.clientHeight;
if (scrollbarHeight != 0) {
// scrollbar is visible. Check if click was on it
var bbox = _this2.wrapper.getBoundingClientRect();
if (e.clientY >= bbox.bottom - scrollbarHeight) {
// ignore mousedown as it was on the scrollbar
return;
}
}
if (_this2.params.interact) {
_this2.fireEvent('click', e, _this2.handleEvent(e));
}
});
this.wrapper.addEventListener('scroll', function (e) {
return _this2.fireEvent('scroll', e);
});
}
/**
* Draw peaks on the canvas
*
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} length The width of the area that should be drawn
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
}, {
key: "drawPeaks",
value: function drawPeaks(peaks, length, start, end) {
if (!this.setWidth(length)) {
this.clearWave();
}
this.params.barWidth ? this.drawBars(peaks, 0, start, end) : this.drawWave(peaks, 0, start, end);
}
/**
* Scroll to the beginning
*/
}, {
key: "resetScroll",
value: function resetScroll() {
if (this.wrapper !== null) {
this.wrapper.scrollLeft = 0;
}
}
/**
* Recenter the view-port at a certain percent of the waveform
*
* @param {number} percent Value from 0 to 1 on the waveform
*/
}, {
key: "recenter",
value: function recenter(percent) {
var position = this.wrapper.scrollWidth * percent;
this.recenterOnPosition(position, true);
}
/**
* Recenter the view-port on a position, either scroll there immediately or
* in steps of 5 pixels
*
* @param {number} position X-offset in pixels
* @param {boolean} immediate Set to true to immediately scroll somewhere
*/
}, {
key: "recenterOnPosition",
value: function recenterOnPosition(position, immediate) {
var scrollLeft = this.wrapper.scrollLeft;
var half = ~~(this.wrapper.clientWidth / 2);
var maxScroll = this.wrapper.scrollWidth - this.wrapper.clientWidth;
var target = position - half;
var offset = target - scrollLeft;
if (maxScroll == 0) {
// no need to continue if scrollbar is not there
return;
} // if the cursor is currently visible...
if (!immediate && -half <= offset && offset < half) {
// we'll limit the "re-center" rate.
var rate = 5;
offset = Math.max(-rate, Math.min(rate, offset));
target = scrollLeft + offset;
} // limit target to valid range (0 to maxScroll)
target = Math.max(0, Math.min(maxScroll, target)); // no use attempting to scroll if we're not moving
if (target != scrollLeft) {
this.wrapper.scrollLeft = target;
}
}
/**
* Get the current scroll position in pixels
*
* @return {number}
*/
}, {
key: "getScrollX",
value: function getScrollX() {
var pixelRatio = this.params.pixelRatio;
var x = Math.round(this.wrapper.scrollLeft * pixelRatio); // In cases of elastic scroll (safari with mouse wheel) you can
// scroll beyond the limits of the container
// Calculate and floor the scrollable extent to make sure an out
// of bounds value is not returned
// Ticket #1312
if (this.params.scrollParent) {
var maxScroll = ~~(this.wrapper.scrollWidth * pixelRatio - this.getWidth());
x = Math.min(maxScroll, Math.max(0, x));
}
return x;
}
/**
* Get the width of the container
*
* @return {number}
*/
}, {
key: "getWidth",
value: function getWidth() {
return Math.round(this.container.clientWidth * this.params.pixelRatio);
}
/**
* Set the width of the container
*
* @param {number} width
*/
}, {
key: "setWidth",
value: function setWidth(width) {
if (this.width == width) {
return false;
}
this.width = width;
if (this.params.fillParent || this.params.scrollParent) {
this.style(this.wrapper, {
width: ''
});
} else {
this.style(this.wrapper, {
width: ~~(this.width / this.params.pixelRatio) + 'px'
});
}
this.updateSize();
return true;
}
/**
* Set the height of the container
*
* @param {number} height
*/
}, {
key: "setHeight",
value: function setHeight(height) {
if (height == this.height) {
return false;
}
this.height = height;
this.style(this.wrapper, {
height: ~~(this.height / this.params.pixelRatio) + 'px'
});
this.updateSize();
return true;
}
/**
* Called by wavesurfer when progress should be rendered
*
* @param {number} progress From 0 to 1
*/
}, {
key: "progress",
value: function progress(_progress) {
var minPxDelta = 1 / this.params.pixelRatio;
var pos = Math.round(_progress * this.width) * minPxDelta;
if (pos < this.lastPos || pos - this.lastPos >= minPxDelta) {
this.lastPos = pos;
if (this.params.scrollParent && this.params.autoCenter) {
var newPos = ~~(this.wrapper.scrollWidth * _progress);
this.recenterOnPosition(newPos);
}
this.updateProgress(pos);
}
}
/**
* This is called when wavesurfer is destroyed
*/
}, {
key: "destroy",
value: function destroy() {
this.unAll();
if (this.wrapper) {
if (this.wrapper.parentNode == this.container) {
this.container.removeChild(this.wrapper);
}
this.wrapper = null;
}
}
/* Renderer-specific methods */
/**
* Called after cursor related params have changed.
*
* @abstract
*/
}, {
key: "updateCursor",
value: function updateCursor() {}
/**
* Called when the size of the container changes so the renderer can adjust
*
* @abstract
*/
}, {
key: "updateSize",
value: function updateSize() {}
/**
* Draw a waveform with bars
*
* @abstract
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
}, {
key: "drawBars",
value: function drawBars(peaks, channelIndex, start, end) {}
/**
* Draw a waveform
*
* @abstract
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
}, {
key: "drawWave",
value: function drawWave(peaks, channelIndex, start, end) {}
/**
* Clear the waveform
*
* @abstract
*/
}, {
key: "clearWave",
value: function clearWave() {}
/**
* Render the new progress
*
* @abstract
* @param {number} position X-Offset of progress position in pixels
*/
}, {
key: "updateProgress",
value: function updateProgress(position) {}
}]);
return Drawer;
}(util.Observer);
exports.default = Drawer;
module.exports = exports.default;
/***/ }),
/***/ "./src/drawer.multicanvas.js":
/*!***********************************!*\
!*** ./src/drawer.multicanvas.js ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _drawer = _interopRequireDefault(__webpack_require__(/*! ./drawer */ "./src/drawer.js"));
var util = _interopRequireWildcard(__webpack_require__(/*! ./util */ "./src/util/index.js"));
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
/**
* @typedef {Object} CanvasEntry
* @private
* @property {HTMLElement} wave The wave node
* @property {CanvasRenderingContext2D} waveCtx The canvas rendering context
* @property {?HTMLElement} progress The progress wave node
* @property {?CanvasRenderingContext2D} progressCtx The progress wave canvas
* rendering context
* @property {?number} start Start of the area the canvas should render, between 0 and 1
* @property {?number} end End of the area the canvas should render, between 0 and 1
*/
/**
* MultiCanvas renderer for wavesurfer. Is currently the default and sole built
* in renderer.
*/
var MultiCanvas =
/*#__PURE__*/
function (_Drawer) {
_inherits(MultiCanvas, _Drawer);
/**
* @param {HTMLElement} container The container node of the wavesurfer instance
* @param {WavesurferParams} params The wavesurfer initialisation options
*/
function MultiCanvas(container, params) {
var _this;
_classCallCheck(this, MultiCanvas);
_this = _possibleConstructorReturn(this, _getPrototypeOf(MultiCanvas).call(this, container, params));
/**
* @type {number}
* @private
*/
_this.maxCanvasWidth = params.maxCanvasWidth;
/**
* @private
* @type {number}
*/
_this.maxCanvasElementWidth = Math.round(params.maxCanvasWidth / params.pixelRatio);
/**
* Whether or not the progress wave is rendered. If the `waveColor`
* and `progressColor` are the same color it is not.
* @type {boolean}
*/
_this.hasProgressCanvas = params.waveColor != params.progressColor;
/**
* @private
* @type {number}
*/
_this.halfPixel = 0.5 / params.pixelRatio;
/**
* @private
* @type {Array}
*/
_this.canvases = [];
/** @private */
_this.progressWave = null;
return _this;
}
/**
* Initialize the drawer
*/
_createClass(MultiCanvas, [{
key: "init",
value: function init() {
this.createWrapper();
this.createElements();
}
/**
* Create the canvas elements and style them
*
* @private
*/
}, {
key: "createElements",
value: function createElements() {
this.progressWave = this.wrapper.appendChild(this.style(document.createElement('wave'), {
position: 'absolute',
zIndex: 3,
left: 0,
top: 0,
bottom: 0,
overflow: 'hidden',
width: '0',
display: 'none',
boxSizing: 'border-box',
borderRightStyle: 'solid',
pointerEvents: 'none'
}));
this.addCanvas();
this.updateCursor();
}
/**
* Update cursor style from params.
*/
}, {
key: "updateCursor",
value: function updateCursor() {
this.style(this.progressWave, {
borderRightWidth: this.params.cursorWidth + 'px',
borderRightColor: this.params.cursorColor
});
}
/**
* Adjust to the updated size by adding or removing canvases
*/
}, {
key: "updateSize",
value: function updateSize() {
var _this2 = this;
var totalWidth = Math.round(this.width / this.params.pixelRatio);
var requiredCanvases = Math.ceil(totalWidth / this.maxCanvasElementWidth);
while (this.canvases.length < requiredCanvases) {
this.addCanvas();
}
while (this.canvases.length > requiredCanvases) {
this.removeCanvas();
}
this.canvases.forEach(function (entry, i) {
// Add some overlap to prevent vertical white stripes, keep the
// width even for simplicity
var canvasWidth = _this2.maxCanvasWidth + 2 * Math.ceil(_this2.params.pixelRatio / 2);
if (i == _this2.canvases.length - 1) {
canvasWidth = _this2.width - _this2.maxCanvasWidth * (_this2.canvases.length - 1);
}
_this2.updateDimensions(entry, canvasWidth, _this2.height);
_this2.clearWaveForEntry(entry);
});
}
/**
* Add a canvas to the canvas list
*
* @private
*/
}, {
key: "addCanvas",
value: function addCanvas() {
var entry = {};
var leftOffset = this.maxCanvasElementWidth * this.canvases.length;
entry.wave = this.wrapper.appendChild(this.style(document.createElement('canvas'), {
position: 'absolute',
zIndex: 2,
left: leftOffset + 'px',
top: 0,
bottom: 0,
height: '100%',
pointerEvents: 'none'
}));
entry.waveCtx = entry.wave.getContext('2d');
if (this.hasProgressCanvas) {
entry.progress = this.progressWave.appendChild(this.style(document.createElement('canvas'), {
position: 'absolute',
left: leftOffset + 'px',
top: 0,
bottom: 0,
height: '100%'
}));
entry.progressCtx = entry.progress.getContext('2d');
}
this.canvases.push(entry);
}
/**
* Pop one canvas from the list
*
* @private
*/
}, {
key: "removeCanvas",
value: function removeCanvas() {
var lastEntry = this.canvases.pop();
lastEntry.wave.parentElement.removeChild(lastEntry.wave);
if (this.hasProgressCanvas) {
lastEntry.progress.parentElement.removeChild(lastEntry.progress);
}
}
/**
* Update the dimensions of a canvas element
*
* @private
* @param {CanvasEntry} entry
* @param {number} width The new width of the element
* @param {number} height The new height of the element
*/
}, {
key: "updateDimensions",
value: function updateDimensions(entry, width, height) {
var elementWidth = Math.round(width / this.params.pixelRatio);
var totalWidth = Math.round(this.width / this.params.pixelRatio); // Where the canvas starts and ends in the waveform, represented as a
// decimal between 0 and 1.
entry.start = entry.waveCtx.canvas.offsetLeft / totalWidth || 0;
entry.end = entry.start + elementWidth / totalWidth;
entry.waveCtx.canvas.width = width;
entry.waveCtx.canvas.height = height;
this.style(entry.waveCtx.canvas, {
width: elementWidth + 'px'
});
this.style(this.progressWave, {
display: 'block'
});
if (this.hasProgressCanvas) {
entry.progressCtx.canvas.width = width;
entry.progressCtx.canvas.height = height;
this.style(entry.progressCtx.canvas, {
width: elementWidth + 'px'
});
}
}
/**
* Clear the whole waveform
*/
}, {
key: "clearWave",
value: function clearWave() {
var _this3 = this;
this.canvases.forEach(function (entry) {
return _this3.clearWaveForEntry(entry);
});
}
/**
* Clear one canvas
*
* @private
* @param {CanvasEntry} entry
*/
}, {
key: "clearWaveForEntry",
value: function clearWaveForEntry(entry) {
entry.waveCtx.clearRect(0, 0, entry.waveCtx.canvas.width, entry.waveCtx.canvas.height);
if (this.hasProgressCanvas) {
entry.progressCtx.clearRect(0, 0, entry.progressCtx.canvas.width, entry.progressCtx.canvas.height);
}
}
/**
* Draw a waveform with bars
*
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0. Must be an integer.
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
}, {
key: "drawBars",
value: function drawBars(peaks, channelIndex, start, end) {
var _this4 = this;
return this.prepareDraw(peaks, channelIndex, start, end, function (_ref) {
var absmax = _ref.absmax,
hasMinVals = _ref.hasMinVals,
height = _ref.height,
offsetY = _ref.offsetY,
halfH = _ref.halfH,
peaks = _ref.peaks;
// if drawBars was called within ws.empty we don't pass a start and
// don't want anything to happen
if (start === undefined) {
return;
} // Skip every other value if there are negatives.
var peakIndexScale = hasMinVals ? 2 : 1;
var length = peaks.length / peakIndexScale;
var bar = _this4.params.barWidth * _this4.params.pixelRatio;
var gap = _this4.params.barGap === null ? Math.max(_this4.params.pixelRatio, ~~(bar / 2)) : Math.max(_this4.params.pixelRatio, _this4.params.barGap * _this4.params.pixelRatio);
var step = bar + gap;
var scale = length / _this4.width;
var first = start;
var last = end;
var i;
for (i = first; i < last; i += step) {
var peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
var h = Math.round(peak / absmax * halfH);
_this4.fillRect(i + _this4.halfPixel, halfH - h + offsetY, bar + _this4.halfPixel, h * 2);
}
});
}
/**
* Draw a waveform
*
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number?} start The x-offset of the beginning of the area that
* should be rendered (If this isn't set only a flat line is rendered)
* @param {number?} end The x-offset of the end of the area that should be
* rendered
*/
}, {
key: "drawWave",
value: function drawWave(peaks, channelIndex, start, end) {
var _this5 = this;
return this.prepareDraw(peaks, channelIndex, start, end, function (_ref2) {
var absmax = _ref2.absmax,
hasMinVals = _ref2.hasMinVals,
height = _ref2.height,
offsetY = _ref2.offsetY,
halfH = _ref2.halfH,
peaks = _ref2.peaks;
if (!hasMinVals) {
var reflectedPeaks = [];
var len = peaks.length;
var i;
for (i = 0; i < len; i++) {
reflectedPeaks[2 * i] = peaks[i];
reflectedPeaks[2 * i + 1] = -peaks[i];
}
peaks = reflectedPeaks;
} // if drawWave was called within ws.empty we don't pass a start and
// end and simply want a flat line
if (start !== undefined) {
_this5.drawLine(peaks, absmax, halfH, offsetY, start, end);
} // Always draw a median line
_this5.fillRect(0, halfH + offsetY - _this5.halfPixel, _this5.width, _this5.halfPixel);
});
}
/**
* Tell the canvas entries to render their portion of the waveform
*
* @private
* @param {number[]} peaks Peak data
* @param {number} absmax Maximum peak value (absolute)
* @param {number} halfH Half the height of the waveform
* @param {number} offsetY Offset to the top
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that
* should be rendered
*/
}, {
key: "drawLine",
value: function drawLine(peaks, absmax, halfH, offsetY, start, end) {
var _this6 = this;
this.canvases.forEach(function (entry) {
_this6.setFillStyles(entry);
_this6.drawLineToContext(entry, entry.waveCtx, peaks, absmax, halfH, offsetY, start, end);
_this6.drawLineToContext(entry, entry.progressCtx, peaks, absmax, halfH, offsetY, start, end);
});
}
/**
* Render the actual waveform line on a canvas
*
* @private
* @param {CanvasEntry} entry
* @param {Canvas2DContextAttributes} ctx Essentially `entry.[wave|progress]Ctx`
* @param {number[]} peaks
* @param {number} absmax Maximum peak value (absolute)
* @param {number} halfH Half the height of the waveform
* @param {number} offsetY Offset to the top
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that
* should be rendered
*/
}, {
key: "drawLineToContext",
value: function drawLineToContext(entry, ctx, peaks, absmax, halfH, offsetY, start, end) {
if (!ctx) {
return;
}
var length = peaks.length / 2;
var scale = this.params.fillParent && this.width != length ? this.width / length : 1;
var first = Math.round(length * entry.start); // Use one more peak value to make sure we join peaks at ends -- unless,
// of course, this is the last canvas.
var last = Math.round(length * entry.end) + 1;
if (first > end || last < start) {
return;
}
var canvasStart = Math.min(first, start);
var canvasEnd = Math.max(last, end);
var i;
var j;
ctx.beginPath();
ctx.moveTo((canvasStart - first) * scale + this.halfPixel, halfH + offsetY);
for (i = canvasStart; i < canvasEnd; i++) {
var peak = peaks[2 * i] || 0;
var h = Math.round(peak / absmax * halfH);
ctx.lineTo((i - first) * scale + this.halfPixel, halfH - h + offsetY);
} // Draw the bottom edge going backwards, to make a single
// closed hull to fill.
for (j = canvasEnd - 1; j >= canvasStart; j--) {
var _peak = peaks[2 * j + 1] || 0;
var _h = Math.round(_peak / absmax * halfH);
ctx.lineTo((j - first) * scale + this.halfPixel, halfH - _h + offsetY);
}
ctx.closePath();
ctx.fill();
}
/**
* Draw a rectangle on the waveform
*
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
}, {
key: "fillRect",
value: function fillRect(x, y, width, height) {
var startCanvas = Math.floor(x / this.maxCanvasWidth);
var endCanvas = Math.min(Math.ceil((x + width) / this.maxCanvasWidth) + 1, this.canvases.length);
var i;
for (i = startCanvas; i < endCanvas; i++) {
var entry = this.canvases[i];
var leftOffset = i * this.maxCanvasWidth;
var intersection = {
x1: Math.max(x, i * this.maxCanvasWidth),
y1: y,
x2: Math.min(x + width, i * this.maxCanvasWidth + entry.waveCtx.canvas.width),
y2: y + height
};
if (intersection.x1 < intersection.x2) {
this.setFillStyles(entry);
this.fillRectToContext(entry.waveCtx, intersection.x1 - leftOffset, intersection.y1, intersection.x2 - intersection.x1, intersection.y2 - intersection.y1);
this.fillRectToContext(entry.progressCtx, intersection.x1 - leftOffset, intersection.y1, intersection.x2 - intersection.x1, intersection.y2 - intersection.y1);
}
}
}
/**
* Performs preparation tasks and calculations which are shared by drawBars and drawWave
*
* @private
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number?} start The x-offset of the beginning of the area that
* should be rendered (If this isn't set only a flat line is rendered)
* @param {number?} end The x-offset of the end of the area that should be
* rendered
* @param {function} fn The render function to call
*/
}, {
key: "prepareDraw",
value: function prepareDraw(peaks, channelIndex, start, end, fn) {
var _this7 = this;
return util.frame(function () {
// Split channels and call this function with the channelIndex set
if (peaks[0] instanceof Array) {
var channels = peaks;
if (_this7.params.splitChannels) {
_this7.setHeight(channels.length * _this7.params.height * _this7.params.pixelRatio);
return channels.forEach(function (channelPeaks, i) {
return _this7.prepareDraw(channelPeaks, i, start, end, fn);
});
}
peaks = channels[0];
} // calculate maximum modulation value, either from the barHeight
// parameter or if normalize=true from the largest value in the peak
// set
var absmax = 1 / _this7.params.barHeight;
if (_this7.params.normalize) {
var max = util.max(peaks);
var min = util.min(peaks);
absmax = -min > max ? -min : max;
} // Bar wave draws the bottom only as a reflection of the top,
// so we don't need negative values
var hasMinVals = [].some.call(peaks, function (val) {
return val < 0;
});
var height = _this7.params.height * _this7.params.pixelRatio;
var offsetY = height * channelIndex || 0;
var halfH = height / 2;
return fn({
absmax: absmax,
hasMinVals: hasMinVals,
height: height,
offsetY: offsetY,
halfH: halfH,
peaks: peaks
});
})();
}
/**
* Draw the actual rectangle on a canvas
*
* @private
* @param {Canvas2DContextAttributes} ctx
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
}, {
key: "fillRectToContext",
value: function fillRectToContext(ctx, x, y, width, height) {
if (!ctx) {
return;
}
ctx.fillRect(x, y, width, height);
}
/**
* Set the fill styles for a certain entry (wave and progress)
*
* @private
* @param {CanvasEntry} entry
*/
}, {
key: "setFillStyles",
value: function setFillStyles(entry) {
entry.waveCtx.fillStyle = this.params.waveColor;
if (this.hasProgressCanvas) {
entry.progressCtx.fillStyle = this.params.progressColor;
}
}
/**
* Return image data of the waveform
*
* @param {string} type='image/png' An optional value of a format type.
* @param {number} quality=0.92 An optional value between 0 and 1.
* @return {string|string[]} images A data URL or an array of data URLs
*/
}, {
key: "getImage",
value: function getImage(type, quality) {
var images = this.canvases.map(function (entry) {
return entry.wave.toDataURL(type, quality);
});
return images.length > 1 ? images : images[0];
}
/**
* Render the new progress
*
* @param {number} position X-Offset of progress position in pixels
*/
}, {
key: "updateProgress",
value: function updateProgress(position) {
this.style(this.progressWave, {
width: position + 'px'
});
}
}]);
return MultiCanvas;
}(_drawer.default);
exports.default = MultiCanvas;
module.exports = exports.default;
/***/ }),
/***/ "./src/mediaelement.js":
/*!*****************************!*\
!*** ./src/mediaelement.js ***!
\*****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _webaudio = _interopRequireDefault(__webpack_require__(/*! ./webaudio */ "./src/webaudio.js"));
var util = _interopRequireWildcard(__webpack_require__(/*! ./util */ "./src/util/index.js"));
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); }
function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
/**
* MediaElement backend
*/
var MediaElement =
/*#__PURE__*/
function (_WebAudio) {
_inherits(MediaElement, _WebAudio);
/**
* Construct the backend
*
* @param {WavesurferParams} params
*/
function MediaElement(params) {
var _this;
_classCallCheck(this, MediaElement);
_this = _possibleConstructorReturn(this, _getPrototypeOf(MediaElement).call(this, params));
/** @private */
_this.params = params; // Dummy media to catch errors
/** @private */
_this.media = {
currentTime: 0,
duration: 0,
paused: true,
playbackRate: 1,
play: function play() {},
pause: function pause() {},
volume: 0
};
/** @private */
_this.mediaType = params.mediaType.toLowerCase();
/** @private */
_this.elementPosition = params.elementPosition;
/** @private */
_this.peaks = null;
/** @private */
_this.playbackRate = 1;
/** @private */
_this.volume = 1;
/** @private */
_this.buffer = null;
/** @private */
_this.onPlayEnd = null;
return _this;
}
/**
* Initialise the backend, called in `wavesurfer.createBackend()`
*/
_createClass(MediaElement, [{
key: "init",
value: function init() {
this.setPlaybackRate(this.params.audioRate);
this.createTimer();
}
/**
* Create a timer to provide a more precise `audioprocess` event.
*
* @private
*/
}, {
key: "createTimer",
value: function createTimer() {
var _this2 = this;
var onAudioProcess = function onAudioProcess() {
if (_this2.isPaused()) {
return;
}
_this2.fireEvent('audioprocess', _this2.getCurrentTime()); // Call again in the next frame
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
requestAnimationFrame(onAudioProcess);
};
this.on('play', onAudioProcess); // Update the progress one more time to prevent it from being stuck in
// case of lower framerates
this.on('pause', function () {
_this2.fireEvent('audioprocess', _this2.getCurrentTime());
});
}
/**
* Create media element with url as its source,
* and append to container element.
*
* @param {string} url Path to media file
* @param {HTMLElement} container HTML element
* @param {number[]|number[][]} peaks Array of peak data
* @param {string} preload HTML 5 preload attribute value
*/
}, {
key: "load",
value: function load(url, container, peaks, preload) {
var media = document.createElement(this.mediaType);
media.controls = this.params.mediaControls;
media.autoplay = this.params.autoplay || false;
media.preload = preload == null ? 'auto' : preload;
media.src = url;
media.style.width = '100%';
var prevMedia = container.querySelector(this.mediaType);
if (prevMedia) {
container.removeChild(prevMedia);
}
container.appendChild(media);
this._load(media, peaks);
}
/**
* Load existing media element.
*
* @param {HTMLMediaElement} elt HTML5 Audio or Video element
* @param {number[]|number[][]} peaks Array of peak data
*/
}, {
key: "loadElt",
value: function loadElt(elt, peaks) {
elt.controls = this.params.mediaControls;
elt.autoplay = this.params.autoplay || false;
this._load(elt, peaks);
}
/**
* Private method called by both load (from url)
* and loadElt (existing media element).
*
* @param {HTMLMediaElement} media HTML5 Audio or Video element
* @param {number[]|number[][]} peaks Array of peak data
* @private
*/
}, {
key: "_load",
value: function _load(media, peaks) {
var _this3 = this;
// load must be called manually on iOS, otherwise peaks won't draw
// until a user interaction triggers load --> 'ready' event
if (typeof media.load == 'function') {
// Resets the media element and restarts the media resource. Any
// pending events are discarded. How much media data is fetched is
// still affected by the preload attribute.
media.load();
}
media.addEventListener('error', function () {
_this3.fireEvent('error', 'Error loading media element');
});
media.addEventListener('canplay', function () {
_this3.fireEvent('canplay');
});
media.addEventListener('ended', function () {
_this3.fireEvent('finish');
}); // Listen to and relay play and pause events to enable
// playback control from the external media element
media.addEventListener('play', function () {
_this3.fireEvent('play');
});
media.addEventListener('pause', function () {
_this3.fireEvent('pause');
});
this.media = media;
this.peaks = peaks;
this.onPlayEnd = null;
this.buffer = null;
this.setPlaybackRate(this.playbackRate);
this.setVolume(this.volume);
}
/**
* Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
*
* @return {boolean}
*/
}, {
key: "isPaused",
value: function isPaused() {
return !this.media || this.media.paused;
}
/**
* Used by `wavesurfer.getDuration()`
*
* @return {number}
*/
}, {
key: "getDuration",
value: function getDuration() {
if (this.explicitDuration) {
return this.explicitDuration;
}
var duration = (this.buffer || this.media).duration;
if (duration >= Infinity) {
// streaming audio
duration = this.media.seekable.end(0);
}
return duration;
}
/**
* Returns the current time in seconds relative to the audio-clip's
* duration.
*
* @return {number}
*/
}, {
key: "getCurrentTime",
value: function getCurrentTime() {
return this.media && this.media.currentTime;
}
/**
* Get the position from 0 to 1
*
* @return {number}
*/
}, {
key: "getPlayedPercents",
value: function getPlayedPercents() {
return this.getCurrentTime() / this.getDuration() || 0;
}
/**
* Get the audio source playback rate.
*
* @return {number}
*/
}, {
key: "getPlaybackRate",
value: function getPlaybackRate() {
return this.playbackRate || this.media.playbackRate;
}
/**
* Set the audio source playback rate.
*
* @param {number} value
*/
}, {
key: "setPlaybackRate",
value: function setPlaybackRate(value) {
this.playbackRate = value || 1;
this.media.playbackRate = this.playbackRate;
}
/**
* Used by `wavesurfer.seekTo()`
*
* @param {number} start Position to start at in seconds
*/
}, {
key: "seekTo",
value: function seekTo(start) {
if (start != null) {
this.media.currentTime = start;
}
this.clearPlayEnd();
}
/**
* 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.
* @emits MediaElement#play
* @return {Promise}
*/
}, {
key: "play",
value: function play(start, end) {
this.seekTo(start);
var promise = this.media.play();
end && this.setPlayEnd(end);
return promise;
}
/**
* Pauses the loaded audio.
*
* @emits MediaElement#pause
* @return {Promise}
*/
}, {
key: "pause",
value: function pause() {
var promise;
if (this.media) {
promise = this.media.pause();
}
this.clearPlayEnd();
return promise;
}
/** @private */
}, {
key: "setPlayEnd",
value: function setPlayEnd(end) {
var _this4 = this;
this._onPlayEnd = function (time) {
if (time >= end) {
_this4.pause();
_this4.seekTo(end);
}
};
this.on('audioprocess', this._onPlayEnd);
}
/** @private */
}, {
key: "clearPlayEnd",
value: function clearPlayEnd() {
if (this._onPlayEnd) {
this.un('audioprocess', this._onPlayEnd);
this._onPlayEnd = null;
}
}
/**
* 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.buffer) {
return _get(_getPrototypeOf(MediaElement.prototype), "getPeaks", this).call(this, length, first, last);
}
return this.peaks || [];
}
/**
* 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) {
if (!this.media.setSinkId) {
return Promise.reject(new Error('setSinkId is not supported in your browser'));
}
return this.media.setSinkId(deviceId);
}
return Promise.reject(new Error('Invalid deviceId: ' + deviceId));
}
/**
* Get the current volume
*
* @return {number} value A floating point value between 0 and 1.
*/
}, {
key: "getVolume",
value: function getVolume() {
return this.volume || this.media.volume;
}
/**
* Set the audio volume
*
* @param {number} value A floating point value between 0 and 1.
*/
}, {
key: "setVolume",
value: function setVolume(value) {
this.volume = value;
this.media.volume = this.volume;
}
/**
* This is called when wavesurfer is destroyed
*
*/
}, {
key: "destroy",
value: function destroy() {
this.pause();
this.unAll();
if (this.params.removeMediaElementOnDestroy && this.media && this.media.parentNode) {
this.media.parentNode.removeChild(this.media);
}
this.media = null;
}
}]);
return MediaElement;
}(_webaudio.default);
exports.default = MediaElement;
module.exports = exports.default;
/***/ }),
/***/ "./src/peakcache.js":
/*!**************************!*\
!*** ./src/peakcache.js ***!
\**************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
/**
* Caches the decoded peaks data to improve rendering speed for large audio
*
* Is used if the option parameter `partialRender` is set to `true`
*/
var PeakCache =
/*#__PURE__*/
function () {
/**
* Instantiate cache
*/
function PeakCache() {
_classCallCheck(this, PeakCache);
this.clearPeakCache();
}
/**
* Empty the cache
*/
_createClass(PeakCache, [{
key: "clearPeakCache",
value: function clearPeakCache() {
/**
* Flat array with entries that are always in pairs to mark the
* beginning and end of each subrange. This is a convenience so we can
* iterate over the pairs for easy set difference operations.
* @private
*/
this.peakCacheRanges = [];
/**
* Length of the entire cachable region, used for resetting the cache
* when this changes (zoom events, for instance).
* @private
*/
this.peakCacheLength = -1;
}
/**
* Add a range of peaks to the cache
*
* @param {number} length The length of the range
* @param {number} start The x offset of the start of the range
* @param {number} end The x offset of the end of the range
* @return {number[][]}
*/
}, {
key: "addRangeToPeakCache",
value: function addRangeToPeakCache(length, start, end) {
if (length != this.peakCacheLength) {
this.clearPeakCache();
this.peakCacheLength = length;
} // Return ranges that weren't in the cache before the call.
var uncachedRanges = [];
var i = 0; // Skip ranges before the current start.
while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] < start) {
i++;
} // If |i| is even, |start| falls after an existing range. Otherwise,
// |start| falls between an existing range, and the uncached region
// starts when we encounter the next node in |peakCacheRanges| or
// |end|, whichever comes first.
if (i % 2 == 0) {
uncachedRanges.push(start);
}
while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] <= end) {
uncachedRanges.push(this.peakCacheRanges[i]);
i++;
} // If |i| is even, |end| is after all existing ranges.
if (i % 2 == 0) {
uncachedRanges.push(end);
} // Filter out the 0-length ranges.
uncachedRanges = uncachedRanges.filter(function (item, pos, arr) {
if (pos == 0) {
return item != arr[pos + 1];
} else if (pos == arr.length - 1) {
return item != arr[pos - 1];
}
return item != arr[pos - 1] && item != arr[pos + 1];
}); // Merge the two ranges together, uncachedRanges will either contain
// wholly new points, or duplicates of points in peakCacheRanges. If
// duplicates are detected, remove both and extend the range.
this.peakCacheRanges = this.peakCacheRanges.concat(uncachedRanges);
this.peakCacheRanges = this.peakCacheRanges.sort(function (a, b) {
return a - b;
}).filter(function (item, pos, arr) {
if (pos == 0) {
return item != arr[pos + 1];
} else if (pos == arr.length - 1) {
return item != arr[pos - 1];
}
return item != arr[pos - 1] && item != arr[pos + 1];
}); // Push the uncached ranges into an array of arrays for ease of
// iteration in the functions that call this.
var uncachedRangePairs = [];
for (i = 0; i < uncachedRanges.length; i += 2) {
uncachedRangePairs.push([uncachedRanges[i], uncachedRanges[i + 1]]);
}
return uncachedRangePairs;
}
/**
* For testing
*
* @return {number[][]}
*/
}, {
key: "getCacheRanges",
value: function getCacheRanges() {
var peakCacheRangePairs = [];
var i;
for (i = 0; i < this.peakCacheRanges.length; i += 2) {
peakCacheRangePairs.push([this.peakCacheRanges[i], this.peakCacheRanges[i + 1]]);
}
return peakCacheRangePairs;
}
}]);
return PeakCache;
}();
exports.default = PeakCache;
module.exports = exports.default;
/***/ }),
/***/ "./src/util/ajax.js":
/*!**************************!*\
!*** ./src/util/ajax.js ***!
\**************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = ajax;
var _observer = _interopRequireDefault(__webpack_require__(/*! ./observer */ "./src/util/observer.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Perform an ajax request
*
* @param {Options} options Description
*
* @returns {Object} Observer instance
*/
function ajax(options) {
var instance = new _observer.default();
var xhr = new XMLHttpRequest();
var fired100 = false;
xhr.open(options.method || 'GET', options.url, true);
xhr.responseType = options.responseType || 'json';
if (options.xhr) {
if (options.xhr.requestHeaders) {
// add custom request headers
options.xhr.requestHeaders.forEach(function (header) {
xhr.setRequestHeader(header.key, header.value);
});
}
if (options.xhr.withCredentials) {
// use credentials
xhr.withCredentials = true;
}
}
xhr.addEventListener('progress', function (e) {
instance.fireEvent('progress', e);
if (e.lengthComputable && e.loaded == e.total) {
fired100 = true;
}
});
xhr.addEventListener('load', function (e) {
if (!fired100) {
instance.fireEvent('progress', e);
}
instance.fireEvent('load', e);
if (200 == xhr.status || 206 == xhr.status) {
instance.fireEvent('success', xhr.response, e);
} else {
instance.fireEvent('error', e);
}
});
xhr.addEventListener('error', function (e) {
return instance.fireEvent('error', e);
});
xhr.send();
instance.xhr = xhr;
return instance;
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/extend.js":
/*!****************************!*\
!*** ./src/util/extend.js ***!
\****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = extend;
/**
* Extend an object shallowly with others
*
* @param {Object} dest The target object
* @param {Object[]} sources The objects to use for extending
*
* @return {Object} Merged object
*/
function extend(dest) {
for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
sources[_key - 1] = arguments[_key];
}
sources.forEach(function (source) {
Object.keys(source).forEach(function (key) {
dest[key] = source[key];
});
});
return dest;
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/frame.js":
/*!***************************!*\
!*** ./src/util/frame.js ***!
\***************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = frame;
var _requestAnimationFrame = _interopRequireDefault(__webpack_require__(/*! ./request-animation-frame */ "./src/util/request-animation-frame.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Create a function which will be called at the next requestAnimationFrame
* cycle
*
* @param {function} func The function to call
*
* @return {func} The function wrapped within a requestAnimationFrame
*/
function frame(func) {
return function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return (0, _requestAnimationFrame.default)(function () {
return func.apply(void 0, args);
});
};
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/get-id.js":
/*!****************************!*\
!*** ./src/util/get-id.js ***!
\****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = getId;
/**
* Get a random prefixed ID
*
* @returns {String} Random ID
*/
function getId() {
return 'wavesurfer_' + Math.random().toString(32).substring(2);
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/index.js":
/*!***************************!*\
!*** ./src/util/index.js ***!
\***************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ajax", {
enumerable: true,
get: function get() {
return _ajax.default;
}
});
Object.defineProperty(exports, "getId", {
enumerable: true,
get: function get() {
return _getId.default;
}
});
Object.defineProperty(exports, "max", {
enumerable: true,
get: function get() {
return _max.default;
}
});
Object.defineProperty(exports, "min", {
enumerable: true,
get: function get() {
return _min.default;
}
});
Object.defineProperty(exports, "Observer", {
enumerable: true,
get: function get() {
return _observer.default;
}
});
Object.defineProperty(exports, "extend", {
enumerable: true,
get: function get() {
return _extend.default;
}
});
Object.defineProperty(exports, "style", {
enumerable: true,
get: function get() {
return _style.default;
}
});
Object.defineProperty(exports, "requestAnimationFrame", {
enumerable: true,
get: function get() {
return _requestAnimationFrame.default;
}
});
Object.defineProperty(exports, "frame", {
enumerable: true,
get: function get() {
return _frame.default;
}
});
Object.defineProperty(exports, "debounce", {
enumerable: true,
get: function get() {
return _debounce.default;
}
});
Object.defineProperty(exports, "preventClick", {
enumerable: true,
get: function get() {
return _preventClick.default;
}
});
var _ajax = _interopRequireDefault(__webpack_require__(/*! ./ajax */ "./src/util/ajax.js"));
var _getId = _interopRequireDefault(__webpack_require__(/*! ./get-id */ "./src/util/get-id.js"));
var _max = _interopRequireDefault(__webpack_require__(/*! ./max */ "./src/util/max.js"));
var _min = _interopRequireDefault(__webpack_require__(/*! ./min */ "./src/util/min.js"));
var _observer = _interopRequireDefault(__webpack_require__(/*! ./observer */ "./src/util/observer.js"));
var _extend = _interopRequireDefault(__webpack_require__(/*! ./extend */ "./src/util/extend.js"));
var _style = _interopRequireDefault(__webpack_require__(/*! ./style */ "./src/util/style.js"));
var _requestAnimationFrame = _interopRequireDefault(__webpack_require__(/*! ./request-animation-frame */ "./src/util/request-animation-frame.js"));
var _frame = _interopRequireDefault(__webpack_require__(/*! ./frame */ "./src/util/frame.js"));
var _debounce = _interopRequireDefault(__webpack_require__(/*! debounce */ "./node_modules/debounce/index.js"));
var _preventClick = _interopRequireDefault(__webpack_require__(/*! ./prevent-click */ "./src/util/prevent-click.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/***/ }),
/***/ "./src/util/max.js":
/*!*************************!*\
!*** ./src/util/max.js ***!
\*************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = max;
/**
* Get the largest value
*
* @param {Array} values Array of numbers
* @returns {Number} Largest number found
*/
function max(values) {
var largest = -Infinity;
Object.keys(values).forEach(function (i) {
if (values[i] > largest) {
largest = values[i];
}
});
return largest;
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/min.js":
/*!*************************!*\
!*** ./src/util/min.js ***!
\*************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = min;
/**
* Get the smallest value
*
* @param {Array} values Array of numbers
* @returns {Number} Smallest number found
*/
function min(values) {
var smallest = Number(Infinity);
Object.keys(values).forEach(function (i) {
if (values[i] < smallest) {
smallest = values[i];
}
});
return smallest;
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/observer.js":
/*!******************************!*\
!*** ./src/util/observer.js ***!
\******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
/**
* @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 =
/*#__PURE__*/
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;
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 = new 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 = new 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(void 0, args);
});
}
}]);
return Observer;
}();
exports.default = Observer;
module.exports = exports.default;
/***/ }),
/***/ "./src/util/prevent-click.js":
/*!***********************************!*\
!*** ./src/util/prevent-click.js ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = preventClick;
function preventClickHandler(e) {
e.stopPropagation();
document.body.removeEventListener('click', preventClickHandler, true);
}
function preventClick(values) {
document.body.addEventListener('click', preventClickHandler, true);
}
module.exports = exports.default;
/***/ }),
/***/ "./src/util/request-animation-frame.js":
/*!*********************************************!*\
!*** ./src/util/request-animation-frame.js ***!
\*********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
/**
* Returns the requestAnimationFrame function for the browser, or a shim with
* setTimeout if none is found
*
* @return {function}
*/
var _default = (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback, element) {
return setTimeout(callback, 1000 / 60);
}).bind(window);
exports.default = _default;
module.exports = exports.default;
/***/ }),
/***/ "./src/util/style.js":
/*!***************************!*\
!*** ./src/util/style.js ***!
\***************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = style;
/**
* Apply a map of styles to an element
*
* @param {HTMLElement} el The element that the styles will be applied to
* @param {Object} styles The map of propName: attribute, both are used as-is
*
* @return {HTMLElement} el
*/
function style(el, styles) {
Object.keys(styles).forEach(function (prop) {
if (el.style[prop] !== styles[prop]) {
el.style[prop] = styles[prop];
}
});
return el;
}
module.exports = exports.default;
/***/ }),
/***/ "./src/wavesurfer.js":
/*!***************************!*\
!*** ./src/wavesurfer.js ***!
\***************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var util = _interopRequireWildcard(__webpack_require__(/*! ./util */ "./src/util/index.js"));
var _drawer = _interopRequireDefault(__webpack_require__(/*! ./drawer.multicanvas */ "./src/drawer.multicanvas.js"));
var _webaudio = _interopRequireDefault(__webpack_require__(/*! ./webaudio */ "./src/webaudio.js"));
var _mediaelement = _interopRequireDefault(__webpack_require__(/*! ./mediaelement */ "./src/mediaelement.js"));
var _peakcache = _interopRequireDefault(__webpack_require__(/*! ./peakcache */ "./src/peakcache.js"));
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
/*
* 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 {ScriptProcessorNode} audioScriptProcessor=null Use your own previously
* initialized ScriptProcessorNode or leave blank.
* @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 separate 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 =
/*#__PURE__*/
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} params={} The plugin params (specific to the plugin)
* @param {Object} ws The wavesurfer instance
*/
}]);
function PluginClass(params, ws) {
_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 =
/*#__PURE__*/
function (_util$Observer) {
_inherits(WaveSurfer, _util$Observer);
_createClass(WaveSurfer, null, [{
key: "create",
/** @private */
/** @private */
/**
* Instantiate this class, call its `init` function and returns it
*
* @param {WavesurferParams} params
* @return {Object} WaveSurfer instance
* @example const wavesurfer = WaveSurfer.create(params);
*/
value: function create(params) {
var wavesurfer = new WaveSurfer(params);
return wavesurfer.init();
}
/**
* The library version number is available as a static property of the
* WaveSurfer class
*
* @type {String}
* @example
* console.log('Using wavesurfer.js ' + WaveSurfer.VERSION);
*/
}]);
/**
* Initialise wavesurfer instance
*
* @param {WavesurferParams} params Instantiation options for wavesurfer
* @example
* const wavesurfer = new WaveSurfer(params);
* @returns {this}
*/
function WaveSurfer(params) {
var _this;
_classCallCheck(this, WaveSurfer);
_this = _possibleConstructorReturn(this, _getPrototypeOf(WaveSurfer).call(this));
/**
* Extract relevant parameters (or defaults)
* @private
*/
_this.defaultParams = {
audioContext: null,
audioScriptProcessor: 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: _drawer.default,
responsive: false,
scrollParent: false,
skipLength: 2,
splitChannels: false,
waveColor: '#999',
xhr: {}
};
_this.backends = {
MediaElement: _mediaelement.default,
WebAudio: _webaudio.default
};
_this.util = util;
_this.params = util.extend({}, _this.defaultParams, params);
/** @private */
_this.container = 'string' == typeof params.container ? document.querySelector(_this.params.container) : _this.params.container;
if (!_this.container) {
throw new Error('Container element not found');
}
if (_this.params.mediaContainer == null) {
/** @private */
_this.mediaContainer = _this.container;
} else if (typeof _this.params.mediaContainer == 'string') {
/** @private */
_this.mediaContainer = document.querySelector(_this.params.mediaContainer);
} else {
/** @private */
_this.mediaContainer = _this.params.mediaContainer;
}
if (!_this.mediaContainer) {
throw new Error('Media Container element not found');
}
if (_this.params.maxCanvasWidth <= 1) {
throw new Error('maxCanvasWidth must be greater than 1');
} else if (_this.params.maxCanvasWidth % 2 == 1) {
throw new Error('maxCanvasWidth must be an even number');
}
/**
* @private Used to save the current volume when muting so we can
* restore once unmuted
* @type {number}
*/
_this.savedVolume = 0;
/**
* @private The current muted state
* @type {boolean}
*/
_this.isMuted = false;
/**
* @private Will hold a list of event descriptors that need to be
* canceled on subsequent loads of audio
* @type {Object[]}
*/
_this.tmpEvents = [];
/**
* @private Holds any running audio downloads
* @type {Observer}
*/
_this.currentAjax = null;
/** @private */
_this.arraybuffer = null;
/** @private */
_this.drawer = null;
/** @private */
_this.backend = null;
/** @private */
_this.peakCache = null; // cache constructor objects
if (typeof _this.params.renderer !== 'function') {
throw new Error('Renderer parameter is invalid');
}
/**
* @private The uninitialised Drawer class
*/
_this.Drawer = _this.params.renderer;
/**
* @private The uninitialised Backend class
*/
_this.Backend = _this.backends[_this.params.backend];
/**
* @private map of plugin names that are currently initialised
*/
_this.initialisedPluginList = {};
/** @private */
_this.isDestroyed = false;
/** @private */
_this.isReady = false; // responsive debounced event listener. If this.params.responsive is not
// set, this is never called. Use 100ms or this.params.responsive as
// timeout for the debounce function.
var prevWidth = 0;
_this._onResize = util.debounce(function () {
if (prevWidth != _this.drawer.wrapper.clientWidth && !_this.params.scrollParent) {
prevWidth = _this.drawer.wrapper.clientWidth;
_this.drawer.fireEvent('redraw');
}
}, typeof _this.params.responsive === 'number' ? _this.params.responsive : 100);
return _possibleConstructorReturn(_this, _assertThisInitialized(_assertThisInitialized(_this)));
}
/**
* Initialise the wave
*
* @example
* var wavesurfer = new WaveSurfer(params);
* wavesurfer.init();
* @return {this}
*/
_createClass(WaveSurfer, [{
key: "init",
value: function init() {
this.registerPlugins(this.params.plugins);
this.createDrawer();
this.createBackend();
this.createPeakCache();
return this;
}
/**
* Add and initialise array of plugins (if `plugin.deferInit` is falsey),
* this function is called in the init function of wavesurfer
*
* @param {PluginDefinition[]} plugins An array of plugin definitions
* @emits {WaveSurfer#plugins-registered} Called with the array of plugin definitions
* @return {this}
*/
}, {
key: "registerPlugins",
value: function registerPlugins(plugins) {
var _this2 = this;
// first instantiate all the plugins
plugins.forEach(function (plugin) {
return _this2.addPlugin(plugin);
}); // now run the init functions
plugins.forEach(function (plugin) {
// call init function of the plugin if deferInit is falsey
// in that case you would manually use initPlugins()
if (!plugin.deferInit) {
_this2.initPlugin(plugin.name);
}
});
this.fireEvent('plugins-registered', plugins);
return this;
}
/**
* Add a plugin object to wavesurfer
*
* @param {PluginDefinition} plugin A plugin definition
* @emits {WaveSurfer#plugin-added} Called with the name of the plugin that was added
* @example wavesurfer.addPlugin(WaveSurfer.minimap());
* @return {this}
*/
}, {
key: "addPlugin",
value: function addPlugin(plugin) {
var _this3 = this;
if (!plugin.name) {
throw new Error('Plugin does not have a name!');
}
if (!plugin.instance) {
throw new Error("Plugin ".concat(plugin.name, " does not have an instance property!"));
} // staticProps properties are applied to wavesurfer instance
if (plugin.staticProps) {
Object.keys(plugin.staticProps).forEach(function (pluginStaticProp) {
/**
* Properties defined in a plugin definition's `staticProps` property are added as
* staticProps properties of the WaveSurfer instance
*/
_this3[pluginStaticProp] = plugin.staticProps[pluginStaticProp];
});
}
var Instance = plugin.instance; // turn the plugin instance into an observer
var observerPrototypeKeys = Object.getOwnPropertyNames(util.Observer.prototype);
observerPrototypeKeys.forEach(function (key) {
Instance.prototype[key] = util.Observer.prototype[key];
});
/**
* Instantiated plugin classes are added as a property of the wavesurfer
* instance
* @type {Object}
*/
this[plugin.name] = new Instance(plugin.params || {}, this);
this.fireEvent('plugin-added', plugin.name);
return this;
}
/**
* Initialise a plugin
*
* @param {string} name A plugin name
* @emits WaveSurfer#plugin-initialised
* @example wavesurfer.initPlugin('minimap');
* @return {this}
*/
}, {
key: "initPlugin",
value: function initPlugin(name) {
if (!this[name]) {
throw new Error("Plugin ".concat(name, " has not been added yet!"));
}
if (this.initialisedPluginList[name]) {
// destroy any already initialised plugins
this.destroyPlugin(name);
}
this[name].init();
this.initialisedPluginList[name] = true;
this.fireEvent('plugin-initialised', name);
return this;
}
/**
* Destroy a plugin
*
* @param {string} name A plugin name
* @emits WaveSurfer#plugin-destroyed
* @example wavesurfer.destroyPlugin('minimap');
* @returns {this}
*/
}, {
key: "destroyPlugin",
value: function destroyPlugin(name) {
if (!this[name]) {
throw new Error("Plugin ".concat(name, " has not been added yet and cannot be destroyed!"));
}
if (!this.initialisedPluginList[name]) {
throw new Error("Plugin ".concat(name, " is not active and cannot be destroyed!"));
}
if (typeof this[name].destroy !== 'function') {
throw new Error("Plugin ".concat(name, " does not have a destroy function!"));
}
this[name].destroy();
delete this.initialisedPluginList[name];
this.fireEvent('plugin-destroyed', name);
return this;
}
/**
* Destroy all initialised plugins. Convenience function to use when
* wavesurfer is removed
*
* @private
*/
}, {
key: "destroyAllPlugins",
value: function destroyAllPlugins() {
var _this4 = this;
Object.keys(this.initialisedPluginList).forEach(function (name) {
return _this4.destroyPlugin(name);
});
}
/**
* Create the drawer and draw the waveform
*
* @private
* @emits WaveSurfer#drawer-created
*/
}, {
key: "createDrawer",
value: function createDrawer() {
var _this5 = this;
this.drawer = new this.Drawer(this.container, this.params);
this.drawer.init();
this.fireEvent('drawer-created', this.drawer);
if (this.params.responsive !== false) {
window.addEventListener('resize', this._onResize, true);
window.addEventListener('orientationchange', this._onResize, true);
}
this.drawer.on('redraw', function () {
_this5.drawBuffer();
_this5.drawer.progress(_this5.backend.getPlayedPercents());
}); // Click-to-seek
this.drawer.on('click', function (e, progress) {
setTimeout(function () {
return _this5.seekTo(progress);
}, 0);
}); // Relay the scroll event from the drawer
this.drawer.on('scroll', function (e) {
if (_this5.params.partialRender) {
_this5.drawBuffer();
}
_this5.fireEvent('scroll', e);
});
}
/**
* Create the backend
*
* @private
* @emits WaveSurfer#backend-created
*/
}, {
key: "createBackend",
value: function createBackend() {
var _this6 = this;
if (this.backend) {
this.backend.destroy();
} // Back compat
if (this.params.backend == 'AudioElement') {
this.params.backend = 'MediaElement';
}
if (this.params.backend == 'WebAudio' && !this.Backend.prototype.supportsWebAudio.call(null)) {
this.params.backend = 'MediaElement';
}
this.backend = new this.Backend(this.params);
this.backend.init();
this.fireEvent('backend-created', this.backend);
this.backend.on('finish', function () {
_this6.drawer.progress(_this6.backend.getPlayedPercents());
_this6.fireEvent('finish');
});
this.backend.on('play', function () {
return _this6.fireEvent('play');
});
this.backend.on('pause', function () {
return _this6.fireEvent('pause');
});
this.backend.on('audioprocess', function (time) {
_this6.drawer.progress(_this6.backend.getPlayedPercents());
_this6.fireEvent('audioprocess', time);
});
}
/**
* Create the peak cache
*
* @private
*/
}, {
key: "createPeakCache",
value: function createPeakCache() {
if (this.params.partialRender) {
this.peakCache = new _peakcache.default();
}
}
/**
* Get the duration of the audio clip
*
* @example const duration = wavesurfer.getDuration();
* @return {number} Duration in seconds
*/
}, {
key: "getDuration",
value: function getDuration() {
return this.backend.getDuration();
}
/**
* Get the current playback position
*
* @example const currentTime = wavesurfer.getCurrentTime();
* @return {number} Playback position in seconds
*/
}, {
key: "getCurrentTime",
value: function getCurrentTime() {
return this.backend.getCurrentTime();
}
/**
* Set the current play time in seconds.
*
* @param {number} seconds A positive number in seconds. E.g. 10 means 10
* seconds, 60 means 1 minute
*/
}, {
key: "setCurrentTime",
value: function setCurrentTime(seconds) {
if (seconds >= this.getDuration()) {
this.seekTo(1);
} else {
this.seekTo(seconds / this.getDuration());
}
}
/**
* Starts playback from the current position. Optional start and end
* measured in seconds can be used to set the range of audio to play.
*
* @param {?number} start Position to start at
* @param {?number} end Position to end at
* @emits WaveSurfer#interaction
* @return {Promise}
* @example
* // play from second 1 to 5
* wavesurfer.play(1, 5);
*/
}, {
key: "play",
value: function play(start, end) {
var _this7 = this;
this.fireEvent('interaction', function () {
return _this7.play(start, end);
});
return this.backend.play(start, end);
}
/**
* Stops playback
*
* @example wavesurfer.pause();
* @return {Promise}
*/
}, {
key: "pause",
value: function pause() {
if (!this.backend.isPaused()) {
return this.backend.pause();
}
}
/**
* Toggle playback
*
* @example wavesurfer.playPause();
* @return {Promise}
*/
}, {
key: "playPause",
value: function playPause() {
return this.backend.isPaused() ? this.play() : this.pause();
}
/**
* Get the current playback state
*
* @example const isPlaying = wavesurfer.isPlaying();
* @return {boolean} False if paused, true if playing
*/
}, {
key: "isPlaying",
value: function isPlaying() {
return !this.backend.isPaused();
}
/**
* Skip backward
*
* @param {?number} seconds Amount to skip back, if not specified `skipLength`
* is used
* @example wavesurfer.skipBackward();
*/
}, {
key: "skipBackward",
value: function skipBackward(seconds) {
this.skip(-seconds || -this.params.skipLength);
}
/**
* Skip forward
*
* @param {?number} seconds Amount to skip back, if not specified `skipLength`
* is used
* @example wavesurfer.skipForward();
*/
}, {
key: "skipForward",
value: function skipForward(seconds) {
this.skip(seconds || this.params.skipLength);
}
/**
* Skip a number of seconds from the current position (use a negative value
* to go backwards).
*
* @param {number} offset Amount to skip back or forwards
* @example
* // go back 2 seconds
* wavesurfer.skip(-2);
*/
}, {
key: "skip",
value: function skip(offset) {
var duration = this.getDuration() || 1;
var position = this.getCurrentTime() || 0;
position = Math.max(0, Math.min(duration, position + (offset || 0)));
this.seekAndCenter(position / duration);
}
/**
* Seeks to a position and centers the view
*
* @param {number} progress Between 0 (=beginning) and 1 (=end)
* @example
* // seek and go to the middle of the audio
* wavesurfer.seekTo(0.5);
*/
}, {
key: "seekAndCenter",
value: function seekAndCenter(progress) {
this.seekTo(progress);
this.drawer.recenter(progress);
}
/**
* Seeks to a position
*
* @param {number} progress Between 0 (=beginning) and 1 (=end)
* @emits WaveSurfer#interaction
* @emits WaveSurfer#seek
* @example
* // seek to the middle of the audio
* wavesurfer.seekTo(0.5);
*/
}, {
key: "seekTo",
value: function seekTo(progress) {
var _this8 = this;
// return an error if progress is not a number between 0 and 1
if (typeof progress !== 'number' || !isFinite(progress) || progress < 0 || progress > 1) {
return console.error('Error calling wavesurfer.seekTo, parameter must be a number between 0 and 1!');
}
this.fireEvent('interaction', function () {
return _this8.seekTo(progress);
});
var paused = this.backend.isPaused(); // avoid draw wrong position while playing backward seeking
if (!paused) {
this.backend.pause();
} // avoid small scrolls while paused seeking
var oldScrollParent = this.params.scrollParent;
this.params.scrollParent = false;
this.backend.seekTo(progress * this.getDuration());
this.drawer.progress(progress);
if (!paused) {
this.backend.play();
}
this.params.scrollParent = oldScrollParent;
this.fireEvent('seek', progress);
}
/**
* Stops and goes to the beginning.
*
* @example wavesurfer.stop();
*/
}, {
key: "stop",
value: function stop() {
this.pause();
this.seekTo(0);
this.drawer.progress(0);
}
/**
* Set the playback volume.
*
* @param {string} deviceId String value representing underlying output device
*/
}, {
key: "setSinkId",
value: function setSinkId(deviceId) {
return this.backend.setSinkId(deviceId);
}
/**
* Set the playback volume.
*
* @param {number} newVolume A value between 0 and 1, 0 being no
* volume and 1 being full volume.
* @emits WaveSurfer#volume
*/
}, {
key: "setVolume",
value: function setVolume(newVolume) {
this.backend.setVolume(newVolume);
this.fireEvent('volume', newVolume);
}
/**
* Get the playback volume.
*
* @return {number} A value between 0 and 1, 0 being no
* volume and 1 being full volume.
*/
}, {
key: "getVolume",
value: function getVolume() {
return this.backend.getVolume();
}
/**
* Set the playback rate.
*
* @param {number} rate A positive number. E.g. 0.5 means half the normal
* speed, 2 means double speed and so on.
* @example wavesurfer.setPlaybackRate(2);
*/
}, {
key: "setPlaybackRate",
value: function setPlaybackRate(rate) {
this.backend.setPlaybackRate(rate);
}
/**
* Get the playback rate.
*
* @return {number}
*/
}, {
key: "getPlaybackRate",
value: function getPlaybackRate() {
return this.backend.getPlaybackRate();
}
/**
* Toggle the volume on and off. If not currently muted it will save the
* current volume value and turn the volume off. If currently muted then it
* will restore the volume to the saved value, and then rest the saved
* value.
*
* @example wavesurfer.toggleMute();
*/
}, {
key: "toggleMute",
value: function toggleMute() {
this.setMute(!this.isMuted);
}
/**
* Enable or disable muted audio
*
* @param {boolean} mute
* @emits WaveSurfer#volume
* @emits WaveSurfer#mute
* @example
* // unmute
* wavesurfer.setMute(false);
*/
}, {
key: "setMute",
value: function setMute(mute) {
// ignore all muting requests if the audio is already in that state
if (mute === this.isMuted) {
this.fireEvent('mute', this.isMuted);
return;
}
if (mute) {
// If currently not muted then save current volume,
// turn off the volume and update the mute properties
this.savedVolume = this.backend.getVolume();
this.backend.setVolume(0);
this.isMuted = true;
this.fireEvent('volume', 0);
} else {
// If currently muted then restore to the saved volume
// and update the mute properties
this.backend.setVolume(this.savedVolume);
this.isMuted = false;
this.fireEvent('volume', this.savedVolume);
}
this.fireEvent('mute', this.isMuted);
}
/**
* Get the current mute status.
*
* @example const isMuted = wavesurfer.getMute();
* @return {boolean}
*/
}, {
key: "getMute",
value: function getMute() {
return this.isMuted;
}
/**
* Get the current ready status.
*
* @example const isReady = wavesurfer.isReady();
* @return {boolean}
*/
}, {
key: "isReady",
value: function isReady() {
return this.isReady;
}
/**
* Get the list of current set filters as an array.
*
* Filters must be set with setFilters method first
*
* @return {array}
*/
}, {
key: "getFilters",
value: function getFilters() {
return this.backend.filters || [];
}
/**
* Toggles `scrollParent` and redraws
*
* @example wavesurfer.toggleScroll();
*/
}, {
key: "toggleScroll",
value: function toggleScroll() {
this.params.scrollParent = !this.params.scrollParent;
this.drawBuffer();
}
/**
* Toggle mouse interaction
*
* @example wavesurfer.toggleInteraction();
*/
}, {
key: "toggleInteraction",
value: function toggleInteraction() {
this.params.interact = !this.params.interact;
}
/**
* Get the fill color of the waveform after the cursor.
*
* @return {string} A CSS color string.
*/
}, {
key: "getWaveColor",
value: function getWaveColor() {
return this.params.waveColor;
}
/**
* Set the fill color of the waveform after the cursor.
*
* @param {string} color A CSS color string.
* @example wavesurfer.setWaveColor('#ddd');
*/
}, {
key: "setWaveColor",
value: function setWaveColor(color) {
this.params.waveColor = color;
this.drawBuffer();
}
/**
* Get the fill color of the waveform behind the cursor.
*
* @return {string} A CSS color string.
*/
}, {
key: "getProgressColor",
value: function getProgressColor() {
return this.params.progressColor;
}
/**
* Set the fill color of the waveform behind the cursor.
*
* @param {string} color A CSS color string.
* @example wavesurfer.setProgressColor('#400');
*/
}, {
key: "setProgressColor",
value: function setProgressColor(color) {
this.params.progressColor = color;
this.drawBuffer();
}
/**
* Get the fill color of the cursor indicating the playhead
* position.
*
* @return {string} A CSS color string.
*/
}, {
key: "getCursorColor",
value: function getCursorColor() {
return this.params.cursorColor;
}
/**
* Set the fill color of the cursor indicating the playhead
* position.
*
* @param {string} color A CSS color string.
* @example wavesurfer.setCursorColor('#222');
*/
}, {
key: "setCursorColor",
value: function setCursorColor(color) {
this.params.cursorColor = color;
this.drawer.updateCursor();
}
/**
* Get the height of the waveform.
*
* @return {number} Height measured in pixels.
*/
}, {
key: "getHeight",
value: function getHeight() {
return this.params.height;
}
/**
* Set the height of the waveform.
*
* @param {number} height Height measured in pixels.
* @example wavesurfer.setHeight(200);
*/
}, {
key: "setHeight",
value: function setHeight(height) {
this.params.height = height;
this.drawer.setHeight(height * this.params.pixelRatio);
this.drawBuffer();
}
/**
* Get the correct peaks for current wave view-port and render wave
*
* @private
* @emits WaveSurfer#redraw
*/
}, {
key: "drawBuffer",
value: function drawBuffer() {
var nominalWidth = Math.round(this.getDuration() * this.params.minPxPerSec * this.params.pixelRatio);
var parentWidth = this.drawer.getWidth();
var width = nominalWidth;
var start = this.drawer.getScrollX();
var end = Math.max(start + parentWidth, width); // Fill container
if (this.params.fillParent && (!this.params.scrollParent || nominalWidth < parentWidth)) {
width = parentWidth;
start = 0;
end = width;
}
var peaks;
if (this.params.partialRender) {
var newRanges = this.peakCache.addRangeToPeakCache(width, start, end);
var i;
for (i = 0; i < newRanges.length; i++) {
peaks = this.backend.getPeaks(width, newRanges[i][0], newRanges[i][1]);
this.drawer.drawPeaks(peaks, width, newRanges[i][0], newRanges[i][1]);
}
} else {
peaks = this.backend.getPeaks(width, start, end);
this.drawer.drawPeaks(peaks, width, start, end);
}
this.fireEvent('redraw', peaks, width);
}
/**
* Horizontally zooms the waveform in and out. It also changes the parameter
* `minPxPerSec` and enables the `scrollParent` option. Calling the function
* with a falsey parameter will reset the zoom state.
*
* @param {?number} pxPerSec Number of horizontal pixels per second of
* audio, if none is set the waveform returns to unzoomed state
* @emits WaveSurfer#zoom
* @example wavesurfer.zoom(20);
*/
}, {
key: "zoom",
value: function zoom(pxPerSec) {
if (!pxPerSec) {
this.params.minPxPerSec = this.defaultParams.minPxPerSec;
this.params.scrollParent = false;
} else {
this.params.minPxPerSec = pxPerSec;
this.params.scrollParent = true;
}
this.drawBuffer();
this.drawer.progress(this.backend.getPlayedPercents());
this.drawer.recenter(this.getCurrentTime() / this.getDuration());
this.fireEvent('zoom', pxPerSec);
}
/**
* Decode buffer and load
*
* @private
* @param {ArrayBuffer} arraybuffer
*/
}, {
key: "loadArrayBuffer",
value: function loadArrayBuffer(arraybuffer) {
var _this9 = this;
this.decodeArrayBuffer(arraybuffer, function (data) {
if (!_this9.isDestroyed) {
_this9.loadDecodedBuffer(data);
}
});
}
/**
* Directly load an externally decoded AudioBuffer
*
* @private
* @param {AudioBuffer} buffer
* @emits WaveSurfer#ready
*/
}, {
key: "loadDecodedBuffer",
value: function loadDecodedBuffer(buffer) {
this.backend.load(buffer);
this.drawBuffer();
this.fireEvent('ready');
this.isReady = true;
}
/**
* Loads audio data from a Blob or File object
*
* @param {Blob|File} blob Audio data
* @example
*/
}, {
key: "loadBlob",
value: function loadBlob(blob) {
var _this10 = this;
// Create file reader
var reader = new FileReader();
reader.addEventListener('progress', function (e) {
return _this10.onProgress(e);
});
reader.addEventListener('load', function (e) {
return _this10.loadArrayBuffer(e.target.result);
});
reader.addEventListener('error', function () {
return _this10.fireEvent('error', 'Error reading file');
});
reader.readAsArrayBuffer(blob);
this.empty();
}
/**
* Loads audio and re-renders the waveform.
*
* @param {string|HTMLMediaElement} url The url of the audio file or the
* audio element with the audio
* @param {?number[]|number[][]} peaks Wavesurfer does not have to decode
* the audio to render the waveform if this is specified
* @param {?string} preload (Use with backend `MediaElement`)
* `'none'|'metadata'|'auto'` Preload attribute for the media element
* @param {?number} duration The duration of the audio. This is used to
* render the peaks data in the correct size for the audio duration (as
* befits the current minPxPerSec and zoom value) without having to decode
* the audio.
* @example
* // using ajax or media element to load (depending on backend)
* wavesurfer.load('http://example.com/demo.wav');
*
* // setting preload attribute with media element backend and supplying
* peaks wavesurfer.load(
* 'http://example.com/demo.wav',
* [0.0218, 0.0183, 0.0165, 0.0198, 0.2137, 0.2888],
* true,
* );
*/
}, {
key: "load",
value: function load(url, peaks, preload, duration) {
this.empty();
if (preload) {
// check whether the preload attribute will be usable and if not log
// a warning listing the reasons why not and nullify the variable
var preloadIgnoreReasons = {
"Preload is not 'auto', 'none' or 'metadata'": ['auto', 'metadata', 'none'].indexOf(preload) === -1,
'Peaks are not provided': !peaks,
'Backend is not of type MediaElement': this.params.backend !== 'MediaElement',
'Url is not of type string': typeof url !== 'string'
};
var activeReasons = Object.keys(preloadIgnoreReasons).filter(function (reason) {
return preloadIgnoreReasons[reason];
});
if (activeReasons.length) {
console.warn('Preload parameter of wavesurfer.load will be ignored because:\n\t- ' + activeReasons.join('\n\t- ')); // stop invalid values from being used
preload = null;
}
}
switch (this.params.backend) {
case 'WebAudio':
return this.loadBuffer(url, peaks, duration);
case 'MediaElement':
return this.loadMediaElement(url, peaks, preload, duration);
}
}
/**
* Loads audio using Web Audio buffer backend.
*
* @private
* @param {string} url
* @param {?number[]|number[][]} peaks
* @param {?number} duration
*/
}, {
key: "loadBuffer",
value: function loadBuffer(url, peaks, duration) {
var _this11 = this;
var load = function load(action) {
if (action) {
_this11.tmpEvents.push(_this11.once('ready', action));
}
return _this11.getArrayBuffer(url, function (data) {
return _this11.loadArrayBuffer(data);
});
};
if (peaks) {
this.backend.setPeaks(peaks, duration);
this.drawBuffer();
this.tmpEvents.push(this.once('interaction', load));
} else {
return load();
}
}
/**
* Either create a media element, or load an existing media element.
*
* @private
* @param {string|HTMLMediaElement} urlOrElt Either a path to a media file, or an
* existing HTML5 Audio/Video Element
* @param {number[]|number[][]} peaks Array of peaks. Required to bypass web audio
* dependency
* @param {?boolean} preload Set to true if the preload attribute of the
* audio element should be enabled
* @param {?number} duration
*/
}, {
key: "loadMediaElement",
value: function loadMediaElement(urlOrElt, peaks, preload, duration) {
var _this12 = this;
var url = urlOrElt;
if (typeof urlOrElt === 'string') {
this.backend.load(url, this.mediaContainer, peaks, preload);
} else {
var elt = urlOrElt;
this.backend.loadElt(elt, peaks); // If peaks are not provided,
// url = element.src so we can get peaks with web audio
url = elt.src;
}
this.tmpEvents.push(this.backend.once('canplay', function () {
_this12.drawBuffer();
_this12.fireEvent('ready');
_this12.isReady = true;
}), this.backend.once('error', function (err) {
return _this12.fireEvent('error', err);
})); // If no pre-decoded peaks provided or pre-decoded peaks are
// provided with forceDecode flag, attempt to download the
// audio file and decode it with Web Audio.
if (peaks) {
this.backend.setPeaks(peaks, duration);
}
if ((!peaks || this.params.forceDecode) && this.backend.supportsWebAudio()) {
this.getArrayBuffer(url, function (arraybuffer) {
_this12.decodeArrayBuffer(arraybuffer, function (buffer) {
_this12.backend.buffer = buffer;
_this12.backend.setPeaks(null);
_this12.drawBuffer();
_this12.fireEvent('waveform-ready');
});
});
}
}
/**
* Decode an array buffer and pass data to a callback
*
* @private
* @param {Object} arraybuffer
* @param {function} callback
*/
}, {
key: "decodeArrayBuffer",
value: function decodeArrayBuffer(arraybuffer, callback) {
var _this13 = this;
this.arraybuffer = arraybuffer;
this.backend.decodeArrayBuffer(arraybuffer, function (data) {
// Only use the decoded data if we haven't been destroyed or
// another decode started in the meantime
if (!_this13.isDestroyed && _this13.arraybuffer == arraybuffer) {
callback(data);
_this13.arraybuffer = null;
}
}, function () {
return _this13.fireEvent('error', 'Error decoding audiobuffer');
});
}
/**
* Load an array buffer by ajax and pass to a callback
*
* @param {string} url
* @param {function} callback
* @private
*/
}, {
key: "getArrayBuffer",
value: function getArrayBuffer(url, callback) {
var _this14 = this;
var ajax = util.ajax({
url: url,
responseType: 'arraybuffer',
xhr: this.params.xhr
});
this.currentAjax = ajax;
this.tmpEvents.push(ajax.on('progress', function (e) {
_this14.onProgress(e);
}), ajax.on('success', function (data, e) {
callback(data);
_this14.currentAjax = null;
}), ajax.on('error', function (e) {
_this14.fireEvent('error', 'XHR error: ' + e.target.statusText);
_this14.currentAjax = null;
}));
return ajax;
}
/**
* Called while the audio file is loading
*
* @private
* @param {Event} e
* @emits WaveSurfer#loading
*/
}, {
key: "onProgress",
value: function onProgress(e) {
var percentComplete;
if (e.lengthComputable) {
percentComplete = e.loaded / e.total;
} else {
// Approximate progress with an asymptotic
// function, and assume downloads in the 1-3 MB range.
percentComplete = e.loaded / (e.loaded + 1000000);
}
this.fireEvent('loading', Math.round(percentComplete * 100), e.target);
}
/**
* Exports PCM data into a JSON array and opens in a new window.
*
* @param {number} length=1024 The scale in which to export the peaks. (Integer)
* @param {number} accuracy=10000 (Integer)
* @param {?boolean} noWindow Set to true to disable opening a new
* window with the JSON
* @param {number} start
* @todo Update exportPCM to work with new getPeaks signature
* @return {string} JSON of peaks
*/
}, {
key: "exportPCM",
value: function exportPCM(length, accuracy, noWindow, start) {
length = length || 1024;
start = start || 0;
accuracy = accuracy || 10000;
noWindow = noWindow || false;
var peaks = this.backend.getPeaks(length, start);
var arr = [].map.call(peaks, function (val) {
return Math.round(val * accuracy) / accuracy;
});
var json = JSON.stringify(arr);
if (!noWindow) {
window.open('data:application/json;charset=utf-8,' + encodeURIComponent(json));
}
return json;
}
/**
* Save waveform image as data URI.
*
* The default format is `image/png`. Other supported types are
* `image/jpeg` and `image/webp`.
*
* @param {string} format='image/png'
* @param {number} quality=1
* @return {string} data URI of image
*/
}, {
key: "exportImage",
value: function exportImage(format, quality) {
if (!format) {
format = 'image/png';
}
if (!quality) {
quality = 1;
}
return this.drawer.getImage(format, quality);
}
/**
* Cancel any ajax request currently in progress
*/
}, {
key: "cancelAjax",
value: function cancelAjax() {
if (this.currentAjax) {
this.currentAjax.xhr.abort();
this.currentAjax = null;
}
}
/**
* @private
*/
}, {
key: "clearTmpEvents",
value: function clearTmpEvents() {
this.tmpEvents.forEach(function (e) {
return e.un();
});
}
/**
* Display empty waveform.
*/
}, {
key: "empty",
value: function empty() {
if (!this.backend.isPaused()) {
this.stop();
this.backend.disconnectSource();
}
this.isReady = false;
this.cancelAjax();
this.clearTmpEvents();
this.drawer.progress(0);
this.drawer.setWidth(0);
this.drawer.drawPeaks({
length: this.drawer.getWidth()
}, 0);
}
/**
* Remove events, elements and disconnect WebAudio nodes.
*
* @emits WaveSurfer#destroy
*/
}, {
key: "destroy",
value: function destroy() {
this.destroyAllPlugins();
this.fireEvent('destroy');
this.cancelAjax();
this.clearTmpEvents();
this.unAll();
if (this.params.responsive !== false) {
window.removeEventListener('resize', this._onResize, true);
window.removeEventListener('orientationchange', this._onResize, true);
}
this.backend.destroy();
this.drawer.destroy();
this.isDestroyed = true;
this.isReady = false;
this.arraybuffer = null;
}
}]);
return WaveSurfer;
}(util.Observer);
exports.default = WaveSurfer;
WaveSurfer.VERSION = "2.1.1";
WaveSurfer.util = util;
module.exports = exports.default;
/***/ }),
/***/ "./src/webaudio.js":
/*!*************************!*\
!*** ./src/webaudio.js ***!
\*************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var util = _interopRequireWildcard(__webpack_require__(/*! ./util */ "./src/util/index.js"));
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
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 (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
// using constants to prevent someone writing the string wrong
var PLAYING = 'playing';
var PAUSED = 'paused';
var FINISHED = 'finished';
/**
* WebAudio backend
*
* @extends {Observer}
*/
var WebAudio =
/*#__PURE__*/
function (_util$Observer) {
_inherits(WebAudio, _util$Observer);
_createClass(WebAudio, [{
key: "supportsWebAudio",
/** @private */
/** @private */
/** @private */
/** @private */
/**
* Does the browser support this backend
*
* @return {boolean}
*/
value: function supportsWebAudio() {
return !!(window.AudioContext || window.webkitAudioContext);
}
/**
* Get the audio context used by this backend or create one
*
* @return {AudioContext}
*/
}, {
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;
var _this;
_classCallCheck(this, WebAudio);
_this = _possibleConstructorReturn(this, _getPrototypeOf(WebAudio).call(this));
/** @private */
_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 = new 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.params.audioScriptProcessor) {
this.scriptNode = this.params.audioScriptProcessor;
} else {
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.srcObject = 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 ? 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;
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;
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 audio-clip'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);
exports.default = WebAudio;
WebAudio.scriptBufferSize = 256;
module.exports = exports.default;
/***/ })
/******/ });
});
//# sourceMappingURL=wavesurfer.js.map