Skip to content

Commit

Permalink
feat: add audio effects settings #4332
Browse files Browse the repository at this point in the history
- Added bass boost and reverb preset settings.
- Added a setting to handle audio focus.
- Added new preferences keys for the new settings.
- Refactored player service to handle audio effects.
- Refactored general settings to handle new audio effects.
- Added new enum for presets.
  • Loading branch information
fast4x committed Feb 7, 2025
1 parent dcedba6 commit ca72244
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalContext
import it.fast4x.rimusic.ui.styling.LocalAppearance
import it.fast4x.rimusic.utils.autosyncKey
import it.fast4x.rimusic.utils.handleAudioFocusEnabledKey
import it.fast4x.rimusic.utils.isConnectionMetered
import it.fast4x.rimusic.utils.isConnectionMeteredEnabledKey
import it.fast4x.rimusic.utils.preferences
Expand Down Expand Up @@ -46,4 +47,5 @@ fun isVideoEnabled() = appContext().preferences.getBoolean(showButtonPlayerVideo

fun isConnectionMetered() = appContext().isConnectionMetered()
fun isConnectionMeteredEnabled() = appContext().preferences.getBoolean(isConnectionMeteredEnabledKey, true)
fun isAutoSyncEnabled() = appContext().preferences.getBoolean(autosyncKey, false)
fun isAutoSyncEnabled() = appContext().preferences.getBoolean(autosyncKey, false)
fun isHandleAudioFocusEnabled() = appContext().preferences.getBoolean(handleAudioFocusEnabledKey, true)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package it.fast4x.rimusic.enums

import android.media.audiofx.PresetReverb

enum class PresetsReverb {
NONE,
SMALLROOM,
MEDIUMROOM,
LARGEROOM,
MEDIUMHALL,
LARGEHALL,
PLATE;

val preset: Short
get() = when (this) {
NONE -> PresetReverb.PRESET_NONE
SMALLROOM -> PresetReverb.PRESET_SMALLROOM
MEDIUMROOM -> PresetReverb.PRESET_MEDIUMROOM
LARGEROOM -> PresetReverb.PRESET_LARGEROOM
MEDIUMHALL -> PresetReverb.PRESET_MEDIUMHALL
LARGEHALL -> PresetReverb.PRESET_LARGEHALL
PLATE -> PresetReverb.PRESET_PLATE
}

val textName: String
get() = when (this) {
NONE -> "None"
SMALLROOM -> "Small Room"
MEDIUMROOM -> "Medium Room"
LARGEROOM -> "Large Room"
MEDIUMHALL -> "Medium Hall"
LARGEHALL -> "Large Hall"
PLATE -> "Plate"
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.audiofx.AudioEffect
import android.media.audiofx.BassBoost
import android.media.audiofx.LoudnessEnhancer
import android.media.audiofx.PresetReverb
import android.os.Bundle
import android.os.Handler
import android.os.Looper
Expand All @@ -35,6 +37,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.text.isDigitsOnly
import androidx.media3.common.AudioAttributes
import androidx.media3.common.AuxEffectInfo
import androidx.media3.common.C
import androidx.media3.common.ForwardingPlayer
import androidx.media3.common.MediaItem
Expand Down Expand Up @@ -192,8 +195,13 @@ import kotlinx.coroutines.plus
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import it.fast4x.rimusic.appContext
import it.fast4x.rimusic.enums.PresetsReverb
import it.fast4x.rimusic.isHandleAudioFocusEnabled
import it.fast4x.rimusic.utils.asMediaItem
import it.fast4x.rimusic.utils.audioReverbPresetKey
import it.fast4x.rimusic.utils.autoDownloadSongWhenLikedKey
import it.fast4x.rimusic.utils.bassboostEnabledKey
import it.fast4x.rimusic.utils.bassboostLevelKey
import timber.log.Timber
import java.io.IOException
import java.io.ObjectInputStream
Expand Down Expand Up @@ -239,6 +247,8 @@ class PlayerServiceModern : MediaLibraryService(),

var loudnessEnhancer: LoudnessEnhancer? = null
private var binder = Binder()
private var bassBoost: BassBoost? = null
private var reverbPreset: PresetReverb? = null
private var showLikeButton = true
private var showDownloadButton = true

Expand All @@ -250,7 +260,6 @@ class PlayerServiceModern : MediaLibraryService(),
val currentMediaItem = MutableStateFlow<MediaItem?>(null)

@kotlin.OptIn(ExperimentalCoroutinesApi::class)

private val currentSong = currentMediaItem.flatMapLatest { mediaItem ->
Database.song(mediaItem?.mediaId)
}.stateIn(coroutineScope, SharingStarted.Lazily, null)
Expand Down Expand Up @@ -394,8 +403,10 @@ class PlayerServiceModern : MediaLibraryService(),
AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build(), true
.build(),
isHandleAudioFocusEnabled()
)
.setUsePlatformDiagnostics(false)
.setSeekBackIncrementMs(5000)
.setSeekForwardIncrementMs(5000)
.build()
Expand Down Expand Up @@ -528,6 +539,10 @@ class PlayerServiceModern : MediaLibraryService(),

maybeResumePlaybackWhenDeviceConnected()

maybeBassBoost()

maybeReverb()

/* Queue is saved in events without scheduling it (remove this in future)*/
// Load persistent queue when start activity and save periodically in background
if (isPersistentQueueEnabled) {
Expand Down Expand Up @@ -678,6 +693,9 @@ class PlayerServiceModern : MediaLibraryService(),
sharedPreferences?.getEnum(queueLoopTypeKey, QueueLoopType.Default)?.type
?: QueueLoopType.Default.type
}

bassboostLevelKey, bassboostEnabledKey -> maybeBassBoost()
audioReverbPresetKey -> maybeReverb()
}
}

