UNPKG

@blerp/wavesurfer

Version:

Interactive navigable audio visualization using Web Audio and Canvas

1,305 lines (1,214 loc) 228 kB
/*! * @blerp/wavesurfer 6.4.2 (2023-02-27) * https://gitlab.com/blerp/wavesurfer * @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; /** * The (optional) hover wave node * * @type {HTMLCanvasElement} */ this.hover = null; /** * The (optional) progress wave canvas rendering context * * @type {CanvasRenderingContext2D} */ this.hoverCtx = 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); } /** * Store the hover wave canvas element and create the 2D rendering * context * * @param {HTMLCanvasElement} element The progress wave `canvas` element. */ }, { key: "initHover", value: function initHover(element) { this.hover = element; this.hoverCtx = this.hover.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); } if (this.hasHoverCanvas) { // set progress canvas dimensions this.hover.width = width; this.hover.height = height; (0, _style.default)(this.hover, 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); } // hover if (this.hasHoverCanvas) { this.hoverCtx.clearRect(0, 0, this.hoverCtx.canvas.width, this.hoverCtx.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 * @param {?string|string[]} hoverColor Fill color for the progress canvas, * or an array of colors to apply as a gradient */ }, { key: "setFillStyles", value: function setFillStyles(waveColor, progressColor, hoverColor) { this.waveCtx.fillStyle = this.getFillStyle(this.waveCtx, waveColor); if (this.hasProgressCanvas) { this.progressCtx.fillStyle = this.getFillStyle(this.progressCtx, progressColor); } if (this.hasHoverCanvas) { this.hoverCtx.fillStyle = this.getFillStyle(this.hoverCtx, hoverColor); } } /** * 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); } if (this.hasHoverCanvas) { this.hoverCtx.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); } if (this.hasHoverCanvas) { this.fillRectToContext(this.hoverCtx, 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); } if (this.hasHoverCanvas) { this.drawLineToContext(this.hoverCtx, 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; this.hoverCtx = null; this.hover = 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; /** * Whether or not the progress wave is rendered. If the `waveColor` * and `hoverColor` are the same color it is not. * * @type {boolean} */ _this.hasHoverCanvas = params.waveColor != params.hoverColor; /** * @type {number} */ _this.halfPixel = 0.5 / params.pixelRatio; /** * List of `CanvasEntry` instances. * * @type {Array} */ _this.canvases = []; /** * @type {HTMLElement} */ _this.progressWave = null; /** * @type {HTMLElement} */ _this.hoverWave = 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: "none", pointerEvents: "none" }); this.hoverWave = util.withOrientation(this.wrapper.appendChild(document.createElement("hoverwave")), this.params.vertical); this.style(this.hoverWave, { position: "absolute", zIndex: 3, left: 0, top: 0, bottom: 0, overflow: "hidden", width: "0" }); 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.hasHoverCanvas = this.hasHoverCanvas; 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); } if (this.hasHoverCanvas) { var hover = util.withOrientation(this.hoverWave.appendChild(document.createElement("canvas")), this.params.vertical); this.style(hover, { position: "absolute", left: leftOffset + "px", top: 0, bottom: 0, height: "100%" }); entry.initHover(hover); } 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); } // hover if (this.hasHoverCanvas) { lastEntry.hover.parentElement.removeChild(lastEntry.hover.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