Improve volume normalization logic

This commit is contained in:
vfsfitvnm 2022-07-18 14:36:57 +02:00
parent 61eeeb07c0
commit f5716937d1
2 changed files with 41 additions and 27 deletions

View file

@ -114,6 +114,9 @@ interface Database {
@Query("UPDATE SongPlaylistMap SET position = position + 1 WHERE playlistId = :playlistId AND position >= :fromPosition AND position <= :toPosition") @Query("UPDATE SongPlaylistMap SET position = position + 1 WHERE playlistId = :playlistId AND position >= :fromPosition AND position <= :toPosition")
fun incrementSongPositions(playlistId: Long, fromPosition: Int, toPosition: Int) fun incrementSongPositions(playlistId: Long, fromPosition: Int, toPosition: Int)
@Query("SELECT loudnessDb FROM Format WHERE songId = :songId")
fun loudnessDb(songId: String): Flow<Float?>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(format: Format) fun insert(format: Format)

View file

@ -51,6 +51,8 @@ import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.system.exitProcess import kotlin.system.exitProcess
import android.os.Binder as AndroidBinder import android.os.Binder as AndroidBinder
@ -85,7 +87,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
private val coroutineScope = CoroutineScope(Dispatchers.IO) + Job() private val coroutineScope = CoroutineScope(Dispatchers.IO) + Job()
private val songPendingLoudnessDb = mutableMapOf<String, Float?>() private var volumeNormalizationJob: Job? = null
private var isVolumeNormalizationEnabled = false private var isVolumeNormalizationEnabled = false
private var isPersistentQueueEnabled = false private var isPersistentQueueEnabled = false
@ -124,7 +126,8 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
isVolumeNormalizationEnabled = preferences.getBoolean(volumeNormalizationKey, false) isVolumeNormalizationEnabled = preferences.getBoolean(volumeNormalizationKey, false)
isInvincibilityEnabled = preferences.getBoolean(isInvincibilityEnabledKey, false) isInvincibilityEnabled = preferences.getBoolean(isInvincibilityEnabledKey, false)
val cacheEvictor = when (val size = preferences.getEnum(exoPlayerDiskCacheMaxSizeKey, ExoPlayerDiskCacheMaxSize.`2GB`)) { val cacheEvictor = when (val size =
preferences.getEnum(exoPlayerDiskCacheMaxSizeKey, ExoPlayerDiskCacheMaxSize.`2GB`)) {
ExoPlayerDiskCacheMaxSize.Unlimited -> NoOpCacheEvictor() ExoPlayerDiskCacheMaxSize.Unlimited -> NoOpCacheEvictor()
else -> LeastRecentlyUsedCacheEvictor(size.bytes) else -> LeastRecentlyUsedCacheEvictor(size.bytes)
} }
@ -221,7 +224,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
} }
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
normalizeVolume() maybeNormalizeVolume()
radio?.let { radio -> radio?.let { radio ->
if (player.mediaItemCount - player.currentMediaItemIndex <= 3) { if (player.mediaItemCount - player.currentMediaItemIndex <= 3) {
@ -283,17 +286,27 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
} }
} }
private fun normalizeVolume() { private fun maybeNormalizeVolume() {
player.volume = if (isVolumeNormalizationEnabled) { if (!isVolumeNormalizationEnabled) {
player.currentMediaItem?.let { mediaItem -> volumeNormalizationJob?.cancel()
songPendingLoudnessDb.getOrElse(mediaItem.mediaId) { player.volume = 1f
mediaItem.mediaMetadata.extras?.getFloatOrNull("loudnessDb") return
}?.takeIf { it > 0 }?.let { loudnessDb -> }
(1f - (0.01f + loudnessDb / 14)).coerceIn(0.1f, 1f)
} player.currentMediaItem?.mediaId?.let { songId ->
} ?: 1f volumeNormalizationJob?.cancel()
} else { volumeNormalizationJob = coroutineScope.launch(Dispatchers.IO) {
1f Database.loudnessDb(songId).cancellable().distinctUntilChanged()
.collect { loudnessDb ->
withContext(Dispatchers.Main) {
player.volume = if (loudnessDb != null && loudnessDb > 0) {
(1f - (0.01f + loudnessDb / 14)).coerceIn(0.1f, 1f)
} else {
1f
}
}
}
}
} }
} }
@ -368,7 +381,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
volumeNormalizationKey -> { volumeNormalizationKey -> {
isVolumeNormalizationEnabled = isVolumeNormalizationEnabled =
sharedPreferences.getBoolean(key, isVolumeNormalizationEnabled) sharedPreferences.getBoolean(key, isVolumeNormalizationEnabled)
normalizeVolume() maybeNormalizeVolume()
} }
isInvincibilityEnabledKey -> isInvincibilityEnabled = isInvincibilityEnabledKey -> isInvincibilityEnabled =
sharedPreferences.getBoolean(key, isInvincibilityEnabled) sharedPreferences.getBoolean(key, isInvincibilityEnabled)
@ -489,14 +502,6 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
val urlResult = runBlocking(Dispatchers.IO) { val urlResult = runBlocking(Dispatchers.IO) {
YouTube.player(videoId) YouTube.player(videoId)
}?.mapCatching { body -> }?.mapCatching { body ->
val loudnessDb = body.playerConfig?.audioConfig?.loudnessDb?.toFloat()
songPendingLoudnessDb[videoId] = loudnessDb
runBlocking(Dispatchers.Main) {
normalizeVolume()
}
when (val status = body.playabilityStatus.status) { when (val status = body.playabilityStatus.status) {
"OK" -> body.streamingData?.adaptiveFormats?.findLast { format -> "OK" -> body.streamingData?.adaptiveFormats?.findLast { format ->
format.itag == 251 || format.itag == 140 format.itag == 251 || format.itag == 140
@ -514,7 +519,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
itag = format.itag, itag = format.itag,
mimeType = format.mimeType, mimeType = format.mimeType,
bitrate = format.bitrate, bitrate = format.bitrate,
loudnessDb = loudnessDb, loudnessDb = body.playerConfig?.audioConfig?.loudnessDb?.toFloat(),
contentLength = format.contentLength, contentLength = format.contentLength,
lastModified = format.lastModified lastModified = format.lastModified
) )
@ -575,10 +580,16 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
.build() .build()
return RenderersFactory { handler: Handler?, _, audioListener: AudioRendererEventListener?, _, _ -> return RenderersFactory { handler: Handler?, _, audioListener: AudioRendererEventListener?, _, _ ->
arrayOf( arrayOf(
MediaCodecAudioRenderer(this, MediaCodecSelector.DEFAULT, handler, audioListener, audioSink) MediaCodecAudioRenderer(
this,
MediaCodecSelector.DEFAULT,
handler,
audioListener,
audioSink
) )
} )
}
} }
inner class Binder : AndroidBinder() { inner class Binder : AndroidBinder() {