Drop androidx.media3

This commit is contained in:
vfsfitvnm
2022-06-25 16:04:52 +02:00
parent 524bea60d9
commit 7e6b5747a2
24 changed files with 626 additions and 666 deletions

View File

@@ -20,14 +20,12 @@ import androidx.media3.common.Player
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.route.empty
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.internal
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongInPlaylist
import it.vfsfitvnm.vimusic.models.SongWithInfo
import it.vfsfitvnm.vimusic.services.DeleteSongCacheCommand
import it.vfsfitvnm.vimusic.services.StartRadioCommand
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.screens.rememberArtistRoute
import it.vfsfitvnm.vimusic.ui.screens.rememberCreatePlaylistRoute
@@ -66,7 +64,7 @@ fun InHistoryMediaItemMenu(
// https://issuetracker.google.com/issues/226410236
onDismiss: () -> Unit = LocalMenuState.current.let { it::hide }
) {
val mediaController = LocalYoutubePlayer.current?.mediaController
val binder = LocalPlayerServiceBinder.current
val coroutineScope = rememberCoroutineScope()
@@ -82,7 +80,7 @@ fun InHistoryMediaItemMenu(
},
onConfirm = {
onDismiss()
mediaController?.sendCustomCommand(DeleteSongCacheCommand, bundleOf("videoId" to song.song.id))
binder?.cache?.removeResource(song.song.id)
coroutineScope.launch(Dispatchers.IO) {
Database.delete(song.song)
}
@@ -147,32 +145,24 @@ fun NonQueuedMediaItemMenu(
onDeleteFromDatabase: (() -> Unit)? = null,
onRemoveFromFavorites: (() -> Unit)? = null,
) {
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
BaseMediaItemMenu(
mediaItem = mediaItem,
onDismiss = onDismiss,
onStartRadio = {
player?.mediaController?.run {
forcePlay(mediaItem)
sendCustomCommand(StartRadioCommand, bundleOf(
"videoId" to mediaItem.mediaId,
"playlistId" to mediaItem.mediaMetadata.extras?.getString("playlistId")
))
}
binder?.player?.forcePlay(mediaItem)
binder?.startRadio(videoId = mediaItem.mediaId, playlistId = mediaItem.mediaMetadata.extras?.getString("playlistId"))
},
onPlaySingle = {
player?.mediaController?.run {
sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
forcePlay(mediaItem)
}
binder?.player?.forcePlay(mediaItem)
},
onPlayNext = {
binder?.player?.addNext(mediaItem)
},
onEnqueue = {
binder?.player?.enqueue(mediaItem)
},
onPlayNext = if (player?.playbackState == Player.STATE_READY) ({
player.mediaController.addNext(mediaItem)
}) else null,
onEnqueue = if (player?.playbackState == Player.STATE_READY) ({
player.mediaController.enqueue(mediaItem)
}) else null,
onRemoveFromPlaylist = onRemoveFromPlaylist,
onDeleteFromDatabase = onDeleteFromDatabase,
onRemoveFromFavorites = onRemoveFromFavorites,
@@ -190,14 +180,14 @@ fun QueuedMediaItemMenu(
onDismiss: () -> Unit = LocalMenuState.current.let { it::hide },
onGlobalRouteEmitted: (() -> Unit)? = null
) {
val player = LocalYoutubePlayer.current
val player = LocalPlayerServiceBinder.current?.player
BaseMediaItemMenu(
mediaItem = mediaItem,
onDismiss = onDismiss,
onRemoveFromQueue = if (player?.mediaItemIndex != indexInQueue) ({
player?.mediaController?.removeMediaItem(indexInQueue)
}) else null,
onRemoveFromQueue = {
player?.removeMediaItem(indexInQueue)
},
onGlobalRouteEmitted = onGlobalRouteEmitted,
modifier = modifier
)

View File

@@ -1,6 +1,5 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.os.Bundle
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -27,10 +26,9 @@ import coil.compose.AsyncImage
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SongWithInfo
import it.vfsfitvnm.vimusic.services.StartArtistRadioCommand
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
@@ -79,7 +77,8 @@ fun ArtistScreen(
}
host {
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val density = LocalDensity.current
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
@@ -160,10 +159,7 @@ fun ArtistScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.sendCustomCommand(
StartArtistRadioCommand,
artist.shuffleEndpoint.asBundle
)
binder?.startRadio(artist.shuffleEndpoint)
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
@@ -180,10 +176,7 @@ fun ArtistScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.sendCustomCommand(
StartArtistRadioCommand,
artist.radioEndpoint.asBundle
)
binder?.startRadio(artist.radioEndpoint)
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
@@ -224,14 +217,8 @@ fun ArtistScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable(enabled = songs.isNotEmpty()) {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayFromBeginning(
songs
.shuffled()
.map(SongWithInfo::asMediaItem)
)
}
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(songs.shuffled().map(SongWithInfo::asMediaItem))
}
.padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp)
@@ -248,10 +235,8 @@ fun ArtistScreen(
song = song,
thumbnailSize = songThumbnailSizePx,
onClick = {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayAtIndex(songs.map(SongWithInfo::asMediaItem), index)
}
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songs.map(SongWithInfo::asMediaItem), index)
},
menuContent = {
InHistoryMediaItemMenu(song = song)

View File

@@ -1,7 +1,6 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.net.Uri
import android.os.Bundle
import androidx.compose.animation.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
@@ -32,15 +31,18 @@ import androidx.compose.ui.zIndex
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.route.fastFade
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.SongCollection
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.models.SongWithInfo
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.*
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.ui.views.PlaylistPreviewItem
@@ -144,7 +146,7 @@ fun HomeScreen() {
}
host {
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val density = LocalDensity.current
val thumbnailSize = remember {
@@ -357,10 +359,8 @@ fun HomeScreen() {
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable(enabled = songCollection.isNotEmpty()) {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayFromBeginning(songCollection.shuffled().map(SongWithInfo::asMediaItem))
}
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(songCollection.shuffled().map(SongWithInfo::asMediaItem))
}
.padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp)
@@ -379,10 +379,8 @@ fun HomeScreen() {
song = song,
thumbnailSize = thumbnailSize,
onClick = {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayAtIndex(songCollection.map(SongWithInfo::asMediaItem), index)
}
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(songCollection.map(SongWithInfo::asMediaItem), index)
},
menuContent = {
when (preferences.homePageSongCollection) {

View File

@@ -1,7 +1,6 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.net.Uri
import android.os.Bundle
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -18,16 +17,15 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import com.valentinilk.shimmer.ShimmerBounds
import com.valentinilk.shimmer.rememberShimmer
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.internal
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongInPlaylist
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.Error
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.Message
@@ -70,7 +68,7 @@ fun IntentUriScreen(uri: Uri) {
val menuState = LocalMenuState.current
val colorPalette = LocalColorPalette.current
val density = LocalDensity.current
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val coroutineScope = rememberCoroutineScope()
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
@@ -164,14 +162,13 @@ fun IntentUriScreen(uri: Uri) {
MenuEntry(
icon = R.drawable.time,
text = "Enqueue",
enabled = player?.playbackState == Player.STATE_READY,
onClick = {
menuState.hide()
items.valueOrNull
?.map(YouTube.Item.Song::asMediaItem)
?.let { mediaItems ->
player?.mediaController?.enqueue(
binder?.player?.enqueue(
mediaItems
)
}
@@ -238,10 +235,8 @@ fun IntentUriScreen(uri: Uri) {
song = item,
thumbnailSizePx = density.run { 54.dp.roundToPx() },
onClick = {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayAtIndex(currentItems.value.map(YouTube.Item.Song::asMediaItem), index)
}
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(currentItems.value.map(YouTube.Item.Song::asMediaItem), index)
}
)
}

View File

@@ -1,6 +1,5 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.os.Bundle
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -22,16 +21,15 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import it.vfsfitvnm.reordering.rememberReorderingState
import it.vfsfitvnm.reordering.verticalDragAfterLongPressToReorder
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
import it.vfsfitvnm.vimusic.models.SongInPlaylist
import it.vfsfitvnm.vimusic.models.SongWithInfo
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.*
@@ -76,7 +74,7 @@ fun LocalPlaylistScreen(
val hapticFeedback = LocalHapticFeedback.current
val menuState = LocalMenuState.current
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
@@ -163,14 +161,10 @@ fun LocalPlaylistScreen(
MenuEntry(
icon = R.drawable.time,
text = "Enqueue",
enabled = playlistWithSongs.songs.isNotEmpty() && player?.playbackState == Player.STATE_READY,
enabled = playlistWithSongs.songs.isNotEmpty(),
onClick = {
menuState.hide()
player?.mediaController?.enqueue(
playlistWithSongs.songs.map(
SongWithInfo::asMediaItem
)
)
binder?.player?.enqueue(playlistWithSongs.songs.map(SongWithInfo::asMediaItem))
}
)
@@ -234,10 +228,8 @@ fun LocalPlaylistScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem).shuffled())
}
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem).shuffled())
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
@@ -254,10 +246,8 @@ fun LocalPlaylistScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem))
}
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem))
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
@@ -280,10 +270,8 @@ fun LocalPlaylistScreen(
song = song,
thumbnailSize = thumbnailSize,
onClick = {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayAtIndex(playlistWithSongs.songs.map(SongWithInfo::asMediaItem), index)
}
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(playlistWithSongs.songs.map(SongWithInfo::asMediaItem), index)
},
menuContent = {
InPlaylistMediaItemMenu(

View File

@@ -1,7 +1,6 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.content.Intent
import android.os.Bundle
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -25,17 +24,16 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import coil.compose.AsyncImage
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.vimusic.internal
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongInPlaylist
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
@@ -87,7 +85,8 @@ fun PlaylistOrAlbumScreen(
host {
val context = LocalContext.current
val density = LocalDensity.current
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
val menuState = LocalMenuState.current
@@ -142,7 +141,6 @@ fun PlaylistOrAlbumScreen(
MenuEntry(
icon = R.drawable.time,
text = "Enqueue",
enabled = player?.playbackState == Player.STATE_READY,
onClick = {
menuState.hide()
playlistOrAlbum.valueOrNull?.let { album ->
@@ -151,7 +149,7 @@ fun PlaylistOrAlbumScreen(
song.toMediaItem(browseId, album)
}
?.let { mediaItems ->
player?.mediaController?.enqueue(
binder?.player?.enqueue(
mediaItems
)
}
@@ -282,16 +280,14 @@ fun PlaylistOrAlbumScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
playlistOrAlbum.items
?.shuffled()
?.mapNotNull { song ->
song.toMediaItem(browseId, playlistOrAlbum)
}?.let { mediaItems ->
it.forcePlayFromBeginning(mediaItems)
}
}
binder?.stopRadio()
playlistOrAlbum.items
?.shuffled()
?.mapNotNull { song ->
song.toMediaItem(browseId, playlistOrAlbum)
}?.let { mediaItems ->
binder?.player?.forcePlayFromBeginning(mediaItems)
}
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
@@ -308,13 +304,11 @@ fun PlaylistOrAlbumScreen(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
playlistOrAlbum.items?.mapNotNull { song ->
song.toMediaItem(browseId, playlistOrAlbum)
}?.let { mediaItems ->
it.forcePlayFromBeginning(mediaItems)
}
binder?.stopRadio()
playlistOrAlbum.items?.mapNotNull { song ->
song.toMediaItem(browseId, playlistOrAlbum)
}?.let { mediaItems ->
binder?.player?.forcePlayFromBeginning(mediaItems)
}
}
.shadow(elevation = 2.dp, shape = CircleShape)
@@ -340,13 +334,11 @@ fun PlaylistOrAlbumScreen(
authors = (song.authors ?: playlistOrAlbum.valueOrNull?.authors)?.joinToString("") { it.name },
durationText = song.durationText,
onClick = {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
playlistOrAlbum.valueOrNull?.items?.mapNotNull { song ->
song.toMediaItem(browseId, playlistOrAlbum.valueOrNull!!)
}?.let { mediaItems ->
it.forcePlayAtIndex(mediaItems, index)
}
binder?.stopRadio()
playlistOrAlbum.valueOrNull?.items?.mapNotNull { song ->
song.toMediaItem(browseId, playlistOrAlbum.valueOrNull!!)
}?.let { mediaItems ->
binder?.player?.forcePlayAtIndex(mediaItems, index)
}
},
startContent = {

View File

@@ -1,6 +1,5 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.os.Bundle
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -26,16 +25,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import coil.compose.AsyncImage
import com.valentinilk.shimmer.Shimmer
import com.valentinilk.shimmer.ShimmerBounds
import com.valentinilk.shimmer.rememberShimmer
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.vimusic.services.StartRadioCommand
import it.vfsfitvnm.vimusic.ui.components.*
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
@@ -45,7 +43,6 @@ import it.vfsfitvnm.vimusic.ui.views.SongItem
import it.vfsfitvnm.vimusic.utils.*
import it.vfsfitvnm.youtubemusic.Outcome
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -60,7 +57,7 @@ fun SearchResultScreen(
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
val preferences = LocalPreferences.current
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val lazyListState = rememberLazyListState()
@@ -218,13 +215,13 @@ fun SearchResultScreen(
is YouTube.Item.Album -> playlistOrAlbumRoute(item.info.endpoint!!.browseId)
is YouTube.Item.Artist -> artistRoute(item.info.endpoint!!.browseId)
is YouTube.Item.Playlist -> playlistOrAlbumRoute(item.info.endpoint!!.browseId)
is YouTube.Item.Song -> player?.mediaController?.let {
it.forcePlay(item.asMediaItem)
it.sendCustomCommand(StartRadioCommand, item.info.endpoint.asBundle)
is YouTube.Item.Song -> {
binder?.player?.forcePlay(item.asMediaItem)
binder?.startRadio(item.info.endpoint)
}
is YouTube.Item.Video -> player?.mediaController?.let {
it.forcePlay(item.asMediaItem)
it.sendCustomCommand(StartRadioCommand, item.info.endpoint.asBundle)
is YouTube.Item.Video -> {
binder?.player?.forcePlay(item.asMediaItem)
binder?.startRadio(item.info.endpoint)
}
}
}
@@ -573,13 +570,3 @@ fun SmallArtistItem(
)
}
}
val NavigationEndpoint.Endpoint.Watch?.asBundle: Bundle
get() = this?.let {
bundleOf(
"videoId" to videoId,
"playlistId" to playlistId,
"playlistSetVideoId" to playlistSetVideoId,
"params" to params,
)
} ?: Bundle.EMPTY

View File

@@ -1,6 +1,5 @@
package it.vfsfitvnm.vimusic.ui.screens.settings
import android.os.Bundle
import android.text.format.Formatter
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.*
@@ -16,19 +15,17 @@ import androidx.compose.ui.unit.dp
import coil.Coil
import coil.annotation.ExperimentalCoilApi
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.services.GetCacheSizeCommand
import it.vfsfitvnm.vimusic.ui.components.SeekBar
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.screens.*
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.LocalPreferences
import it.vfsfitvnm.vimusic.utils.LocalYoutubePlayer
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
@OptIn(ExperimentalCoilApi::class)
@@ -58,9 +55,7 @@ fun OtherSettingsScreen() {
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
val preferences = LocalPreferences.current
val mediaController = LocalYoutubePlayer.current?.mediaController
val coilDiskCache = Coil.imageLoader(context).diskCache
val binder = LocalPlayerServiceBinder.current
val coroutineScope = rememberCoroutineScope()
@@ -97,7 +92,7 @@ fun OtherSettingsScreen() {
)
}
coilDiskCache?.let { diskCache ->
Coil.imageLoader(context).diskCache?.let { diskCache ->
var diskCacheSize by remember(diskCache) {
mutableStateOf(diskCache.size)
}
@@ -168,9 +163,11 @@ fun OtherSettingsScreen() {
)
}
mediaController?.let { mediaController ->
val diskCacheSize by produceState(initialValue = 0L) {
value = mediaController.sendCustomCommand(GetCacheSizeCommand, Bundle.EMPTY).await().extras.getLong("cacheSize")
binder?.cache?.let { cache ->
val diskCacheSize by remember {
derivedStateOf {
cache.cacheSpace
}
}
var scrubbingDiskCacheMaxSize by remember {

View File

@@ -2,7 +2,6 @@ package it.vfsfitvnm.vimusic.ui.screens.settings
import android.content.Intent
import android.media.audiofx.AudioEffect
import android.os.Bundle
import android.text.format.DateUtils
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -18,12 +17,9 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.media3.common.C
import androidx.media3.session.SessionResult
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.services.*
import it.vfsfitvnm.vimusic.ui.components.ChunkyButton
import it.vfsfitvnm.vimusic.ui.components.Pager
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
@@ -32,10 +28,10 @@ import it.vfsfitvnm.vimusic.ui.components.themed.DefaultDialog
import it.vfsfitvnm.vimusic.ui.screens.*
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.utils.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.isActive
import it.vfsfitvnm.vimusic.utils.LocalPreferences
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.semiBold
import kotlinx.coroutines.flow.flowOf
@ExperimentalAnimationApi
@@ -64,39 +60,23 @@ fun PlayerSettingsScreen() {
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
val preferences = LocalPreferences.current
val mediaController = LocalYoutubePlayer.current?.mediaController
val binder = LocalPlayerServiceBinder.current
val activityResultLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
}
val audioSessionId by produceState(initialValue = C.AUDIO_SESSION_ID_UNSET, mediaController) {
val audioSessionId = remember(binder) {
val hasEqualizer = context.packageManager.resolveActivity(
Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL),
0
) != null
if (hasEqualizer) {
value =
mediaController?.sendCustomCommand(GetAudioSessionIdCommand, Bundle.EMPTY)
?.await()?.extras?.getInt("audioSessionId", C.AUDIO_SESSION_ID_UNSET)
?: C.AUDIO_SESSION_ID_UNSET
}
if (hasEqualizer) binder?.player?.audioSessionId else null
}
var sleepTimerMillisLeft by remember {
mutableStateOf<Long?>(null)
}
LaunchedEffect(mediaController) {
while (isActive) {
sleepTimerMillisLeft =
mediaController?.syncCommand(GetSleepTimerMillisLeftCommand)
?.takeIf { it.resultCode == SessionResult.RESULT_SUCCESS }
?.extras?.getLong("millisLeft")
delay(1000)
}
}
val sleepTimerMillisLeft by (binder?.sleepTimerMillisLeft
?: flowOf(null)).collectAsState(initial = null)
var isShowingSleepTimerDialog by remember {
mutableStateOf(false)
@@ -112,8 +92,7 @@ fun PlayerSettingsScreen() {
isShowingSleepTimerDialog = false
},
onConfirm = {
mediaController?.syncCommand(CancelSleepTimerCommand)
sleepTimerMillisLeft = null
binder?.cancelSleepTimer()
}
)
} else {
@@ -199,14 +178,7 @@ fun PlayerSettingsScreen() {
shape = RoundedCornerShape(36.dp),
isEnabled = hours > 0 || minutes > 0,
onClick = {
mediaController?.syncCommand(
SetSleepTimerCommand,
bundleOf("delayMillis" to (hours * 60 + minutes * 15) * 60 * 1000L)
)
sleepTimerMillisLeft =
mediaController?.syncCommand(GetSleepTimerMillisLeftCommand)?.extras?.getLong(
"millisLeft"
)
binder?.startSleepTimer((hours * 60 + minutes * 15) * 60 * 1000L)
isShowingSleepTimerDialog = false
}
)
@@ -253,10 +225,7 @@ fun PlayerSettingsScreen() {
text = "Skip silent parts during playback",
isChecked = preferences.skipSilence,
onCheckedChange = {
mediaController?.sendCustomCommand(
SetSkipSilenceCommand,
bundleOf("skipSilence" to it)
)
binder?.player?.skipSilenceEnabled = it
preferences.skipSilence = it
}
)
@@ -284,7 +253,7 @@ fun PlayerSettingsScreen() {
}
)
},
isEnabled = audioSessionId != C.AUDIO_SESSION_ID_UNSET && audioSessionId != AudioEffect.ERROR_BAD_VALUE
isEnabled = audioSessionId != null
)
SettingsEntry(

View File

@@ -6,14 +6,15 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -25,33 +26,29 @@ import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import com.valentinilk.shimmer.ShimmerBounds
import com.valentinilk.shimmer.rememberShimmer
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.components.BottomSheetState
import it.vfsfitvnm.vimusic.ui.components.Error
import it.vfsfitvnm.vimusic.ui.components.MusicBars
import it.vfsfitvnm.vimusic.ui.components.themed.QueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.screens.SmallSongItemShimmer
import it.vfsfitvnm.vimusic.ui.styling.LightColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.utils.LocalYoutubePlayer
import it.vfsfitvnm.vimusic.utils.YoutubePlayer
import it.vfsfitvnm.reordering.rememberReorderingState
import it.vfsfitvnm.reordering.verticalDragAfterLongPressToReorder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.youtubemusic.Outcome
import kotlinx.coroutines.launch
import it.vfsfitvnm.vimusic.ui.components.BottomSheetState
import it.vfsfitvnm.vimusic.ui.components.MusicBars
import it.vfsfitvnm.vimusic.ui.components.themed.QueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.styling.LightColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.utils.PlayerState
@ExperimentalAnimationApi
@Composable
fun CurrentPlaylistView(
player: Player?,
playerState: PlayerState?,
layoutState: BottomSheetState,
onGlobalRouteEmitted: () -> Unit,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
val density = LocalDensity.current
val player = LocalYoutubePlayer.current
val colorPalette = LocalColorPalette.current
val thumbnailSize = remember {
@@ -61,30 +58,26 @@ fun CurrentPlaylistView(
}
val isPaused by derivedStateOf {
player?.playbackState == Player.STATE_ENDED || player?.playWhenReady == false
playerState?.playbackState == Player.STATE_ENDED || playerState?.playWhenReady == false
}
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
val coroutineScope = rememberCoroutineScope()
val lazyListState =
rememberLazyListState(initialFirstVisibleItemIndex = player?.mediaItemIndex ?: 0)
rememberLazyListState(initialFirstVisibleItemIndex = playerState?.mediaItemIndex ?: 0)
val reorderingState = rememberReorderingState(player?.mediaItems ?: emptyList())
val reorderingState = rememberReorderingState(playerState?.mediaItems ?: emptyList())
LazyColumn(
state = lazyListState,
modifier = modifier
.nestedScroll(remember {
layoutState.nestedScrollConnection(player?.mediaItemIndex == 0)
layoutState.nestedScrollConnection(playerState?.mediaItemIndex == 0)
})
) {
itemsIndexed(
items = player?.mediaItems ?: emptyList()
items = playerState?.mediaItems ?: emptyList()
) { index, mediaItem ->
val isPlayingThisMediaItem by derivedStateOf {
player?.mediaItemIndex == index
playerState?.mediaItemIndex == index
}
SongItem(
@@ -93,13 +86,13 @@ fun CurrentPlaylistView(
onClick = {
if (isPlayingThisMediaItem) {
if (isPaused) {
player?.mediaController?.play()
player?.play()
} else {
player?.mediaController?.pause()
player?.pause()
}
} else {
player?.mediaController?.playWhenReady = true
player?.mediaController?.seekToDefaultPosition(index)
player?.playWhenReady = true
player?.seekToDefaultPosition(index)
}
},
menuContent = {
@@ -151,7 +144,7 @@ fun CurrentPlaylistView(
)
},
onDragEnd = { reachedIndex ->
player?.mediaController?.moveMediaItem(index, reachedIndex)
player?.moveMediaItem(index, reachedIndex)
}
)
)
@@ -165,7 +158,7 @@ fun CurrentPlaylistView(
// SideEffect {
// coroutineScope.launch {
// YoutubePlayer.Radio.process(
// player.mediaController,
// playerState.mediaController,
// force = true
// )
// }
@@ -194,7 +187,7 @@ fun CurrentPlaylistView(
// error = nextContinuation.error,
// onRetry = {
// coroutineScope.launch {
// YoutubePlayer.Radio.process(player.mediaController, force = true)
// YoutubePlayer.Radio.process(playerState.mediaController, force = true)
// }
// }
// )

View File

@@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import it.vfsfitvnm.route.Route
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.route.empty
@@ -44,13 +45,13 @@ import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
fun PlayerBottomSheet(
player: Player?,
playerState: PlayerState?,
layoutState: BottomSheetState,
song: Song?,
onGlobalRouteEmitted: () -> Unit,
modifier: Modifier = Modifier,
) {
val player = LocalYoutubePlayer.current ?: return
val colorPalette = LocalColorPalette.current
val typography = LocalTypography.current
@@ -60,7 +61,7 @@ fun PlayerBottomSheet(
var route by rememberRoute()
var nextOutcome by remember(player.mediaItem!!.mediaId) {
var nextOutcome by remember(playerState?.mediaItem?.mediaId) {
mutableStateOf<Outcome<YouTube.NextResult>>(Outcome.Initial)
}
@@ -183,13 +184,19 @@ fun PlayerBottomSheet(
coroutineScope.launch(Dispatchers.Main) {
lyricsOutcome = Outcome.Loading
val mediaItem = player?.currentMediaItem!!
if (nextOutcome.isEvaluable) {
nextOutcome = Outcome.Loading
val mediaItemIndex = player.currentMediaItemIndex
nextOutcome = withContext(Dispatchers.IO) {
YouTube.next(
player.mediaItem!!.mediaId,
player.mediaItem!!.mediaMetadata.extras?.getString("playlistId"),
player.mediaItemIndex
mediaItem.mediaId,
mediaItem.mediaMetadata.extras?.getString("playlistId"),
mediaItemIndex
)
}
}
@@ -200,7 +207,7 @@ fun PlayerBottomSheet(
lyrics ?: ""
}.map { lyrics ->
withContext(Dispatchers.IO) {
(song ?: player.mediaItem?.let(Database::insert))?.let {
(song ?: mediaItem.let(Database::insert)).let {
Database.update(it.copy(lyrics = lyrics))
}
}
@@ -209,7 +216,7 @@ fun PlayerBottomSheet(
}
},
onSearchOnline = {
player.mediaMetadata.let {
player?.mediaMetadata?.let {
context.startActivity(Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(
SearchManager.QUERY,
@@ -219,8 +226,9 @@ fun PlayerBottomSheet(
}
},
onLyricsUpdate = { lyrics ->
val mediaItem = player?.currentMediaItem
coroutineScope.launch(Dispatchers.IO) {
(song ?: player.mediaItem?.let(Database::insert))?.let {
(song ?: mediaItem?.let(Database::insert))?.let {
Database.update(it.copy(lyrics = lyrics))
}
}
@@ -230,6 +238,8 @@ fun PlayerBottomSheet(
host {
CurrentPlaylistView(
player = player,
playerState = playerState,
layoutState = layoutState,
onGlobalRouteEmitted = onGlobalRouteEmitted,
modifier = Modifier

View File

@@ -30,15 +30,16 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.datasource.cache.Cache
import androidx.media3.datasource.cache.CacheSpan
import coil.compose.AsyncImage
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
import it.vfsfitvnm.vimusic.services.GetSongCacheSizeCommand
import it.vfsfitvnm.vimusic.ui.components.*
import it.vfsfitvnm.vimusic.ui.components.themed.QueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.styling.BlackColorPalette
@@ -67,12 +68,15 @@ fun PlayerView(
val typography = LocalTypography.current
val density = LocalDensity.current
val configuration = LocalConfiguration.current
val player = LocalYoutubePlayer.current
val binder = LocalPlayerServiceBinder.current
val context = LocalContext.current
val player = binder?.player
val playerState = rememberYoutubePlayer(player)
val coroutineScope = rememberCoroutineScope()
player?.mediaItem ?: return
playerState?.mediaItem ?: return
val smallThumbnailSize = remember {
density.run { 64.dp.roundToPx() }
@@ -108,7 +112,7 @@ fun PlayerView(
y = 1.dp.toPx()
),
end = Offset(
x = ((size.width - offset) * player.progress) + offset,
x = ((size.width - offset) * playerState.progress) + offset,
y = 1.dp.toPx()
),
strokeWidth = 2.dp.toPx()
@@ -116,7 +120,7 @@ fun PlayerView(
}
) {
AsyncImage(
model = player.mediaMetadata.artworkUri.thumbnail(smallThumbnailSize),
model = playerState.mediaMetadata.artworkUri.thumbnail(smallThumbnailSize),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
@@ -129,13 +133,13 @@ fun PlayerView(
.weight(1f)
) {
BasicText(
text = player.mediaMetadata.title?.toString() ?: "",
text = playerState.mediaMetadata.title?.toString() ?: "",
style = typography.xs.semiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
BasicText(
text = player.mediaMetadata.artist?.toString() ?: "",
text = playerState.mediaMetadata.artist?.toString() ?: "",
style = typography.xs,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@@ -143,16 +147,16 @@ fun PlayerView(
}
when {
player.playbackState == Player.STATE_ENDED || !player.playWhenReady -> Image(
playerState.playbackState == Player.STATE_ENDED || !playerState.playWhenReady -> Image(
painter = painterResource(R.drawable.play),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
if (player.playbackState == Player.STATE_IDLE) {
player.mediaController.prepare()
if (playerState.playbackState == Player.STATE_IDLE) {
player?.prepare()
}
player.mediaController.play()
player?.play()
}
.padding(vertical = 8.dp)
.padding(horizontal = 16.dp)
@@ -164,7 +168,7 @@ fun PlayerView(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player.mediaController.pause()
player?.pause()
}
.padding(vertical = 8.dp)
.padding(horizontal = 16.dp)
@@ -175,10 +179,11 @@ fun PlayerView(
}
}
) {
val song by remember(player.mediaItem?.mediaId) {
player.mediaItem?.mediaId?.let(Database::songFlow)?.distinctUntilChanged() ?: flowOf(
null
)
val song by remember(playerState.mediaItem?.mediaId) {
playerState.mediaItem?.mediaId?.let(Database::songFlow)?.distinctUntilChanged()
?: flowOf(
null
)
}.collectAsState(initial = null, context = Dispatchers.IO)
var isShowingStatsForNerds by rememberSaveable {
@@ -192,7 +197,7 @@ fun PlayerView(
.padding(bottom = 72.dp)
.fillMaxSize()
) {
var scrubbingPosition by remember(player.mediaItemIndex) {
var scrubbingPosition by remember(playerState.mediaItemIndex) {
mutableStateOf<Long?>(null)
}
@@ -211,8 +216,8 @@ fun PlayerView(
.clickable {
menuState.display {
QueuedMediaItemMenu(
mediaItem = player.mediaItem ?: MediaItem.EMPTY,
indexInQueue = player.mediaItemIndex,
mediaItem = playerState.mediaItem ?: MediaItem.EMPTY,
indexInQueue = playerState.mediaItemIndex,
onDismiss = menuState::hide,
onGlobalRouteEmitted = layoutState.collapse
)
@@ -223,9 +228,9 @@ fun PlayerView(
)
}
if (player.error == null) {
if (playerState.error == null) {
AnimatedContent(
targetState = player.mediaItemIndex,
targetState = playerState.mediaItemIndex,
transitionSpec = {
val slideDirection =
if (targetState > initialState) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right
@@ -240,7 +245,7 @@ fun PlayerView(
.align(Alignment.CenterHorizontally)
) {
val artworkUri = remember(it) {
player.mediaController.getMediaItemAt(it).mediaMetadata.artworkUri.thumbnail(
player?.getMediaItemAt(it)?.mediaMetadata?.artworkUri.thumbnail(
thumbnailSizePx
)
}
@@ -273,13 +278,37 @@ fun PlayerView(
enter = fadeIn(),
exit = fadeOut(),
) {
val cachedPercentage = remember(song?.contentLength) {
song?.contentLength?.let { contentLength ->
player.mediaController.syncCommand(
GetSongCacheSizeCommand,
bundleOf("videoId" to song?.id)
).extras.getLong("cacheSize").toFloat() / contentLength * 100
}?.roundToInt() ?: 0
var cachedBytes by remember(song?.id) {
mutableStateOf(binder?.cache?.getCachedBytes(song?.id ?: "", 0, -1) ?: 0L)
}
DisposableEffect(song?.id) {
val listener = object : Cache.Listener {
override fun onSpanAdded(cache: Cache, span: CacheSpan) {
cachedBytes += span.length
}
override fun onSpanRemoved(cache: Cache, span: CacheSpan) {
cachedBytes -= span.length
}
override fun onSpanTouched(
cache: Cache,
oldSpan: CacheSpan,
newSpan: CacheSpan
) = Unit
}
song?.id?.let { key ->
binder?.cache?.addListener(key, listener)
}
onDispose {
song?.id?.let { key ->
binder?.cache?.removeListener(key, listener)
}
}
}
Column(
@@ -321,7 +350,7 @@ fun PlayerView(
Column {
BasicText(
text = "${player.volume.times(100).roundToInt()}%",
text = "${playerState.volume.times(100).roundToInt()}%",
style = typography.xs.semiBold.color(BlackColorPalette.text)
)
BasicText(
@@ -332,12 +361,21 @@ fun PlayerView(
)
BasicText(
text = song?.contentLength?.let { contentLength ->
Formatter.formatShortFileSize(context, contentLength)
Formatter.formatShortFileSize(
context,
contentLength
)
} ?: "Unknown",
style = typography.xs.semiBold.color(BlackColorPalette.text)
)
BasicText(
text = "$cachedPercentage%",
text = buildString {
append(Formatter.formatShortFileSize(context, cachedBytes))
song?.contentLength?.let { contentLenght ->
append(" (${(cachedBytes.toFloat() / contentLenght * 100).roundToInt()}%)")
}
},
style = typography.xs.semiBold.color(BlackColorPalette.text)
)
}
@@ -354,16 +392,20 @@ fun PlayerView(
onClick = {
song?.let { song ->
coroutineScope.launch(Dispatchers.IO) {
YouTube.player(song.id).map { body ->
Database.update(
song.copy(
loudnessDb = body.playerConfig?.audioConfig?.loudnessDb?.toFloat(),
contentLength = body.streamingData?.adaptiveFormats?.findLast { format ->
format.itag == 251 || format.itag == 140
}?.let(PlayerResponse.StreamingData.AdaptiveFormat::contentLength)
YouTube
.player(song.id)
.map { body ->
Database.update(
song.copy(
loudnessDb = body.playerConfig?.audioConfig?.loudnessDb?.toFloat(),
contentLength = body.streamingData?.adaptiveFormats
?.findLast { format ->
format.itag == 251 || format.itag == 140
}
?.let(PlayerResponse.StreamingData.AdaptiveFormat::contentLength)
)
)
)
}
}
}
}
}
@@ -387,18 +429,18 @@ fun PlayerView(
.size(thumbnailSizeDp)
) {
Error(
error = Outcome.Error.Unhandled(player.error!!),
error = Outcome.Error.Unhandled(playerState.error!!),
onRetry = {
player.mediaController.playWhenReady = true
player.mediaController.prepare()
player.error = null
player?.playWhenReady = true
player?.prepare()
playerState.error = null
}
)
}
}
BasicText(
text = player.mediaMetadata.title?.toString() ?: "",
text = playerState.mediaMetadata.title?.toString() ?: "",
style = typography.l.bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@@ -407,7 +449,7 @@ fun PlayerView(
)
BasicText(
text = player.mediaMetadata.extras?.getStringArrayList("artistNames")
text = playerState.mediaMetadata.extras?.getStringArrayList("artistNames")
?.joinToString("") ?: "",
style = typography.s.semiBold.secondary,
maxLines = 1,
@@ -417,24 +459,24 @@ fun PlayerView(
)
SeekBar(
value = scrubbingPosition ?: player.currentPosition,
value = scrubbingPosition ?: playerState.currentPosition,
minimumValue = 0,
maximumValue = player.duration,
maximumValue = playerState.duration,
onDragStart = {
scrubbingPosition = it
},
onDrag = { delta ->
scrubbingPosition = if (player.duration != C.TIME_UNSET) {
scrubbingPosition?.plus(delta)?.coerceIn(0, player.duration)
scrubbingPosition = if (playerState.duration != C.TIME_UNSET) {
scrubbingPosition?.plus(delta)?.coerceIn(0, playerState.duration)
} else {
null
}
},
onDragEnd = {
player.mediaController.seekTo(
scrubbingPosition ?: player.mediaController.currentPosition
)
player.currentPosition = player.mediaController.currentPosition
scrubbingPosition?.let { scrubbingPosition ->
player?.seekTo(scrubbingPosition)
playerState.currentPosition = scrubbingPosition
}
scrubbingPosition = null
},
color = colorPalette.text,
@@ -456,16 +498,16 @@ fun PlayerView(
) {
BasicText(
text = DateUtils.formatElapsedTime(
(scrubbingPosition ?: player.currentPosition) / 1000
(scrubbingPosition ?: playerState.currentPosition) / 1000
),
style = typography.xxs.semiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (player.duration != C.TIME_UNSET) {
if (playerState.duration != C.TIME_UNSET) {
BasicText(
text = DateUtils.formatElapsedTime(player.duration / 1000),
text = DateUtils.formatElapsedTime(playerState.duration / 1000),
style = typography.xxs.semiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@@ -488,7 +530,7 @@ fun PlayerView(
modifier = Modifier
.clickable {
coroutineScope.launch(Dispatchers.IO) {
(song ?: player.mediaItem?.let(Database::insert))?.let {
(song ?: playerState.mediaItem?.let(Database::insert))?.let {
Database.update(it.toggleLike())
}
}
@@ -503,24 +545,24 @@ fun PlayerView(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player.mediaController.seekToPrevious()
player?.seekToPrevious()
}
.padding(horizontal = 16.dp)
.size(32.dp)
)
when {
player.playbackState == Player.STATE_ENDED || !player.playWhenReady -> Image(
playerState.playbackState == Player.STATE_ENDED || !playerState.playWhenReady -> Image(
painter = painterResource(R.drawable.play_circle),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
if (player.playbackState == Player.STATE_IDLE) {
player.mediaController.prepare()
if (player?.playbackState == Player.STATE_IDLE) {
player.prepare()
}
player.mediaController.play()
player?.play()
}
.size(64.dp)
)
@@ -530,7 +572,7 @@ fun PlayerView(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player.mediaController.pause()
player?.pause()
}
.size(64.dp)
)
@@ -542,7 +584,7 @@ fun PlayerView(
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player.mediaController.seekToNext()
player?.seekToNext()
}
.padding(horizontal = 16.dp)
.size(32.dp)
@@ -551,7 +593,7 @@ fun PlayerView(
Image(
painter = painterResource(
if (player.repeatMode == Player.REPEAT_MODE_ONE) {
if (playerState.repeatMode == Player.REPEAT_MODE_ONE) {
R.drawable.repeat_one
} else {
R.drawable.repeat
@@ -559,7 +601,7 @@ fun PlayerView(
),
contentDescription = null,
colorFilter = ColorFilter.tint(
if (player.repeatMode == Player.REPEAT_MODE_OFF) {
if (playerState.repeatMode == Player.REPEAT_MODE_OFF) {
colorPalette.textDisabled
} else {
colorPalette.text
@@ -567,10 +609,13 @@ fun PlayerView(
),
modifier = Modifier
.clickable {
player.mediaController.repeatMode =
(player.mediaController.repeatMode + 2) % 3
preferences.repeatMode = player.mediaController.repeatMode
player?.repeatMode
?.plus(2)
?.mod(3)
?.let { repeatMode ->
player.repeatMode = repeatMode
preferences.repeatMode = repeatMode
}
}
.padding(horizontal = 16.dp)
.size(28.dp)
@@ -579,6 +624,8 @@ fun PlayerView(
}
PlayerBottomSheet(
player = player,
playerState = playerState,
layoutState = rememberBottomSheetState(64.dp, layoutState.upperBound - 128.dp),
onGlobalRouteEmitted = layoutState.collapse,
song = song,