Improve volume normalization logic
This commit is contained in:
parent
61eeeb07c0
commit
f5716937d1
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue