@ahanapediatrics/react-volume-meter
Version:
A volume meter React component
293 lines (279 loc) • 11.3 kB
JavaScript
module.exports =
/******/ (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 = "/";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./src/index.ts");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./node_modules/react/index.js":
/*!*************************************!*\
!*** ./node_modules/react/index.js ***!
\*************************************/
/*! no static exports found */
/***/ (function(module, exports) {
throw new Error("Module build failed: Error: Final loader (./node_modules/awesome-typescript-loader/dist/entry.js) didn't return a Buffer or String\n at runLoaders (/Users/danrumney/WebstormProjects/react-volume-meter/node_modules/webpack/lib/NormalModule.js:318:18)\n at /Users/danrumney/WebstormProjects/react-volume-meter/node_modules/loader-runner/lib/LoaderRunner.js:373:3\n at iterateNormalLoaders (/Users/danrumney/WebstormProjects/react-volume-meter/node_modules/loader-runner/lib/LoaderRunner.js:214:10)\n at iterateNormalLoaders (/Users/danrumney/WebstormProjects/react-volume-meter/node_modules/loader-runner/lib/LoaderRunner.js:221:10)\n at /Users/danrumney/WebstormProjects/react-volume-meter/node_modules/loader-runner/lib/LoaderRunner.js:236:3\n at context.callback (/Users/danrumney/WebstormProjects/react-volume-meter/node_modules/loader-runner/lib/LoaderRunner.js:111:13)\n at process._tickCallback (internal/process/next_tick.js:68:7)");
/***/ }),
/***/ "./src/components/VolumeMeter.tsx":
/*!****************************************!*\
!*** ./src/components/VolumeMeter.tsx ***!
\****************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(__webpack_require__(/*! react */ "./node_modules/react/index.js"));
var VmShape;
(function (VmShape) {
VmShape[VmShape["VM_STEPPED"] = 0] = "VM_STEPPED";
VmShape[VmShape["VM_FLAT"] = 1] = "VM_FLAT";
})(VmShape = exports.VmShape || (exports.VmShape = {}));
class MeterRenderer {
constructor(ctx, { height, width, shape = VmShape.VM_STEPPED, blocks = 5, }) {
this.canvasCtx = ctx;
this.height = height;
this.width = width;
this.shape = shape;
this.blocks = blocks;
this.barWidth = width / (blocks + 1);
this.blockMaxima = Array(blocks).fill(0).map((_, i) => (i + 1) / blocks);
}
start() {
this.prevVolume = 0;
}
stop() {
this.prevVolume = 0;
this.draw(0);
}
clear() {
const { canvasCtx, width, height, } = this;
canvasCtx.clearRect(0, 0, width, height);
}
draw(volume) {
const { prevVolume, canvasCtx, barWidth, height, shape, blocks, } = this;
const vol = Math.max(volume, prevVolume * 0.95);
this.prevVolume = vol;
this.clear();
const volPerBlock = 1 / blocks;
this.blockMaxima.forEach((blockMax, i) => {
let color = 'grey';
if (vol > 0.8 && blockMax >= 1) {
color = 'red';
}
else if (vol > blockMax) {
color = 'green';
}
canvasCtx.fillStyle = color;
const x = barWidth * (blocks + 1) / blocks * i;
const y = height - (shape === VmShape.VM_STEPPED ? height * (i + 1) * (1 / (blocks + 1)) : height);
canvasCtx.fillRect(x, y, barWidth, height - y);
if (vol < 1 && blockMax < vol) {
canvasCtx.fillStyle = 'green';
canvasCtx.fillRect(x, y,
// ((v % B) / B) is a sawtooth with period B and amplitude 1
((vol % (volPerBlock)) / (volPerBlock)) * (barWidth), height - y);
}
});
}
}
class VolumeMeter extends react_1.Component {
constructor() {
super(...arguments);
this.canvas = react_1.default.createRef();
this.getCanvasContext = () => {
const canvas = this.canvas.current;
if (!canvas) {
throw new Error('Cannot get a reference to the canvas');
}
const canvasCtx = canvas.getContext('2d');
if (!canvasCtx) {
throw new Error('2D context not avaiable');
}
return canvasCtx;
};
this.changedSource = (oldSrc, newSrc) => {
if (!oldSrc && !newSrc) {
return false;
}
if (!oldSrc) {
return true;
}
if (!newSrc) {
return true;
}
return oldSrc.mediaStream.id !== newSrc.mediaStream.id;
};
this.start = () => {
this.drawer.start();
const drawLoop = () => {
const { enabled } = this.props;
if (!enabled) {
return;
}
const volume = this.getVolume();
this.drawer.draw(volume);
this.rafId = window.requestAnimationFrame(drawLoop);
};
drawLoop();
};
this.stop = () => {
this.drawer.stop();
window.cancelAnimationFrame(this.rafId);
};
this.setupAnalyzer = () => {
const { audioContext, src, enabled } = this.props;
if (!src) {
return;
}
this.analyser = audioContext.createAnalyser();
src.connect(this.analyser);
this.array = new Uint8Array(this.analyser.frequencyBinCount);
if (enabled) {
this.start();
}
};
this.getVolume = () => {
this.analyser.getByteTimeDomainData(this.array);
return this.array.reduce((max, vol) => Math.max(max, Math.abs(vol - 128))) / 128;
};
}
componentDidMount() {
const { width, height, shape, blocks = 5, audioContext, src, } = this.props;
const canvasCtx = this.getCanvasContext();
this.drawer = new MeterRenderer(canvasCtx, {
width, height, shape, blocks,
});
this.drawer.stop();
if (audioContext && src) {
this.setupAnalyzer();
}
}
componentDidUpdate(prevProps) {
const { src, enabled } = this.props;
if (this.changedSource(prevProps.src, src)) {
this.stop();
this.setupAnalyzer();
}
if (!enabled && prevProps.enabled) {
this.stop();
}
if (this.analyser && enabled && !prevProps.enabled) {
this.start();
}
}
componentWillUnmount() {
this.stop();
}
render() {
const { width, height, style } = this.props;
return (react_1.default.createElement("canvas", { className: "volume-meter", ref: this.canvas, width: width, height: height, style: style }));
}
}
VolumeMeter.defaultProps = {
enabled: true,
blocks: 5,
};
exports.VolumeMeter = VolumeMeter;
/***/ }),
/***/ "./src/index.ts":
/*!**********************!*\
!*** ./src/index.ts ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(__webpack_require__(/*! ./components/VolumeMeter */ "./src/components/VolumeMeter.tsx"));
/***/ })
/******/ });
//# sourceMappingURL=index.ts.js.map