From 59c0a8d31cd30d94b93375d43225d4529bda4a6b Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Sun, 17 Jul 2022 22:14:49 +0200 Subject: [PATCH] Fix #68 --- .../vimusic/ui/screens/PlaylistScreen.kt | 4 +- .../it/vfsfitvnm/youtubemusic/YouTube.kt | 128 ++++++++++++------ .../models/ContinuationResponse.kt | 4 +- 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/PlaylistScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/PlaylistScreen.kt index e8da6cc..7ebdf14 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/PlaylistScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/PlaylistScreen.kt @@ -84,7 +84,9 @@ fun PlaylistScreen( val onLoad = relaunchableEffect(Unit) { playlist = withContext(Dispatchers.IO) { - YouTube.playlistOrAlbum(browseId) + YouTube.playlistOrAlbum(browseId)?.map { + it.next() + } } } diff --git a/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt b/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt index 9a89f2e..b40ab90 100644 --- a/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt +++ b/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/YouTube.kt @@ -41,6 +41,11 @@ object YouTube { } } + @Serializable + data class EmptyBody( + val context: Context, + ) + @Serializable data class BrowseBody( val context: Context, @@ -706,7 +711,82 @@ object YouTube { val durationText: String?, val album: Info?, val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail?, - ) + ) { + companion object { + fun from(renderer: MusicResponsiveListItemRenderer): Item? { + return Item( + info = renderer + .flexColumns + .getOrNull(0) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.getOrNull(0) + ?.let { Info.from(it) } ?: return null, + authors = renderer + .flexColumns + .getOrNull(1) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.map { Info.from(it) } + ?.takeIf { it.isNotEmpty() }, + durationText = renderer + .fixedColumns + ?.getOrNull(0) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.getOrNull(0) + ?.text, + album = renderer + .flexColumns + .getOrNull(2) + ?.musicResponsiveListItemFlexColumnRenderer + ?.text + ?.runs + ?.firstOrNull() + ?.let { Info.from(it) }, + thumbnail = renderer + .thumbnail + ?.musicThumbnailRenderer + ?.thumbnail + ?.thumbnails + ?.firstOrNull() + ) + } + } + } + + suspend fun next(): PlaylistOrAlbum { + return continuation?.let { + runCatching { + client.post("/youtubei/v1/browse") { + contentType(ContentType.Application.Json) + setBody(EmptyBody(context = Context.DefaultWeb)) + parameter("key", Key) + parameter("prettyPrint", false) + parameter("continuation", continuation) + }.body().let { continuationResponse -> + copy( + items = items?.plus(continuationResponse + .continuationContents + .musicShelfContinuation + ?.contents + ?.map(MusicShelfRenderer.Content::musicResponsiveListItemRenderer) + ?.mapNotNull(Item.Companion::from) ?: emptyList()), + continuation = continuationResponse + .continuationContents + .musicShelfContinuation + ?.continuations + ?.firstOrNull() + ?.nextRadioContinuationData + ?.continuation + ).next() + } + }?.recoverIfCancelled()?.getOrNull() + } ?: this + } suspend fun withAudioSources(): PlaylistOrAlbum { @Serializable @@ -788,48 +868,7 @@ object YouTube { ?.musicShelfRenderer ?.contents ?.map(MusicShelfRenderer.Content::musicResponsiveListItemRenderer) - ?.mapNotNull { renderer -> - PlaylistOrAlbum.Item( - info = renderer - .flexColumns - .getOrNull(0) - ?.musicResponsiveListItemFlexColumnRenderer - ?.text - ?.runs - ?.getOrNull(0) - ?.let { Info.from(it) } ?: return@mapNotNull null, - authors = renderer - .flexColumns - .getOrNull(1) - ?.musicResponsiveListItemFlexColumnRenderer - ?.text - ?.runs - ?.map { Info.from(it) } - ?.takeIf { it.isNotEmpty() }, - durationText = renderer - .fixedColumns - ?.getOrNull(0) - ?.musicResponsiveListItemFlexColumnRenderer - ?.text - ?.runs - ?.getOrNull(0) - ?.text, - album = renderer - .flexColumns - .getOrNull(2) - ?.musicResponsiveListItemFlexColumnRenderer - ?.text - ?.runs - ?.firstOrNull() - ?.let { Info.from(it) }, - thumbnail = renderer - .thumbnail - ?.musicThumbnailRenderer - ?.thumbnail - ?.thumbnails - ?.firstOrNull() - ) - } + ?.mapNotNull(PlaylistOrAlbum.Item.Companion::from) // ?.filter { it.info.endpoint != null } , url = body @@ -844,6 +883,9 @@ object YouTube { ?.tabRenderer ?.content ?.sectionListRenderer + ?.contents + ?.firstOrNull() + ?.musicShelfRenderer ?.continuations ?.firstOrNull() ?.nextRadioContinuationData diff --git a/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/models/ContinuationResponse.kt b/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/models/ContinuationResponse.kt index dc7eed5..ac27911 100644 --- a/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/models/ContinuationResponse.kt +++ b/youtube-music/src/main/kotlin/it/vfsfitvnm/youtubemusic/models/ContinuationResponse.kt @@ -1,6 +1,7 @@ package it.vfsfitvnm.youtubemusic.models import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames @Serializable data class ContinuationResponse( @@ -8,7 +9,8 @@ data class ContinuationResponse( ) { @Serializable data class ContinuationContents( - val musicShelfContinuation: MusicShelfRenderer + @JsonNames("musicPlaylistShelfContinuation") + val musicShelfContinuation: MusicShelfRenderer? ) { // @Serializable // data class MusicShelfContinuation(