@ahanapediatrics/react-volume-meter
Version:
A volume meter React component
162 lines • 5.62 kB
JavaScript
"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(require("react"));
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;
//# sourceMappingURL=VolumeMeter.js.map