Fix incorrect thumbnail url and song metadata inference
This commit is contained in:
@@ -367,7 +367,7 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
|||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
lastBitmap = Coil.imageLoader(applicationContext).execute(
|
lastBitmap = Coil.imageLoader(applicationContext).execute(
|
||||||
ImageRequest.Builder(applicationContext)
|
ImageRequest.Builder(applicationContext)
|
||||||
.data("${mediaMetadata.artworkUri}-w${notificationThumbnailSize}-h${notificationThumbnailSize}")
|
.data(mediaMetadata.artworkUri.thumbnail(notificationThumbnailSize))
|
||||||
.build()
|
.build()
|
||||||
).drawable?.let {
|
).drawable?.let {
|
||||||
lastArtworkUri = mediaMetadata.artworkUri
|
lastArtworkUri = mediaMetadata.artworkUri
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
@@ -102,7 +101,7 @@ fun PlayerView(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = "${player.mediaMetadata.artworkUri}-w$smallThumbnailSize-h$smallThumbnailSize",
|
model = player.mediaMetadata.artworkUri.thumbnail(smallThumbnailSize),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -220,11 +219,11 @@ fun PlayerView(
|
|||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
) {
|
) {
|
||||||
val artworkUri = remember(it) {
|
val artworkUri = remember(it) {
|
||||||
player.mediaController.getMediaItemAt(it).mediaMetadata.artworkUri
|
player.mediaController.getMediaItemAt(it).mediaMetadata.artworkUri.thumbnail(thumbnailSizePx)
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = "$artworkUri-w$thumbnailSizePx-h$thumbnailSizePx",
|
model = artworkUri,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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.color
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ fun PlaylistPreviewItem(
|
|||||||
) {
|
) {
|
||||||
if (thumbnails.toSet().size == 1) {
|
if (thumbnails.toSet().size == 1) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = "${thumbnails.first()}-w${thumbnailSizePx * 2}-h${thumbnailSizePx * 2}",
|
model = thumbnails.first().thumbnail(thumbnailSizePx * 2),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -68,7 +69,7 @@ fun PlaylistPreviewItem(
|
|||||||
Alignment.BottomEnd
|
Alignment.BottomEnd
|
||||||
).forEachIndexed { index, alignment ->
|
).forEachIndexed { index, alignment ->
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = "${thumbnails.getOrNull(index)}-w$thumbnailSizePx-h$thumbnailSizePx",
|
model = thumbnails.getOrNull(index).thumbnail(thumbnailSizePx),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -49,7 +50,7 @@ fun SongItem(
|
|||||||
SongItem(
|
SongItem(
|
||||||
thumbnailModel = ImageRequest.Builder(LocalContext.current)
|
thumbnailModel = ImageRequest.Builder(LocalContext.current)
|
||||||
.diskCacheKey(mediaItem.mediaId)
|
.diskCacheKey(mediaItem.mediaId)
|
||||||
.data("${mediaItem.mediaMetadata.artworkUri}-w$thumbnailSize-h$thumbnailSize")
|
.data(mediaItem.mediaMetadata.artworkUri.thumbnail(thumbnailSize))
|
||||||
.build(),
|
.build(),
|
||||||
title = mediaItem.mediaMetadata.title!!.toString(),
|
title = mediaItem.mediaMetadata.title!!.toString(),
|
||||||
authors = mediaItem.mediaMetadata.artist.toString(),
|
authors = mediaItem.mediaMetadata.artist.toString(),
|
||||||
@@ -75,7 +76,7 @@ fun SongItem(
|
|||||||
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
SongItem(
|
SongItem(
|
||||||
thumbnailModel = "${song.song.thumbnailUrl}-w$thumbnailSize-h$thumbnailSize",
|
thumbnailModel = song.song.thumbnailUrl?.thumbnail(thumbnailSize),
|
||||||
title = song.song.title,
|
title = song.song.title,
|
||||||
authors = song.authors?.joinToString("") { it.text } ?: "",
|
authors = song.authors?.joinToString("") { it.text } ?: "",
|
||||||
durationText = song.song.durationText,
|
durationText = song.song.durationText,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package it.vfsfitvnm.vimusic.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
@@ -171,4 +172,16 @@ fun YouTube.PlaylistOrAlbum.Item.toMediaItem(
|
|||||||
)
|
)
|
||||||
.setMediaId(info.endpoint?.videoId ?: return null)
|
.setMediaId(info.endpoint?.videoId ?: return null)
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String?.thumbnail(size: Int): String? {
|
||||||
|
return when {
|
||||||
|
this?.startsWith("https://lh3.googleusercontent.com") == true -> "$this-w$size-h$size"
|
||||||
|
this?.startsWith("https://yt3.ggpht.com") == true -> "$this-s$size"
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Uri?.thumbnail(size: Int): Uri? {
|
||||||
|
return toString().thumbnail(size)?.toUri()
|
||||||
}
|
}
|
||||||
@@ -159,18 +159,32 @@ object YouTube {
|
|||||||
override fun from(content: MusicShelfRenderer.Content): Song {
|
override fun from(content: MusicShelfRenderer.Content): Song {
|
||||||
val (mainRuns, otherRuns) = content.runs
|
val (mainRuns, otherRuns) = content.runs
|
||||||
|
|
||||||
|
// Possible configurations:
|
||||||
|
// "song" • author(s) • album • duration
|
||||||
|
// "song" • author(s) • duration
|
||||||
|
// author(s) • album • duration
|
||||||
|
// author(s) • duration
|
||||||
|
|
||||||
|
val album: Info<NavigationEndpoint.Endpoint.Browse>? = otherRuns
|
||||||
|
.getOrNull(otherRuns.lastIndex - 1)
|
||||||
|
?.firstOrNull()
|
||||||
|
?.takeIf { run ->
|
||||||
|
run
|
||||||
|
.navigationEndpoint
|
||||||
|
?.browseEndpoint
|
||||||
|
?.type == "MUSIC_PAGE_TYPE_ALBUM"
|
||||||
|
}
|
||||||
|
?.let(Info.Companion::from)
|
||||||
|
|
||||||
return Song(
|
return Song(
|
||||||
info = Info.from(mainRuns.first()),
|
info = Info.from(mainRuns.first()),
|
||||||
authors = otherRuns
|
authors = otherRuns
|
||||||
.getOrNull(otherRuns.lastIndex - 2)
|
.getOrNull(otherRuns.lastIndex - if (album == null) 1 else 2)
|
||||||
?.map(Info.Companion::from)
|
?.map(Info.Companion::from)
|
||||||
?: emptyList(),
|
?: emptyList(),
|
||||||
album = otherRuns
|
album = album,
|
||||||
.getOrNull(otherRuns.lastIndex - 1)
|
|
||||||
?.firstOrNull()
|
|
||||||
?.let(Info.Companion::from),
|
|
||||||
durationText = otherRuns
|
durationText = otherRuns
|
||||||
.getOrNull(otherRuns.lastIndex)
|
.lastOrNull()
|
||||||
?.firstOrNull()?.text,
|
?.firstOrNull()?.text,
|
||||||
thumbnail = content
|
thumbnail = content
|
||||||
.thumbnail
|
.thumbnail
|
||||||
@@ -603,8 +617,13 @@ object YouTube {
|
|||||||
thumbnail = renderer
|
thumbnail = renderer
|
||||||
.thumbnail
|
.thumbnail
|
||||||
.thumbnails
|
.thumbnails
|
||||||
.getOrNull(0),
|
.also {
|
||||||
durationText = renderer.lengthText.text
|
println(it)
|
||||||
|
}
|
||||||
|
.firstOrNull(),
|
||||||
|
durationText = renderer
|
||||||
|
.lengthText
|
||||||
|
.text
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
lyrics = NextResult.Lyrics(
|
lyrics = NextResult.Lyrics(
|
||||||
|
|||||||
@@ -56,10 +56,8 @@ data class NavigationEndpoint(
|
|||||||
val endpoint: Endpoint?
|
val endpoint: Endpoint?
|
||||||
get() = watchEndpoint ?: browseEndpoint ?: watchPlaylistEndpoint ?: searchEndpoint
|
get() = watchEndpoint ?: browseEndpoint ?: watchPlaylistEndpoint ?: searchEndpoint
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class Endpoint {
|
sealed class Endpoint {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Watch(
|
data class Watch(
|
||||||
val params: String? = null,
|
val params: String? = null,
|
||||||
@@ -69,6 +67,10 @@ data class NavigationEndpoint(
|
|||||||
val playlistSetVideoId: String? = null,
|
val playlistSetVideoId: String? = null,
|
||||||
val watchEndpointMusicSupportedConfigs: WatchEndpointMusicSupportedConfigs? = null,
|
val watchEndpointMusicSupportedConfigs: WatchEndpointMusicSupportedConfigs? = null,
|
||||||
) : Endpoint() {
|
) : Endpoint() {
|
||||||
|
val type: String?
|
||||||
|
get() = watchEndpointMusicSupportedConfigs
|
||||||
|
?.watchEndpointMusicConfig
|
||||||
|
?.musicVideoType
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class WatchEndpointMusicSupportedConfigs(
|
data class WatchEndpointMusicSupportedConfigs(
|
||||||
@@ -82,7 +84,6 @@ data class NavigationEndpoint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class WatchPlaylist(
|
data class WatchPlaylist(
|
||||||
val params: String?,
|
val params: String?,
|
||||||
@@ -96,6 +97,10 @@ data class NavigationEndpoint(
|
|||||||
val browseId: String,
|
val browseId: String,
|
||||||
val browseEndpointContextSupportedConfigs: BrowseEndpointContextSupportedConfigs?,
|
val browseEndpointContextSupportedConfigs: BrowseEndpointContextSupportedConfigs?,
|
||||||
) : Endpoint() {
|
) : Endpoint() {
|
||||||
|
val type: String?
|
||||||
|
get() = browseEndpointContextSupportedConfigs
|
||||||
|
?.browseEndpointContextMusicConfig
|
||||||
|
?.pageType
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BrowseEndpointContextSupportedConfigs(
|
data class BrowseEndpointContextSupportedConfigs(
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ data class Runs(
|
|||||||
run.text == " • " -> listOf(index - 1, index + 1)
|
run.text == " • " -> listOf(index - 1, index + 1)
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
}.windowed(size = 2, step = 2) { (from, to) -> runs.slice(from..to) }
|
}.windowed(size = 2, step = 2) { (from, to) -> runs.slice(from..to) }.let {
|
||||||
|
it.ifEmpty {
|
||||||
|
listOf(runs)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -45,3 +45,4 @@ data class ThumbnailRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user