Add "skip silence" feature
This commit is contained in:
@@ -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()
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user