Catalog-Digitization / src /app /static /assets /js /wavesurfer.js
gamingflexer
file static
4fbc5a4
raw
history blame
471 kB
/*!
* wavesurfer.js 5.1.0 (2021-06-20)
* https://wavesurfer-js.org
* @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();
})(this, function () {
return /******/ (() => {
// webpackBootstrap
/******/ var __webpack_modules__ = {
/***/ "./src/drawer.canvasentry.js":
/*!***********************************!*\
!*** ./src/drawer.canvasentry.js ***!
\***********************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _style = _interopRequireDefault(
__webpack_require__(
/*! ./util/style */ "./src/util/style.js"
)
);
var _getId = _interopRequireDefault(
__webpack_require__(
/*! ./util/get-id */ "./src/util/get-id.js"
)
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: 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;
}
/**
* The `CanvasEntry` class represents an element consisting of a wave `canvas`
* and an (optional) progress wave `canvas`.
*
* The `MultiCanvas` renderer uses one or more `CanvasEntry` instances to
* render a waveform, depending on the zoom level.
*/
var CanvasEntry = /*#__PURE__*/ (function () {
function CanvasEntry() {
_classCallCheck(this, CanvasEntry);
/**
* The wave node
*
* @type {HTMLCanvasElement}
*/
this.wave = null;
/**
* The wave canvas rendering context
*
* @type {CanvasRenderingContext2D}
*/
this.waveCtx = null;
/**
* The (optional) progress wave node
*
* @type {HTMLCanvasElement}
*/
this.progress = null;
/**
* The (optional) progress wave canvas rendering context
*
* @type {CanvasRenderingContext2D}
*/
this.progressCtx = null;
/**
* Start of the area the canvas should render, between 0 and 1
*
* @type {number}
*/
this.start = 0;
/**
* End of the area the canvas should render, between 0 and 1
*
* @type {number}
*/
this.end = 1;
/**
* Unique identifier for this entry
*
* @type {string}
*/
this.id = (0, _getId.default)(
typeof this.constructor.name !== "undefined"
? this.constructor.name.toLowerCase() + "_"
: "canvasentry_"
);
/**
* Canvas 2d context attributes
*
* @type {object}
*/
this.canvasContextAttributes = {};
}
/**
* Store the wave canvas element and create the 2D rendering context
*
* @param {HTMLCanvasElement} element The wave `canvas` element.
*/
_createClass(CanvasEntry, [
{
key: "initWave",
value: function initWave(element) {
this.wave = element;
this.waveCtx = this.wave.getContext(
"2d",
this.canvasContextAttributes
);
},
/**
* Store the progress wave canvas element and create the 2D rendering
* context
*
* @param {HTMLCanvasElement} element The progress wave `canvas` element.
*/
},
{
key: "initProgress",
value: function initProgress(element) {
this.progress = element;
this.progressCtx = this.progress.getContext(
"2d",
this.canvasContextAttributes
);
},
/**
* Update the dimensions
*
* @param {number} elementWidth Width of the entry
* @param {number} totalWidth Total width of the multi canvas renderer
* @param {number} width The new width of the element
* @param {number} height The new height of the element
*/
},
{
key: "updateDimensions",
value: function updateDimensions(
elementWidth,
totalWidth,
width,
height
) {
// where the canvas starts and ends in the waveform, represented as a
// decimal between 0 and 1
this.start =
this.wave.offsetLeft / totalWidth || 0;
this.end =
this.start + elementWidth / totalWidth; // set wave canvas dimensions
this.wave.width = width;
this.wave.height = height;
var elementSize = {
width: elementWidth + "px",
};
(0, _style.default)(this.wave, elementSize);
if (this.hasProgressCanvas) {
// set progress canvas dimensions
this.progress.width = width;
this.progress.height = height;
(0, _style.default)(
this.progress,
elementSize
);
}
},
/**
* Clear the wave and progress rendering contexts
*/
},
{
key: "clearWave",
value: function clearWave() {
// wave
this.waveCtx.clearRect(
0,
0,
this.waveCtx.canvas.width,
this.waveCtx.canvas.height
); // progress
if (this.hasProgressCanvas) {
this.progressCtx.clearRect(
0,
0,
this.progressCtx.canvas.width,
this.progressCtx.canvas.height
);
}
},
/**
* Set the fill styles for wave and progress
*
* @param {string} waveColor Fill color for the wave canvas
* @param {?string} progressColor Fill color for the progress canvas
*/
},
{
key: "setFillStyles",
value: function setFillStyles(
waveColor,
progressColor
) {
this.waveCtx.fillStyle = waveColor;
if (this.hasProgressCanvas) {
this.progressCtx.fillStyle =
progressColor;
}
},
/**
* Set the canvas transforms for wave and progress
*
* @param {boolean} vertical Whether to render vertically
*/
},
{
key: "applyCanvasTransforms",
value: function applyCanvasTransforms(
vertical
) {
if (vertical) {
// Reflect the waveform across the line y = -x
this.waveCtx.setTransform(
0,
1,
1,
0,
0,
0
);
if (this.hasProgressCanvas) {
this.progressCtx.setTransform(
0,
1,
1,
0,
0,
0
);
}
}
},
/**
* Draw a rectangle for wave and progress
*
* @param {number} x X start position
* @param {number} y Y start position
* @param {number} width Width of the rectangle
* @param {number} height Height of the rectangle
* @param {number} radius Radius of the rectangle
*/
},
{
key: "fillRects",
value: function fillRects(
x,
y,
width,
height,
radius
) {
this.fillRectToContext(
this.waveCtx,
x,
y,
width,
height,
radius
);
if (this.hasProgressCanvas) {
this.fillRectToContext(
this.progressCtx,
x,
y,
width,
height,
radius
);
}
},
/**
* Draw the actual rectangle on a `canvas` element
*
* @param {CanvasRenderingContext2D} ctx Rendering context of target canvas
* @param {number} x X start position
* @param {number} y Y start position
* @param {number} width Width of the rectangle
* @param {number} height Height of the rectangle
* @param {number} radius Radius of the rectangle
*/
},
{
key: "fillRectToContext",
value: function fillRectToContext(
ctx,
x,
y,
width,
height,
radius
) {
if (!ctx) {
return;
}
if (radius) {
this.drawRoundedRect(
ctx,
x,
y,
width,
height,
radius
);
} else {
ctx.fillRect(x, y, width, height);
}
},
/**
* Draw a rounded rectangle on Canvas
*
* @param {CanvasRenderingContext2D} ctx Canvas context
* @param {number} x X-position of the rectangle
* @param {number} y Y-position of the rectangle
* @param {number} width Width of the rectangle
* @param {number} height Height of the rectangle
* @param {number} radius Radius of the rectangle
*
* @return {void}
* @example drawRoundedRect(ctx, 50, 50, 5, 10, 3)
*/
},
{
key: "drawRoundedRect",
value: function drawRoundedRect(
ctx,
x,
y,
width,
height,
radius
) {
if (height === 0) {
return;
} // peaks are float values from -1 to 1. Use absolute height values in
// order to correctly calculate rounded rectangle coordinates
if (height < 0) {
height *= -1;
y -= height;
}
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(
x + width,
y,
x + width,
y + radius
);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(
x + width,
y + height,
x + width - radius,
y + height
);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(
x,
y + height,
x,
y + height - radius
);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
},
/**
* Render the actual wave and progress lines
*
* @param {number[]} peaks Array with peaks 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: "drawLines",
value: function drawLines(
peaks,
absmax,
halfH,
offsetY,
start,
end
) {
this.drawLineToContext(
this.waveCtx,
peaks,
absmax,
halfH,
offsetY,
start,
end
);
if (this.hasProgressCanvas) {
this.drawLineToContext(
this.progressCtx,
peaks,
absmax,
halfH,
offsetY,
start,
end
);
}
},
/**
* Render the actual waveform line on a `canvas` element
*
* @param {CanvasRenderingContext2D} ctx Rendering context of target canvas
* @param {number[]} peaks Array with peaks 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: "drawLineToContext",
value: function drawLineToContext(
ctx,
peaks,
absmax,
halfH,
offsetY,
start,
end
) {
if (!ctx) {
return;
}
var length = peaks.length / 2;
var first = Math.round(length * this.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 * this.end) + 1;
var canvasStart = first;
var canvasEnd = last;
var scale =
this.wave.width /
(canvasEnd - canvasStart - 1); // optimization
var halfOffset = halfH + offsetY;
var absmaxHalf = absmax / halfH;
ctx.beginPath();
ctx.moveTo(
(canvasStart - first) * scale,
halfOffset
);
ctx.lineTo(
(canvasStart - first) * scale,
halfOffset -
Math.round(
(peaks[2 * canvasStart] || 0) /
absmaxHalf
)
);
var i, peak, h;
for (i = canvasStart; i < canvasEnd; i++) {
peak = peaks[2 * i] || 0;
h = Math.round(peak / absmaxHalf);
ctx.lineTo(
(i - first) * scale +
this.halfPixel,
halfOffset - h
);
} // draw the bottom edge going backwards, to make a single
// closed hull to fill
var j = canvasEnd - 1;
for (j; j >= canvasStart; j--) {
peak = peaks[2 * j + 1] || 0;
h = Math.round(peak / absmaxHalf);
ctx.lineTo(
(j - first) * scale +
this.halfPixel,
halfOffset - h
);
}
ctx.lineTo(
(canvasStart - first) * scale,
halfOffset -
Math.round(
(peaks[2 * canvasStart + 1] ||
0) / absmaxHalf
)
);
ctx.closePath();
ctx.fill();
},
/**
* Destroys this entry
*/
},
{
key: "destroy",
value: function destroy() {
this.waveCtx = null;
this.wave = null;
this.progressCtx = null;
this.progress = null;
},
/**
* Return image data of the wave `canvas` element
*
* When using a `type` of `'blob'`, this will return a `Promise` that
* resolves with a `Blob` instance.
*
* @param {string} format='image/png' An optional value of a format type.
* @param {number} quality=0.92 An optional value between 0 and 1.
* @param {string} type='dataURL' Either 'dataURL' or 'blob'.
* @return {string|Promise} When using the default `'dataURL'` `type` this
* returns a data URL. When using the `'blob'` `type` this returns a
* `Promise` that resolves with a `Blob` instance.
*/
},
{
key: "getImage",
value: function getImage(
format,
quality,
type
) {
var _this = this;
if (type === "blob") {
return new Promise(function (resolve) {
_this.wave.toBlob(
resolve,
format,
quality
);
});
} else if (type === "dataURL") {
return this.wave.toDataURL(
format,
quality
);
}
},
},
]);
return CanvasEntry;
})();
exports.default = CanvasEntry;
module.exports = exports.default;
/***/
},
/***/ "./src/drawer.js":
/*!***********************!*\
!*** ./src/drawer.js ***!
\***********************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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);
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var util = _interopRequireWildcard(
__webpack_require__(/*! ./util */ "./src/util/index.js")
);
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache =
function _getRequireWildcardCache(nodeInterop) {
return nodeInterop
? cacheNodeInterop
: cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (
obj === null ||
(_typeof(obj) !== "object" &&
typeof obj !== "function")
) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty &&
Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (
key !== "default" &&
Object.prototype.hasOwnProperty.call(obj, key)
) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
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 _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 _createSuper(Derived) {
var hasNativeReflectConstruct =
_isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget =
_getPrototypeOf(this).constructor;
result = Reflect.construct(
Super,
arguments,
NewTarget
);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (
typeof Reflect === "undefined" ||
!Reflect.construct
)
return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return (
o.__proto__ || Object.getPrototypeOf(o)
);
};
return _getPrototypeOf(o);
}
/**
* Parent class for renderers
*
* @extends {Observer}
*/
var Drawer = /*#__PURE__*/ (function (_util$Observer) {
_inherits(Drawer, _util$Observer);
var _super = _createSuper(Drawer);
/**
* @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 = _super.call(this);
_this.container = util.withOrientation(
container,
params.vertical
);
/**
* @type {WavesurferParams}
*/
_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;
_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 = util.withOrientation(
this.container.appendChild(
document.createElement("wave")
),
this.params.vertical
);
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 = util.withOrientation(
e.targetTouches
? e.targetTouches[0]
: e,
this.params.vertical
).clientX;
var bbox =
this.wrapper.getBoundingClientRect();
var nominalWidth = this.width;
var parentWidth = this.getWidth();
var progressPixels = this.getProgressPixels(
bbox,
clientX
);
var progress;
if (
!this.params.fillParent &&
nominalWidth < parentWidth
) {
progress =
progressPixels *
(this.params.pixelRatio /
nominalWidth) || 0;
} else {
progress =
(progressPixels +
this.wrapper.scrollLeft) /
this.wrapper.scrollWidth || 0;
}
return util.clamp(progress, 0, 1);
},
},
{
key: "getProgressPixels",
value: function getProgressPixels(
wrapperBbox,
clientX
) {
if (this.params.rtl) {
return wrapperBbox.right - clientX;
} else {
return clientX - wrapperBbox.left;
}
},
},
{
key: "setupWrapperEvents",
value: function setupWrapperEvents() {
var _this2 = this;
this.wrapper.addEventListener(
"click",
function (e) {
var orientedEvent =
util.withOrientation(
e,
_this2.params.vertical
);
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 (
orientedEvent.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(
"dblclick",
function (e) {
if (_this2.params.interact) {
_this2.fireEvent(
"dblclick",
e,
_this2.handleEvent(e)
);
}
}
);
this.wrapper.addEventListener(
"scroll",
function (e) {
return _this2.fireEvent(
"scroll",
e
);
}
);
},
/**
* Draw peaks on the canvas
*
* @param {number[]|Number.<Array[]>} 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
) {
// set rate at which waveform is centered
var rate = this.params.autoCenterRate; // make rate depend on width of view and length of waveform
rate /= half;
rate *= maxScroll;
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} Horizontal scroll position in pixels
*/
},
{
key: "getScrollX",
value: function getScrollX() {
var x = 0;
if (this.wrapper) {
var pixelRatio = this.params.pixelRatio;
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} The width of the container
*/
},
{
key: "getWidth",
value: function getWidth() {
return Math.round(
this.container.clientWidth *
this.params.pixelRatio
);
},
/**
* Set the width of the container
*
* @param {number} width The new width of the container
* @return {boolean} Whether the width of the container was updated or not
*/
},
{
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 {
var newWidth =
~~(
this.width /
this.params.pixelRatio
) + "px";
this.style(this.wrapper, {
width: newWidth,
});
}
this.updateSize();
return true;
},
/**
* Set the height of the container
*
* @param {number} height The new height of the container.
* @return {boolean} Whether the height of the container was updated or not
*/
},
{
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.params
.autoCenterImmediately
);
}
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.domElement
) {
this.container.removeChild(
this.wrapper.domElement
);
}
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.<Array[]>} 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.<Array[]>} 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 ***!
\***********************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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);
}
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")
);
var _drawer2 = _interopRequireDefault(
__webpack_require__(
/*! ./drawer.canvasentry */ "./src/drawer.canvasentry.js"
)
);
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache =
function _getRequireWildcardCache(nodeInterop) {
return nodeInterop
? cacheNodeInterop
: cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (
obj === null ||
(_typeof(obj) !== "object" &&
typeof obj !== "function")
) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty &&
Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (
key !== "default" &&
Object.prototype.hasOwnProperty.call(obj, key)
) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: 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 _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 _createSuper(Derived) {
var hasNativeReflectConstruct =
_isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget =
_getPrototypeOf(this).constructor;
result = Reflect.construct(
Super,
arguments,
NewTarget
);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (
typeof Reflect === "undefined" ||
!Reflect.construct
)
return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return (
o.__proto__ || Object.getPrototypeOf(o)
);
};
return _getPrototypeOf(o);
}
/**
* MultiCanvas renderer for wavesurfer. Is currently the default and sole
* builtin renderer.
*
* A `MultiCanvas` consists of one or more `CanvasEntry` instances, depending
* on the zoom level.
*/
var MultiCanvas = /*#__PURE__*/ (function (_Drawer) {
_inherits(MultiCanvas, _Drawer);
var _super = _createSuper(MultiCanvas);
/**
* @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 = _super.call(this, container, params);
/**
* @type {number}
*/
_this.maxCanvasWidth = params.maxCanvasWidth;
/**
* @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;
/**
* @type {number}
*/
_this.halfPixel = 0.5 / params.pixelRatio;
/**
* List of `CanvasEntry` instances.
*
* @type {Array}
*/
_this.canvases = [];
/**
* @type {HTMLElement}
*/
_this.progressWave = null;
/**
* Class used to generate entries.
*
* @type {function}
*/
_this.EntryClass = _drawer2.default;
/**
* Canvas 2d context attributes.
*
* @type {object}
*/
_this.canvasContextAttributes =
params.drawingContextAttributes;
/**
* Overlap added between entries to prevent vertical white stripes
* between `canvas` elements.
*
* @type {number}
*/
_this.overlap =
2 * Math.ceil(params.pixelRatio / 2);
/**
* The radius of the wave bars. Makes bars rounded
*
* @type {number}
*/
_this.barRadius = params.barRadius || 0;
/**
* Whether to render the waveform vertically. Defaults to false.
*
* @type {boolean}
*/
_this.vertical = params.vertical;
return _this;
}
/**
* Initialize the drawer
*/
_createClass(MultiCanvas, [
{
key: "init",
value: function init() {
this.createWrapper();
this.createElements();
},
/**
* Create the canvas elements and style them
*
*/
},
{
key: "createElements",
value: function createElements() {
this.progressWave = util.withOrientation(
this.wrapper.appendChild(
document.createElement("wave")
),
this.params.vertical
);
this.style(this.progressWave, {
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
*/
},
{
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 +
this.overlap)
); // add required canvases
while (
this.canvases.length < requiredCanvases
) {
this.addCanvas();
} // remove older existing canvases, if any
while (
this.canvases.length > requiredCanvases
) {
this.removeCanvas();
}
var canvasWidth =
this.maxCanvasWidth + this.overlap;
var lastCanvas = this.canvases.length - 1;
this.canvases.forEach(function (entry, i) {
if (i == lastCanvas) {
canvasWidth =
_this2.width -
_this2.maxCanvasWidth *
lastCanvas;
}
_this2.updateDimensions(
entry,
canvasWidth,
_this2.height
);
entry.clearWave();
});
},
/**
* Add a canvas to the canvas list
*
*/
},
{
key: "addCanvas",
value: function addCanvas() {
var entry = new this.EntryClass();
entry.canvasContextAttributes =
this.canvasContextAttributes;
entry.hasProgressCanvas =
this.hasProgressCanvas;
entry.halfPixel = this.halfPixel;
var leftOffset =
this.maxCanvasElementWidth *
this.canvases.length; // wave
var wave = util.withOrientation(
this.wrapper.appendChild(
document.createElement("canvas")
),
this.params.vertical
);
this.style(wave, {
position: "absolute",
zIndex: 2,
left: leftOffset + "px",
top: 0,
bottom: 0,
height: "100%",
pointerEvents: "none",
});
entry.initWave(wave); // progress
if (this.hasProgressCanvas) {
var progress = util.withOrientation(
this.progressWave.appendChild(
document.createElement("canvas")
),
this.params.vertical
);
this.style(progress, {
position: "absolute",
left: leftOffset + "px",
top: 0,
bottom: 0,
height: "100%",
});
entry.initProgress(progress);
}
this.canvases.push(entry);
},
/**
* Pop single canvas from the list
*
*/
},
{
key: "removeCanvas",
value: function removeCanvas() {
var lastEntry =
this.canvases[this.canvases.length - 1]; // wave
lastEntry.wave.parentElement.removeChild(
lastEntry.wave.domElement
); // progress
if (this.hasProgressCanvas) {
lastEntry.progress.parentElement.removeChild(
lastEntry.progress.domElement
);
} // cleanup
if (lastEntry) {
lastEntry.destroy();
lastEntry = null;
}
this.canvases.pop();
},
/**
* Update the dimensions of a canvas element
*
* @param {CanvasEntry} entry Target 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
); // update canvas dimensions
entry.updateDimensions(
elementWidth,
totalWidth,
width,
height
); // style element
this.style(this.progressWave, {
display: "block",
});
},
/**
* Clear the whole multi-canvas
*/
},
{
key: "clearWave",
value: function clearWave() {
var _this3 = this;
util.frame(function () {
_this3.canvases.forEach(function (
entry
) {
return entry.clearWave();
});
})();
},
/**
* Draw a waveform with bars
*
* @param {number[]|Number.<Array[]>} 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
* @returns {void}
*/
},
{
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,
ch = _ref.channelIndex;
// 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 = first;
for (i; i < last; i += step) {
var peak =
peaks[
Math.floor(
i *
scale *
peakIndexScale
)
] || 0;
var h = Math.round(
(peak / absmax) * halfH
);
/* in case of silences, allow the user to specify that we
* always draw *something* (normally a 1px high bar) */
if (
h == 0 &&
_this4.params.barMinHeight
) {
h =
_this4.params
.barMinHeight;
}
_this4.fillRect(
i + _this4.halfPixel,
halfH - h + offsetY,
bar + _this4.halfPixel,
h * 2,
_this4.barRadius,
ch
);
}
}
);
},
/**
* Draw a waveform
*
* @param {number[]|Number.<Array[]>} 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
* @returns {void}
*/
},
{
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,
channelIndex =
_ref2.channelIndex;
if (!hasMinVals) {
var reflectedPeaks = [];
var len = peaks.length;
var i = 0;
for (i; 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,
channelIndex
);
} // always draw a median line
_this5.fillRect(
0,
halfH +
offsetY -
_this5.halfPixel,
_this5.width,
_this5.halfPixel,
_this5.barRadius,
channelIndex
);
}
);
},
/**
* Tell the canvas entries to render their portion of the waveform
*
* @param {number[]} peaks Peaks 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
* @param {channelIndex} channelIndex The channel index of the line drawn
*/
},
{
key: "drawLine",
value: function drawLine(
peaks,
absmax,
halfH,
offsetY,
start,
end,
channelIndex
) {
var _this6 = this;
var _ref3 =
this.params.splitChannelsOptions
.channelColors[channelIndex] ||
{},
waveColor = _ref3.waveColor,
progressColor = _ref3.progressColor;
this.canvases.forEach(function (entry, i) {
_this6.setFillStyles(
entry,
waveColor,
progressColor
);
_this6.applyCanvasTransforms(
entry,
_this6.params.vertical
);
entry.drawLines(
peaks,
absmax,
halfH,
offsetY,
start,
end
);
});
},
/**
* Draw a rectangle on the multi-canvas
*
* @param {number} x X-position of the rectangle
* @param {number} y Y-position of the rectangle
* @param {number} width Width of the rectangle
* @param {number} height Height of the rectangle
* @param {number} radius Radius of the rectangle
* @param {channelIndex} channelIndex The channel index of the bar drawn
*/
},
{
key: "fillRect",
value: function fillRect(
x,
y,
width,
height,
radius,
channelIndex
) {
var startCanvas = Math.floor(
x / this.maxCanvasWidth
);
var endCanvas = Math.min(
Math.ceil(
(x + width) / this.maxCanvasWidth
) + 1,
this.canvases.length
);
var i = startCanvas;
for (i; 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.wave.width
),
y2: y + height,
};
if (intersection.x1 < intersection.x2) {
var _ref4 =
this.params
.splitChannelsOptions
.channelColors[
channelIndex
] || {},
waveColor = _ref4.waveColor,
progressColor =
_ref4.progressColor;
this.setFillStyles(
entry,
waveColor,
progressColor
);
this.applyCanvasTransforms(
entry,
this.params.vertical
);
entry.fillRects(
intersection.x1 - leftOffset,
intersection.y1,
intersection.x2 -
intersection.x1,
intersection.y2 -
intersection.y1,
radius
);
}
}
},
/**
* Returns whether to hide the channel from being drawn based on params.
*
* @param {number} channelIndex The index of the current channel.
* @returns {bool} True to hide the channel, false to draw.
*/
},
{
key: "hideChannel",
value: function hideChannel(channelIndex) {
return (
this.params.splitChannels &&
this.params.splitChannelsOptions.filterChannels.includes(
channelIndex
)
);
},
/**
* Performs preparation tasks and calculations which are shared by `drawBars`
* and `drawWave`
*
* @param {number[]|Number.<Array[]>} 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, e.g. `drawWave`
* @param {number} drawIndex The index of the current channel after filtering.
* @param {number?} normalizedMax Maximum modulation value across channels for use with relativeNormalization. Ignored when undefined
* @returns {void}
*/
},
{
key: "prepareDraw",
value: function prepareDraw(
peaks,
channelIndex,
start,
end,
fn,
drawIndex,
normalizedMax
) {
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) {
var filteredChannels =
channels.filter(function (
c,
i
) {
return !_this7.hideChannel(
i
);
});
if (
!_this7.params
.splitChannelsOptions
.overlay
) {
_this7.setHeight(
Math.max(
filteredChannels.length,
1
) *
_this7.params
.height *
_this7.params
.pixelRatio
);
}
var overallAbsMax;
if (
_this7.params
.splitChannelsOptions &&
_this7.params
.splitChannelsOptions
.relativeNormalization
) {
// calculate maximum peak across channels to use for normalization
overallAbsMax = util.max(
channels.map(function (
channelPeaks
) {
return util.absMax(
channelPeaks
);
})
);
}
return channels.forEach(
function (channelPeaks, i) {
return _this7.prepareDraw(
channelPeaks,
i,
start,
end,
fn,
filteredChannels.indexOf(
channelPeaks
),
overallAbsMax
);
}
);
}
peaks = channels[0];
} // Return and do not draw channel peaks if hidden.
if (_this7.hideChannel(channelIndex)) {
return;
} // 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) {
absmax =
normalizedMax === undefined
? util.absMax(peaks)
: normalizedMax;
} // 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 halfH = height / 2;
var offsetY = height * drawIndex || 0; // Override offsetY if overlay is true
if (
_this7.params
.splitChannelsOptions &&
_this7.params.splitChannelsOptions
.overlay
) {
offsetY = 0;
}
return fn({
absmax: absmax,
hasMinVals: hasMinVals,
height: height,
offsetY: offsetY,
halfH: halfH,
peaks: peaks,
channelIndex: channelIndex,
});
})();
},
/**
* Set the fill styles for a certain entry (wave and progress)
*
* @param {CanvasEntry} entry Target entry
* @param {string} waveColor Wave color to draw this entry
* @param {string} progressColor Progress color to draw this entry
*/
},
{
key: "setFillStyles",
value: function setFillStyles(entry) {
var waveColor =
arguments.length > 1 &&
arguments[1] !== undefined
? arguments[1]
: this.params.waveColor;
var progressColor =
arguments.length > 2 &&
arguments[2] !== undefined
? arguments[2]
: this.params.progressColor;
entry.setFillStyles(
waveColor,
progressColor
);
},
/**
* Set the canvas transforms for a certain entry (wave and progress)
*
* @param {CanvasEntry} entry Target entry
* @param {boolean} vertical Whether to render the waveform vertically
*/
},
{
key: "applyCanvasTransforms",
value: function applyCanvasTransforms(entry) {
var vertical =
arguments.length > 1 &&
arguments[1] !== undefined
? arguments[1]
: false;
entry.applyCanvasTransforms(vertical);
},
/**
* Return image data of the multi-canvas
*
* When using a `type` of `'blob'`, this will return a `Promise`.
*
* @param {string} format='image/png' An optional value of a format type.
* @param {number} quality=0.92 An optional value between 0 and 1.
* @param {string} type='dataURL' Either 'dataURL' or 'blob'.
* @return {string|string[]|Promise} When using the default `'dataURL'`
* `type` this returns a single data URL or an array of data URLs,
* one for each canvas. When using the `'blob'` `type` this returns a
* `Promise` that resolves with an array of `Blob` instances, one for each
* canvas.
*/
},
{
key: "getImage",
value: function getImage(
format,
quality,
type
) {
if (type === "blob") {
return Promise.all(
this.canvases.map(function (entry) {
return entry.getImage(
format,
quality,
type
);
})
);
} else if (type === "dataURL") {
var images = this.canvases.map(
function (entry) {
return entry.getImage(
format,
quality,
type
);
}
);
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-webaudio.js":
/*!**************************************!*\
!*** ./src/mediaelement-webaudio.js ***!
\**************************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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);
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _mediaelement = _interopRequireDefault(
__webpack_require__(
/*! ./mediaelement */ "./src/mediaelement.js"
)
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: 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 _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 _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 _createSuper(Derived) {
var hasNativeReflectConstruct =
_isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget =
_getPrototypeOf(this).constructor;
result = Reflect.construct(
Super,
arguments,
NewTarget
);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (
typeof Reflect === "undefined" ||
!Reflect.construct
)
return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return (
o.__proto__ || Object.getPrototypeOf(o)
);
};
return _getPrototypeOf(o);
}
/**
* MediaElementWebAudio backend: load audio via an HTML5 audio tag, but playback with the WebAudio API.
* The advantage here is that the html5 <audio> tag can perform range requests on the server and not
* buffer the entire file in one request, and you still get the filtering and scripting functionality
* of the webaudio API.
* Note that in order to use range requests and prevent buffering, you must provide peak data.
*
* @since 3.2.0
*/
var MediaElementWebAudio = /*#__PURE__*/ (function (
_MediaElement
) {
_inherits(MediaElementWebAudio, _MediaElement);
var _super = _createSuper(MediaElementWebAudio);
/**
* Construct the backend
*
* @param {WavesurferParams} params Wavesurfer parameters
*/
function MediaElementWebAudio(params) {
var _this;
_classCallCheck(this, MediaElementWebAudio);
_this = _super.call(this, params);
/** @private */
_this.params = params;
/** @private */
_this.sourceMediaElement = null;
return _this;
}
/**
* Initialise the backend, called in `wavesurfer.createBackend()`
*/
_createClass(MediaElementWebAudio, [
{
key: "init",
value: function init() {
this.setPlaybackRate(this.params.audioRate);
this.createTimer();
this.createVolumeNode();
this.createScriptNode();
this.createAnalyserNode();
},
/**
* Private method called by both `load` (from url)
* and `loadElt` (existing media element) methods.
*
* @param {HTMLMediaElement} media HTML5 Audio or Video element
* @param {number[]|Number.<Array[]>} peaks Array of peak data
* @param {string} preload HTML 5 preload attribute value
* @private
*/
},
{
key: "_load",
value: function _load(media, peaks, preload) {
_get(
_getPrototypeOf(
MediaElementWebAudio.prototype
),
"_load",
this
).call(this, media, peaks, preload);
this.createMediaElementSource(media);
},
/**
* Create MediaElementSource node
*
* @since 3.2.0
* @param {HTMLMediaElement} mediaElement HTML5 Audio to load
*/
},
{
key: "createMediaElementSource",
value: function createMediaElementSource(
mediaElement
) {
this.sourceMediaElement =
this.ac.createMediaElementSource(
mediaElement
);
this.sourceMediaElement.connect(
this.analyser
);
},
},
{
key: "play",
value: function play(start, end) {
this.resumeAudioContext();
return _get(
_getPrototypeOf(
MediaElementWebAudio.prototype
),
"play",
this
).call(this, start, end);
},
/**
* This is called when wavesurfer is destroyed
*
*/
},
{
key: "destroy",
value: function destroy() {
_get(
_getPrototypeOf(
MediaElementWebAudio.prototype
),
"destroy",
this
).call(this);
this.destroyWebAudio();
},
},
]);
return MediaElementWebAudio;
})(_mediaelement.default);
exports.default = MediaElementWebAudio;
module.exports = exports.default;
/***/
},
/***/ "./src/mediaelement.js":
/*!*****************************!*\
!*** ./src/mediaelement.js ***!
\*****************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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);
}
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 _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache =
function _getRequireWildcardCache(nodeInterop) {
return nodeInterop
? cacheNodeInterop
: cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (
obj === null ||
(_typeof(obj) !== "object" &&
typeof obj !== "function")
) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty &&
Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (
key !== "default" &&
Object.prototype.hasOwnProperty.call(obj, key)
) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: 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 _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 _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 _createSuper(Derived) {
var hasNativeReflectConstruct =
_isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget =
_getPrototypeOf(this).constructor;
result = Reflect.construct(
Super,
arguments,
NewTarget
);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (
typeof Reflect === "undefined" ||
!Reflect.construct
)
return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return (
o.__proto__ || Object.getPrototypeOf(o)
);
};
return _getPrototypeOf(o);
}
/**
* MediaElement backend
*/
var MediaElement = /*#__PURE__*/ (function (_WebAudio) {
_inherits(MediaElement, _WebAudio);
var _super = _createSuper(MediaElement);
/**
* Construct the backend
*
* @param {WavesurferParams} params Wavesurfer parameters
*/
function MediaElement(params) {
var _this;
_classCallCheck(this, MediaElement);
_this = _super.call(this, params);
/** @private */
_this.params = params;
/**
* Initially a dummy media element to catch errors. Once `_load` is
* called, this will contain the actual `HTMLMediaElement`.
* @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.isMuted = false;
/** @private */
_this.buffer = null;
/** @private */
_this.onPlayEnd = null;
/** @private */
_this.mediaListeners = {};
return _this;
}
/**
* Initialise the backend, called in `wavesurfer.createBackend()`
*/
_createClass(MediaElement, [
{
key: "init",
value: function init() {
this.setPlaybackRate(this.params.audioRate);
this.createTimer();
},
/**
* Attach event listeners to media element.
*/
},
{
key: "_setupMediaListeners",
value: function _setupMediaListeners() {
var _this2 = this;
this.mediaListeners.error = function () {
_this2.fireEvent(
"error",
"Error loading media element"
);
};
this.mediaListeners.canplay = function () {
_this2.fireEvent("canplay");
};
this.mediaListeners.ended = function () {
_this2.fireEvent("finish");
}; // listen to and relay play, pause and seeked events to enable
// playback control from the external media element
this.mediaListeners.play = function () {
_this2.fireEvent("play");
};
this.mediaListeners.pause = function () {
_this2.fireEvent("pause");
};
this.mediaListeners.seeked = function (
event
) {
_this2.fireEvent("seek");
};
this.mediaListeners.volumechange =
function (event) {
_this2.isMuted = _this2.media.muted;
if (_this2.isMuted) {
_this2.volume = 0;
} else {
_this2.volume =
_this2.media.volume;
}
_this2.fireEvent("volume");
}; // reset event listeners
Object.keys(this.mediaListeners).forEach(
function (id) {
_this2.media.removeEventListener(
id,
_this2.mediaListeners[id]
);
_this2.media.addEventListener(
id,
_this2.mediaListeners[id]
);
}
);
},
/**
* Create a timer to provide a more precise `audioprocess` event.
*/
},
{
key: "createTimer",
value: function createTimer() {
var _this3 = this;
var onAudioProcess =
function onAudioProcess() {
if (_this3.isPaused()) {
return;
}
_this3.fireEvent(
"audioprocess",
_this3.getCurrentTime()
); // Call again in the next frame
util.frame(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 () {
_this3.fireEvent(
"audioprocess",
_this3.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.<Array[]>} peaks Array of peak data
* @param {string} preload HTML 5 preload attribute value
* @throws Will throw an error if the `url` argument is not a valid media
* element.
*/
},
{
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, preload);
},
/**
* Load existing media element.
*
* @param {HTMLMediaElement} elt HTML5 Audio or Video element
* @param {number[]|Number.<Array[]>} 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, elt.preload);
},
/**
* Method called by both `load` (from url)
* and `loadElt` (existing media element) methods.
*
* @param {HTMLMediaElement} media HTML5 Audio or Video element
* @param {number[]|Number.<Array[]>} peaks Array of peak data
* @param {string} preload HTML 5 preload attribute value
* @throws Will throw an error if the `media` argument is not a valid media
* element.
* @private
*/
},
{
key: "_load",
value: function _load(media, peaks, preload) {
// verify media element is valid
if (
!(media instanceof HTMLMediaElement) ||
typeof media.addEventListener ===
"undefined"
) {
throw new Error(
"media parameter is not a valid media element"
);
} // load must be called manually on iOS, otherwise peaks won't draw
// until a user interaction triggers load --> 'ready' event
//
// note that we avoid calling media.load here when given peaks and preload == 'none'
// as this almost always triggers some browser fetch of the media.
if (
typeof media.load == "function" &&
!(peaks && preload == "none")
) {
// 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();
}
this.media = media;
this._setupMediaListeners();
this.peaks = peaks;
this.onPlayEnd = null;
this.buffer = null;
this.isMuted = media.muted;
this.setPlaybackRate(this.playbackRate);
this.setVolume(this.volume);
},
/**
* Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
*
* @return {boolean} Media paused or not
*/
},
{
key: "isPaused",
value: function isPaused() {
return !this.media || this.media.paused;
},
/**
* Used by `wavesurfer.getDuration()`
*
* @return {number} Duration
*/
},
{
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} Current time
*/
},
{
key: "getCurrentTime",
value: function getCurrentTime() {
return this.media && this.media.currentTime;
},
/**
* Get the position from 0 to 1
*
* @return {number} Current position
*/
},
{
key: "getPlayedPercents",
value: function getPlayedPercents() {
return (
this.getCurrentTime() /
this.getDuration() || 0
);
},
/**
* Get the audio source playback rate.
*
* @return {number} Playback rate
*/
},
{
key: "getPlaybackRate",
value: function getPlaybackRate() {
return (
this.playbackRate ||
this.media.playbackRate
);
},
/**
* Set the audio source playback rate.
*
* @param {number} value Playback rate
*/
},
{
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} Result
*/
},
{
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} Result
*/
},
{
key: "pause",
value: function pause() {
var promise;
if (this.media) {
promise = this.media.pause();
}
this.clearPlayEnd();
return promise;
},
/**
* Set the play end
*
* @param {number} end Where to end
*/
},
{
key: "setPlayEnd",
value: function setPlayEnd(end) {
var _this4 = this;
this.clearPlayEnd();
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[]>} 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.
* @returns {Promise} A Promise that resolves to `undefined` when there
* are no errors.
*/
},
{
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;
},
/**
* Set the audio volume
*
* @param {number} value A floating point value between 0 and 1.
*/
},
{
key: "setVolume",
value: function setVolume(value) {
this.volume = value; // no need to change when it's already at that volume
if (this.media.volume !== this.volume) {
this.media.volume = this.volume;
}
},
/**
* Enable or disable muted audio
*
* @since 4.0.0
* @param {boolean} muted Specify `true` to mute audio.
*/
},
{
key: "setMute",
value: function setMute(muted) {
// This causes a volume change to be emitted too through the
// volumechange event listener.
this.isMuted = this.media.muted = muted;
},
/**
* This is called when wavesurfer is destroyed
*
*/
},
{
key: "destroy",
value: function destroy() {
var _this5 = this;
this.pause();
this.unAll();
this.destroyed = true; // cleanup media event listeners
Object.keys(this.mediaListeners).forEach(
function (id) {
if (_this5.media) {
_this5.media.removeEventListener(
id,
_this5.mediaListeners[id]
);
}
}
);
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 ***!
\**************************/
/***/ (module, exports) => {
"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.<Array[]>} Array with arrays of numbers
*/
},
{
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.<Array[]>} Array with arrays of numbers
*/
},
{
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/absMax.js":
/*!****************************!*\
!*** ./src/util/absMax.js ***!
\****************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = absMax;
var _max = _interopRequireDefault(
__webpack_require__(/*! ./max */ "./src/util/max.js")
);
var _min = _interopRequireDefault(
__webpack_require__(/*! ./min */ "./src/util/min.js")
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* Get the largest absolute value in an array
*
* @param {Array} values Array of numbers
* @returns {Number} Largest number found
* @example console.log(max([-3, 2, 1]), max([-3, 2, 4])); // logs 3 4
* @since 4.3.0
*/
function absMax(values) {
var max = (0, _max.default)(values);
var min = (0, _min.default)(values);
return -min > max ? -min : max;
}
module.exports = exports.default;
/***/
},
/***/ "./src/util/clamp.js":
/*!***************************!*\
!*** ./src/util/clamp.js ***!
\***************************/
/***/ (module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = clamp;
/**
* Returns a number limited to the given range.
*
* @param {number} val The number to be limited to a range
* @param {number} min The lower boundary of the limit range
* @param {number} max The upper boundary of the limit range
* @returns {number} A number in the range [min, max]
*/
function clamp(val, min, max) {
return Math.min(Math.max(min, val), max);
}
module.exports = exports.default;
/***/
},
/***/ "./src/util/fetch.js":
/*!***************************!*\
!*** ./src/util/fetch.js ***!
\***************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = fetchFile;
var _observer = _interopRequireDefault(
__webpack_require__(
/*! ./observer */ "./src/util/observer.js"
)
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: 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;
}
var ProgressHandler = /*#__PURE__*/ (function () {
/**
* Instantiate ProgressHandler
*
* @param {Observer} instance The `fetchFile` observer instance.
* @param {Number} contentLength Content length.
* @param {Response} response Response object.
*/
function ProgressHandler(
instance,
contentLength,
response
) {
_classCallCheck(this, ProgressHandler);
this.instance = instance;
this.instance._reader = response.body.getReader();
this.total = parseInt(contentLength, 10);
this.loaded = 0;
}
/**
* A method that is called once, immediately after the `ReadableStream``
* is constructed.
*
* @param {ReadableStreamDefaultController} controller Controller instance
* used to control the stream.
*/
_createClass(ProgressHandler, [
{
key: "start",
value: function start(controller) {
var _this = this;
var read = function read() {
// instance._reader.read() returns a promise that resolves
// when a value has been received
_this.instance._reader
.read()
.then(function (_ref) {
var done = _ref.done,
value = _ref.value;
// result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (done) {
// ensure onProgress called when content-length=0
if (_this.total === 0) {
_this.instance.onProgress.call(
_this.instance,
{
loaded: _this.loaded,
total: _this.total,
lengthComputable: false,
}
);
} // no more data needs to be consumed, close the stream
controller.close();
return;
}
_this.loaded +=
value.byteLength;
_this.instance.onProgress.call(
_this.instance,
{
loaded: _this.loaded,
total: _this.total,
lengthComputable: !(
_this.total === 0
),
}
); // enqueue the next data chunk into our target stream
controller.enqueue(value);
read();
})
.catch(function (error) {
controller.error(error);
});
};
read();
},
},
]);
return ProgressHandler;
})();
/**
* Load a file using `fetch`.
*
* @param {object} options Request options to use. See example below.
* @returns {Observer} Observer instance
* @example
* // default options
* let options = {
* url: undefined,
* method: 'GET',
* mode: 'cors',
* credentials: 'same-origin',
* cache: 'default',
* responseType: 'json',
* requestHeaders: [],
* redirect: 'follow',
* referrer: 'client'
* };
*
* // override some options
* options.url = '../media/demo.wav';
* // available types: 'arraybuffer', 'blob', 'json' or 'text'
* options.responseType = 'arraybuffer';
*
* // make fetch call
* let request = util.fetchFile(options);
*
* // listen for events
* request.on('progress', e => {
* console.log('progress', e);
* });
*
* request.on('success', data => {
* console.log('success!', data);
* });
*
* request.on('error', e => {
* console.warn('fetchFile error: ', e);
* });
*/
function fetchFile(options) {
if (!options) {
throw new Error("fetch options missing");
} else if (!options.url) {
throw new Error("fetch url missing");
}
var instance = new _observer.default();
var fetchHeaders = new Headers();
var fetchRequest = new Request(options.url); // add ability to abort
instance.controller = new AbortController(); // check if headers have to be added
if (options && options.requestHeaders) {
// add custom request headers
options.requestHeaders.forEach(function (header) {
fetchHeaders.append(header.key, header.value);
});
} // parse fetch options
var responseType = options.responseType || "json";
var fetchOptions = {
method: options.method || "GET",
headers: fetchHeaders,
mode: options.mode || "cors",
credentials: options.credentials || "same-origin",
cache: options.cache || "default",
redirect: options.redirect || "follow",
referrer: options.referrer || "client",
signal: instance.controller.signal,
};
fetch(fetchRequest, fetchOptions)
.then(function (response) {
// store response reference
instance.response = response;
var progressAvailable = true;
if (!response.body) {
// ReadableStream is not yet supported in this browser
// see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
progressAvailable = false;
} // Server must send CORS header "Access-Control-Expose-Headers: content-length"
var contentLength =
response.headers.get("content-length");
if (contentLength === null) {
// Content-Length server response header missing.
// Don't evaluate download progress if we can't compare against a total size
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Expose-Headers
progressAvailable = false;
}
if (!progressAvailable) {
// not able to check download progress so skip it
return response;
} // fire progress event when during load
instance.onProgress = function (e) {
instance.fireEvent("progress", e);
};
return new Response(
new ReadableStream(
new ProgressHandler(
instance,
contentLength,
response
)
),
fetchOptions
);
})
.then(function (response) {
var errMsg;
if (response.ok) {
switch (responseType) {
case "arraybuffer":
return response.arrayBuffer();
case "json":
return response.json();
case "blob":
return response.blob();
case "text":
return response.text();
default:
errMsg =
"Unknown responseType: " +
responseType;
break;
}
}
if (!errMsg) {
errMsg =
"HTTP error status: " + response.status;
}
throw new Error(errMsg);
})
.then(function (response) {
instance.fireEvent("success", response);
})
.catch(function (error) {
instance.fireEvent("error", error);
}); // return the fetch request
instance.fetchRequest = fetchRequest;
return instance;
}
module.exports = exports.default;
/***/
},
/***/ "./src/util/frame.js":
/*!***************************!*\
!*** ./src/util/frame.js ***!
\***************************/
/***/ (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 ***!
\****************************/
/***/ (module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getId;
/**
* Get a random prefixed ID
*
* @param {String} prefix Prefix to use. Default is `'wavesurfer_'`.
* @returns {String} Random prefixed ID
* @example
* console.log(getId()); // logs 'wavesurfer_b5pors4ru6g'
*
* let prefix = 'foo-';
* console.log(getId(prefix)); // logs 'foo-b5pors4ru6g'
*/
function getId(prefix) {
if (prefix === undefined) {
prefix = "wavesurfer_";
}
return prefix + Math.random().toString(32).substring(2);
}
module.exports = exports.default;
/***/
},
/***/ "./src/util/index.js":
/*!***************************!*\
!*** ./src/util/index.js ***!
\***************************/
/***/ (
__unused_webpack_module,
exports,
__webpack_require__
) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
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, "absMax", {
enumerable: true,
get: function get() {
return _absMax.default;
},
});
Object.defineProperty(exports, "Observer", {
enumerable: true,
get: function get() {
return _observer.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;
},
});
Object.defineProperty(exports, "fetchFile", {
enumerable: true,
get: function get() {
return _fetch.default;
},
});
Object.defineProperty(exports, "clamp", {
enumerable: true,
get: function get() {
return _clamp.default;
},
});
Object.defineProperty(exports, "withOrientation", {
enumerable: true,
get: function get() {
return _orientation.default;
},
});
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 _absMax = _interopRequireDefault(
__webpack_require__(
/*! ./absMax */ "./src/util/absMax.js"
)
);
var _observer = _interopRequireDefault(
__webpack_require__(
/*! ./observer */ "./src/util/observer.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"
)
);
var _fetch = _interopRequireDefault(
__webpack_require__(
/*! ./fetch */ "./src/util/fetch.js"
)
);
var _clamp = _interopRequireDefault(
__webpack_require__(
/*! ./clamp */ "./src/util/clamp.js"
)
);
var _orientation = _interopRequireDefault(
__webpack_require__(
/*! ./orientation */ "./src/util/orientation.js"
)
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/***/
},
/***/ "./src/util/max.js":
/*!*************************!*\
!*** ./src/util/max.js ***!
\*************************/
/***/ (module, exports) => {
"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
* @example console.log(max([1, 2, 3])); // logs 3
*/
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 ***!
\*************************/
/***/ (module, exports) => {
"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
* @example console.log(min([1, 2, 3])); // logs 1
*/
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 ***!
\******************************/
/***/ (module, exports) => {
"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._disabledEventEmissions = [];
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} The event descriptor
*/
_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} The event descriptor
*/
},
{
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);
},
/**
* Disable firing a list of events by name. When specified, event handlers for any event type
* passed in here will not be called.
*
* @since 4.0.0
* @param {string[]} eventNames an array of event names to disable emissions for
* @example
* // disable seek and interaction events
* wavesurfer.setDisabledEventEmissions(['seek', 'interaction']);
*/
},
{
key: "setDisabledEventEmissions",
value: function setDisabledEventEmissions(
eventNames
) {
this._disabledEventEmissions = eventNames;
},
/**
* plugins borrow part of this class without calling the constructor,
* so we have to be careful about _disabledEventEmissions
*/
},
{
key: "_isDisabledEventEmission",
value: function _isDisabledEventEmission(
event
) {
return (
this._disabledEventEmissions &&
this._disabledEventEmissions.includes(
event
)
);
},
/**
* 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 ||
this._isDisabledEventEmission(event)
) {
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/orientation.js":
/*!*********************************!*\
!*** ./src/util/orientation.js ***!
\*********************************/
/***/ (module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = withOrientation;
var verticalPropMap = {
width: "height",
height: "width",
overflowX: "overflowY",
overflowY: "overflowX",
clientWidth: "clientHeight",
clientHeight: "clientWidth",
clientX: "clientY",
clientY: "clientX",
scrollWidth: "scrollHeight",
scrollLeft: "scrollTop",
offsetLeft: "offsetTop",
offsetTop: "offsetLeft",
offsetHeight: "offsetWidth",
offsetWidth: "offsetHeight",
left: "top",
right: "bottom",
top: "left",
bottom: "right",
borderRightStyle: "borderBottomStyle",
borderRightWidth: "borderBottomWidth",
borderRightColor: "borderBottomColor",
};
/**
* Convert a horizontally-oriented property name to a vertical one.
*
* @param {string} prop A property name
* @param {bool} vertical Whether the element is oriented vertically
* @returns {string} prop, converted appropriately
*/
function mapProp(prop, vertical) {
if (
Object.prototype.hasOwnProperty.call(
verticalPropMap,
prop
)
) {
return vertical ? verticalPropMap[prop] : prop;
} else {
return prop;
}
}
var isProxy = Symbol("isProxy");
/**
* Returns an appropriately oriented object based on vertical.
* If vertical is true, attribute getting and setting will be mapped through
* verticalPropMap, so that e.g. getting the object's .width will give its
* .height instead.
* Certain methods of an oriented object will return oriented objects as well.
* Oriented objects can't be added to the DOM directly since they are Proxy objects
* and thus fail typechecks. Use domElement to get the actual element for this.
*
* @param {object} target The object to be wrapped and oriented
* @param {bool} vertical Whether the element is oriented vertically
* @returns {Proxy} An oriented object with attr translation via verticalAttrMap
* @since 5.0.0
*/
function withOrientation(target, vertical) {
if (target[isProxy]) {
return target;
} else {
return new Proxy(target, {
get: function get(obj, prop, receiver) {
if (prop === isProxy) {
return true;
} else if (prop === "domElement") {
return obj;
} else if (prop === "style") {
return withOrientation(
obj.style,
vertical
);
} else if (prop === "canvas") {
return withOrientation(
obj.canvas,
vertical
);
} else if (
prop === "getBoundingClientRect"
) {
return function () {
return withOrientation(
obj.getBoundingClientRect.apply(
obj,
arguments
),
vertical
);
};
} else if (prop === "getContext") {
return function () {
return withOrientation(
obj.getContext.apply(
obj,
arguments
),
vertical
);
};
} else {
var value =
obj[mapProp(prop, vertical)];
return typeof value == "function"
? value.bind(obj)
: value;
}
},
set: function set(obj, prop, value) {
obj[mapProp(prop, vertical)] = value;
return true;
},
});
}
}
module.exports = exports.default;
/***/
},
/***/ "./src/util/prevent-click.js":
/*!***********************************!*\
!*** ./src/util/prevent-click.js ***!
\***********************************/
/***/ (module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = preventClick;
/**
* Stops propagation of click event and removes event listener
*
* @private
* @param {object} event The click event
*/
function preventClickHandler(event) {
event.stopPropagation();
document.body.removeEventListener(
"click",
preventClickHandler,
true
);
}
/**
* Starts listening for click event and prevent propagation
*
* @param {object} values Values
*/
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 ***!
\*********************************************/
/***/ (module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
/* eslint-disable valid-jsdoc */
/**
* Returns the `requestAnimationFrame` function for the browser, or a shim with
* `setTimeout` if the function is not found
*
* @return {function} Available `requestAnimationFrame` function for the browser
*/
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 ***!
\***************************/
/***/ (module, exports) => {
"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 ***!
\***************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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);
}
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"
)
);
var _mediaelementWebaudio = _interopRequireDefault(
__webpack_require__(
/*! ./mediaelement-webaudio */ "./src/mediaelement-webaudio.js"
)
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache =
function _getRequireWildcardCache(nodeInterop) {
return nodeInterop
? cacheNodeInterop
: cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (
obj === null ||
(_typeof(obj) !== "object" &&
typeof obj !== "function")
) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty &&
Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (
key !== "default" &&
Object.prototype.hasOwnProperty.call(obj, key)
) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
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 _createSuper(Derived) {
var hasNativeReflectConstruct =
_isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget =
_getPrototypeOf(this).constructor;
result = Reflect.construct(
Super,
arguments,
NewTarget
);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (
typeof Reflect === "undefined" ||
!Reflect.construct
)
return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return (
o.__proto__ || Object.getPrototypeOf(o)
);
};
return _getPrototypeOf(o);
}
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 on current progress
* @property {number} autoCenterRate=5 If autoCenter is active, rate at which the
* waveform is centered
* @property {boolean} autoCenterImmediately=false If autoCenter is active, immediately
* center waveform on current progress
* @property {string} backend='WebAudio' `'WebAudio'|'MediaElement'|'MediaElementWebAudio'` In most cases
* you don't have to set this manually. MediaElement is a fallback for unsupported browsers.
* MediaElementWebAudio allows to use WebAudio API also with big audio files, loading audio like with
* MediaElement backend (HTML5 audio tag). You have to use the same methods of MediaElement backend for loading and
* playback, giving also peaks, so the audio data are not decoded. In this way you can use WebAudio features, like filters,
* also with audio with big duration. For example:
* ` wavesurfer.load(url | HTMLMediaElement, peaks, preload, duration);
* wavesurfer.play();
* wavesurfer.setFilter(customFilter);
* `
* @property {string} backgroundColor=null Change background color of the
* waveform container.
* @property {number} barHeight=1 The height of the wave bars.
* @property {number} barRadius=0 The radius of the wave bars. Makes bars rounded
* @property {number} barGap=null The optional spacing between bars of the wave,
* if not provided will be calculated in legacy format.
* @property {number} barWidth=null Draw the waveform using bars.
* @property {number} barMinHeight=null If specified, draw at least a bar of this height,
* eliminating waveform gaps
* @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 {object} drawingContextAttributes={desynchronized: false} Drawing context
* attributes.
* @property {number} duration=null Optional audio length so pre-rendered peaks
* can be display immediately for example.
* @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` or `MediaElementWebAudio`)
* this enables the native controls for the media element
* @property {string} mediaType='audio' (Use with backend `MediaElement` or `MediaElementWebAudio`)
* `'audio'|'video'` ('video' only for `MediaElement`)
* @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. When `progressColor` and `waveColor` are the same
* the progress wave is not rendered at all.
* @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} rtl=false If set to `true`, renders waveform from
* right-to-left.
* @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 {SplitChannelOptions} splitChannelsOptions={} Options for splitChannel rendering
* @property {boolean} vertical=false Render the waveform vertically instead of horizontally.
* @property {string} waveColor='#999' The fill color of the waveform after the
* cursor.
* @property {object} xhr={} XHR options. For example:
* `let xhr = {
* cache: 'default',
* mode: 'cors',
* method: 'GET',
* credentials: 'same-origin',
* redirect: 'follow',
* referrer: 'client',
* requestHeaders: [
* {
* key: 'Authorization',
* value: 'my-token'
* }
* ]
* };`
*/
/**
* @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.
*/
/**
* @typedef {Object} SplitChannelOptions
* @desc parameters applied when splitChannels option is true
* @property {boolean} overlay=false determines whether channels are rendered on top of each other or on separate tracks
* @property {object} channelColors={} object describing color for each channel. Example:
* {
* 0: {
* progressColor: 'green',
* waveColor: 'pink'
* },
* 1: {
* progressColor: 'orange',
* waveColor: 'purple'
* }
* }
* @property {number[]} filterChannels=[] indexes of channels to be hidden from rendering
* @property {boolean} relativeNormalization=false determines whether
* normalization is done per channel or maintains proportionality between
* channels. Only applied when normalize and splitChannels are both true.
* @since 4.3.0
*/
/**
* @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 () {
/**
* 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: "create",
value:
/**
* Plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* It returns a `PluginDefinition` object representing the plugin.
*
* @param {Object} params={} The plugin params (specific to the plugin)
*/
function create(params) {},
},
{
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);
var _super = _createSuper(WaveSurfer);
/**
* Initialise wavesurfer instance
*
* @param {WavesurferParams} params Instantiation options for wavesurfer
* @example
* const wavesurfer = new WaveSurfer(params);
* @returns {this} Wavesurfer instance
*/
function WaveSurfer(params) {
var _this;
_classCallCheck(this, WaveSurfer);
_this = _super.call(this);
/**
* Extract relevant parameters (or defaults)
* @private
*/
_this.defaultParams = {
audioContext: null,
audioScriptProcessor: null,
audioRate: 1,
autoCenter: true,
autoCenterRate: 5,
autoCenterImmediately: false,
backend: "WebAudio",
backgroundColor: null,
barHeight: 1,
barRadius: 0,
barGap: null,
barMinHeight: null,
container: null,
cursorColor: "#333",
cursorWidth: 1,
dragSelection: true,
drawingContextAttributes: {
// Boolean that hints the user agent to reduce the latency
// by desynchronizing the canvas paint cycle from the event
// loop
desynchronized: false,
},
duration: null,
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,
rtl: false,
scrollParent: false,
skipLength: 2,
splitChannels: false,
splitChannelsOptions: {
overlay: false,
channelColors: {},
filterChannels: [],
relativeNormalization: false,
},
vertical: false,
waveColor: "#999",
xhr: {},
};
_this.backends = {
MediaElement: _mediaelement.default,
WebAudio: _webaudio.default,
MediaElementWebAudio:
_mediaelementWebaudio.default,
};
_this.util = util;
_this.params = Object.assign(
{},
_this.defaultParams,
params
);
_this.params.splitChannelsOptions = Object.assign(
{},
_this.defaultParams.splitChannelsOptions,
params.splitChannelsOptions
);
/** @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"
);
}
if (_this.params.rtl === true) {
if (_this.params.vertical === true) {
util.style(_this.container, {
transform: "rotateX(180deg)",
});
} else {
util.style(_this.container, {
transform: "rotateY(180deg)",
});
}
}
if (_this.params.backgroundColor) {
_this.setBackgroundColor(
_this.params.backgroundColor
);
}
/**
* @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.currentRequest = 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
*/
// Back compat
if (_this.params.backend == "AudioElement") {
_this.params.backend = "MediaElement";
}
if (
(_this.params.backend == "WebAudio" ||
_this.params.backend ===
"MediaElementWebAudio") &&
!_webaudio.default.prototype.supportsWebAudio.call(
null
)
) {
_this.params.backend = "MediaElement";
}
_this.Backend =
_this.backends[_this.params.backend];
/**
* @private map of plugin names that are currently initialised
*/
_this.initialisedPluginList = {};
/** @private */
_this.isDestroyed = false;
/**
* Get the current ready status.
*
* @example const isReady = wavesurfer.isReady;
* @return {boolean}
*/
_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(_this)
);
}
/**
* Initialise the wave
*
* @example
* var wavesurfer = new WaveSurfer(params);
* wavesurfer.init();
* @return {this} The wavesurfer instance
*/
_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} The wavesurfer instance
*/
},
{
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;
},
/**
* Get a map of plugin names that are currently initialised
*
* @example wavesurfer.getPlugins();
* @return {Object} Object with plugin names
*/
},
{
key: "getActivePlugins",
value: function getActivePlugins() {
return this.initialisedPluginList;
},
/**
* 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} The wavesurfer instance
*/
},
{
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} The wavesurfer instance
*/
},
{
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} The wavesurfer instance
*/
},
{
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();
}
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
);
}
); // only needed for MediaElement and MediaElementWebAudio backend
if (
this.params.backend ===
"MediaElement" ||
this.params.backend ===
"MediaElementWebAudio"
) {
this.backend.on(
"seek",
function () {
_this6.drawer.progress(
_this6.backend.getPlayedPercents()
);
}
);
this.backend.on(
"volume",
function () {
var newVolume =
_this6.getVolume();
_this6.fireEvent(
"volume",
newVolume
);
if (
_this6.backend
.isMuted !==
_this6.isMuted
) {
_this6.isMuted =
_this6.backend.isMuted;
_this6.fireEvent(
"mute",
_this6.isMuted
);
}
}
);
}
},
/**
* 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} Result of the backend play method
* @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);
},
/**
* Set a point in seconds for playback to stop at.
*
* @param {number} position Position (in seconds) to stop at
* @version 3.3.0
*/
},
{
key: "setPlayEnd",
value: function setPlayEnd(position) {
this.backend.setPlayEnd(position);
},
/**
* Stops and pauses playback
*
* @example wavesurfer.pause();
* @return {Promise} Result of the backend pause method
*/
},
{
key: "pause",
value: function pause() {
if (!this.backend.isPaused()) {
return this.backend.pause();
}
},
/**
* Toggle playback
*
* @example wavesurfer.playPause();
* @return {Promise} Result of the backend play or pause method
*/
},
{
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
) {
throw new Error(
"Error calling wavesurfer.seekTo, parameter must be a number between 0 and 1!"
);
}
this.fireEvent(
"interaction",
function () {
return _this8.seekTo(progress);
}
);
var isWebAudioBackend =
this.params.backend === "WebAudio";
var paused = this.backend.isPaused();
if (isWebAudioBackend && !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 (isWebAudioBackend && !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);
},
/**
* Sets the ID of the audio device to use for output and returns a Promise.
*
* @param {string} deviceId String value representing underlying output
* device
* @returns {Promise} `Promise` that resolves to `undefined` when there are
* no errors detected.
*/
},
{
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} The current playback rate.
*/
},
{
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 Specify `true` to mute audio.
* @emits WaveSurfer#volume
* @emits WaveSurfer#mute
* @example
* // unmute
* wavesurfer.setMute(false);
* console.log(wavesurfer.getMute()) // logs 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 (this.backend.setMute) {
// Backends such as the MediaElement backend have their own handling
// of mute, let them handle it.
this.backend.setMute(mute);
this.isMuted = mute;
} else {
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} Current mute status
*/
},
{
key: "getMute",
value: function getMute() {
return this.isMuted;
},
/**
* Get the list of current set filters as an array.
*
* Filters must be set with setFilters method first
*
* @return {array} List of enabled filters
*/
},
{
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 background color of the waveform container.
*
* @return {string} A CSS color string.
*/
},
{
key: "getBackgroundColor",
value: function getBackgroundColor() {
return this.params.backgroundColor;
},
/**
* Set the background color of the waveform container.
*
* @param {string} color A CSS color string.
* @example wavesurfer.setBackgroundColor('#FF00FF');
*/
},
{
key: "setBackgroundColor",
value: function setBackgroundColor(color) {
this.params.backgroundColor = color;
util.style(this.container, {
background:
this.params.backgroundColor,
});
},
/**
* 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();
},
/**
* Hide channels from being drawn on the waveform if splitting channels.
*
* For example, if we want to draw only the peaks for the right stereo channel:
*
* const wavesurfer = new WaveSurfer.create({...splitChannels: true});
* wavesurfer.load('stereo_audio.mp3');
*
* wavesurfer.setFilteredChannel([0]); <-- hide left channel peaks.
*
* @param {array} channelIndices Channels to be filtered out from drawing.
* @version 4.0.0
*/
},
{
key: "setFilteredChannels",
value: function setFilteredChannels(
channelIndices
) {
this.params.splitChannelsOptions.filterChannels =
channelIndices;
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; // always start at 0 after zooming for scrolling : issue redraw left part
var start = 0;
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 Buffer to process
*/
},
{
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 Buffer to process
* @emits WaveSurfer#ready
*/
},
{
key: "loadDecodedBuffer",
value: function loadDecodedBuffer(buffer) {
this.backend.load(buffer);
this.drawBuffer();
this.isReady = true;
this.fireEvent("ready");
},
/**
* 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.<Array[]>} peaks Wavesurfer does not have to decode
* the audio to render the waveform if this is specified
* @param {?string} preload (Use with backend `MediaElement` and `MediaElementWebAudio`)
* `'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.
* @returns {void}
* @throws Will throw an error if the `url` argument is empty.
* @example
* // uses fetch or media element to load file (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
) {
if (!url) {
throw new Error(
"url parameter cannot be empty"
);
}
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' or 'MediaElementWebAudio'":
[
"MediaElement",
"MediaElementWebAudio",
].indexOf(
this.params.backend
) === -1,
"Url is not of type string":
typeof url !== "string",
};
var activeReasons = Object.keys(
preloadIgnoreReasons
).filter(function (reason) {
return preloadIgnoreReasons[
reason
];
});
if (activeReasons.length) {
// eslint-disable-next-line no-console
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;
}
} // loadBuffer(url, peaks, duration) requires that url is a string
// but users can pass in a HTMLMediaElement to WaveSurfer
if (
this.params.backend ===
"WebAudio" &&
url instanceof HTMLMediaElement
) {
url = url.src;
}
switch (this.params.backend) {
case "WebAudio":
return this.loadBuffer(
url,
peaks,
duration
);
case "MediaElement":
case "MediaElementWebAudio":
return this.loadMediaElement(
url,
peaks,
preload,
duration
);
}
},
/**
* Loads audio using Web Audio buffer backend.
*
* @private
* @emits WaveSurfer#waveform-ready
* @param {string} url URL of audio file
* @param {number[]|Number.<Array[]>} peaks Peaks data
* @param {?number} duration Optional duration of audio file
* @returns {void}
*/
},
{
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.fireEvent("waveform-ready");
this.tmpEvents.push(
this.once("interaction", load)
);
} else {
return load();
}
},
/**
* Either create a media element, or load an existing media element.
*
* @private
* @emits WaveSurfer#waveform-ready
* @param {string|HTMLMediaElement} urlOrElt Either a path to a media file, or an
* existing HTML5 Audio/Video Element
* @param {number[]|Number.<Array[]>} 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 Optional duration of audio file
*/
},
{
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 () {
// ignore when backend was already destroyed
if (
!_this12.backend
.destroyed
) {
_this12.drawBuffer();
_this12.isReady = true;
_this12.fireEvent(
"ready"
);
}
}
),
this.backend.once(
"error",
function (err) {
return _this12.fireEvent(
"error",
err
);
}
)
); // If peaks are provided, render them and fire the `waveform-ready` event.
if (peaks) {
this.backend.setPeaks(
peaks,
duration
);
this.drawBuffer();
this.fireEvent("waveform-ready");
} // If no pre-decoded peaks are provided, or are provided with
// forceDecode flag, attempt to download the audio file and decode it
// with Web Audio.
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 The array buffer to decode
* @param {function} callback The function to call on complete
*/
},
{
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 using fetch and pass the result to a callback
*
* @param {string} url The URL of the file object
* @param {function} callback The function to call on complete
* @returns {util.fetchFile} fetch call
* @private
*/
},
{
key: "getArrayBuffer",
value: function getArrayBuffer(
url,
callback
) {
var _this14 = this;
var options = Object.assign(
{
url: url,
responseType: "arraybuffer",
},
this.params.xhr
);
var request = util.fetchFile(options);
this.currentRequest = request;
this.tmpEvents.push(
request.on(
"progress",
function (e) {
_this14.onProgress(e);
}
),
request.on(
"success",
function (data) {
callback(data);
_this14.currentRequest =
null;
}
),
request.on("error", function (e) {
_this14.fireEvent("error", e);
_this14.currentRequest = null;
})
);
return request;
},
/**
* Called while the audio file is loading
*
* @private
* @param {Event} e Progress event
* @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 optionally opens in a new window
* as valid JSON Blob instance.
*
* @param {number} length=1024 The scale in which to export the peaks
* @param {number} accuracy=10000
* @param {?boolean} noWindow Set to true to disable opening a new
* window with the JSON
* @param {number} start Start index
* @param {number} end End index
* @return {Promise} Promise that resolves with array of peaks
*/
},
{
key: "exportPCM",
value: function exportPCM(
length,
accuracy,
noWindow,
start,
end
) {
length = length || 1024;
start = start || 0;
accuracy = accuracy || 10000;
noWindow = noWindow || false;
var peaks = this.backend.getPeaks(
length,
start,
end
);
var arr = [].map.call(
peaks,
function (val) {
return (
Math.round(val * accuracy) /
accuracy
);
}
);
return new Promise(function (
resolve,
reject
) {
if (!noWindow) {
var blobJSON = new Blob(
[JSON.stringify(arr)],
{
type: "application/json;charset=utf-8",
}
);
var objURL =
URL.createObjectURL(
blobJSON
);
window.open(objURL);
URL.revokeObjectURL(objURL);
}
resolve(arr);
});
},
/**
* 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' A string indicating the image format.
* The default format type is `'image/png'`.
* @param {number} quality=1 A number between 0 and 1 indicating the image
* quality to use for image formats that use lossy compression such as
* `'image/jpeg'`` and `'image/webp'`.
* @param {string} type Image data type to return. Either 'dataURL' (default)
* or 'blob'.
* @return {string|string[]|Promise} When using `'dataURL'` type this returns
* a single data URL or an array of data URLs, one for each canvas. When using
* `'blob'` type this returns a `Promise` resolving with an array of `Blob`
* instances, one for each canvas.
*/
},
{
key: "exportImage",
value: function exportImage(
format,
quality,
type
) {
if (!format) {
format = "image/png";
}
if (!quality) {
quality = 1;
}
if (!type) {
type = "dataURL";
}
return this.drawer.getImage(
format,
quality,
type
);
},
/**
* Cancel any fetch request currently in progress
*/
},
{
key: "cancelAjax",
value: function cancelAjax() {
if (
this.currentRequest &&
this.currentRequest.controller
) {
// If the current request has a ProgressHandler, then its ReadableStream might need to be cancelled too
// See: Wavesurfer issue #2042
// See Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1583815
if (this.currentRequest._reader) {
// Ignoring exceptions thrown by call to cancel()
this.currentRequest._reader
.cancel()
.catch(function (err) {});
}
this.currentRequest.controller.abort();
this.currentRequest = 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(); // empty drawer
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
);
}
if (this.backend) {
this.backend.destroy(); // clears memory usage
this.backend = null;
}
if (this.drawer) {
this.drawer.destroy();
}
this.isDestroyed = true;
this.isReady = false;
this.arraybuffer = null;
},
},
],
[
{
key: "create",
value:
/** @private */
/** @private */
/**
* Instantiate this class, call its `init` function and returns it
*
* @param {WavesurferParams} params The wavesurfer parameters
* @return {Object} WaveSurfer instance
* @example const wavesurfer = WaveSurfer.create(params);
*/
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);
*/
},
]
);
return WaveSurfer;
})(util.Observer);
exports.default = WaveSurfer;
WaveSurfer.VERSION = "5.1.0";
WaveSurfer.util = util;
module.exports = exports.default;
/***/
},
/***/ "./src/webaudio.js":
/*!*************************!*\
!*** ./src/webaudio.js ***!
\*************************/
/***/ (module, exports, __webpack_require__) => {
"use strict";
function _typeof(obj) {
"@babel/helpers - typeof";
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);
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var util = _interopRequireWildcard(
__webpack_require__(/*! ./util */ "./src/util/index.js")
);
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache =
function _getRequireWildcardCache(nodeInterop) {
return nodeInterop
? cacheNodeInterop
: cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (
obj === null ||
(_typeof(obj) !== "object" &&
typeof obj !== "function")
) {
return { default: obj };
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty &&
Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (
key !== "default" &&
Object.prototype.hasOwnProperty.call(obj, key)
) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError(
"Cannot call a class as a function"
);
}
}
function _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);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct =
_isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget =
_getPrototypeOf(this).constructor;
result = Reflect.construct(
Super,
arguments,
NewTarget
);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
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 _isNativeReflectConstruct() {
if (
typeof Reflect === "undefined" ||
!Reflect.construct
)
return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return (
o.__proto__ || Object.getPrototypeOf(o)
);
};
return _getPrototypeOf(o);
}
// 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);
var _super = _createSuper(WebAudio);
/**
* Construct the backend
*
* @param {WavesurferParams} params Wavesurfer parameters
*/
function WebAudio(params) {
var _this$stateBehaviors, _this$states;
var _this;
_classCallCheck(this, WebAudio);
_this = _super.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;
/** ac: Audio Context instance */
_this.ac =
params.audioContext ||
(_this.supportsWebAudio()
? _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.buffer = null;
/** @private */
_this.filters = [];
/** gainNode: allows to control audio volume */
_this.gainNode = null;
/** @private */
_this.mergedPeaks = null;
/** @private */
_this.offlineAc = null;
/** @private */
_this.peaks = null;
/** @private */
_this.playbackRate = 1;
/** analyser: provides audio analysis information */
_this.analyser = null;
/** scriptNode: allows processing audio */
_this.scriptNode = null;
/** @private */
_this.source = null;
/** @private */
_this.splitPeaks = [];
/** @private */
_this.state = null;
/** @private */
_this.explicitDuration = params.duration;
/**
* Boolean indicating if the backend was destroyed.
*/
_this.destroyed = false;
return _this;
}
/**
* Initialise the backend, called in `wavesurfer.createBackend()`
*/
_createClass(WebAudio, [
{
key: "supportsWebAudio",
value:
/** scriptBufferSize: size of the processing buffer */
/** audioContext: allows to process audio with WebAudio API */
/** @private */
/** @private */
/**
* Does the browser support this backend
*
* @return {boolean} Whether or not this browser supports this backend
*/
function supportsWebAudio() {
return !!(
window.AudioContext ||
window.webkitAudioContext
);
},
/**
* Get the audio context used by this backend or create one
*
* @return {AudioContext} Existing audio context, or creates a new one
*/
},
{
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 The sample rate to use
* @return {OfflineAudioContext} Existing offline audio context, or creates
* a new one
*/
},
{
key: "getOfflineAudioContext",
value: function getOfflineAudioContext(
sampleRate
) {
if (!window.WaveSurferOfflineAudioContext) {
window.WaveSurferOfflineAudioContext =
new (window.OfflineAudioContext ||
window.webkitOfflineAudioContext)(
1,
2,
sampleRate
);
}
return window.WaveSurferOfflineAudioContext;
},
},
{
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
*
* @param {string} state The new state
*/
},
{
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 One or more filters to set
*/
},
{
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);
}
},
/** Create ScriptProcessorNode to process audio */
},
{
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;
},
/** Create analyser node to perform audio analysis */
},
{
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.
*
*/
},
{
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.
* @returns {Promise} A Promise that resolves to `undefined` when there
* are no errors.
*/
},
{
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;
},
/**
* Decode an array buffer and pass data to a callback
*
* @private
* @param {ArrayBuffer} arraybuffer The array buffer to decode
* @param {function} callback The function to call on complete.
* @param {function} errback The function to call on error.
*/
},
{
key: "decodeArrayBuffer",
value: function decodeArrayBuffer(
arraybuffer,
callback,
errback
) {
if (!this.offlineAc) {
this.offlineAc =
this.getOfflineAudioContext(
this.ac && this.ac.sampleRate
? this.ac.sampleRate
: 44100
);
}
if ("webkitAudioContext" in window) {
// Safari: no support for Promise-based decodeAudioData enabled
// Enable it in Safari using the Experimental Features > Modern WebAudio API option
this.offlineAc.decodeAudioData(
arraybuffer,
function (data) {
return callback(data);
},
errback
);
} else {
this.offlineAc
.decodeAudioData(arraybuffer)
.then(function (data) {
return callback(data);
})
.catch(function (err) {
return errback(err);
});
}
},
/**
* Set pre-decoded peaks
*
* @param {number[]|Number.<Array[]>} peaks Peaks data
* @param {?number} duration Explicit duration
*/
},
{
key: "setPeaks",
value: function setPeaks(peaks, duration) {
if (duration != null) {
this.explicitDuration = duration;
}
this.peaks = peaks;
},
/**
* Set the rendered length (different from the length of the audio)
*
* @param {number} length The rendered 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[]>} 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;
}
if (!this.buffer) {
return [];
}
first = first || 0;
last = last || length - 1;
this.setLength(length);
if (!this.buffer) {
return this.params.splitChannels
? this.splitPeaks
: this.mergedPeaks;
}
/**
* 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);
/**
* Initialize the max and min to the first sample of this
* subrange, so that even if the samples are entirely
* on one side of zero, we still return the true max and
* min values in the subrange.
*/
var min = chan[start];
var max = min;
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} Position
*/
},
{
key: "getPlayedPercents",
value: function getPlayedPercents() {
return this.state.getPlayedPercents.call(
this
);
},
/** @private */
},
{
key: "disconnectSource",
value: function disconnectSource() {
if (this.source) {
this.source.disconnect();
}
},
/**
* Destroy all references with WebAudio, disconnecting audio nodes and closing Audio Context
*/
},
{
key: "destroyWebAudio",
value: function destroyWebAudio() {
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;
}
},
/**
* This is called when wavesurfer is destroyed
*/
},
{
key: "destroy",
value: function destroy() {
if (!this.isPaused()) {
this.pause();
}
this.unAll();
this.buffer = null;
this.destroyed = true;
this.destroyWebAudio();
},
/**
* Loaded a decoded audio buffer
*
* @param {Object} buffer Decoded audio buffer to load
*/
},
{
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.setPlaybackRate(this.playbackRate);
this.source.buffer = this.buffer;
this.source.connect(this.analyser);
},
/**
* @private
*
* some browsers require an explicit call to #resume before they will play back audio
*/
},
{
key: "resumeAudioContext",
value: function resumeAudioContext() {
if (this.ac.state == "suspended") {
this.ac.resume && this.ac.resume();
}
},
/**
* Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
*
* @return {boolean} Whether or not this backend is currently paused
*/
},
{
key: "isPaused",
value: function isPaused() {
return this.state !== this.states[PLAYING];
},
/**
* Used by `wavesurfer.getDuration()`
*
* @return {number} Duration of loaded buffer
*/
},
{
key: "getDuration",
value: function getDuration() {
if (this.explicitDuration) {
return this.explicitDuration;
}
if (!this.buffer) {
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}} Object containing start and end
* positions
*/
},
{
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} The playback position in seconds
*/
},
{
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);
this.resumeAudioContext();
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} The current time in seconds
*/
},
{
key: "getCurrentTime",
value: function getCurrentTime() {
return this.state.getCurrentTime.call(this);
},
/**
* Returns the current playback rate. (0=no playback, 1=normal playback)
*
* @return {number} The current playback rate
*/
},
{
key: "getPlaybackRate",
value: function getPlaybackRate() {
return this.playbackRate;
},
/**
* Set the audio source playback rate.
*
* @param {number} value The playback rate to use
*/
},
{
key: "setPlaybackRate",
value: function setPlaybackRate(value) {
this.playbackRate = value || 1;
this.source &&
this.source.playbackRate.setValueAtTime(
this.playbackRate,
this.ac.currentTime
);
},
/**
* Set a point in seconds for playback to stop at.
*
* @param {number} end Position to end at
* @version 3.3.0
*/
},
{
key: "setPlayEnd",
value: function setPlayEnd(end) {
this.scheduledPause = end;
},
},
]);
return WebAudio;
})(util.Observer);
exports.default = WebAudio;
WebAudio.scriptBufferSize = 256;
module.exports = exports.default;
/***/
},
/***/ "./node_modules/debounce/index.js":
/*!****************************************!*\
!*** ./node_modules/debounce/index.js ***!
\****************************************/
/***/ (module) => {
/**
* 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;
/***/
},
/******/
};
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/ var module = (__webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {},
/******/
});
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](
module,
module.exports,
__webpack_require__
);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/
}
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__(
"./src/wavesurfer.js"
);
/******/
/******/ return __webpack_exports__;
/******/
})();
});
//# sourceMappingURL=wavesurfer.js.map