playback: add replaygain setting

Add a settings option for ReplayGain.
This commit is contained in:
OxygenCobalt 2022-01-06 17:27:45 -07:00
parent dc43edd6cb
commit 9a7571a59a
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
14 changed files with 167 additions and 66 deletions

View file

@ -81,7 +81,6 @@ class PlaybackFragment : Fragment() {
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_queue) { if (item.itemId == R.id.action_queue) {
findNavController().navigate(MainFragmentDirections.actionShowQueue()) findNavController().navigate(MainFragmentDirections.actionShowQueue())
true true
} else { } else {
false false

View file

@ -42,25 +42,25 @@ enum class LoopMode {
*/ */
fun toInt(): Int { fun toInt(): Int {
return when (this) { return when (this) {
NONE -> CONST_NONE NONE -> INT_NONE
ALL -> CONST_ALL ALL -> INT_ALL
TRACK -> CONST_TRACK TRACK -> INT_TRACK
} }
} }
companion object { companion object {
private const val CONST_NONE = 0xA100 private const val INT_NONE = 0xA100
private const val CONST_ALL = 0xA101 private const val INT_ALL = 0xA101
private const val CONST_TRACK = 0xA102 private const val INT_TRACK = 0xA102
/** /**
* Convert an int [constant] into a LoopMode, or null if it isnt valid. * Convert an int [constant] into a LoopMode, or null if it isnt valid.
*/ */
fun fromInt(constant: Int): LoopMode? { fun fromInt(constant: Int): LoopMode? {
return when (constant) { return when (constant) {
CONST_NONE -> NONE INT_NONE -> NONE
CONST_ALL -> ALL INT_ALL -> ALL
CONST_TRACK -> TRACK INT_TRACK -> TRACK
else -> null else -> null
} }

View file

@ -38,30 +38,30 @@ enum class PlaybackMode {
*/ */
fun toInt(): Int { fun toInt(): Int {
return when (this) { return when (this) {
ALL_SONGS -> CONST_ALL_SONGS ALL_SONGS -> INT_ALL_SONGS
IN_ALBUM -> CONST_IN_ALBUM IN_ALBUM -> INT_IN_ALBUM
IN_ARTIST -> CONST_IN_ARTIST IN_ARTIST -> INT_IN_ARTIST
IN_GENRE -> CONST_IN_GENRE IN_GENRE -> INT_IN_GENRE
} }
} }
companion object { companion object {
// Kept in reverse order because of backwards compat, do not re-order these // Kept in reverse order because of backwards compat, do not re-order these
private const val CONST_ALL_SONGS = 0xA106 private const val INT_ALL_SONGS = 0xA106
private const val CONST_IN_ALBUM = 0xA105 private const val INT_IN_ALBUM = 0xA105
private const val CONST_IN_ARTIST = 0xA104 private const val INT_IN_ARTIST = 0xA104
private const val CONST_IN_GENRE = 0xA103 private const val INT_IN_GENRE = 0xA103
/** /**
* Get a [PlaybackMode] for an int [constant] * Get a [PlaybackMode] for an int [constant]
* @return The mode, null if there isnt one for this. * @return The mode, null if there isn't one for this.
*/ */
fun fromInt(constant: Int): PlaybackMode? { fun fromInt(constant: Int): PlaybackMode? {
return when (constant) { return when (constant) {
CONST_ALL_SONGS -> ALL_SONGS INT_ALL_SONGS -> ALL_SONGS
CONST_IN_ALBUM -> IN_ALBUM INT_IN_ALBUM -> IN_ALBUM
CONST_IN_ARTIST -> IN_ARTIST INT_IN_ARTIST -> IN_ARTIST
CONST_IN_GENRE -> IN_GENRE INT_IN_GENRE -> IN_GENRE
else -> null else -> null
} }
} }

View file

@ -37,7 +37,7 @@ import kotlin.math.pow
* Manages the current volume and playback state across ReplayGain and AudioFocus events. * Manages the current volume and playback state across ReplayGain and AudioFocus events.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AudioReactor(context: Context) : AudioManager.OnAudioFocusChangeListener { class AudioReactor(context: Context) : AudioManager.OnAudioFocusChangeListener, SettingsManager.Callback {
private data class Gain(val track: Float, val album: Float) private data class Gain(val track: Float, val album: Float)
private val playbackManager = PlaybackStateManager.maybeGetInstance() private val playbackManager = PlaybackStateManager.maybeGetInstance()
@ -62,13 +62,24 @@ class AudioReactor(context: Context) : AudioManager.OnAudioFocusChangeListener {
get() = field * multiplier get() = field * multiplier
private set private set
init {
settingsManager.addCallback(this)
}
/**
* Request the android system for audio focus
*/
fun requestFocus() {
AudioManagerCompat.requestAudioFocus(audioManager, request)
}
/** /**
* Updates the rough volume adjustment for [Metadata] with ReplayGain tags. * Updates the rough volume adjustment for [Metadata] with ReplayGain tags.
* This is based off Vanilla Music's implementation. * This is based off Vanilla Music's implementation.
*/ */
fun applyReplayGain(metadata: Metadata?) { fun applyReplayGain(metadata: Metadata?) {
if (metadata == null) { if (settingsManager.replayGainMode == ReplayGainMode.OFF || metadata == null) {
logD("No parsable ReplayGain tags, returning volume to 1.") logD("ReplayGain is disabled or cannot be determined for this track, resetting volume.")
volume = 1f volume = 1f
return return
} }
@ -76,14 +87,14 @@ class AudioReactor(context: Context) : AudioManager.OnAudioFocusChangeListener {
val gain = parseReplayGain(metadata) val gain = parseReplayGain(metadata)
// Currently we consider both the album and the track gain. // Currently we consider both the album and the track gain.
// TODO: Add configuration here
var adjust = 0f var adjust = 0f
if (gain != null) { if (gain != null) {
adjust = if (gain.album != 0f) { // Allow the user to configure a preferred mode for ReplayGain.
gain.album adjust = if (settingsManager.replayGainMode == ReplayGainMode.TRACK) {
if (gain.track != 0f) gain.track else gain.album
} else { } else {
gain.track if (gain.album != 0f) gain.album else gain.track
} }
} }
@ -155,20 +166,16 @@ class AudioReactor(context: Context) : AudioManager.OnAudioFocusChangeListener {
} }
} }
/**
* Request the android system for audio focus
*/
fun requestFocus() {
AudioManagerCompat.requestAudioFocus(audioManager, request)
}
/** /**
* Abandon the current focus request, functionally "Destroying it". * Abandon the current focus request, functionally "Destroying it".
*/ */
fun release() { fun release() {
AudioManagerCompat.abandonAudioFocusRequest(audioManager, request) AudioManagerCompat.abandonAudioFocusRequest(audioManager, request)
settingsManager.removeCallback(this)
} }
// --- INTERNAL AUDIO FOCUS ---
override fun onAudioFocusChange(focusChange: Int) { override fun onAudioFocusChange(focusChange: Int) {
if (!settingsManager.doAudioFocus) { if (!settingsManager.doAudioFocus) {
// Don't do audio focus if its not enabled // Don't do audio focus if its not enabled
@ -219,6 +226,14 @@ class AudioReactor(context: Context) : AudioManager.OnAudioFocusChangeListener {
logD("Unducked volume, now $volume") logD("Unducked volume, now $volume")
} }
// --- SETTINGS MANAGEMENT ---
override fun onAudioFocusUpdate(focus: Boolean) {
if (!focus) {
onGain()
}
}
companion object { companion object {
private const val MULTIPLIER_DUCK = 0.2f private const val MULTIPLIER_DUCK = 0.2f

View file

@ -353,6 +353,10 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
} }
} }
override fun onReplayGainUpdate(mode: ReplayGainMode) {
onTracksInfoChanged(player.currentTracksInfo)
}
// --- OTHER FUNCTIONS --- // --- OTHER FUNCTIONS ---
/** /**

View file

@ -0,0 +1,30 @@
package org.oxycblt.auxio.playback.system
enum class ReplayGainMode {
OFF,
TRACK,
ALBUM;
fun toInt(): Int {
return when (this) {
OFF -> INT_OFF
TRACK -> INT_TRACK
ALBUM -> INT_ALBUM
}
}
companion object {
private const val INT_OFF = 0xA110
private const val INT_TRACK = 0xA111
private const val INT_ALBUM = 0xA112
fun fromInt(value: Int): ReplayGainMode? {
return when (value) {
INT_OFF -> OFF
INT_TRACK -> TRACK
INT_ALBUM -> ALBUM
else -> null
}
}
}
}

View file

@ -25,6 +25,7 @@ import androidx.preference.PreferenceManager
import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.system.ReplayGainMode
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
@ -102,6 +103,11 @@ class SettingsManager private constructor(context: Context) :
val doPlugMgt: Boolean val doPlugMgt: Boolean
get() = sharedPrefs.getBoolean(KEY_PLUG_MANAGEMENT, true) get() = sharedPrefs.getBoolean(KEY_PLUG_MANAGEMENT, true)
/** The current ReplayGain configuration */
val replayGainMode: ReplayGainMode
get() = ReplayGainMode.fromInt(sharedPrefs.getInt(KEY_REPLAY_GAIN, Int.MIN_VALUE))
?: ReplayGainMode.OFF
/** What queue to create when a song is selected (ex. From All Songs or Search) */ /** What queue to create when a song is selected (ex. From All Songs or Search) */
val songPlaybackMode: PlaybackMode val songPlaybackMode: PlaybackMode
get() = handleSongPlayModeCompat(sharedPrefs) get() = handleSongPlayModeCompat(sharedPrefs)
@ -236,6 +242,14 @@ class SettingsManager private constructor(context: Context) :
KEY_LIB_TABS -> callbacks.forEach { KEY_LIB_TABS -> callbacks.forEach {
it.onLibTabsUpdate(libTabs) it.onLibTabsUpdate(libTabs)
} }
KEY_AUDIO_FOCUS -> callbacks.forEach {
it.onAudioFocusUpdate(doAudioFocus)
}
KEY_REPLAY_GAIN -> callbacks.forEach {
it.onReplayGainUpdate(replayGainMode)
}
} }
} }
@ -250,6 +264,8 @@ class SettingsManager private constructor(context: Context) :
fun onNotifActionUpdate(useAltAction: Boolean) {} fun onNotifActionUpdate(useAltAction: Boolean) {}
fun onShowCoverUpdate(showCovers: Boolean) {} fun onShowCoverUpdate(showCovers: Boolean) {}
fun onQualityCoverUpdate(doQualityCovers: Boolean) {} fun onQualityCoverUpdate(doQualityCovers: Boolean) {}
fun onAudioFocusUpdate(focus: Boolean) {}
fun onReplayGainUpdate(mode: ReplayGainMode) {}
} }
companion object { companion object {
@ -267,6 +283,7 @@ class SettingsManager private constructor(context: Context) :
const val KEY_AUDIO_FOCUS = "KEY_AUDIO_FOCUS" const val KEY_AUDIO_FOCUS = "KEY_AUDIO_FOCUS"
const val KEY_PLUG_MANAGEMENT = "KEY_PLUG_MGT" const val KEY_PLUG_MANAGEMENT = "KEY_PLUG_MGT"
const val KEY_REPLAY_GAIN = "auxio_replay_gain"
const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2" const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
const val KEY_KEEP_SHUFFLE = "KEY_KEEP_SHUFFLE" const val KEY_KEEP_SHUFFLE = "KEY_KEEP_SHUFFLE"

View file

@ -47,11 +47,11 @@ enum class DisplayMode {
} }
companion object { companion object {
private const val CONST_NULL = 0xA107 private const val INT_NULL = 0xA107
private const val CONST_SHOW_GENRES = 0xA108 private const val INT_SHOW_GENRES = 0xA108
private const val CONST_SHOW_ARTISTS = 0xA109 private const val INT_SHOW_ARTISTS = 0xA109
private const val CONST_SHOW_ALBUMS = 0xA10A private const val INT_SHOW_ALBUMS = 0xA10A
private const val CONST_SHOW_SONGS = 0xA10B private const val INT_SHOW_SONGS = 0xA10B
/** /**
* Convert this enum into an integer for filtering. * Convert this enum into an integer for filtering.
@ -60,11 +60,11 @@ enum class DisplayMode {
*/ */
fun toFilterInt(value: DisplayMode?): Int { fun toFilterInt(value: DisplayMode?): Int {
return when (value) { return when (value) {
SHOW_SONGS -> CONST_SHOW_SONGS SHOW_SONGS -> INT_SHOW_SONGS
SHOW_ALBUMS -> CONST_SHOW_ALBUMS SHOW_ALBUMS -> INT_SHOW_ALBUMS
SHOW_ARTISTS -> CONST_SHOW_ARTISTS SHOW_ARTISTS -> INT_SHOW_ARTISTS
SHOW_GENRES -> CONST_SHOW_GENRES SHOW_GENRES -> INT_SHOW_GENRES
null -> CONST_NULL null -> INT_NULL
} }
} }
@ -75,10 +75,10 @@ enum class DisplayMode {
*/ */
fun fromFilterInt(value: Int): DisplayMode? { fun fromFilterInt(value: Int): DisplayMode? {
return when (value) { return when (value) {
CONST_SHOW_SONGS -> SHOW_SONGS INT_SHOW_SONGS -> SHOW_SONGS
CONST_SHOW_ALBUMS -> SHOW_ALBUMS INT_SHOW_ALBUMS -> SHOW_ALBUMS
CONST_SHOW_ARTISTS -> SHOW_ARTISTS INT_SHOW_ARTISTS -> SHOW_ARTISTS
CONST_SHOW_GENRES -> SHOW_GENRES INT_SHOW_GENRES -> SHOW_GENRES
else -> null else -> null
} }
} }

View file

@ -163,10 +163,10 @@ sealed class Sort(open val isAscending: Boolean) {
*/ */
fun toInt(): Int { fun toInt(): Int {
return when (this) { return when (this) {
is ByName -> CONST_NAME is ByName -> INT_NAME
is ByArtist -> CONST_ARTIST is ByArtist -> INT_ARTIST
is ByAlbum -> CONST_ALBUM is ByAlbum -> INT_ALBUM
is ByYear -> CONST_YEAR is ByYear -> INT_YEAR
}.shl(1) or if (isAscending) 1 else 0 }.shl(1) or if (isAscending) 1 else 0
} }
@ -202,10 +202,10 @@ sealed class Sort(open val isAscending: Boolean) {
} }
companion object { companion object {
private const val CONST_NAME = 0xA10C private const val INT_NAME = 0xA10C
private const val CONST_ARTIST = 0xA10D private const val INT_ARTIST = 0xA10D
private const val CONST_ALBUM = 0xA10E private const val INT_ALBUM = 0xA10E
private const val CONST_YEAR = 0xA10F private const val INT_YEAR = 0xA10F
/** /**
* Convert a sort's integer representation into a [Sort] instance. * Convert a sort's integer representation into a [Sort] instance.
@ -216,10 +216,10 @@ sealed class Sort(open val isAscending: Boolean) {
val ascending = (value and 1) == 1 val ascending = (value and 1) == 1
return when (value.shr(1)) { return when (value.shr(1)) {
CONST_NAME -> ByName(ascending) INT_NAME -> ByName(ascending)
CONST_ARTIST -> ByArtist(ascending) INT_ARTIST -> ByArtist(ascending)
CONST_ALBUM -> ByAlbum(ascending) INT_ALBUM -> ByAlbum(ascending)
CONST_YEAR -> ByYear(ascending) INT_YEAR -> ByYear(ascending)
else -> null else -> null
} }
} }

View file

@ -73,6 +73,10 @@
<string name="set_focus_desc">Pausieren wenn andere Töne abspielt wird [Bsp. Anrufe]</string> <string name="set_focus_desc">Pausieren wenn andere Töne abspielt wird [Bsp. Anrufe]</string>
<string name="set_plug_mgt">Kopfhörerfokus</string> <string name="set_plug_mgt">Kopfhörerfokus</string>
<string name="set_plug_mgt_desc">Abspielen/Pausieren wenn sich die Kopfhörerverbindung ändert</string> <string name="set_plug_mgt_desc">Abspielen/Pausieren wenn sich die Kopfhörerverbindung ändert</string>
<string name="set_replay_gain">ReplayGain (Nur MP3/FLAC)</string>
<string name="set_replay_gain_off">Aus</string>
<string name="set_replay_gain_track">Titel bevorzugen</string>
<string name="set_replay_gain_album">Album bevorzugen</string>
<string name="set_behavior">Verhalten</string> <string name="set_behavior">Verhalten</string>
<string name="set_song_mode">Wenn ein Lied ausgewählt wird</string> <string name="set_song_mode">Wenn ein Lied ausgewählt wird</string>

View file

@ -6,7 +6,7 @@
<attr name="entryValues" format="reference" /> <attr name="entryValues" format="reference" />
</declare-styleable> </declare-styleable>
<string-array name="entires_theme"> <string-array name="entries_theme">
<item>@string/set_theme_auto</item> <item>@string/set_theme_auto</item>
<item>@string/set_theme_day</item> <item>@string/set_theme_day</item>
<item>@string/set_theme_night</item> <item>@string/set_theme_night</item>
@ -32,6 +32,18 @@
<item>@integer/play_mode_genre</item> <item>@integer/play_mode_genre</item>
</string-array> </string-array>
<array name="entries_replay_gain">
<item>@string/set_replay_gain_off</item>
<item>@string/set_replay_gain_track</item>
<item>@string/set_replay_gain_album</item>
</array>
<string-array name="values_replay_gain">
<item>@integer/replay_gain_off</item>
<item>@integer/replay_gain_track</item>
<item>@integer/replay_gain_album</item>
</string-array>
<integer name="theme_auto">-1</integer> <integer name="theme_auto">-1</integer>
<integer name="theme_light">1</integer> <integer name="theme_light">1</integer>
<integer name="theme_dark">2</integer> <integer name="theme_dark">2</integer>
@ -40,4 +52,8 @@
<integer name="play_mode_artist">0xA104</integer> <integer name="play_mode_artist">0xA104</integer>
<integer name="play_mode_album">0xA105</integer> <integer name="play_mode_album">0xA105</integer>
<integer name="play_mode_songs">0xA106</integer> <integer name="play_mode_songs">0xA106</integer>
<integer name="replay_gain_off">0xA110</integer>
<integer name="replay_gain_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</integer>
</resources> </resources>

View file

@ -85,6 +85,10 @@
<string name="set_focus_desc">Pause when other audio plays [ex. Calls]</string> <string name="set_focus_desc">Pause when other audio plays [ex. Calls]</string>
<string name="set_plug_mgt">Headset focus</string> <string name="set_plug_mgt">Headset focus</string>
<string name="set_plug_mgt_desc">Play/Pause when the headset connection changes</string> <string name="set_plug_mgt_desc">Play/Pause when the headset connection changes</string>
<string name="set_replay_gain">ReplayGain (MP3/FLAC Only)</string>
<string name="set_replay_gain_off">Off</string>
<string name="set_replay_gain_track">Prefer track</string>
<string name="set_replay_gain_album">Prefer album</string>
<string name="set_behavior">Behavior</string> <string name="set_behavior">Behavior</string>
<string name="set_song_mode">When a song is selected</string> <string name="set_song_mode">When a song is selected</string>

View file

@ -7,7 +7,7 @@
<org.oxycblt.auxio.settings.pref.IntListPreference <org.oxycblt.auxio.settings.pref.IntListPreference
app:defaultValue="@integer/theme_auto" app:defaultValue="@integer/theme_auto"
app:isPreferenceVisible="@bool/enable_theme_settings" app:isPreferenceVisible="@bool/enable_theme_settings"
app:entries="@array/entires_theme" app:entries="@array/entries_theme"
app:entryValues="@array/values_theme" app:entryValues="@array/values_theme"
app:icon="@drawable/ic_day" app:icon="@drawable/ic_day"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
@ -86,13 +86,21 @@
app:title="@string/set_focus" /> app:title="@string/set_focus" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:allowDividerBelow="false"
app:defaultValue="true" app:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_PLUG_MGT" app:key="KEY_PLUG_MGT"
app:summary="@string/set_plug_mgt_desc" app:summary="@string/set_plug_mgt_desc"
app:title="@string/set_plug_mgt" /> app:title="@string/set_plug_mgt" />
<org.oxycblt.auxio.settings.pref.IntListPreference
app:defaultValue="@integer/replay_gain_off"
app:key="auxio_replay_gain"
app:allowDividerBelow="false"
app:iconSpaceReserved="false"
app:entries="@array/entries_replay_gain"
app:entryValues="@array/values_replay_gain"
app:title="@string/set_replay_gain" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View file

@ -198,6 +198,10 @@ To prevent any strange bugs, all integer representations must be unique. A table
0xA10D | Sort.Artist 0xA10D | Sort.Artist
0xA10E | Sort.Album 0xA10E | Sort.Album
0xA10F | Sort.Year 0xA10F | Sort.Year
0xA110 | ReplayGainMode.OFF
0xA111 | ReplayGainMode.TRACK
0xA112 | ReplayGainMode.ALBUM
``` ```
Some datatypes [like `Tab` and `Sort`] have even more fine-grained integer representations for other data. More information can be found in Some datatypes [like `Tab` and `Sort`] have even more fine-grained integer representations for other data. More information can be found in