diff --git a/README.md b/README.md index e4457cb6a..4835cd537 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ I primarily built Auxio for myself, but you can use it too, I guess. ## Building -Auxio relies on a local version of ExoPlayer that enables some extra features. So, the build process is as follows: +Auxio relies on a custom version of ExoPlayer that enables some extra features. So, the build process is as follows: -1. Change into the project directory +1. Enter into the project directory 2. Run `python3 prebuild.py`, which installs ExoPlayer and it's extensions. 3. Build the project normally in Android Studio. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt index 99fabac36..ee84720c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt @@ -27,6 +27,7 @@ import androidx.media.AudioManagerCompat import com.google.android.exoplayer2.metadata.Metadata import com.google.android.exoplayer2.metadata.id3.TextInformationFrame import com.google.android.exoplayer2.metadata.vorbis.VorbisComment +import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.getSystemServiceSafe @@ -88,30 +89,54 @@ class AudioReactor( * This is based off Vanilla Music's implementation. */ fun applyReplayGain(metadata: Metadata?) { - if (settingsManager.replayGainMode == ReplayGainMode.OFF || metadata == null) { - logD("ReplayGain is disabled or cannot be determined for this track, resetting volume.") + if (metadata == null) { + logD("No metadata.") volume = 1f return } + // ReplayGain is configurable, so determine what to do based off of the mode. + val useAlbumGain: (Gain) -> Boolean = when (settingsManager.replayGainMode) { + ReplayGainMode.OFF -> { + logD("ReplayGain is off.") + volume = 1f + return + } + + // User wants track gain to be preferred + ReplayGainMode.TRACK -> + { gain -> + gain.track == 0f + } + + ReplayGainMode.ALBUM -> + { gain -> + gain.album != 0f + } + + ReplayGainMode.DYNAMIC -> + { _ -> + playbackManager.parent is Album && + playbackManager.song?.album == playbackManager.parent + } + } val gain = parseReplayGain(metadata) - // Currently we consider both the album and the track gain. - var adjust = 0f - - if (gain != null) { - // Allow the user to configure a preferred mode for ReplayGain. - adjust = if (settingsManager.replayGainMode == ReplayGainMode.TRACK) { - if (gain.track != 0f) gain.track else gain.album + val adjust = if (gain != null) { + if (useAlbumGain(gain)) { + logD("Using album gain.") + gain.album } else { - if (gain.album != 0f) gain.album else gain.track + logD("Using track gain.") + gain.track } + } else { + 0f } // Final adjustment along the volume curve. // Ensure this is clamped to 0 or 1 so that it can be used as a volume. volume = MathUtils.clamp((10f.pow((adjust / 20f))), 0f, 1f) - logD("Applied ReplayGain adjustment: $volume") } private fun parseReplayGain(metadata: Metadata): Gain? { @@ -126,14 +151,25 @@ class AudioReactor( for (i in 0 until metadata.length()) { val entry = metadata.get(i) - // Sometimes the ReplayGain keys will be lowercase, so make them uppercase. - if (entry is TextInformationFrame && entry.description?.uppercase() in REPLAY_GAIN_TAGS) { - tags.add(GainTag(entry.description!!.uppercase(), parseReplayGainFloat(entry.value))) - continue + val key: String? + val value: String + + when (entry) { + is TextInformationFrame -> { + key = entry.description?.uppercase() + value = entry.value + } + + is VorbisComment -> { + key = entry.key + value = entry.value + } + + else -> continue } - if (entry is VorbisComment && entry.key.uppercase() in REPLAY_GAIN_TAGS) { - tags.add(GainTag(entry.key.uppercase(), parseReplayGainFloat(entry.value))) + if (key in REPLAY_GAIN_TAGS) { + tags.add(GainTag(key!!, parseReplayGainFloat(value))) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt index 9c085b080..b28d2ef4a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt @@ -3,13 +3,15 @@ package org.oxycblt.auxio.playback.system enum class ReplayGainMode { OFF, TRACK, - ALBUM; + ALBUM, + DYNAMIC; fun toInt(): Int { return when (this) { OFF -> INT_OFF TRACK -> INT_TRACK ALBUM -> INT_ALBUM + DYNAMIC -> INT_DYNAMIC } } @@ -17,12 +19,14 @@ enum class ReplayGainMode { private const val INT_OFF = 0xA110 private const val INT_TRACK = 0xA111 private const val INT_ALBUM = 0xA112 + private const val INT_DYNAMIC = 0xA113 fun fromInt(value: Int): ReplayGainMode? { return when (value) { INT_OFF -> OFF INT_TRACK -> TRACK INT_ALBUM -> ALBUM + INT_DYNAMIC -> DYNAMIC else -> null } } diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 3da1a79ac..e027cb94f 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -36,12 +36,14 @@ @string/set_replay_gain_off @string/set_replay_gain_track @string/set_replay_gain_album + @string/set_replay_gain_dynamic @integer/replay_gain_off @integer/replay_gain_track @integer/replay_gain_album + @integer/replay_gain_dynamic -1 @@ -56,4 +58,5 @@ 0xA110 0xA111 0xA112 + 0xA113 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4b83a1ea..4b1250834 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -85,10 +85,11 @@ Pause when other audio plays (ex. Calls) Headset focus Play/Pause when the headset connection changes - ReplayGain (MP3/FLAC Only) + ReplayGain Off Prefer track Prefer album + Dynamic Behavior When a song is selected diff --git a/info/FAQ.md b/info/FAQ.md index 28d95654a..b918951d3 100644 --- a/info/FAQ.md +++ b/info/FAQ.md @@ -30,6 +30,19 @@ As per the [Supported ExoPlayer Formats](https://exoplayer.dev/supported-formats MP4, MP3, MKA, OGG, WAV, MPEG, AAC on all versions of Android. Auxio also supports FLAC on all versions of Android through the use of the ExoPlayer FLAC extension. +#### ReplayGain isn't working on my music! + +This is for a couple reason: +- Auxio doesn't extract ReplayGain tags for your format. This is the case with MP4 files since there's no +defined ReplayGain standard for those. +- Auxio doesn't recognize your ReplayGain tags. This is usually because of a non-standard tag like ID3v2's `RVAD` or +an unrecognized name. + +#### What is dynamic ReplayGain? + +Dynamic ReplayGain is a quirk based off the FooBar2000 plugin that dynamically switches from track gain to album +gain depending on if the current playback is from an album or not. + #### Why are accents lighter/less saturated in dark mode? As per the [Material Design Guidelines](https://material.io/design/color/dark-theme.html), accents should be less diff --git a/prebuild.py b/prebuild.py index 2e0ef6401..48fb76b11 100755 --- a/prebuild.py +++ b/prebuild.py @@ -33,7 +33,7 @@ def sh(cmd): exoplayer_path = os.path.join(os.path.abspath(os.curdir), "deps", "exoplayer") if os.path.exists(exoplayer_path): - reinstall = input(INFO + "info:" + NC + " ExoPlayer is already installed. Would you like to reinstall it? [y/n] ") + reinstall = input(INFO + "info:" + NC + " exoplayer is already installed. would you like to reinstall it? [y/n] ") if not re.match("[yY][eE][sS]|[yY]", reinstall): sys.exit(0) @@ -55,31 +55,31 @@ if ndk_path is None or not os.path.isfile(os.path.join(ndk_path, "ndk_build")): candidates.append(entry.path) if len(candidates) > 0: - print(WARN + "warn:" + NC + " NDK_PATH was not set or invalid. Multiple candidates were found however:") + print(WARN + "warn:" + NC + " NDK_PATH was not set or invalid. multiple candidates were found however:") for i, candidate in enumerate(candidates): print("[" + str(i) + "] " + candidate) try: - ndk_path = candidates[int(input("Enter the NDK to use [Default 0]: "))] + ndk_path = candidates[int(input("Enter the ndk to use [Default 0]: "))] except: ndk_path = candidates[0] else: - print(FATAL + "fatal:" + NC + " NDK_PATH is either invalid, or the Android NDK was not installed at a recognized location.") + print(FATAL + "fatal:" + NC + " NDK_PATH is either invalid, or the android ndk was not installed at a recognized location.") system.exit(1) # Now try to install ExoPlayer. sh("rm -rf deps") -print(INFO + "info:" + NC + " Cloning ExoPlayer...") +print(INFO + "info:" + NC + " cloning ExoPlayer...") sh("git clone https://github.com/oxygencobalt/ExoPlayer.git " + exoplayer_path) os.chdir(exoplayer_path) sh("git checkout release-v2") flac_ext_jni_path = os.path.join("extensions", "flac", "src", "main", "jni") -print(INFO + "info:" + NC + " Installing FLAC extension...") +print(INFO + "info:" + NC + " installing FLAC extension...") os.chdir(flac_ext_jni_path) sh('curl "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-' + FLAC_VERSION + '.tar.xz" | tar xJ && mv "flac-' + FLAC_VERSION + '" flac') sh(ndk_path + "/ndk-build APP_ABI=all -j4") -print(OK + "success:" + NC + " Completed pre-build.") +print(OK + "success:" + NC + " completed pre-build.")