wavesurfer.js
Version:
Interactive navigable audio visualization using Web Audio and Canvas
1,275 lines (1,185 loc) • 225 kB
JavaScript
/*!
* wavesurfer.js 6.6.3 (2023-04-04)
* 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();
})(self, () => {
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 _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
/**
* 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|string[]} waveColor Fill color for the wave canvas,
* or an array of colors to apply as a gradient
* @param {?string|string[]} progressColor Fill color for the progress canvas,
* or an array of colors to apply as a gradient
*/
}, {
key: "setFillStyles",
value: function setFillStyles(waveColor, progressColor) {
this.waveCtx.fillStyle = this.getFillStyle(this.waveCtx, waveColor);
if (this.hasProgressCanvas) {
this.progressCtx.fillStyle = this.getFillStyle(this.progressCtx, progressColor);
}
}
/**
* Utility function to handle wave color arguments
*
* When the color argument type is a string or CanvasGradient instance,
* it will be returned as is. Otherwise, it will be treated as an array,
* and a new CanvasGradient will be returned
*
* @since 6.0.0
* @param {CanvasRenderingContext2D} ctx Rendering context of target canvas
* @param {string|string[]|CanvasGradient} color Either a single fill color
* for the wave canvas, an existing CanvasGradient instance, or an array
* of colors to apply as a gradient
* @returns {string|CanvasGradient} Returns a string fillstyle value, or a
* canvas gradient
*/
}, {
key: "getFillStyle",
value: function getFillStyle(ctx, color) {
if (typeof color == 'string' || color instanceof CanvasGradient) {
return color;
}
var waveGradient = ctx.createLinearGradient(0, 0, 0, ctx.canvas.height);
color.forEach(function (value, index) {
return waveGradient.addColorStop(index / color.length, value);
});
return waveGradient;
}
/**
* 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"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
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 } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : 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; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } 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.bind() : 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%',
cursor: this.params.hideCursor ? 'none' : 'auto',
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"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
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 } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : 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; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } 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.bind() : 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 peakIndex = first;
for (peakIndex; peakIndex < last; peakIndex += step) {
// search for the highest peak in the range this bar falls into
var peak = 0;
var peakIndexRange = Math.floor(peakIndex * scale) * peakIndexScale; // start index
var peakIndexEnd = Math.floor((peakIndex + step) * scale) * peakIndexScale;
do {
// do..while makes sure at least one peak is always evaluated
var newPeak = Math.abs(peaks[peakIndexRange]); // for arrays starting with negative values
if (newPeak > peak) {
peak = newPeak; // higher
}
peakIndexRange += peakIndexScale; // skip every other value for negatives
} while (peakIndexRange < peakIndexEnd);
// calculate the height of this bar according to the highest peak found
var h = Math.round(peak / absmax * halfH);
// raise the bar height to the specified minimum height
// Math.max is used to replace any value smaller than barMinHeight (not just 0) with barMinHeight
if (_this4.params.barMinHeight) {
h = Math.max(h, _this4.params.barMinHeight);
}
_this4.fillRect(peakIndex + _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