@@ -368,7 +368,7 @@ fun MediaItemMenu(
|
||||
)
|
||||
}
|
||||
|
||||
onSetSleepTimer?.let { onSetSleepTimer ->
|
||||
onSetSleepTimer?.let {
|
||||
val binder = LocalPlayerServiceBinder.current
|
||||
val (colorPalette, typography) = LocalAppearance.current
|
||||
|
||||
|
||||
@@ -28,8 +28,13 @@ import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
|
||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
||||
import it.vfsfitvnm.vimusic.ui.styling.*
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
||||
import it.vfsfitvnm.vimusic.utils.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -70,10 +75,10 @@ fun BuiltInPlaylistScreen(
|
||||
val songs by remember(binder?.cache, builtInPlaylist) {
|
||||
when (builtInPlaylist) {
|
||||
BuiltInPlaylist.Favorites -> Database.favorites()
|
||||
BuiltInPlaylist.Cached -> Database.songsByRowIdDesc().map { songs ->
|
||||
BuiltInPlaylist.Cached -> Database.songsWithContentLength().map { songs ->
|
||||
songs.filter { song ->
|
||||
song.song.contentLength?.let { contentLength ->
|
||||
binder?.cache?.isCached(song.song.id, 0, contentLength)
|
||||
song.contentLength?.let {
|
||||
binder?.cache?.isCached(song.song.id, 0, song.contentLength)
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -43,6 +45,7 @@ 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.models.Format
|
||||
import it.vfsfitvnm.vimusic.models.Song
|
||||
import it.vfsfitvnm.vimusic.query
|
||||
import it.vfsfitvnm.vimusic.ui.components.*
|
||||
@@ -50,8 +53,10 @@ import it.vfsfitvnm.vimusic.ui.components.themed.BaseMediaItemMenu
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.LoadingOrError
|
||||
import it.vfsfitvnm.vimusic.ui.styling.*
|
||||
import it.vfsfitvnm.vimusic.utils.*
|
||||
import it.vfsfitvnm.youtubemusic.YouTube
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
@@ -186,7 +191,6 @@ fun PlayerView(
|
||||
) {
|
||||
Thumbnail(
|
||||
playerState = playerState,
|
||||
song = song,
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
@@ -217,7 +221,6 @@ fun PlayerView(
|
||||
) {
|
||||
Thumbnail(
|
||||
playerState = playerState,
|
||||
song = song,
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
@@ -314,7 +317,6 @@ fun PlayerView(
|
||||
@Composable
|
||||
private fun Thumbnail(
|
||||
playerState: PlayerState,
|
||||
song: Song?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val (_, typography) = LocalAppearance.current
|
||||
@@ -378,25 +380,17 @@ private fun Thumbnail(
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
var cachedBytes by remember(song?.id) {
|
||||
mutableStateOf(binder.cache.getCachedBytes(playerState.mediaItem.mediaId, 0, -1))
|
||||
val key = playerState.mediaItem.mediaId
|
||||
|
||||
var cachedBytes by remember(key) {
|
||||
mutableStateOf(binder.cache.getCachedBytes(key, 0, -1))
|
||||
}
|
||||
|
||||
val loudnessDb by remember {
|
||||
derivedStateOf {
|
||||
song?.loudnessDb ?: playerState.mediaMetadata.extras?.getFloatOrNull("loudnessDb")
|
||||
}
|
||||
}
|
||||
|
||||
val contentLength by remember {
|
||||
derivedStateOf {
|
||||
song?.contentLength ?: playerState.mediaMetadata.extras?.getLongOrNull("contentLength")
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(song?.id) {
|
||||
val key = playerState.mediaItem.mediaId
|
||||
val format by remember(key) {
|
||||
Database.format(key)
|
||||
}.collectAsState(initial = null, context = Dispatchers.IO)
|
||||
|
||||
DisposableEffect(key) {
|
||||
val listener = object : Cache.Listener {
|
||||
override fun onSpanAdded(cache: Cache, span: CacheSpan) {
|
||||
cachedBytes += span.length
|
||||
@@ -451,6 +445,10 @@ private fun Thumbnail(
|
||||
text = "Loudness",
|
||||
style = typography.xs.semiBold.color(BlackColorPalette.text)
|
||||
)
|
||||
BasicText(
|
||||
text = "Bitrate",
|
||||
style = typography.xs.semiBold.color(BlackColorPalette.text)
|
||||
)
|
||||
BasicText(
|
||||
text = "Size",
|
||||
style = typography.xs.semiBold.color(BlackColorPalette.text)
|
||||
@@ -471,13 +469,19 @@ private fun Thumbnail(
|
||||
style = typography.xs.semiBold.color(BlackColorPalette.text)
|
||||
)
|
||||
BasicText(
|
||||
text = loudnessDb?.let { loudnessDb ->
|
||||
text = format?.loudnessDb?.let { loudnessDb ->
|
||||
"%.2f dB".format(loudnessDb)
|
||||
} ?: "Unknown",
|
||||
style = typography.xs.semiBold.color(BlackColorPalette.text)
|
||||
)
|
||||
BasicText(
|
||||
text = contentLength?.let { contentLength ->
|
||||
text = format?.bitrate?.let { bitrate ->
|
||||
"${bitrate / 1000} kbps"
|
||||
} ?: "Unknown",
|
||||
style = typography.xs.semiBold.color(BlackColorPalette.text)
|
||||
)
|
||||
BasicText(
|
||||
text = format?.contentLength?.let { contentLength ->
|
||||
Formatter.formatShortFileSize(
|
||||
context,
|
||||
contentLength
|
||||
@@ -489,7 +493,7 @@ private fun Thumbnail(
|
||||
text = buildString {
|
||||
append(Formatter.formatShortFileSize(context, cachedBytes))
|
||||
|
||||
contentLength?.let { contentLength ->
|
||||
format?.contentLength?.let { contentLength ->
|
||||
append(" (${(cachedBytes.toFloat() / contentLength * 100).roundToInt()}%)")
|
||||
}
|
||||
},
|
||||
@@ -497,6 +501,41 @@ private fun Thumbnail(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (format != null && format?.itag == null) {
|
||||
BasicText(
|
||||
text = "FETCH MISSING DATA",
|
||||
style = typography.xxs.semiBold.color(BlackColorPalette.text),
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = rememberRipple(bounded = true),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = {
|
||||
query {
|
||||
runBlocking(Dispatchers.IO) {
|
||||
YouTube.player(key)?.map { response ->
|
||||
response.streamingData?.adaptiveFormats?.findLast { format ->
|
||||
format.itag == 251 || format.itag == 140
|
||||
}?.let { format ->
|
||||
Format(
|
||||
songId = key,
|
||||
itag = format.itag,
|
||||
mimeType = format.mimeType,
|
||||
bitrate = format.bitrate,
|
||||
loudnessDb = response.playerConfig?.audioConfig?.loudnessDb?.toFloat(),
|
||||
contentLength = format.contentLength,
|
||||
lastModified = format.lastModified
|
||||
)
|
||||
}
|
||||
}
|
||||
}?.getOrNull()?.let(Database::insert)
|
||||
}
|
||||
}
|
||||
)
|
||||
.padding(all = 16.dp)
|
||||
.align(Alignment.End)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user