Expand Down Expand Up @@ -882,6 +900,56 @@ class PlayerServiceModern : MediaLibraryService(),
}
}

private fun maybeBassBoost() {
if (!preferences.getBoolean(bassboostEnabledKey, false)) {
runCatching {
bassBoost?.enabled = false
bassBoost?.release()
}
bassBoost = null
maybeNormalizeVolume()
return
}

runCatching {
if (bassBoost == null) bassBoost = BassBoost(0, player.audioSessionId)
val bassboostLevel =
(preferences.getFloat(bassboostLevelKey, 0.5f) * 1000f).toInt().toShort()
println("PlayerServiceModern maybeBassBoost bassboostLevel $bassboostLevel")
bassBoost?.enabled = false
bassBoost?.setStrength(bassboostLevel)
bassBoost?.enabled = true
}.onFailure {
SmartMessage(
"Can't enable bass boost",
context = this@PlayerServiceModern
)
}
}

private fun maybeReverb() {
val presetType = preferences.getEnum(audioReverbPresetKey, PresetsReverb.NONE)
println("PlayerServiceModern maybeReverb presetType $presetType")
if (presetType == PresetsReverb.NONE) {
runCatching {
reverbPreset?.enabled = false
player.clearAuxEffectInfo()
reverbPreset?.release()
}
reverbPreset = null
return
}

runCatching {
if (reverbPreset == null) reverbPreset = PresetReverb(1, player.audioSessionId)

reverbPreset?.enabled = false
reverbPreset?.preset = presetType.preset
reverbPreset?.enabled = true
reverbPreset?.id?.let { player.setAuxEffectInfo(AuxEffectInfo(it, 1f)) }
}
}

