ReplayGain support + audio normalization (web player) (#1988)
* ReplayGain support - extract ReplayGain tags from files, expose via native api - use metadata to normalize audio in web player * make pre-push happy * remove unnecessary prints * remove another unnecessary print * add tooltips, see metadata * address comments, use settings instead * remove console.log * use better language for gain modes
This commit is contained in:
@@ -24,6 +24,16 @@ import locale from './locale'
|
||||
import { keyMap } from '../hotkeys'
|
||||
import keyHandlers from './keyHandlers'
|
||||
|
||||
function calculateReplayGain(preAmp, gain, peak) {
|
||||
if (gain === undefined || peak === undefined) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification§ion=19
|
||||
// Normalized to max gain
|
||||
return Math.min(10 ** ((gain + preAmp) / 20), 1 / peak)
|
||||
}
|
||||
|
||||
const Player = () => {
|
||||
const theme = useCurrentTheme()
|
||||
const translate = useTranslate()
|
||||
@@ -50,6 +60,70 @@ const Player = () => {
|
||||
const showNotifications = useSelector(
|
||||
(state) => state.settings.notifications || false
|
||||
)
|
||||
const gainInfo = useSelector((state) => state.replayGain)
|
||||
const [context, setContext] = useState(null)
|
||||
const [gainNode, setGainNode] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
context === null &&
|
||||
audioInstance &&
|
||||
config.enableReplayGain &&
|
||||
'AudioContext' in window
|
||||
) {
|
||||
const ctx = new AudioContext()
|
||||
// we need this to support radios in firefox
|
||||
audioInstance.crossOrigin = 'anonymous'
|
||||
const source = ctx.createMediaElementSource(audioInstance)
|
||||
const gain = ctx.createGain()
|
||||
|
||||
source.connect(gain)
|
||||
gain.connect(ctx.destination)
|
||||
|
||||
setContext(ctx)
|
||||
setGainNode(gain)
|
||||
}
|
||||
}, [audioInstance, context])
|
||||
|
||||
useEffect(() => {
|
||||
if (gainNode) {
|
||||
const current = playerState.current || {}
|
||||
const song = current.song || {}
|
||||
|
||||
let numericGain
|
||||
|
||||
switch (gainInfo.gainMode) {
|
||||
case 'album': {
|
||||
numericGain = calculateReplayGain(
|
||||
gainInfo.preAmp,
|
||||
song.rgAlbumGain,
|
||||
song.rgAlbumPeak
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'track': {
|
||||
numericGain = calculateReplayGain(
|
||||
gainInfo.preAmp,
|
||||
song.rgTrackGain,
|
||||
song.rgTrackPeak
|
||||
)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
numericGain = 1
|
||||
}
|
||||
}
|
||||
|
||||
gainNode.gain.setValueAtTime(numericGain, context.currentTime)
|
||||
}
|
||||
}, [
|
||||
audioInstance,
|
||||
context,
|
||||
gainNode,
|
||||
gainInfo.gainMode,
|
||||
gainInfo.preAmp,
|
||||
playerState,
|
||||
])
|
||||
|
||||
const defaultOptions = useMemo(
|
||||
() => ({
|
||||
@@ -75,11 +149,15 @@ const Player = () => {
|
||||
},
|
||||
volumeFade: { fadeIn: 200, fadeOut: 200 },
|
||||
renderAudioTitle: (audioInfo, isMobile) => (
|
||||
<AudioTitle audioInfo={audioInfo} isMobile={isMobile} />
|
||||
<AudioTitle
|
||||
audioInfo={audioInfo}
|
||||
gainInfo={gainInfo}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
),
|
||||
locale: locale(translate),
|
||||
}),
|
||||
[isDesktop, playerTheme, translate]
|
||||
[gainInfo, isDesktop, playerTheme, translate]
|
||||
)
|
||||
|
||||
const options = useMemo(() => {
|
||||
@@ -151,6 +229,12 @@ const Player = () => {
|
||||
|
||||
const onAudioPlay = useCallback(
|
||||
(info) => {
|
||||
// Do this to start the context; on chrome-based browsers, the context
|
||||
// will start paused since it is created prior to user interaction
|
||||
if (context && context.state !== 'running') {
|
||||
context.resume()
|
||||
}
|
||||
|
||||
dispatch(currentPlaying(info))
|
||||
if (startTime === null) {
|
||||
setStartTime(Date.now())
|
||||
@@ -178,7 +262,7 @@ const Player = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch, showNotifications, startTime]
|
||||
[context, dispatch, showNotifications, startTime]
|
||||
)
|
||||
|
||||
const onAudioPlayTrackChange = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user