Add the ability to open YouTube playlist links
This commit is contained in:
parent
756eb48176
commit
c3b2435623
7 changed files with 235 additions and 146 deletions
|
@ -35,7 +35,13 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https"
|
||||||
|
android:host="music.youtube.com"
|
||||||
|
android:pathPrefix="/playlist" />
|
||||||
|
|
||||||
|
<data android:scheme="https"
|
||||||
|
android:host="www.youtube.com"
|
||||||
|
android:pathPrefix="/playlist" />
|
||||||
|
|
||||||
<data android:scheme="https"
|
<data android:scheme="https"
|
||||||
android:host="music.youtube.com"
|
android:host="music.youtube.com"
|
||||||
|
|
|
@ -60,8 +60,6 @@ class MainActivity : ComponentActivity() {
|
||||||
val sessionToken = SessionToken(this, ComponentName(this, PlayerService::class.java))
|
val sessionToken = SessionToken(this, ComponentName(this, PlayerService::class.java))
|
||||||
mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
|
mediaControllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
|
||||||
|
|
||||||
val intentVideoId = intent?.data?.getQueryParameter("v")
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val preferences by rememberPreferences(dataStore)
|
val preferences by rememberPreferences(dataStore)
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
|
@ -131,7 +129,7 @@ class MainActivity : ComponentActivity() {
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(LocalColorPalette.current.background)
|
.background(LocalColorPalette.current.background)
|
||||||
) {
|
) {
|
||||||
HomeScreen(intentVideoId = intentVideoId)
|
HomeScreen(intentUri = intent?.data)
|
||||||
|
|
||||||
BottomSheetMenu(
|
BottomSheetMenu(
|
||||||
state = LocalMenuState.current,
|
state = LocalMenuState.current,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
@ -50,9 +51,12 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(intentVideoId: String?) {
|
fun HomeScreen(
|
||||||
|
intentUri: Uri?
|
||||||
|
) {
|
||||||
val colorPalette = LocalColorPalette.current
|
val colorPalette = LocalColorPalette.current
|
||||||
val typography = LocalTypography.current
|
val typography = LocalTypography.current
|
||||||
|
|
||||||
|
@ -60,7 +64,7 @@ fun HomeScreen(intentVideoId: String?) {
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
val intentVideoRoute = rememberIntentVideoRoute(intentVideoId)
|
val intentUriRoute = rememberIntentUriRoute(intentUri)
|
||||||
val settingsRoute = rememberSettingsRoute()
|
val settingsRoute = rememberSettingsRoute()
|
||||||
val playlistRoute = rememberLocalPlaylistRoute()
|
val playlistRoute = rememberLocalPlaylistRoute()
|
||||||
val searchRoute = rememberSearchRoute()
|
val searchRoute = rememberSearchRoute()
|
||||||
|
@ -68,7 +72,7 @@ fun HomeScreen(intentVideoId: String?) {
|
||||||
val albumRoute = rememberPlaylistOrAlbumRoute()
|
val albumRoute = rememberPlaylistOrAlbumRoute()
|
||||||
val artistRoute = rememberArtistRoute()
|
val artistRoute = rememberArtistRoute()
|
||||||
|
|
||||||
val (route, onRouteChanged) = rememberRoute(intentVideoId?.let { intentVideoRoute })
|
val (route, onRouteChanged) = rememberRoute(intentUri?.let { intentUriRoute })
|
||||||
|
|
||||||
val playlistPreviews by remember {
|
val playlistPreviews by remember {
|
||||||
Database.playlistPreviews()
|
Database.playlistPreviews()
|
||||||
|
@ -97,9 +101,9 @@ fun HomeScreen(intentVideoId: String?) {
|
||||||
onRouteChanged = onRouteChanged,
|
onRouteChanged = onRouteChanged,
|
||||||
listenToGlobalEmitter = true
|
listenToGlobalEmitter = true
|
||||||
) {
|
) {
|
||||||
intentVideoRoute { videoId ->
|
intentUriRoute { uri ->
|
||||||
IntentVideoScreen(
|
IntentUriScreen(
|
||||||
videoId = videoId ?: error("videoId must be not null")
|
uri = uri ?: error("uri must be not null")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,9 +140,7 @@ fun HomeScreen(intentVideoId: String?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
albumRoute { browseId ->
|
albumRoute { browseId ->
|
||||||
PlaylistOrAlbumScreen(
|
PlaylistOrAlbumScreen(browseId = browseId ?: error("browseId cannot be null"))
|
||||||
browseId = browseId ?: error("browseId cannot be null")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
artistRoute { browseId ->
|
artistRoute { browseId ->
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
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.ColorFilter
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.valentinilk.shimmer.ShimmerBounds
|
||||||
|
import com.valentinilk.shimmer.rememberShimmer
|
||||||
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.Error
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.Message
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
||||||
|
import it.vfsfitvnm.vimusic.utils.*
|
||||||
|
import it.vfsfitvnm.youtubemusic.Outcome
|
||||||
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
import it.vfsfitvnm.youtubemusic.toNullable
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun IntentUriScreen(uri: Uri) {
|
||||||
|
val albumRoute = rememberPlaylistOrAlbumRoute()
|
||||||
|
val artistRoute = rememberArtistRoute()
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
|
RouteHandler(listenToGlobalEmitter = true) {
|
||||||
|
albumRoute { browseId ->
|
||||||
|
PlaylistOrAlbumScreen(
|
||||||
|
browseId = browseId ?: error("browseId cannot be null")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
artistRoute { browseId ->
|
||||||
|
ArtistScreen(
|
||||||
|
browseId = browseId ?: error("browseId cannot be null")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
host {
|
||||||
|
val colorPalette = LocalColorPalette.current
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val player = LocalYoutubePlayer.current
|
||||||
|
|
||||||
|
val shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.Window)
|
||||||
|
|
||||||
|
|
||||||
|
var items by remember {
|
||||||
|
mutableStateOf<Outcome<List<YouTube.Item.Song>>>(Outcome.Loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
val onLoad = relaunchableEffect(Unit) {
|
||||||
|
items = withContext(Dispatchers.IO) {
|
||||||
|
uri.getQueryParameter("list")?.let { playlistId ->
|
||||||
|
YouTube.queue(playlistId).toNullable()?.map { songList ->
|
||||||
|
songList
|
||||||
|
}
|
||||||
|
} ?: uri.getQueryParameter("v")?.let { videoId ->
|
||||||
|
YouTube.song(videoId).toNullable()?.map { listOf(it) }
|
||||||
|
} ?: Outcome.Error.Network
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
contentPadding = PaddingValues(bottom = 64.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
TopAppBar(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(52.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.chevron_back),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = pop)
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val currentItems = items) {
|
||||||
|
is Outcome.Error -> item {
|
||||||
|
Error(
|
||||||
|
error = currentItems,
|
||||||
|
onRetry = onLoad,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Outcome.Recovered -> item {
|
||||||
|
Error(
|
||||||
|
error = currentItems.error,
|
||||||
|
onRetry = onLoad,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Outcome.Loading, is Outcome.Initial -> items(count = 5) { index ->
|
||||||
|
SmallSongItemShimmer(
|
||||||
|
shimmer = shimmer,
|
||||||
|
thumbnailSizeDp = 54.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.alpha(1f - index * 0.175f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp, horizontal = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Outcome.Success -> {
|
||||||
|
if (currentItems.value.isEmpty()) {
|
||||||
|
item {
|
||||||
|
Message(
|
||||||
|
text = "No songs were found",
|
||||||
|
modifier = Modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemsIndexed(currentItems.value) { index, item ->
|
||||||
|
SmallSongItem(
|
||||||
|
song = item,
|
||||||
|
thumbnailSizePx = density.run { 54.dp.roundToPx() },
|
||||||
|
onClick = {
|
||||||
|
YoutubePlayer.Radio.reset()
|
||||||
|
|
||||||
|
player?.mediaController?.forcePlayAtIndex(currentItems.value.map(YouTube.Item.Song::asMediaItem), index)
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
package it.vfsfitvnm.vimusic.ui.screens
|
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import com.valentinilk.shimmer.ShimmerBounds
|
|
||||||
import com.valentinilk.shimmer.rememberShimmer
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.LocalYoutubePlayer
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
|
||||||
import it.vfsfitvnm.youtubemusic.Outcome
|
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
|
||||||
import it.vfsfitvnm.youtubemusic.toNullable
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun IntentVideoScreen(videoId: String) {
|
|
||||||
val albumRoute = rememberPlaylistOrAlbumRoute()
|
|
||||||
val artistRoute = rememberArtistRoute()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
albumRoute { browseId ->
|
|
||||||
PlaylistOrAlbumScreen(
|
|
||||||
browseId = browseId ?: error("browseId cannot be null")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
artistRoute { browseId ->
|
|
||||||
ArtistScreen(
|
|
||||||
browseId = browseId ?: error("browseId cannot be null")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
host {
|
|
||||||
val colorPalette = LocalColorPalette.current
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val player = LocalYoutubePlayer.current
|
|
||||||
|
|
||||||
val mediaItem by produceState<Outcome<MediaItem>>(initialValue = Outcome.Loading) {
|
|
||||||
value = withContext(Dispatchers.IO) {
|
|
||||||
Database.songWithInfo(videoId)?.let { songWithInfo ->
|
|
||||||
Outcome.Success(songWithInfo.asMediaItem)
|
|
||||||
} ?: YouTube.getQueue(videoId).toNullable()
|
|
||||||
?.map(YouTube.Item.Song::asMediaItem)
|
|
||||||
?: Outcome.Error.Network
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
OutcomeItem(
|
|
||||||
outcome = mediaItem,
|
|
||||||
onLoading = {
|
|
||||||
SmallSongItemShimmer(
|
|
||||||
shimmer = rememberShimmer(shimmerBounds = ShimmerBounds.View),
|
|
||||||
thumbnailSizeDp = 54.dp,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 4.dp, horizontal = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { mediaItem ->
|
|
||||||
SongItem(
|
|
||||||
mediaItem = mediaItem,
|
|
||||||
thumbnailSize = remember {
|
|
||||||
density.run {
|
|
||||||
54.dp.roundToPx()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
player?.mediaController?.forcePlay(mediaItem)
|
|
||||||
pop()
|
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
NonQueuedMediaItemMenu(mediaItem = mediaItem)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -8,12 +9,12 @@ import it.vfsfitvnm.route.Route0
|
||||||
import it.vfsfitvnm.route.Route1
|
import it.vfsfitvnm.route.Route1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberIntentVideoRoute(intentVideoId: String?): Route1<String?> {
|
fun rememberIntentUriRoute(intentUri: Uri?): Route1<Uri?> {
|
||||||
val videoId = rememberSaveable {
|
val uri = rememberSaveable {
|
||||||
mutableStateOf(intentVideoId)
|
mutableStateOf(intentUri)
|
||||||
}
|
}
|
||||||
return remember {
|
return remember {
|
||||||
Route1("rememberIntentVideoRoute", videoId)
|
Route1("rememberIntentUriRoute", uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,8 @@ object YouTube {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GetQueueBody(
|
data class GetQueueBody(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val videoIds: List<String>
|
val videoIds: List<String>?,
|
||||||
|
val playlistId: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -407,41 +408,65 @@ object YouTube {
|
||||||
}.bodyCatching()
|
}.bodyCatching()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getQueue(videoId: String): Outcome<Item.Song?> {
|
private suspend fun getQueue(body: GetQueueBody): Outcome<List<Item.Song>?> {
|
||||||
return client.postCatching("/youtubei/v1/music/get_queue") {
|
return client.postCatching("/youtubei/v1/music/get_queue") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(
|
setBody(body)
|
||||||
GetQueueBody(
|
|
||||||
context = Context.DefaultWeb,
|
|
||||||
videoIds = listOf(videoId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parameter("key", Key)
|
parameter("key", Key)
|
||||||
parameter("prettyPrint", false)
|
parameter("prettyPrint", false)
|
||||||
}
|
}
|
||||||
.bodyCatching<GetQueueResponse>()
|
.bodyCatching<GetQueueResponse>()
|
||||||
.map { body ->
|
.map { body ->
|
||||||
body.queueDatas?.firstOrNull()?.content?.playlistPanelVideoRenderer?.let { renderer ->
|
body.queueDatas?.mapNotNull { queueData ->
|
||||||
Item.Song(
|
queueData.content?.playlistPanelVideoRenderer?.let { renderer ->
|
||||||
info = Info(
|
Item.Song(
|
||||||
name = renderer.title.text,
|
info = Info(
|
||||||
endpoint = renderer.navigationEndpoint.watchEndpoint
|
name = renderer
|
||||||
),
|
.title
|
||||||
authors = renderer.longBylineText?.splitBySeparator()?.getOrNull(0)
|
.text,
|
||||||
?.map { run ->
|
endpoint = renderer
|
||||||
Info.from(run)
|
.navigationEndpoint
|
||||||
} ?: emptyList(),
|
.watchEndpoint
|
||||||
album = renderer.longBylineText?.splitBySeparator()?.getOrNull(1)?.get(0)
|
),
|
||||||
?.let { run ->
|
authors = renderer
|
||||||
Info.from(run)
|
.longBylineText
|
||||||
},
|
?.splitBySeparator()
|
||||||
thumbnail = renderer.thumbnail.thumbnails[0],
|
?.getOrNull(0)
|
||||||
durationText = renderer.lengthText.text
|
?.map { Info.from(it) } ?: emptyList(),
|
||||||
)
|
album = renderer
|
||||||
|
.longBylineText
|
||||||
|
?.splitBySeparator()
|
||||||
|
?.getOrNull(1)
|
||||||
|
?.get(0)
|
||||||
|
?.let { Info.from(it) },
|
||||||
|
thumbnail = renderer.thumbnail.thumbnails[0],
|
||||||
|
durationText = renderer.lengthText.text
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun song(videoId: String): Outcome<Item.Song?> {
|
||||||
|
return getQueue(
|
||||||
|
GetQueueBody(
|
||||||
|
context = Context.DefaultWeb,
|
||||||
|
videoIds = listOf(videoId),
|
||||||
|
playlistId = null
|
||||||
|
)
|
||||||
|
).map { it?.firstOrNull() }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun queue(playlistId: String): Outcome<List<Item.Song>?> {
|
||||||
|
return getQueue(
|
||||||
|
GetQueueBody(
|
||||||
|
context = Context.DefaultWeb,
|
||||||
|
videoIds = null,
|
||||||
|
playlistId = playlistId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun next(
|
suspend fun next(
|
||||||
videoId: String,
|
videoId: String,
|
||||||
playlistId: String?,
|
playlistId: String?,
|
||||||
|
|
Loading…
Reference in a new issue