import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import hark from 'hark'; import Styled from './styles'; const VOL_POLLING_INTERVAL_MS = 100; const VOL_FLOOR = 0; const VOL_CEIL = 50; const DB_AMPL = 65; const propTypes = { stream: PropTypes.object, volumePollingInterval: PropTypes.number, volumeFloor: PropTypes.number, volumeRange: PropTypes.number, optimum: PropTypes.number, high: PropTypes.number, }; const AudioStreamVolume = ({ volumeFloor, volumeRange, low, optimum, high, stream, }) => { const harkObserver = useRef(null); const volumeRef = useRef(0); const [volume, setVolume] = useState(0); useEffect(() => { observeVolumeChanges(); return stopObservingVolumeChanges; }, []) useEffect(() => { stopObservingVolumeChanges(); observeVolumeChanges(stream); }, [stream]); const observeVolumeChanges = (_stream) => { if (_stream) { harkObserver.current = hark(_stream, { interval: VOL_POLLING_INTERVAL_MS }); harkObserver.current.on('volume_change', handleVolumeChange); } } const stopObservingVolumeChanges = () => { harkObserver.current?.stop(); harkObserver.current = null; } const handleVolumeChange = (dbVolume) => { const previousVolume = volumeRef.current; // Normalize it into 0 - range . DB_AMPL is the "relevance factor" - // original formula is / 20 const linearVolume = Math.pow(10, dbVolume / DB_AMPL) * (volumeRange); // If the current linear volume is lower than 1/10 of the total volume range, // ignore to minimize re-renders. Otherwise: generate the next volume val // by smoothing the transition with the previous value and rounding it up const nextVolume = (linearVolume <= (volumeRange / 10)) ? volumeFloor : Math.round((0.65 * previousVolume) + (0.35 * linearVolume)); if (previousVolume !== nextVolume) { volumeRef.current = nextVolume; setVolume(nextVolume); } } return ( ); } AudioStreamVolume.propTypes = propTypes; AudioStreamVolume.defaultProps = { volumeFloor: VOL_FLOOR, volumeRange: VOL_CEIL, low: VOL_FLOOR, optimum: Math.round(0.3 * VOL_CEIL), high: Math.round(0.4 * VOL_CEIL), stream: null, }; export default React.memo(AudioStreamVolume);