UNPKG

wavesurfer.js

Version:

Interactive navigable audio visualization using Web Audio and Canvas

466 lines (436 loc) 16.2 kB
/*! * wavesurfer.js microphone plugin 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"] = root["WaveSurfer"] || {}, root["WaveSurfer"]["microphone"] = factory(); })(self, () => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./src/plugin/microphone/index.js": /*!****************************************!*\ !*** ./src/plugin/microphone/index.js ***! \****************************************/ /***/ ((module, exports) => { Object.defineProperty(exports, "__esModule", ({ value: true })); exports["default"] = void 0; 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); } /** * @typedef {Object} MicrophonePluginParams * @property {MediaStreamConstraints} constraints The constraints parameter is a * MediaStreamConstaints object with two members: video and audio, describing * the media types requested. Either or both must be specified. * @property {number} bufferSize=4096 The buffer size in units of sample-frames. * If specified, the bufferSize must be one of the following values: `256`, * `512`, `1024`, `2048`, `4096`, `8192`, `16384` * @property {number} numberOfInputChannels=1 Integer specifying the number of * channels for this node's input. Values of up to 32 are supported. * @property {number} numberOfOutputChannels=1 Integer specifying the number of * channels for this node's output. * @property {?boolean} deferInit Set to true to manually call * `initPlugin('microphone')` */ /** * Visualize microphone input in a wavesurfer instance. * * @implements {PluginClass} * @extends {Observer} * @example * // es6 * import MicrophonePlugin from 'wavesurfer.microphone.js'; * * // commonjs * var MicrophonePlugin = require('wavesurfer.microphone.js'); * * // if you are using <script> tags * var MicrophonePlugin = window.WaveSurfer.microphone; * * // ... initialising wavesurfer with the plugin * var wavesurfer = WaveSurfer.create({ * // wavesurfer options ... * plugins: [ * MicrophonePlugin.create({ * // plugin options ... * }) * ] * }); */ var MicrophonePlugin = /*#__PURE__*/function () { function MicrophonePlugin(params, ws) { var _this = this; _classCallCheck(this, MicrophonePlugin); this.params = params; this.wavesurfer = ws; this.active = false; this.paused = false; this.browser = this.detectBrowser(); this.reloadBufferFunction = function (e) { return _this.reloadBuffer(e); }; // cross-browser getUserMedia var promisifiedOldGUM = function promisifiedOldGUM(constraints, successCallback, errorCallback) { // get a hold of getUserMedia, if present var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; // Some browsers just don't implement it - return a rejected // promise with an error to keep a consistent interface if (!getUserMedia) { return Promise.reject(new Error('getUserMedia is not implemented in this browser')); } // otherwise, wrap the call to the old navigator.getUserMedia with // a Promise return new Promise(function (successCallback, errorCallback) { getUserMedia.call(navigator, constraints, successCallback, errorCallback); }); }; // Older browsers might not implement mediaDevices at all, so we set an // empty object first if (navigator.mediaDevices === undefined) { navigator.mediaDevices = {}; } // Some browsers partially implement mediaDevices. We can't just assign // an object with getUserMedia as it would overwrite existing // properties. Here, we will just add the getUserMedia property if it's // missing. if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = promisifiedOldGUM; } this.constraints = this.params.constraints || { video: false, audio: true }; this.bufferSize = this.params.bufferSize || 4096; this.numberOfInputChannels = this.params.numberOfInputChannels || 1; this.numberOfOutputChannels = this.params.numberOfOutputChannels || 1; this._onBackendCreated = function () { // wavesurfer's AudioContext where we'll route the mic signal to _this.micContext = _this.wavesurfer.backend.getAudioContext(); }; } _createClass(MicrophonePlugin, [{ key: "init", value: function init() { this.wavesurfer.on('backend-created', this._onBackendCreated); if (this.wavesurfer.backend) { this._onBackendCreated(); } } /** * Destroy the microphone plugin. */ }, { key: "destroy", value: function destroy() { // make sure the buffer is not redrawn during // cleanup and demolition of this plugin. this.paused = true; this.wavesurfer.un('backend-created', this._onBackendCreated); this.stop(); } /** * Allow user to select audio input device, e.g. microphone, and * start the visualization. */ }, { key: "start", value: function start() { var _this2 = this; navigator.mediaDevices.getUserMedia(this.constraints).then(function (data) { return _this2.gotStream(data); }).catch(function (data) { return _this2.deviceError(data); }); } /** * Pause/resume visualization. */ }, { key: "togglePlay", value: function togglePlay() { if (!this.active) { // start it first this.start(); } else { // toggle paused this.paused = !this.paused; if (this.paused) { this.pause(); } else { this.play(); } } } /** * Play visualization. */ }, { key: "play", value: function play() { this.paused = false; this.connect(); } /** * Pause visualization. */ }, { key: "pause", value: function pause() { this.paused = true; // disconnect sources so they can be used elsewhere // (eg. during audio playback) this.disconnect(); } /** * Stop the device stream and remove any remaining waveform drawing from * the wavesurfer canvas. */ }, { key: "stop", value: function stop() { if (this.active) { // stop visualization and device this.stopDevice(); // empty last frame this.wavesurfer.empty(); } } /** * Stop the device and the visualization. */ }, { key: "stopDevice", value: function stopDevice() { this.active = false; // stop visualization this.disconnect(); // stop stream from device if (this.stream && this.stream.getTracks) { this.stream.getTracks().forEach(function (stream) { return stream.stop(); }); } } /** * Connect the media sources that feed the visualization. */ }, { key: "connect", value: function connect() { if (this.stream !== undefined) { // Create a local buffer for data to be copied to the Wavesurfer buffer for Edge if (this.browser.browser === 'edge') { this.localAudioBuffer = this.micContext.createBuffer(this.numberOfInputChannels, this.bufferSize, this.micContext.sampleRate); } // Create an AudioNode from the stream. this.mediaStreamSource = this.micContext.createMediaStreamSource(this.stream); this.levelChecker = this.micContext.createScriptProcessor(this.bufferSize, this.numberOfInputChannels, this.numberOfOutputChannels); this.mediaStreamSource.connect(this.levelChecker); this.levelChecker.connect(this.micContext.destination); this.levelChecker.onaudioprocess = this.reloadBufferFunction; } } /** * Disconnect the media sources that feed the visualization. */ }, { key: "disconnect", value: function disconnect() { if (this.mediaStreamSource !== undefined) { this.mediaStreamSource.disconnect(); } if (this.levelChecker !== undefined) { this.levelChecker.disconnect(); this.levelChecker.onaudioprocess = undefined; } if (this.localAudioBuffer !== undefined) { this.localAudioBuffer = undefined; } } /** * Redraw the waveform. * * @param {object} event Audioprocess event */ }, { key: "reloadBuffer", value: function reloadBuffer(event) { if (!this.paused) { this.wavesurfer.empty(); if (this.browser.browser === 'edge') { // copy audio data to a local audio buffer, // from https://github.com/audiojs/audio-buffer-utils var channel, l; for (channel = 0, l = Math.min(this.localAudioBuffer.numberOfChannels, event.inputBuffer.numberOfChannels); channel < l; channel++) { this.localAudioBuffer.getChannelData(channel).set(event.inputBuffer.getChannelData(channel)); } this.wavesurfer.loadDecodedBuffer(this.localAudioBuffer); } else { this.wavesurfer.loadDecodedBuffer(event.inputBuffer); } } } /** * Audio input device is ready. * * @param {MediaStream} stream The microphone's media stream. */ }, { key: "gotStream", value: function gotStream(stream) { this.stream = stream; this.active = true; // start visualization this.play(); // notify listeners this.fireEvent('deviceReady', stream); } /** * Device error callback. * * @param {string} code Error message */ }, { key: "deviceError", value: function deviceError(code) { // notify listeners this.fireEvent('deviceError', code); } /** * Extract browser version out of the provided user agent string. * @param {!string} uastring userAgent string. * @param {!string} expr Regular expression used as match criteria. * @param {!number} pos position in the version string to be returned. * @return {!number} browser version. */ }, { key: "extractVersion", value: function extractVersion(uastring, expr, pos) { var match = uastring.match(expr); return match && match.length >= pos && parseInt(match[pos], 10); } /** * Browser detector. * @return {object} result containing browser, version and minVersion * properties. */ }, { key: "detectBrowser", value: function detectBrowser() { // Returned result object. var result = {}; result.browser = null; result.version = null; result.minVersion = null; // Non supported browser. if (typeof window === 'undefined' || !window.navigator) { result.browser = 'Not a supported browser.'; return result; } if (navigator.mozGetUserMedia) { // Firefox result.browser = 'firefox'; result.version = this.extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); result.minVersion = 31; return result; } else if (navigator.webkitGetUserMedia) { // Chrome/Chromium/Webview/Opera result.browser = 'chrome'; result.version = this.extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); result.minVersion = 38; return result; } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge result.browser = 'edge'; result.version = this.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); result.minVersion = 10547; return result; } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari result.browser = 'safari'; result.minVersion = 11; result.version = this.extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); return result; } // Non supported browser default. result.browser = 'Not a supported browser.'; return result; } }], [{ key: "create", value: /** * Microphone plugin definition factory * * This function must be used to create a plugin definition which can be * used by wavesurfer to correctly instantiate the plugin. * * @param {MicrophonePluginParams} params parameters use to initialise the plugin * @return {PluginDefinition} an object representing the plugin */ function create(params) { return { name: 'microphone', deferInit: params && params.deferInit ? params.deferInit : false, params: params, instance: MicrophonePlugin }; } }]); return MicrophonePlugin; }(); exports["default"] = MicrophonePlugin; module.exports = exports.default; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module is referenced by other modules so it can't be inlined /******/ var __webpack_exports__ = __webpack_require__("./src/plugin/microphone/index.js"); /******/ /******/ return __webpack_exports__; /******/ })() ; }); //# sourceMappingURL=wavesurfer.microphone.js.map