@UnstableApi
private fun maybeNormalizeVolume() {
if (!preferences.getBoolean(volumeNormalizationKey, false)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,16 @@ import it.fast4x.rimusic.utils.useVolumeKeysToChangeSongKey
import it.fast4x.rimusic.utils.visualizerEnabledKey
import it.fast4x.rimusic.utils.volumeNormalizationKey
import it.fast4x.rimusic.colorPalette
import it.fast4x.rimusic.enums.PresetsReverb
import it.fast4x.rimusic.ui.components.themed.Search
import it.fast4x.rimusic.typography
import it.fast4x.rimusic.utils.audioReverbPresetKey
import it.fast4x.rimusic.utils.autoDownloadSongKey
import it.fast4x.rimusic.utils.autoDownloadSongWhenAlbumBookmarkedKey
import it.fast4x.rimusic.utils.autoDownloadSongWhenLikedKey
import it.fast4x.rimusic.utils.bassboostEnabledKey
import it.fast4x.rimusic.utils.bassboostLevelKey
import it.fast4x.rimusic.utils.handleAudioFocusEnabledKey
import it.fast4x.rimusic.utils.isConnectionMeteredEnabledKey


Expand Down Expand Up @@ -319,6 +324,11 @@ fun GeneralSettings(
var loudnessBaseGain by rememberPreference(loudnessBaseGainKey, 5.00f)
var autoLoadSongsInQueue by rememberPreference(autoLoadSongsInQueueKey, true)

var bassboostEnabled by rememberPreference(bassboostEnabledKey,false)
var bassboostLevel by rememberPreference(bassboostLevelKey, 0.5f)
var audioReverb by rememberPreference(audioReverbPresetKey, PresetsReverb.NONE)
var audioFocusEnabled by rememberPreference(handleAudioFocusEnabledKey, true)

var enablePictureInPicture by rememberPreference(enablePictureInPictureKey, false)
var enablePictureInPictureAuto by rememberPreference(enablePictureInPictureAutoKey, false)
var pipModule by rememberPreference(pipModuleKey, PipModule.Cover)
Expand All @@ -328,6 +338,8 @@ fun GeneralSettings(
var autoDownloadSongWhenLiked by rememberPreference(autoDownloadSongWhenLikedKey, false)
var autoDownloadSongWhenAlbumBookmarked by rememberPreference(autoDownloadSongWhenAlbumBookmarkedKey, false)



Column(
modifier = Modifier
.background(colorPalette().background0)
Expand Down Expand Up @@ -714,6 +726,21 @@ fun GeneralSettings(
}
)

if (search.input.isBlank() || stringResource(R.string.resume_playback).contains(search.input,true)) {
if (isAtLeastAndroid6) {
SwitchSettingEntry(
title = stringResource(R.string.resume_playback),
text = stringResource(R.string.when_device_is_connected),
isChecked = resumePlaybackWhenDeviceConnected,
onCheckedChange = {
resumePlaybackWhenDeviceConnected = it
restartService = true
}
)
RestartPlayerService(restartService, onRestart = { restartService = false })
}
}

if (search.input.isBlank() || stringResource(R.string.persistent_queue).contains(search.input,true)) {
SwitchSettingEntry(
title = stringResource(R.string.persistent_queue),
Expand Down Expand Up @@ -744,22 +771,6 @@ fun GeneralSettings(
}
}


if (search.input.isBlank() || stringResource(R.string.resume_playback).contains(search.input,true)) {
if (isAtLeastAndroid6) {
SwitchSettingEntry(
title = stringResource(R.string.resume_playback),
text = stringResource(R.string.when_device_is_connected),
isChecked = resumePlaybackWhenDeviceConnected,
onCheckedChange = {
resumePlaybackWhenDeviceConnected = it
restartService = true
}
)
RestartPlayerService(restartService, onRestart = { restartService = false })
}
}

if (search.input.isBlank() || stringResource(R.string.close_app_with_back_button).contains(search.input,true)) {
SwitchSettingEntry(
isEnabled = Build.VERSION.SDK_INT >= 33,
Expand Down Expand Up @@ -872,6 +883,64 @@ fun GeneralSettings(
}
}

if (search.input.isBlank() || "Bass boost".contains(search.input,true)) {
SwitchSettingEntry(
title = "Bass boost",
text = "Bass boost info",
isChecked = bassboostEnabled,
onCheckedChange = {
bassboostEnabled = it
}
)
AnimatedVisibility(visible = bassboostEnabled) {
val initialValue by remember { derivedStateOf { bassboostLevel } }
var newValue by remember(initialValue) { mutableFloatStateOf(initialValue) }


Column(
modifier = Modifier.padding(start = 25.dp)
) {
SliderSettingsEntry(
title = "Bass boost level",
text = "Bass boost level info",
state = newValue,
onSlide = { newValue = it },
onSlideComplete = {
bassboostLevel = newValue
},
toDisplay = { "%.1f".format(bassboostLevel).replace(",", ".") },
range = 0f..1f
)
}
}
}

if (search.input.isBlank() || "Audio reverb".contains(search.input,true)) {
EnumValueSelectorSettingsEntry(
title = "Audio Reverb",
text = "Apply a depth effect to the audio",
selectedValue = audioReverb,
onValueSelected = {
audioReverb = it
restartService = true
},
valueText = {
it.textName
}
)
RestartPlayerService(restartService, onRestart = { restartService = false } )
}

if (search.input.isBlank() || "Audio focus".contains(search.input,true)) {
SwitchSettingEntry(
title = "Audio focus",
text = "Allows automatic pause and resume playback after a call for example",
isChecked = audioFocusEnabled,
onCheckedChange = {
audioFocusEnabled = it
}
)
}

if (search.input.isBlank() || stringResource(R.string.event_volumekeys).contains(search.input,true)) {
SwitchSettingEntry(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ inline fun StringListValueSelectorSettingsEntry(
inline fun <reified T : Enum<T>> EnumValueSelectorSettingsEntry(
title: String,
titleSecondary: String? = null,
text: String? = null,
selectedValue: T,
noinline onValueSelected: (T) -> Unit,
modifier: Modifier = Modifier,
Expand All @@ -169,6 +170,7 @@ inline fun <reified T : Enum<T>> EnumValueSelectorSettingsEntry(
ValueSelectorSettingsEntry(
title = title,
titleSecondary = titleSecondary,
text = text,
selectedValue = selectedValue,
values = enumValues<T>().toList(),
onValueSelected = onValueSelected,
Expand All @@ -183,6 +185,7 @@ inline fun <reified T : Enum<T>> EnumValueSelectorSettingsEntry(
fun <T> ValueSelectorSettingsEntry(
title: String,
titleSecondary: String? = null,
text: String? = null,
selectedValue: T,
values: List<T>,
onValueSelected: (T) -> Unit,
Expand Down Expand Up @@ -215,6 +218,15 @@ fun <T> ValueSelectorSettingsEntry(
onClick = { isShowingDialog = true },
trailingContent = trailingContent
)

text?.let {
BasicText(
text = it,
style = typography().xs.semiBold.copy(color = colorPalette().textSecondary),
modifier = Modifier
.padding(start = 12.dp)
)
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,11 @@ const val ytAccountChannelHandleKey = "ytAccountChannelHandle"
const val ytAccountThumbnailKey = "ytAccountThumbnail"
const val filterByKey = "filterBy"

const val bassboostEnabledKey = "bassboostEnabled"
const val bassboostLevelKey = "bassboostLevel"
const val audioReverbPresetKey = "audioReverbPreset"
const val handleAudioFocusEnabledKey = "handleAudioFocusEnabled"

/*
@PublishedApi
internal val defaultJson = Json {
Expand Down

0 comments on commit ca72244

Please sign in to comment.