Explorar el Código

Improve volume normalization logic

vfsfitvnm hace 3 años
padre
commit
f5716937d1

+ 3 - 0
app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt

@@ -114,6 +114,9 @@ interface Database {
     @Query("UPDATE SongPlaylistMap SET position = position + 1 WHERE playlistId = :playlistId AND position >= :fromPosition AND position <= :toPosition")
     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)
     fun insert(format: Format)
 

+ 38 - 27
app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt

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