Add "skip silence" feature

This commit is contained in:
vfsfitvnm
2022-06-12 19:05:35 +02:00
parent 81e2f2d9a9
commit 6c1e90cd0c
5 changed files with 40 additions and 19 deletions

View File

@@ -122,9 +122,7 @@ class MainActivity : ComponentActivity() {
LocalColorPalette provides colorPalette, LocalColorPalette provides colorPalette,
LocalShimmerTheme provides shimmerTheme, LocalShimmerTheme provides shimmerTheme,
LocalTypography provides rememberTypography(colorPalette.text), LocalTypography provides rememberTypography(colorPalette.text),
LocalYoutubePlayer provides rememberYoutubePlayer(mediaControllerFuture) { LocalYoutubePlayer provides rememberYoutubePlayer(mediaControllerFuture),
it.repeatMode = preferences.repeatMode
},
LocalMenuState provides rememberMenuState(), LocalMenuState provides rememberMenuState(),
LocalHapticFeedback provides rememberHapticFeedback() LocalHapticFeedback provides rememberHapticFeedback()
) { ) {

View File

@@ -56,6 +56,8 @@ val GetCacheSizeCommand = SessionCommand("GetCacheSizeCommand", Bundle.EMPTY)
val DeleteSongCacheCommand = SessionCommand("DeleteSongCacheCommand", Bundle.EMPTY) val DeleteSongCacheCommand = SessionCommand("DeleteSongCacheCommand", Bundle.EMPTY)
val SetSkipSilenceCommand = SessionCommand("SetSkipSilenceCommand", Bundle.EMPTY)
@ExperimentalAnimationApi @ExperimentalAnimationApi
@ExperimentalFoundationApi @ExperimentalFoundationApi
class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller, class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
@@ -70,6 +72,8 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
private lateinit var cache: SimpleCache private lateinit var cache: SimpleCache
private lateinit var player: ExoPlayer
private lateinit var mediaSession: MediaSession private lateinit var mediaSession: MediaSession
private lateinit var notificationManager: NotificationManager private lateinit var notificationManager: NotificationManager
@@ -90,7 +94,7 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
val cacheEvictor = LeastRecentlyUsedCacheEvictor(preferences.exoPlayerDiskCacheMaxSizeBytes) val cacheEvictor = LeastRecentlyUsedCacheEvictor(preferences.exoPlayerDiskCacheMaxSizeBytes)
cache = SimpleCache(cacheDir, cacheEvictor, StandaloneDatabaseProvider(this)) cache = SimpleCache(cacheDir, cacheEvictor, StandaloneDatabaseProvider(this))
val player = ExoPlayer.Builder(this) player = ExoPlayer.Builder(this)
.setHandleAudioBecomingNoisy(true) .setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_LOCAL) .setWakeMode(C.WAKE_MODE_LOCAL)
.setMediaSourceFactory(DefaultMediaSourceFactory(createDataSourceFactory())) .setMediaSourceFactory(DefaultMediaSourceFactory(createDataSourceFactory()))
@@ -102,10 +106,11 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
true true
) )
.build() .build()
.also { player ->
player.playWhenReady = true player.repeatMode = preferences.repeatMode
player.addAnalyticsListener(PlaybackStatsListener(false, this)) player.skipSilenceEnabled = preferences.skipSilence
} player.playWhenReady = true
player.addAnalyticsListener(PlaybackStatsListener(false, this))
mediaSession = MediaSession.Builder(this, player) mediaSession = MediaSession.Builder(this, player)
.withSessionActivity() .withSessionActivity()
@@ -146,9 +151,9 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
override fun onDestroy() { override fun onDestroy() {
if (preferences.persistentQueue) { if (preferences.persistentQueue) {
val mediaItems = mediaSession.player.currentTimeline.mediaItems val mediaItems = player.currentTimeline.mediaItems
val mediaItemIndex = mediaSession.player.currentMediaItemIndex val mediaItemIndex = player.currentMediaItemIndex
val mediaItemPosition = mediaSession.player.currentPosition val mediaItemPosition = player.currentPosition
Database.internal.queryExecutor.execute { Database.internal.queryExecutor.execute {
Database.clearQueuedMediaItems() Database.clearQueuedMediaItems()
@@ -163,7 +168,7 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
} }
} }
mediaSession.player.release() player.release()
mediaSession.release() mediaSession.release()
cache.release() cache.release()
super.onDestroy() super.onDestroy()
@@ -183,6 +188,7 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
.add(StopRadioCommand) .add(StopRadioCommand)
.add(GetCacheSizeCommand) .add(GetCacheSizeCommand)
.add(DeleteSongCacheCommand) .add(DeleteSongCacheCommand)
.add(SetSkipSilenceCommand)
.build() .build()
val playerCommands = Player.Commands.Builder().addAllCommands().build() val playerCommands = Player.Commands.Builder().addAllCommands().build()
return MediaSession.ConnectionResult.accept(sessionCommands, playerCommands) return MediaSession.ConnectionResult.accept(sessionCommands, playerCommands)
@@ -205,8 +211,8 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
).let { ).let {
coroutineScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
when (customCommand) { when (customCommand) {
StartRadioCommand -> mediaSession.player.addMediaItems(it.process().drop(1)) StartRadioCommand -> player.addMediaItems(it.process().drop(1))
StartArtistRadioCommand -> mediaSession.player.forcePlayFromBeginning(it.process()) StartArtistRadioCommand -> player.forcePlayFromBeginning(it.process())
} }
radio = it radio = it
} }
@@ -221,6 +227,9 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
cache.removeResource(videoId) cache.removeResource(videoId)
} }
} }
SetSkipSilenceCommand -> {
player.skipSilenceEnabled = args.getBoolean("skipSilence")
}
} }
return super.onCustomCommand(session, controller, customCommand, args) return super.onCustomCommand(session, controller, customCommand, args)
@@ -241,9 +250,9 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
radio?.let { radio -> radio?.let { radio ->
if (mediaSession.player.mediaItemCount - mediaSession.player.currentMediaItemIndex <= 3) { if (player.mediaItemCount - player.currentMediaItemIndex <= 3) {
coroutineScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {
mediaSession.player.addMediaItems(radio.process()) player.addMediaItems(radio.process())
} }
} }
} }

