Rename youtube-music module to innertube and rewrite it

This commit is contained in:
vfsfitvnm
2022-10-02 15:25:07 +02:00
parent 4bc3671be1
commit 917e194d63
126 changed files with 2210 additions and 2145 deletions

View File

@@ -61,12 +61,13 @@ import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.albumPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
@@ -88,19 +89,21 @@ fun AlbumOverview(
withContext(Dispatchers.IO) {
Database.album(browseId).collect { album ->
if (album?.timestamp == null) {
YouTube.album(browseId)?.onSuccess { youtubeAlbum ->
Innertube.albumPage(BrowseBody(browseId = browseId))?.onSuccess { albumPage ->
Database.upsert(
Album(
id = browseId,
title = youtubeAlbum.title,
thumbnailUrl = youtubeAlbum.thumbnail?.url,
year = youtubeAlbum.year,
authorsText = youtubeAlbum.authors?.joinToString("") { it.name ?: "" },
shareUrl = youtubeAlbum.url,
title = albumPage.title,
thumbnailUrl = albumPage.thumbnail?.url,
year = albumPage.year,
authorsText = albumPage.authors?.joinToString("") { it.name ?: "" },
shareUrl = albumPage.url,
timestamp = System.currentTimeMillis()
),
youtubeAlbum.songs
?.map(YouTube.Item.Song::asMediaItem)
albumPage
.songsPage
?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { position, mediaItem ->
SongAlbumMap(

View File

@@ -8,7 +8,6 @@ import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -26,19 +25,19 @@ import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.produceSaveableRelaunchableOneShotState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
inline fun <T : YouTube.Item> ArtistContent(
inline fun <T : Innertube.Item> ArtistContent(
artist: Artist?,
youtubeArtist: YouTube.Artist?,
youtubeArtistPage: Innertube.ArtistPage?,
isLoading: Boolean,
isError: Boolean,
stateSaver: ListSaver<T, List<Any?>>,
crossinline itemsProvider: suspend (String?) -> Result<Pair<String?, List<T>?>>?,
crossinline itemsPageProvider: suspend (String?) -> Result<Innertube.ItemsPage<T>?>?,
crossinline bookmarkIconContent: @Composable () -> Unit,
crossinline shareIconContent: @Composable () -> Unit,
crossinline itemContent: @Composable LazyItemScope.(T) -> Unit,
@@ -61,18 +60,18 @@ inline fun <T : YouTube.Item> ArtistContent(
val (continuationState, fetch) = produceSaveableRelaunchableOneShotState(
initialValue = null,
stateSaver = autoSaver<String?>(),
youtubeArtist
youtubeArtistPage
) {
if (youtubeArtist == null) return@produceSaveableRelaunchableOneShotState
if (youtubeArtistPage == null) return@produceSaveableRelaunchableOneShotState
println("loading... $value")
isLoadingItems = true
withContext(Dispatchers.IO) {
itemsProvider(value)?.onSuccess { (continuation, newItems) ->
value = continuation
newItems?.let {
items = items.plus(it).distinctBy(YouTube.Item::key)
itemsPageProvider(value)?.onSuccess { itemsPage ->
value = itemsPage?.continuation
itemsPage?.items?.let {
items = items.plus(it).distinctBy(Innertube.Item::key)
}
isErrorItems = false
isLoadingItems = false
@@ -105,7 +104,7 @@ inline fun <T : YouTube.Item> ArtistContent(
items(
items = items,
key = YouTube.Item::key,
key = Innertube.Item::key,
itemContent = itemContent
)

View File

@@ -57,14 +57,14 @@ import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
@ExperimentalAnimationApi
@Composable
fun ArtistOverview(
artist: Artist?,
youtubeArtist: YouTube.Artist?,
youtubeArtistPage: Innertube.ArtistPage?,
isLoading: Boolean,
isError: Boolean,
onViewAllSongsClick: () -> Unit,
@@ -100,7 +100,7 @@ fun ArtistOverview(
when {
artist != null -> {
Header(title = artist.name ?: "Unknown") {
youtubeArtist?.radioEndpoint?.let { radioEndpoint ->
youtubeArtistPage?.radioEndpoint?.let { radioEndpoint ->
SecondaryTextButton(
text = "Start radio",
onClick = {
@@ -130,8 +130,8 @@ fun ArtistOverview(
)
when {
youtubeArtist != null -> {
youtubeArtist.songs?.let { songs ->
youtubeArtistPage != null -> {
youtubeArtistPage.songs?.let { songs ->
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -144,7 +144,7 @@ fun ArtistOverview(
modifier = sectionTextModifier
)
youtubeArtist.songsEndpoint?.let {
youtubeArtistPage.songsEndpoint?.let {
BasicText(
text = "View all",
style = typography.xs.secondary,
@@ -174,7 +174,7 @@ fun ArtistOverview(
}
}
youtubeArtist.albums?.let { albums ->
youtubeArtistPage.albums?.let { albums ->
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -187,7 +187,7 @@ fun ArtistOverview(
modifier = sectionTextModifier
)
youtubeArtist.albumsEndpoint?.let {
youtubeArtistPage.albumsEndpoint?.let {
BasicText(
text = "View all",
style = typography.xs.secondary,
@@ -207,7 +207,7 @@ fun ArtistOverview(
) {
items(
items = albums,
key = YouTube.Item.Album::key
key = Innertube.AlbumItem::key
) { album ->
AlternativeAlbumItem(
album = album,
@@ -224,7 +224,7 @@ fun ArtistOverview(
}
}
youtubeArtist.singles?.let { singles ->
youtubeArtistPage.singles?.let { singles ->
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween,
@@ -237,7 +237,7 @@ fun ArtistOverview(
modifier = sectionTextModifier
)
youtubeArtist.singlesEndpoint?.let {
youtubeArtistPage.singlesEndpoint?.let {
BasicText(
text = "View all",
style = typography.xs.secondary,
@@ -257,7 +257,7 @@ fun ArtistOverview(
) {
items(
items = singles,
key = YouTube.Item.Album::key
key = Innertube.AlbumItem::key
) { album ->
AlternativeAlbumItem(
album = album,
@@ -330,7 +330,7 @@ fun ArtistOverview(
}
}
youtubeArtist?.shuffleEndpoint?.let { shuffleEndpoint ->
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
PrimaryButton(
iconId = R.drawable.shuffle,
onClick = {

View File

@@ -26,14 +26,13 @@ import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.PartialArtist
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.ArtistSaver
import it.vfsfitvnm.vimusic.savers.YouTubeAlbumListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeArtistPageSaver
import it.vfsfitvnm.vimusic.savers.YouTubeSongListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeArtistPageSaver
import it.vfsfitvnm.vimusic.savers.InnertubeSongItemListSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
import it.vfsfitvnm.vimusic.ui.screens.searchresult.SearchResult
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
@@ -47,7 +46,12 @@ import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.produceSaveableLazyOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
import it.vfsfitvnm.youtubemusic.requests.artistPage
import it.vfsfitvnm.youtubemusic.requests.itemsPage
import it.vfsfitvnm.youtubemusic.utils.from
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
@@ -72,22 +76,22 @@ fun ArtistScreen(browseId: String) {
val youtubeArtist by produceSaveableLazyOneShotState(
initialValue = null,
stateSaver = nullableSaver(YouTubeArtistPageSaver)
stateSaver = nullableSaver(InnertubeArtistPageSaver)
) {
println("${System.currentTimeMillis()}, computing lazyEffect (youtubeArtistResult = ${value?.name})!")
isLoading = true
withContext(Dispatchers.IO) {
YouTube.artist(browseId)?.onSuccess { youtubeArtist ->
value = youtubeArtist
Innertube.artistPage(browseId)?.onSuccess { artistPage ->
value = artistPage
query {
Database.upsert(
PartialArtist(
id = browseId,
name = youtubeArtist.name,
thumbnailUrl = youtubeArtist.thumbnail?.url,
info = youtubeArtist.description,
name = artistPage.name,
thumbnailUrl = artistPage.thumbnail?.url,
info = artistPage.description,
timestamp = System.currentTimeMillis()
)
)
@@ -136,10 +140,13 @@ fun ArtistScreen(browseId: String) {
colorFilter = ColorFilter.tint(LocalAppearance.current.colorPalette.accent),
modifier = Modifier
.clickable {
val bookmarkedAt = if (artist?.bookmarkedAt == null) System.currentTimeMillis() else null
val bookmarkedAt =
if (artist?.bookmarkedAt == null) System.currentTimeMillis() else null
query {
artist?.copy(bookmarkedAt = bookmarkedAt)?.let(Database::update)
artist
?.copy(bookmarkedAt = bookmarkedAt)
?.let(Database::update)
}
}
.padding(all = 4.dp)
@@ -194,7 +201,7 @@ fun ArtistScreen(browseId: String) {
when (currentTabIndex) {
0 -> ArtistOverview(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
bookmarkIconContent = bookmarkIconContent,
@@ -204,6 +211,7 @@ fun ArtistScreen(browseId: String) {
onViewAllAlbumsClick = { onTabIndexChanged(2) },
onViewAllSinglesClick = { onTabIndexChanged(3) },
)
1 -> {
val binder = LocalPlayerServiceBinder.current
val thumbnailSizeDp = Dimensions.thumbnails.song
@@ -211,20 +219,26 @@ fun ArtistScreen(browseId: String) {
ArtistContent(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
stateSaver = YouTubeSongListSaver,
stateSaver = InnertubeSongItemListSaver,
bookmarkIconContent = bookmarkIconContent,
shareIconContent = shareIconContent,
itemsProvider = { continuation ->
youtubeArtist
itemsPageProvider = { continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicResponsiveListItemRenderer = Innertube.SongItem::from,
)
} ?: youtubeArtist
?.songsEndpoint
?.browseId
?.let { browseId ->
YouTube.items(browseId, continuation, YouTube.Item.Song::from)?.map { result ->
result?.continuation to result?.items
}
Innertube.itemsPage(
body = BrowseBody(browseId = browseId),
fromMusicResponsiveListItemRenderer = Innertube.SongItem::from,
)
}
},
itemContent = { song ->
@@ -243,25 +257,33 @@ fun ArtistScreen(browseId: String) {
}
)
}
2 -> {
val thumbnailSizeDp = 108.dp
val thumbnailSizePx = thumbnailSizeDp.px
ArtistContent(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
stateSaver = YouTubeAlbumListSaver,
stateSaver = InnertubeAlbumItemListSaver,
bookmarkIconContent = bookmarkIconContent,
shareIconContent = shareIconContent,
itemsProvider = {
youtubeArtist
?.albumsEndpoint
?.let { endpoint ->
YouTube.items2(browseId, endpoint.params, YouTube.Item.Album::from)?.map { result ->
result?.continuation to result?.items
}
itemsPageProvider = { continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
} ?: youtubeArtist
?.songsEndpoint
?.browseId
?.let { browseId ->
Innertube.itemsPage(
body = BrowseBody(browseId = browseId),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
}
},
itemContent = { album ->
@@ -282,25 +304,33 @@ fun ArtistScreen(browseId: String) {
}
)
}
3 -> {
val thumbnailSizeDp = 108.dp
val thumbnailSizePx = thumbnailSizeDp.px
ArtistContent(
artist = artist,
youtubeArtist = youtubeArtist,
youtubeArtistPage = youtubeArtist,
isLoading = isLoading,
isError = isError,
stateSaver = YouTubeAlbumListSaver,
stateSaver = InnertubeAlbumItemListSaver,
bookmarkIconContent = bookmarkIconContent,
shareIconContent = shareIconContent,
itemsProvider = {
youtubeArtist
?.singlesEndpoint
?.let { endpoint ->
YouTube.items2(browseId, endpoint.params, YouTube.Item.Album::from)?.map { result ->
result?.continuation to result?.items
}
itemsPageProvider = { continuation ->
continuation?.let {
Innertube.itemsPage(
body = ContinuationBody(continuation = continuation),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
} ?: youtubeArtist
?.songsEndpoint
?.browseId
?.let { browseId ->
Innertube.itemsPage(
body = BrowseBody(browseId = browseId),
fromMusicTwoRowItemRenderer = Innertube.AlbumItem::from,
)
}
},
itemContent = { album ->
@@ -321,6 +351,7 @@ fun ArtistScreen(browseId: String) {
}
)
}
4 -> ArtistLocalSongsList(
browseId = browseId,
artist = artist,

View File

@@ -34,7 +34,7 @@ import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.savers.DetailedSongSaver
import it.vfsfitvnm.vimusic.savers.YouTubeRelatedSaver
import it.vfsfitvnm.vimusic.savers.InnertubeRelatedPageSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@@ -60,8 +60,10 @@ import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.vimusic.utils.thumbnail
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
import it.vfsfitvnm.youtubemusic.requests.relatedPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
@@ -88,30 +90,13 @@ fun QuickPicks(
.collect { value = it }
}
val relatedResult by produceSaveableOneShotState(
val relatedPageResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = resultSaver(nullableSaver(YouTubeRelatedSaver)),
stateSaver = resultSaver(nullableSaver(InnertubeRelatedPageSaver)),
trending?.id
) {
trending?.id?.let { trendingVideoId ->
value = YouTube.related(trendingVideoId)?.map { related ->
related?.copy(
albums = related.albums?.map { album ->
album.copy(
authors = trending?.artists?.map { info ->
YouTube.Info(
name = info.name,
endpoint = NavigationEndpoint.Endpoint.Browse(
browseId = info.id,
params = null,
browseEndpointContextSupportedConfigs = null
)
)
}
)
}
)
}
value = Innertube.relatedPage(NextBody(videoId = trendingVideoId))
}
}
@@ -140,7 +125,7 @@ fun QuickPicks(
) {
Header(title = "Quick picks")
relatedResult?.getOrNull()?.let { related ->
relatedPageResult?.getOrNull()?.let { related ->
LazyHorizontalGrid(
rows = GridCells.Fixed(4),
modifier = Modifier
@@ -171,7 +156,7 @@ fun QuickPicks(
items(
items = related.songs ?: emptyList(),
key = YouTube.Item.Song::key
key = Innertube.SongItem::key
) { song ->
SmallSongItem(
song = song,
@@ -204,7 +189,7 @@ fun QuickPicks(
) {
items(
items = related.albums ?: emptyList(),
key = YouTube.Item.Album::key
key = Innertube.AlbumItem::key
) { album ->
AlbumItem(
album = album,
@@ -235,7 +220,7 @@ fun QuickPicks(
) {
items(
items = related.artists ?: emptyList(),
key = YouTube.Item.Artist::key,
key = Innertube.ArtistItem::key,
) { artist ->
ArtistItem(
artist = artist,
@@ -268,7 +253,7 @@ fun QuickPicks(
) {
items(
items = related.playlists ?: emptyList(),
key = YouTube.Item.Playlist::key,
key = Innertube.PlaylistItem::key,
) { playlist ->
PlaylistItem(
playlist = playlist,
@@ -284,7 +269,7 @@ fun QuickPicks(
)
}
}
} ?: relatedResult?.exceptionOrNull()?.let {
} ?: relatedPageResult?.exceptionOrNull()?.let {
BasicText(
text = "An error has occurred",
style = typography.s.secondary.center,

View File

@@ -34,6 +34,7 @@ import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.PlaylistWithSongsSaver
import it.vfsfitvnm.vimusic.savers.nullableSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@@ -50,7 +51,9 @@ import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.playlistPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.runBlocking
@@ -68,7 +71,7 @@ fun LocalPlaylistSongList(
val playlistWithSongs by produceSaveableState(
initialValue = null,
stateSaver = PlaylistWithSongsSaver
stateSaver = nullableSaver(PlaylistWithSongsSaver)
) {
Database
.playlistWithSongs(playlistId)
@@ -165,13 +168,16 @@ fun LocalPlaylistSongList(
transaction {
runBlocking(Dispatchers.IO) {
withContext(Dispatchers.IO) {
YouTube.playlist(browseId)?.map { it.next() }
// TODO: fetch all songs!
Innertube.playlistPage(BrowseBody(browseId = browseId))
}
}?.getOrNull()?.let { remotePlaylist ->
Database.clearPlaylist(playlistId)
remotePlaylist.songs
?.map(YouTube.Item.Song::asMediaItem)
remotePlaylist.
songsPage
?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { position, mediaItem ->
SongPlaylistMap(

View File

@@ -69,7 +69,9 @@ import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.relaunchableEffect
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.verticalFadingEdge
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
import it.vfsfitvnm.youtubemusic.requests.lyrics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -134,8 +136,7 @@ fun Lyrics(
duration = duration / 1000
)?.map { it?.value }
} else {
YouTube.next(mediaId, null)
?.map { nextResult -> nextResult.lyrics()?.getOrNull() }
Innertube.lyrics(NextBody(videoId = mediaId))
}?.map { newLyrics ->
onLyricsUpdate(isShowingSynchronizedLyrics, mediaId, newLyrics ?: "")
state = state.copy(isLoading = false)

View File

@@ -40,7 +40,9 @@ import it.vfsfitvnm.vimusic.ui.styling.overlay
import it.vfsfitvnm.vimusic.utils.color
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.rememberVolume
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.PlayerBody
import it.vfsfitvnm.youtubemusic.requests.player
import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -195,7 +197,7 @@ fun StatsForNerds(
onClick = {
query {
runBlocking(Dispatchers.IO) {
YouTube.player(mediaId)
Innertube.player(PlayerBody(videoId = mediaId))
?.map { response ->
response.streamingData?.adaptiveFormats
?.findLast { format ->

View File

@@ -27,7 +27,9 @@ fun PlaylistScreen(browseId: String) {
}
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
PlaylistSongList(browseId = browseId)
when (currentTabIndex) {
0 -> PlaylistSongList(browseId = browseId)
}
}
}
}

View File

@@ -39,7 +39,7 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.savers.YouTubePlaylistOrAlbumSaver
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.components.themed.Header
@@ -58,11 +58,12 @@ import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
import it.vfsfitvnm.youtubemusic.requests.playlistPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
@@ -76,12 +77,13 @@ fun PlaylistSongList(
val binder = LocalPlayerServiceBinder.current
val context = LocalContext.current
val playlistResult by produceSaveableOneShotState(
val playlistPageResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = resultSaver(YouTubePlaylistOrAlbumSaver),
stateSaver = resultSaver(InnertubePlaylistOrAlbumPageSaver),
) {
value = withContext(Dispatchers.IO) {
YouTube.playlist(browseId)?.map { it.next() }
// TODO: fetch all songs!
Innertube.playlistPage(BrowseBody(browseId = browseId))
}
}
@@ -102,7 +104,7 @@ fun PlaylistSongList(
val songThumbnailSizeDp = Dimensions.thumbnails.song
val songThumbnailSizePx = songThumbnailSizeDp.px
playlistResult?.getOrNull()?.let { playlist ->
playlistPageResult?.getOrNull()?.let { playlist ->
LazyColumn(
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
@@ -117,9 +119,9 @@ fun PlaylistSongList(
Header(title = playlist.title ?: "Unknown") {
SecondaryTextButton(
text = "Enqueue",
isEnabled = playlist.songs?.isNotEmpty() == true,
isEnabled = playlist.songsPage?.items?.isNotEmpty() == true,
onClick = {
playlist.songs?.map(YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.player?.enqueue(mediaItems)
}
}
@@ -147,8 +149,8 @@ fun PlaylistSongList(
)
)
playlist.songs
?.map(YouTube.Item.Song::asMediaItem)
playlist.songsPage?.items
?.map(Innertube.SongItem::asMediaItem)
?.onEach(Database::insert)
?.mapIndexed { index, mediaItem ->
SongPlaylistMap(
@@ -196,13 +198,13 @@ fun PlaylistSongList(
}
}
itemsIndexed(items = playlist.songs ?: emptyList()) { index, song ->
itemsIndexed(items = playlist.songsPage?.items ?: emptyList()) { index, song ->
SongItem(
title = song.info?.name,
authors = (song.authors ?: playlist.authors)?.joinToString("") { it.name ?: "" },
durationText = song.durationText,
onClick = {
playlist.songs?.map(YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(mediaItems, index)
}
@@ -226,15 +228,15 @@ fun PlaylistSongList(
PrimaryButton(
iconId = R.drawable.shuffle,
isEnabled = playlist.songs?.isNotEmpty() == true,
isEnabled = playlist.songsPage?.items?.isNotEmpty() == true,
onClick = {
playlist.songs?.map(YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(mediaItems.shuffled())
}
}
)
} ?: playlistResult?.exceptionOrNull()?.let {
} ?: playlistPageResult?.exceptionOrNull()?.let {
Box(
modifier = Modifier
.align(Alignment.Center)

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
@@ -41,7 +42,7 @@ import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.savers.SearchQueryListSaver
import it.vfsfitvnm.vimusic.savers.StringListResultSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
@@ -51,7 +52,9 @@ import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.bodies.SearchSuggestionsBody
import it.vfsfitvnm.youtubemusic.requests.searchSuggestions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -80,11 +83,11 @@ fun OnlineSearch(
val suggestionsResult by produceSaveableOneShotState(
initialValue = null,
stateSaver = StringListResultSaver,
stateSaver = resultSaver(autoSaver<List<String>?>()),
key1 = textFieldValue.text
) {
if (textFieldValue.text.isNotEmpty()) {
value = YouTube.getSearchSuggestions(textFieldValue.text)
value = Innertube.searchSuggestions(SearchSuggestionsBody(input = textFieldValue.text))
}
}

View File

@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -23,23 +24,28 @@ import androidx.compose.ui.input.pointer.pointerInput
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
import it.vfsfitvnm.vimusic.savers.ListSaver
import it.vfsfitvnm.vimusic.savers.StringResultSaver
import it.vfsfitvnm.vimusic.savers.resultSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.center
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableRelaunchableOneShotState
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.models.MusicShelfRenderer
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
import it.vfsfitvnm.youtubemusic.models.bodies.SearchBody
import it.vfsfitvnm.youtubemusic.requests.searchPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ExperimentalAnimationApi
@Composable
inline fun <T : YouTube.Item> SearchResult(
inline fun <T : Innertube.Item> SearchResult(
query: String,
filter: String,
stateSaver: ListSaver<T, List<Any?>>,
noinline fromMusicShelfRendererContent: (MusicShelfRenderer.Content) -> T?,
crossinline onSearchAgain: () -> Unit,
crossinline itemContent: @Composable LazyItemScope.(T) -> Unit,
noinline itemShimmer: @Composable BoxScope.() -> Unit,
@@ -52,21 +58,30 @@ inline fun <T : YouTube.Item> SearchResult(
val (continuationResultState, fetch) = produceSaveableRelaunchableOneShotState(
initialValue = null,
stateSaver = StringResultSaver
stateSaver = resultSaver(autoSaver<String?>())
) {
val token = value?.getOrNull()
value = null
value = withContext(Dispatchers.IO) {
YouTube.search(query, filter, token)
}?.map { searchResult ->
@Suppress("UNCHECKED_CAST")
(searchResult.items as List<T>?)?.let {
items = items.plus(it).distinctBy(YouTube.Item::key)
if (token == null) {
Innertube.searchPage(
body = SearchBody(query = query, params = filter),
fromMusicShelfRendererContent = fromMusicShelfRendererContent
)
} else {
Innertube.searchPage(
body = ContinuationBody(continuation = token),
fromMusicShelfRendererContent = fromMusicShelfRendererContent
)
}
}?.map { itemsPage ->
itemsPage?.items?.let {
items = items.plus(it).distinctBy(Innertube.Item::key)
}
searchResult.continuation
itemsPage?.continuation
}
}
@@ -94,7 +109,7 @@ inline fun <T : YouTube.Item> SearchResult(
items(
items = items,
key = YouTube.Item::key,
key = Innertube.Item::key,
itemContent = itemContent
)

View File

@@ -13,16 +13,15 @@ import androidx.compose.ui.unit.dp
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.savers.YouTubeAlbumListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeArtistListSaver
import it.vfsfitvnm.vimusic.savers.YouTubePlaylistListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeSongListSaver
import it.vfsfitvnm.vimusic.savers.YouTubeVideoListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeAlbumItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeArtistItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeSongItemListSaver
import it.vfsfitvnm.vimusic.savers.InnertubeVideoItemListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
import it.vfsfitvnm.vimusic.ui.screens.artistRoute
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
import it.vfsfitvnm.vimusic.ui.screens.playlist.PlaylistScreen
import it.vfsfitvnm.vimusic.ui.screens.playlistRoute
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.px
@@ -40,7 +39,8 @@ import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.rememberPreference
import it.vfsfitvnm.vimusic.utils.searchResultScreenTabIndexKey
import it.vfsfitvnm.youtubemusic.YouTube
import it.vfsfitvnm.youtubemusic.Innertube
import it.vfsfitvnm.youtubemusic.utils.from
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@@ -52,12 +52,6 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()
playlistRoute { browseId ->
PlaylistScreen(
browseId = browseId ?: "browseId cannot be null"
)
}
host {
Scaffold(
topIconButtonId = R.drawable.chevron_back,
@@ -74,12 +68,12 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
}
) { tabIndex ->
val searchFilter = when (tabIndex) {
0 -> YouTube.Item.Song.Filter
1 -> YouTube.Item.Album.Filter
2 -> YouTube.Item.Artist.Filter
3 -> YouTube.Item.Video.Filter
4 -> YouTube.Item.CommunityPlaylist.Filter
5 -> YouTube.Item.FeaturedPlaylist.Filter
0 -> Innertube.SearchFilter.Song
1 -> Innertube.SearchFilter.Album
2 -> Innertube.SearchFilter.Artist
3 -> Innertube.SearchFilter.Video
4 -> Innertube.SearchFilter.CommunityPlaylist
5 -> Innertube.SearchFilter.FeaturedPlaylist
else -> error("unreachable")
}.value
@@ -94,7 +88,8 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
query = query,
filter = searchFilter,
onSearchAgain = onSearchAgain,
stateSaver = YouTubeSongListSaver,
stateSaver = InnertubeSongItemListSaver,
fromMusicShelfRendererContent = Innertube.SongItem.Companion::from,
itemContent = { song ->
SmallSongItem(
song = song,
@@ -119,8 +114,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubeAlbumListSaver,
stateSaver = InnertubeAlbumItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.AlbumItem.Companion::from,
itemContent = { album ->
AlbumItem(
album = album,
@@ -148,8 +144,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubeArtistListSaver,
stateSaver = InnertubeArtistItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.ArtistItem.Companion::from,
itemContent = { artist ->
ArtistItem(
artist = artist,
@@ -176,8 +173,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubeVideoListSaver,
stateSaver = InnertubeVideoItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.VideoItem.Companion::from,
itemContent = { video ->
VideoItem(
video = video,
@@ -206,8 +204,9 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
SearchResult(
query = query,
filter = searchFilter,
stateSaver = YouTubePlaylistListSaver,
stateSaver = InnertubePlaylistItemListSaver,
onSearchAgain = onSearchAgain,
fromMusicShelfRendererContent = Innertube.PlaylistItem.Companion::from,
itemContent = { playlist ->
PlaylistItem(
playlist = playlist,