From af5343f9db10a35625409e70be3ab047aaa92203 Mon Sep 17 00:00:00 2001 From: fspecii <4722521+fspecii@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:03:14 +0200 Subject: [PATCH] Add missing wavesurfer.js for AudioMass editor AudioMass uses a custom fork of wavesurfer.js with getLoudness() and other extensions. The dist/ folder was excluded by server/.gitignore. Added gitignore exception for audio-editor/dist/. --- server/.gitignore | 1 + .../dist/plugin/wavesurfer.regions.js | 1305 +++ server/audio-editor/dist/wavesurfer.js | 6973 +++++++++++++++++ 3 files changed, 8279 insertions(+) create mode 100644 server/audio-editor/dist/plugin/wavesurfer.regions.js create mode 100644 server/audio-editor/dist/wavesurfer.js diff --git a/server/.gitignore b/server/.gitignore index aa0926a..a4beced 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,4 +1,5 @@ node_modules/ dist/ +!audio-editor/dist/ .env *.log diff --git a/server/audio-editor/dist/plugin/wavesurfer.regions.js b/server/audio-editor/dist/plugin/wavesurfer.regions.js new file mode 100644 index 0000000..6859406 --- /dev/null +++ b/server/audio-editor/dist/plugin/wavesurfer.regions.js @@ -0,0 +1,1305 @@ +/*! + * wavesurfer.js 2.0.5 (Thu Jun 28 2018 18:37:22 GMT-0700 (Pacific Daylight Time)) + * https://github.com/katspaugh/wavesurfer.js + * @license BSD-3-Clause + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define("regions", [], factory); + else if(typeof exports === 'object') + exports["regions"] = factory(); + else + root["WaveSurfer"] = root["WaveSurfer"] || {}, root["WaveSurfer"]["regions"] = factory(); +})(window, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "localhost:8080/dist/plugin/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./src/plugin/regions.js"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "./src/plugin/regions.js": +/*!*******************************!*\ + !*** ./src/plugin/regions.js ***! + \*******************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/** + * (Single) Region plugin class + * + * Must be turned into an observer before instantiating. This is done in + * RegionsPlugin (main plugin class) + * + * @extends {Observer} + */ +var Region = function () { + function Region(params, ws) { + var _this = this; + + _classCallCheck(this, Region); + + this.wavesurfer = ws; + this.wrapper = ws.drawer.wrapper; + this.util = ws.util; + this.style = this.util.style; + + this.id = params.id == null ? ws.util.getId() : params.id; + this.start = Number(params.start) || 0; + this.end = params.end == null ? // small marker-like region + this.start + 4 / this.wrapper.scrollWidth * this.wavesurfer.getDuration() : Number(params.end); + this.resize = params.resize === undefined ? true : Boolean(params.resize); + this.drag = params.drag === undefined ? true : Boolean(params.drag); + this.loop = Boolean(params.loop); + this.color = params.color || 'rgba(30, 30, 162, 0.51)'; + this.data = params.data || {}; + this.attributes = params.attributes || {}; + + this.direction = params.direction || 2; + this.maxLength = params.maxLength; + this.minLength = params.minLength; + this._onRedraw = function () { + return _this.updateRender(); + }; + + this.scroll = params.scroll !== false && ws.params.scrollParent; + this.scrollSpeed = params.scrollSpeed || 1; + this.scrollThreshold = params.scrollThreshold || 28; + + this.bindInOut(); + this.render(); + this.wavesurfer.on('zoom', this._onRedraw); + this.wavesurfer.on('redraw', this._onRedraw); + this.wavesurfer.fireEvent('region-created', this); + } + + /* Update region params. */ + + + _createClass(Region, [{ + key: 'update', + value: function update(params) { + if (null != params.start) { + this.start = Number(params.start); + } + if (null != params.end) { + this.end = Number(params.end); + } + if (null != params.loop) { + this.loop = Boolean(params.loop); + } + if (null != params.color) { + this.color = params.color; + } + if (null != params.data) { + this.data = params.data; + } + if (null != params.resize) { + this.resize = Boolean(params.resize); + } + if (null != params.drag) { + this.drag = Boolean(params.drag); + } + if (null != params.maxLength) { + this.maxLength = Number(params.maxLength); + } + if (null != params.minLength) { + this.minLength = Number(params.minLength); + } + if (null != params.attributes) { + this.attributes = params.attributes; + } + + this.updateRender(); + this.fireEvent('update'); + this.wavesurfer.fireEvent('region-updated', this); + } + + /* Remove a single region. */ + + }, { + key: 'remove', + value: function remove() { + if (this.element) { + this.wrapper.removeChild(this.element); + this.element = null; + this.fireEvent('remove'); + this.wavesurfer.un('zoom', this._onRedraw); + this.wavesurfer.un('redraw', this._onRedraw); + this.wavesurfer.fireEvent('region-removed', this); + } + } + + /* Play the audio region. */ + + }, { + key: 'play', + value: function play() { + this.wavesurfer.play(this.start, this.end); + this.fireEvent('play'); + this.wavesurfer.fireEvent('region-play', this); + } + + /* Play the region in loop. */ + + }, { + key: 'playLoop', + value: function playLoop() { + var _this2 = this; + + this.play(); + this.once('out', function () { + return _this2.playLoop(); + }); + } + + /* Render a region as a DOM element. */ + + }, { + key: 'render', + value: function render() { + var regionEl = document.createElement('region'); + regionEl.className = 'wavesurfer-region'; + regionEl.title = this.formatTime(this.start, this.end); + regionEl.setAttribute('data-id', this.id); + + for (var attrname in this.attributes) { + regionEl.setAttribute('data-region-' + attrname, this.attributes[attrname]); + } + + var width = this.wrapper.scrollWidth; + this.style(regionEl, { + position: 'absolute', + zIndex: 2, + height: '100%', + top: '0px' + }); + + /* Resize handles */ + if (this.resize) { + var handleLeft = regionEl.appendChild(document.createElement('handle')); + var handleRight = regionEl.appendChild(document.createElement('handle')); + handleLeft.className = 'wavesurfer-handle wavesurfer-handle-start'; + handleRight.className = 'wavesurfer-handle wavesurfer-handle-end'; + var css = { + cursor: 'col-resize', + position: 'absolute', + left: '0px', + top: '0px', + width: '1%', + maxWidth: '4px', + height: '100%' + }; + this.style(handleLeft, css); + this.style(handleRight, css); + this.style(handleRight, { + left: '100%' + }); + } + + this.element = this.wrapper.appendChild(regionEl); + this.updateRender(); + this.bindEvents(regionEl); + } + }, { + key: 'formatTime', + value: function formatTime(start, end) { + return (start == end ? [start] : [start, end]).map(function (time) { + return [Math.floor(time % 3600 / 60), // minutes + ('00' + Math.floor(time % 60)).slice(-2) // seconds + ].join(':'); + }).join('-'); + } + }, { + key: 'getWidth', + value: function getWidth() { + return this.wavesurfer.drawer.width / this.wavesurfer.params.pixelRatio; + } + + /* Update element's position, width, color. */ + + }, { + key: 'updateRender', + value: function updateRender() { + var dur = this.wavesurfer.getDuration(); + var width = this.getWidth(); + + if (this.start < 0) { + this.start = 0; + this.end = this.end - this.start; + } + if (this.end > dur) { + this.end = dur; + this.start = dur - (this.end - this.start); + } + + if (this.minLength != null) { + this.end = Math.max(this.start + this.minLength, this.end); + } + + if (this.maxLength != null) { + this.end = Math.min(this.start + this.maxLength, this.end); + } + + if (this.element != null) { + // Calculate the left and width values of the region such that + // no gaps appear between regions. + var left = this.start / dur * width; + var regionWidth = this.end / dur * width - left; + + this.style(this.element, { + left: this.wavesurfer.UnzoomifyPixelValue(left) + 'px', + width: regionWidth * this.wavesurfer.ZoomFactor + 'px', + //backgroundColor: this.color, + cursor: this.drag ? 'move' : 'default' + }); + + for (var attrname in this.attributes) { + this.element.setAttribute('data-region-' + attrname, this.attributes[attrname]); + } + + this.element.title = this.formatTime(this.start, this.end); + } + } + + /* Bind audio events. */ + + }, { + key: 'bindInOut', + value: function bindInOut() { + var _this3 = this; + + this.firedIn = false; + this.firedOut = false; + + var onProcess = function onProcess(time) { + // #### ok, it seems that latency is at 5.8ms, so we cannot accurately stop... Thus the 0.0028 magic number + // this will probably be worse on other systems, but until we kill wavesurfer and migrate to AudioWorklet + // this should do... + + if (!_this3.firedOut && _this3.firedIn && (_this3.start >= Math.round(time * 1000) / 1000 || _this3.end <= (time + 0.0028) ) ) { + // if (!_this3.firedOut && _this3.firedIn && (_this3.start >= Math.round(time * 1000) / 1000 || _this3.end <= Math.round(time * 1000) / 1000)) { + _this3.firedOut = true; + _this3.firedIn = false; + _this3.fireEvent('out'); + _this3.wavesurfer.fireEvent('region-out', _this3); + } + if (!_this3.firedIn && _this3.start <= time && _this3.end > time) { + _this3.firedIn = true; + _this3.firedOut = false; + _this3.fireEvent('in'); + _this3.wavesurfer.fireEvent('region-in', _this3); + } + }; + + this.wavesurfer.backend.on('audioprocess', onProcess); + + this.on('remove', function () { + _this3.wavesurfer.backend.un('audioprocess', onProcess); + }); + + /* Loop playback. */ + this.on('out', function () { + //debugger; + if (_this3.loop) { + _this3.wavesurfer.play(_this3.start); + } else { + // #### + _this3.wavesurfer.seekTo(_this3.start / _this3.wavesurfer.getDuration()); + _this3.wavesurfer.stop(); + } + }); + } + + /* Bind DOM events. */ + + }, { + key: 'bindEvents', + value: function bindEvents() { + var _this4 = this; + + this.element.addEventListener('mouseenter', function (e) { + _this4.fireEvent('mouseenter', e); + _this4.wavesurfer.fireEvent('region-mouseenter', _this4, e); + }); + + this.element.addEventListener('mouseleave', function (e) { + _this4.fireEvent('mouseleave', e); + _this4.wavesurfer.fireEvent('region-mouseleave', _this4, e); + }); + + this.element.addEventListener('click', function (e) { + e.preventDefault(); + _this4.fireEvent('click', e); + _this4.wavesurfer.fireEvent('region-click', _this4, e); + }); + + this.element.addEventListener('dblclick', function (e) { + e.stopPropagation(); + e.preventDefault(); + _this4.fireEvent('dblclick', e); + _this4.wavesurfer.fireEvent('region-dblclick', _this4, e); + }); + + /* Drag or resize on mousemove. */ + (this.drag || this.resize) && function () { + var container = _this4.wavesurfer.drawer.container; + var duration = _this4.wavesurfer.getDuration(); + var scrollSpeed = _this4.scrollSpeed; + var scrollThreshold = _this4.scrollThreshold; + var startTime = void 0; + var touchId = void 0; + var drag = void 0; + var maxScroll = void 0; + var resize = void 0; + var updated = false; + var scrollDirection = void 0; + var wrapperRect = void 0; + + // Scroll when the user is dragging within the threshold + var edgeScroll = function edgeScroll(e, direction) { + if (!scrollDirection || !drag && !resize) { + return; + } + + // Update scroll position + /* + let scrollLeft = + this.wrapper.scrollLeft + scrollSpeed * scrollDirection; + this.wrapper.scrollLeft = scrollLeft = Math.min( + maxScroll, + Math.max(0, scrollLeft) + ); + */ + + var scroll_speed = scrollSpeed; + var rect = _this4.wavesurfer.drawer.wrapper.getBoundingClientRect(); + if (direction === -1) { + var multiplier = Math.abs(rect.left - e.clientX) - 10; + if (multiplier > 0) scroll_speed *= multiplier / 2; + } else if (direction === 1) { + + var multiplier = Math.abs(e.clientX - rect.right) - 10; + if (multiplier > 0) scroll_speed *= multiplier / 2; + } + + var scrollLeft = scroll_speed * scrollDirection / _this4.wavesurfer.getDuration(); + _this4.wavesurfer.LeftProgress += scrollLeft; + if (_this4.wavesurfer.LeftProgress < 0) _this4.wavesurfer.LeftProgress = 0;else if (_this4.wavesurfer.LeftProgress + _this4.wavesurfer.VisibleDuration > _this4.wavesurfer.getDuration()) { + _this4.wavesurfer.LeftProgress = _this4.wavesurfer.getDuration() - _this4.wavesurfer.VisibleDuration; + } + _this4.wavesurfer.ForceDraw(); + // fire event that stuff is happening?? #### + // #### + + // Update time + var time = _this4.wavesurfer.ZoomifyValue(_this4.wavesurfer.drawer.handleEvent(e)) * duration; + var delta = time - startTime; + startTime = time; + + // Continue dragging or resizing + drag ? _this4.onDrag(delta) : _this4.onResize(delta, resize); + + _this4.wavesurfer.fireEvent ('DidZoom'); + + + // Repeat + window.requestAnimationFrame(function () { + edgeScroll(e, direction); + }); + }; + + var onDown = function onDown(e) { + if (e.touches && e.touches.length > 1) { + return; + } + if (e.which === 3) { + return; + } + touchId = e.targetTouches ? e.targetTouches[0].identifier : null; + + _this4.wavesurfer.Interacting |= 1 << 0; + + e.stopPropagation(); + + // #### hack for context menu + var event = new Event ('killCTX', {bubbles: true}); + document.body.dispatchEvent (event); + + startTime = _this4.wavesurfer.ZoomifyValue(_this4.wavesurfer.drawer.handleEvent(e, true)) * duration; + + // Store for scroll calculations + maxScroll = _this4.wrapper.scrollWidth - _this4.wrapper.clientWidth; + wrapperRect = _this4.wrapper.getBoundingClientRect(); + + if (e.target.tagName.toLowerCase() == 'handle') { + if (e.target.classList.contains('wavesurfer-handle-start')) { + resize = 'start'; + } else if (e.target.classList.contains('wavesurfer-handle-end')) { + resize = 'end'; + } + } else if (PKAudioEditor.ui.KeyHandler.keyMap[16]) { + if (_this4.direction === 2) { + resize = 'end'; + + _this4.update({ + start: _this4.start, + end: startTime + }); + } else { + resize = 'start'; + _this4.update({ + start: startTime, + end: _this4.end + }); + } + } else { + drag = true; + resize = false; + } + }; + var onUp = function onUp(e) { + if (e.touches && e.touches.length > 1) { + return; + } + if (e.which === 3) { + return; + } + + _this4.wavesurfer.Interacting &= ~(1 << 0); + + if (drag || resize) { + drag = false; + scrollDirection = null; + resize = false; + } + + if (updated) { + updated = false; + _this4.util.preventClick(); + _this4.fireEvent('update-end', e); + _this4.wavesurfer.fireEvent('region-update-end', _this4, e); + } + }; + var onMove = function onMove(e) { + // if (e.target.tagName === 'HANDLE') return ; + + if (e.touches && e.touches.length > 1) { + return; + } + if (e.which === 3) { + return; + } + if (e.targetTouches && e.targetTouches[0].identifier != touchId) { + return; + } + + if (drag || resize) { + var oldTime = startTime; + var time = _this4.wavesurfer.ZoomifyValue(_this4.wavesurfer.drawer.handleEvent(e)) * duration; + var delta = time - startTime; + startTime = time; + + // Drag + if (_this4.drag && drag) { + updated = updated || !!delta; + _this4.onDrag(delta); + } + + // Resize + if (_this4.resize && resize) { + updated = updated || !!delta; + resize = _this4.onResize(delta, resize); + } + + if (_this4.wavesurfer.ZoomFactor > 1) { + if (drag) { + // The threshold is not between the mouse and the container edge + // but is between the region and the container edge + var regionRect = _this4.element.getBoundingClientRect(); + var x = regionRect.left - wrapperRect.left; + + // Check direction + if (time < oldTime && x >= 0) { + scrollDirection = -1; + } else if (time > oldTime && x + regionRect.width <= wrapperRect.right) { + scrollDirection = 1; + } + + // Check that we are still beyond the threshold + if (scrollDirection === -1 && x > scrollThreshold || scrollDirection === 1 && x + regionRect.width < wrapperRect.right - scrollThreshold) { + scrollDirection = null; + } + } else { + // Mouse based threshold + var _x = e.clientX - wrapperRect.left; + + // Check direction + if (_x <= scrollThreshold) { + scrollDirection = -1; + } else if (_x >= wrapperRect.right - wrapperRect.left - scrollThreshold) { + scrollDirection = 1; + } else { + scrollDirection = null; + } + } + + scrollDirection && edgeScroll(e, scrollDirection); + } + } + }; + + //if ( ('PointerEvent' in window) && !(window.ontouchstart)) + //{ + // _this4.element.addEventListener('pointerdown', onDown); + // _this4.wrapper.addEventListener('pointermove', onMove); + // document.body.addEventListener('pointerup', onUp); + //} + //else + //{ + _this4.element.addEventListener('mousedown', onDown); + _this4.element.addEventListener('touchstart', onDown); + + _this4.wrapper.addEventListener('mousemove', onMove); + _this4.wrapper.addEventListener('touchmove', onMove); + + document.body.addEventListener('mouseup', onUp); + document.body.addEventListener('touchend', onUp); + //} + + _this4.on('remove', function () { + document.body.removeEventListener('mouseup', onUp); + document.body.removeEventListener('touchend', onUp); + _this4.wrapper.removeEventListener('mousemove', onMove); + _this4.wrapper.removeEventListener('touchmove', onMove); + + document.body.removeEventListener('pointerup', onUp); + _this4.wrapper.removeEventListener('pointermove', onMove); + }); + + _this4.wavesurfer.on('destroy', function () { + document.body.removeEventListener('pointerup', onUp); + document.body.removeEventListener('mouseup', onUp); + document.body.removeEventListener('touchend', onUp); + }); + }(); + } + }, { + key: 'onDrag', + value: function onDrag(delta) { + var maxEnd = this.wavesurfer.getDuration(); + if (this.end + delta > maxEnd || this.start + delta < 0) { + return; + } + + // #### + /* + var engine = PKAudioEditor.engine; + var wv = engine.wavesurfer; + var bk = wv.backend; + if (!this.buffer) { + this.buffer = engine.GetSel (); + + bk.reg = { + pos: { + start: (this.start * bk.buffer.sampleRate) >> 0, + end: (this.end * bk.buffer.sampleRate) >> 0 + }, + initpos: { + start: (this.start * bk.buffer.sampleRate) >> 0, + end: (this.end * bk.buffer.sampleRate) >>0 + } + }; + } + + bk.reg.pos.start = ((this.start + delta) * bk.buffer.sampleRate) >> 0; + bk.reg.pos.end = ((this.end + delta) * bk.buffer.sampleRate) >> 0; + wv.drawBuffer (true); + */ + + //-------- + + this.update({ + start: this.start + delta, + end: this.end + delta + }); + } + }, { + key: 'onResize', + value: function onResize(delta, direction) { + + if (direction == 'start') { + if (this.start + delta > this.end) + { + direction = 'end'; + + this.update({ + start: this.end, + end: this.start + delta + }); + } + else + { + this.update({ + start: Math.min(this.start + delta, this.end), + end: Math.max(this.start + delta, this.end) + }); + } + } else { + if (this.start > this.end + delta) + { + direction = 'start'; + + this.update({ + start: this.start + delta, + end: this.start + }); + } + else + { + this.update({ + start: Math.min(this.end + delta, this.start), + end: Math.max(this.end + delta, this.start) + }); + } + } + + return direction; + } + }]); + + return Region; +}(); + +/** + * @typedef {Object} RegionsPluginParams + * @property {?boolean} dragSelection Enable creating regions by dragging wih + * the mouse + * @property {?RegionParams[]} regions Regions that should be added upon + * initialisation + * @property {number} slop=2 The sensitivity of the mouse dragging + * @property {?boolean} deferInit Set to true to manually call + * `initPlugin('regions')` + */ + +/** + * @typedef {Object} RegionParams + * @desc The parameters used to describe a region. + * @example wavesurfer.addRegion(regionParams); + * @property {string} id=→random The id of the region + * @property {number} start=0 The start position of the region (in seconds). + * @property {number} end=0 The end position of the region (in seconds). + * @property {?boolean} loop Whether to loop the region when played back. + * @property {boolean} drag=true Allow/dissallow dragging the region. + * @property {boolean} resize=true Allow/dissallow resizing the region. + * @property {string} [color='rgba(0, 0, 0, 0.1)'] HTML color code. + */ + +/** + * Regions are visual overlays on waveform that can be used to play and loop + * portions of audio. Regions can be dragged and resized. + * + * Visual customization is possible via CSS (using the selectors + * `.wavesurfer-region` and `.wavesurfer-handle`). + * + * @implements {PluginClass} + * @extends {Observer} + * + * @example + * // es6 + * import RegionsPlugin from 'wavesurfer.regions.js'; + * + * // commonjs + * var RegionsPlugin = require('wavesurfer.regions.js'); + * + * // if you are using