View File

@@ -9,13 +9,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import it.vfsfitvnm.route.RouteHandler import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.services.SetSkipSilenceCommand
import it.vfsfitvnm.vimusic.ui.components.TopAppBar import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.screens.* import it.vfsfitvnm.vimusic.ui.screens.*
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.LocalPreferences import it.vfsfitvnm.vimusic.utils.LocalPreferences
import it.vfsfitvnm.vimusic.utils.LocalYoutubePlayer
import it.vfsfitvnm.vimusic.utils.semiBold import it.vfsfitvnm.vimusic.utils.semiBold
@ExperimentalAnimationApi @ExperimentalAnimationApi
@@ -43,6 +46,7 @@ fun PlayerSettingsScreen() {
val colorPalette = LocalColorPalette.current val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current val typography = LocalTypography.current
val preferences = LocalPreferences.current val preferences = LocalPreferences.current
val mediaController = LocalYoutubePlayer.current?.mediaController
Column( Column(
modifier = Modifier modifier = Modifier
@@ -86,6 +90,16 @@ fun PlayerSettingsScreen() {
}, },
isEnabled = false isEnabled = false
) )
SwitchSettingEntry(
title = "Skip silence",
text = "Skip silent parts during playback",
isChecked = preferences.skipSilence,
onCheckedChange = {
mediaController?.sendCustomCommand(SetSkipSilenceCommand, bundleOf("skipSilence" to it))
preferences.skipSilence = it
}
)
} }
} }
} }

View File

@@ -26,6 +26,7 @@ class Preferences(holder: SharedPreferences) : SharedPreferences by holder {
var exoPlayerDiskCacheMaxSizeBytes by preference("exoPlayerDiskCacheMaxSizeBytes", 512L * 1024 * 1024) var exoPlayerDiskCacheMaxSizeBytes by preference("exoPlayerDiskCacheMaxSizeBytes", 512L * 1024 * 1024)
var displayLikeButtonInNotification by preference("displayLikeButtonInNotification", false) var displayLikeButtonInNotification by preference("displayLikeButtonInNotification", false)
var persistentQueue by preference("persistentQueue", false) var persistentQueue by preference("persistentQueue", false)
var skipSilence by preference("skipSilence", false)
} }
val Context.preferences: Preferences val Context.preferences: Preferences

View File

@@ -50,11 +50,10 @@ val LocalYoutubePlayer = compositionLocalOf<YoutubePlayer?> { null }
@Composable @Composable
fun rememberYoutubePlayer( fun rememberYoutubePlayer(
mediaControllerFuture: ListenableFuture<MediaController>, mediaControllerFuture: ListenableFuture<MediaController>
block: (MediaController) -> Unit,
): YoutubePlayer? { ): YoutubePlayer? {
val mediaController by produceState<MediaController?>(initialValue = null) { val mediaController by produceState<MediaController?>(initialValue = null) {
value = mediaControllerFuture.await().also(block) value = mediaControllerFuture.await()
} }
val playerState = remember(mediaController) { val playerState = remember(mediaController) {