Rework YouTube Radio
This commit is contained in:
@@ -32,29 +32,33 @@ import androidx.media3.exoplayer.analytics.AnalyticsListener
|
|||||||
import androidx.media3.exoplayer.analytics.PlaybackStats
|
import androidx.media3.exoplayer.analytics.PlaybackStats
|
||||||
import androidx.media3.exoplayer.analytics.PlaybackStatsListener
|
import androidx.media3.exoplayer.analytics.PlaybackStatsListener
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.session.MediaController
|
import androidx.media3.session.*
|
||||||
import androidx.media3.session.MediaNotification
|
|
||||||
import androidx.media3.session.MediaNotification.ActionFactory
|
import androidx.media3.session.MediaNotification.ActionFactory
|
||||||
import androidx.media3.session.MediaSession
|
|
||||||
import androidx.media3.session.MediaSessionService
|
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.MainActivity
|
import it.vfsfitvnm.vimusic.MainActivity
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.utils.RingBuffer
|
import it.vfsfitvnm.vimusic.utils.RingBuffer
|
||||||
import it.vfsfitvnm.vimusic.utils.YoutubePlayer
|
import it.vfsfitvnm.vimusic.utils.YoutubePlayer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
||||||
import it.vfsfitvnm.vimusic.utils.insert
|
import it.vfsfitvnm.vimusic.utils.insert
|
||||||
import it.vfsfitvnm.youtubemusic.Outcome
|
import it.vfsfitvnm.youtubemusic.Outcome
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
|
val StartRadioCommand = SessionCommand("StartRadioCommand", Bundle.EMPTY)
|
||||||
|
val StartArtistRadioCommand = SessionCommand("StartArtistRadioCommand", Bundle.EMPTY)
|
||||||
|
val StopRadioCommand = SessionCommand("StopRadioCommand", Bundle.EMPTY)
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
||||||
MediaNotification.Provider,
|
MediaNotification.Provider,
|
||||||
PlaybackStatsListener.Callback, Player.Listener,YoutubePlayer.Radio.Listener {
|
MediaSession.SessionCallback,
|
||||||
|
PlaybackStatsListener.Callback, Player.Listener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NotificationId = 1001
|
private const val NotificationId = 1001
|
||||||
@@ -74,6 +78,8 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
|||||||
private var lastArtworkUri: Uri? = null
|
private var lastArtworkUri: Uri? = null
|
||||||
private var lastBitmap: Bitmap? = null
|
private var lastBitmap: Bitmap? = null
|
||||||
|
|
||||||
|
private var radio: YoutubePlayer.Radio? = null
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO) + Job()
|
private val coroutineScope = CoroutineScope(Dispatchers.IO) + Job()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@@ -101,11 +107,11 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
|||||||
|
|
||||||
mediaSession = MediaSession.Builder(this, player)
|
mediaSession = MediaSession.Builder(this, player)
|
||||||
.withSessionActivity()
|
.withSessionActivity()
|
||||||
|
.setSessionCallback(this)
|
||||||
.setMediaItemFiller(this)
|
.setMediaItemFiller(this)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
player.addListener(this)
|
player.addListener(this)
|
||||||
YoutubePlayer.Radio.listener = this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -119,6 +125,49 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
|||||||
return mediaSession
|
return mediaSession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConnect(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo
|
||||||
|
): MediaSession.ConnectionResult {
|
||||||
|
val sessionCommands = SessionCommands.Builder()
|
||||||
|
.add(StartRadioCommand)
|
||||||
|
.add(StartArtistRadioCommand)
|
||||||
|
.add(StopRadioCommand)
|
||||||
|
.build()
|
||||||
|
val playerCommands = Player.Commands.Builder().addAllCommands().build()
|
||||||
|
return MediaSession.ConnectionResult.accept(sessionCommands,playerCommands)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCustomCommand(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo,
|
||||||
|
customCommand: SessionCommand,
|
||||||
|
args: Bundle
|
||||||
|
): ListenableFuture<SessionResult> {
|
||||||
|
when (customCommand) {
|
||||||
|
StartRadioCommand, StartArtistRadioCommand -> {
|
||||||
|
radio = null
|
||||||
|
YoutubePlayer.Radio(
|
||||||
|
videoId = args.getString("videoId"),
|
||||||
|
playlistId = args.getString("playlistId"),
|
||||||
|
playlistSetVideoId = args.getString("playlistSetVideoId"),
|
||||||
|
parameters = args.getString("params"),
|
||||||
|
).let {
|
||||||
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
|
when (customCommand) {
|
||||||
|
StartRadioCommand -> mediaSession.player.addMediaItems(it.process().drop(1))
|
||||||
|
StartArtistRadioCommand -> mediaSession.player.forcePlayFromBeginning(it.process())
|
||||||
|
}
|
||||||
|
radio = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StopRadioCommand -> radio = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onCustomCommand(session, controller, customCommand, args)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPlaybackStatsReady(
|
override fun onPlaybackStatsReady(
|
||||||
eventTime: AnalyticsListener.EventTime,
|
eventTime: AnalyticsListener.EventTime,
|
||||||
playbackStats: PlaybackStats
|
playbackStats: PlaybackStats
|
||||||
@@ -132,18 +181,12 @@ class PlayerService : MediaSessionService(), MediaSession.MediaItemFiller,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun process(play: Boolean) {
|
|
||||||
if (YoutubePlayer.Radio.isActive) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
YoutubePlayer.Radio.process(mediaSession.player, play = play)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
if (YoutubePlayer.Radio.isActive) {
|
radio?.let { radio ->
|
||||||
coroutineScope.launch {
|
if (mediaSession.player.mediaItemCount - mediaSession.player.currentMediaItemIndex <= 3) {
|
||||||
YoutubePlayer.Radio.process(mediaSession.player)
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
|
mediaSession.player.addMediaItems(radio.process())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.components.themed
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.AnimatedContentScope
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.with
|
import androidx.compose.animation.with
|
||||||
@@ -13,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
@@ -23,6 +25,8 @@ import it.vfsfitvnm.vimusic.internal
|
|||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
||||||
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
||||||
|
import it.vfsfitvnm.vimusic.services.StartRadioCommand
|
||||||
|
import it.vfsfitvnm.vimusic.services.StopRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.rememberArtistRoute
|
import it.vfsfitvnm.vimusic.ui.screens.rememberArtistRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.rememberCreatePlaylistRoute
|
import it.vfsfitvnm.vimusic.ui.screens.rememberCreatePlaylistRoute
|
||||||
@@ -145,13 +149,19 @@ fun NonQueuedMediaItemMenu(
|
|||||||
mediaItem = mediaItem,
|
mediaItem = mediaItem,
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
onStartRadio = {
|
onStartRadio = {
|
||||||
val playlistId = mediaItem.mediaMetadata.extras?.getString("playlistId")
|
player?.mediaController?.run {
|
||||||
YoutubePlayer.Radio.setup(playlistId = playlistId)
|
forcePlay(mediaItem)
|
||||||
player?.mediaController?.forcePlay(mediaItem)
|
sendCustomCommand(StartRadioCommand, bundleOf(
|
||||||
|
"videoId" to mediaItem.mediaId,
|
||||||
|
"playlistId" to mediaItem.mediaMetadata.extras?.getString("playlistId")
|
||||||
|
))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onPlaySingle = {
|
onPlaySingle = {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.run {
|
||||||
player?.mediaController?.forcePlay(mediaItem)
|
sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
|
forcePlay(mediaItem)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onPlayNext = if (player?.playbackState == Player.STATE_READY) ({
|
onPlayNext = if (player?.playbackState == Player.STATE_READY) ({
|
||||||
player.mediaController.addNext(mediaItem)
|
player.mediaController.addNext(mediaItem)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import coil.compose.AsyncImage
|
|||||||
import com.valentinilk.shimmer.shimmer
|
import com.valentinilk.shimmer.shimmer
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.services.StartArtistRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ExpandableText
|
import it.vfsfitvnm.vimusic.ui.components.ExpandableText
|
||||||
import it.vfsfitvnm.vimusic.ui.components.Message
|
import it.vfsfitvnm.vimusic.ui.components.Message
|
||||||
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
|
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
|
||||||
@@ -69,6 +70,7 @@ fun ArtistScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
host {
|
host {
|
||||||
|
val player = LocalYoutubePlayer.current
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val colorPalette = LocalColorPalette.current
|
val colorPalette = LocalColorPalette.current
|
||||||
val typography = LocalTypography.current
|
val typography = LocalTypography.current
|
||||||
@@ -137,8 +139,7 @@ fun ArtistScreen(
|
|||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.sendCustomCommand(StartArtistRadioCommand, artist.shuffleEndpoint.asBundle)
|
||||||
artist.shuffleEndpoint?.let(YoutubePlayer.Radio::setup)
|
|
||||||
}
|
}
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
.background(color = colorPalette.elevatedBackground, shape = CircleShape)
|
.background(color = colorPalette.elevatedBackground, shape = CircleShape)
|
||||||
@@ -152,8 +153,7 @@ fun ArtistScreen(
|
|||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.sendCustomCommand(StartArtistRadioCommand, artist.radioEndpoint.asBundle)
|
||||||
artist.radioEndpoint?.let(YoutubePlayer.Radio::setup)
|
|
||||||
}
|
}
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
.background(color = colorPalette.elevatedBackground, shape = CircleShape)
|
.background(color = colorPalette.elevatedBackground, shape = CircleShape)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
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
|
||||||
@@ -36,6 +37,7 @@ import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
|||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.models.SearchQuery
|
import it.vfsfitvnm.vimusic.models.SearchQuery
|
||||||
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
||||||
|
import it.vfsfitvnm.vimusic.services.StopRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
||||||
@@ -331,11 +333,10 @@ fun HomeScreen(
|
|||||||
enabled = songCollection.isNotEmpty(),
|
enabled = songCollection.isNotEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlayFromBeginning(
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
songCollection
|
it.forcePlayFromBeginning(songCollection.map(SongWithInfo::asMediaItem))
|
||||||
.map(SongWithInfo::asMediaItem)
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -345,12 +346,10 @@ fun HomeScreen(
|
|||||||
enabled = songCollection.isNotEmpty(),
|
enabled = songCollection.isNotEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlayFromBeginning(
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
songCollection
|
it.forcePlayFromBeginning(songCollection.shuffled().map(SongWithInfo::asMediaItem))
|
||||||
.shuffled()
|
}
|
||||||
.map(SongWithInfo::asMediaItem)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -385,11 +384,10 @@ fun HomeScreen(
|
|||||||
song = song,
|
song = song,
|
||||||
thumbnailSize = thumbnailSize,
|
thumbnailSize = thumbnailSize,
|
||||||
onClick = {
|
onClick = {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlayAtIndex(
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
songCollection.map(SongWithInfo::asMediaItem),
|
it.forcePlayAtIndex(songCollection.map(SongWithInfo::asMediaItem), index)
|
||||||
index
|
}
|
||||||
)
|
|
||||||
},
|
},
|
||||||
menuContent = {
|
menuContent = {
|
||||||
when (preferences.homePageSongCollection) {
|
when (preferences.homePageSongCollection) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -26,6 +27,7 @@ import it.vfsfitvnm.vimusic.R
|
|||||||
import it.vfsfitvnm.vimusic.internal
|
import it.vfsfitvnm.vimusic.internal
|
||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
||||||
|
import it.vfsfitvnm.vimusic.services.StopRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.Error
|
import it.vfsfitvnm.vimusic.ui.components.Error
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.Message
|
import it.vfsfitvnm.vimusic.ui.components.Message
|
||||||
@@ -238,13 +240,10 @@ fun IntentUriScreen(uri: Uri) {
|
|||||||
song = item,
|
song = item,
|
||||||
thumbnailSizePx = density.run { 54.dp.roundToPx() },
|
thumbnailSizePx = density.run { 54.dp.roundToPx() },
|
||||||
onClick = {
|
onClick = {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
player?.mediaController?.forcePlayAtIndex(
|
it.forcePlayAtIndex(currentItems.value.map(YouTube.Item.Song::asMediaItem), index)
|
||||||
currentItems.value.map(
|
}
|
||||||
YouTube.Item.Song::asMediaItem
|
|
||||||
), index
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -30,6 +31,7 @@ import it.vfsfitvnm.vimusic.R
|
|||||||
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
|
import it.vfsfitvnm.vimusic.models.PlaylistWithSongs
|
||||||
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
||||||
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
||||||
|
import it.vfsfitvnm.vimusic.services.StopRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
||||||
@@ -232,12 +234,10 @@ fun LocalPlaylistScreen(
|
|||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlayFromBeginning(
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
playlistWithSongs.songs
|
it.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem).shuffled())
|
||||||
.map(SongWithInfo::asMediaItem)
|
}
|
||||||
.shuffled()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
.background(
|
.background(
|
||||||
@@ -254,12 +254,10 @@ fun LocalPlaylistScreen(
|
|||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlayFromBeginning(
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
playlistWithSongs.songs.map(
|
it.forcePlayFromBeginning(playlistWithSongs.songs.map(SongWithInfo::asMediaItem))
|
||||||
SongWithInfo::asMediaItem
|
}
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
.background(
|
.background(
|
||||||
@@ -282,12 +280,10 @@ fun LocalPlaylistScreen(
|
|||||||
song = song,
|
song = song,
|
||||||
thumbnailSize = thumbnailSize,
|
thumbnailSize = thumbnailSize,
|
||||||
onClick = {
|
onClick = {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlayAtIndex(
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
playlistWithSongs.songs.map(
|
it.forcePlayAtIndex(playlistWithSongs.songs.map(SongWithInfo::asMediaItem), index)
|
||||||
SongWithInfo::asMediaItem
|
}
|
||||||
), index
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
menuContent = {
|
menuContent = {
|
||||||
InPlaylistMediaItemMenu(
|
InPlaylistMediaItemMenu(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -22,21 +23,22 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.valentinilk.shimmer.shimmer
|
import com.valentinilk.shimmer.shimmer
|
||||||
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
||||||
import it.vfsfitvnm.vimusic.internal
|
import it.vfsfitvnm.vimusic.internal
|
||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
import it.vfsfitvnm.vimusic.models.SongInPlaylist
|
||||||
|
import it.vfsfitvnm.vimusic.services.StopRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
|
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
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.ui.views.SongItem
|
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
||||||
import it.vfsfitvnm.vimusic.utils.*
|
import it.vfsfitvnm.vimusic.utils.*
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.*
|
|
||||||
import it.vfsfitvnm.youtubemusic.Outcome
|
import it.vfsfitvnm.youtubemusic.Outcome
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -276,14 +278,16 @@ fun PlaylistOrAlbumScreen(
|
|||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
playlistOrAlbum.items
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
?.shuffled()
|
playlistOrAlbum.items
|
||||||
?.mapNotNull { song ->
|
?.shuffled()
|
||||||
song.toMediaItem(browseId, playlistOrAlbum)
|
?.mapNotNull { song ->
|
||||||
}?.let { mediaItems ->
|
song.toMediaItem(browseId, playlistOrAlbum)
|
||||||
player?.mediaController?.forcePlayFromBeginning(mediaItems)
|
}?.let { mediaItems ->
|
||||||
}
|
it.forcePlayFromBeginning(mediaItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
.background(
|
.background(
|
||||||
@@ -300,12 +304,13 @@ fun PlaylistOrAlbumScreen(
|
|||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
playlistOrAlbum.items?.mapNotNull { song ->
|
playlistOrAlbum.items?.mapNotNull { song ->
|
||||||
song.toMediaItem(browseId, playlistOrAlbum)
|
song.toMediaItem(browseId, playlistOrAlbum)
|
||||||
}?.let { mediaItems ->
|
}?.let { mediaItems ->
|
||||||
player?.mediaController?.forcePlayFromBeginning(mediaItems)
|
it.forcePlayFromBeginning(mediaItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
@@ -326,12 +331,13 @@ fun PlaylistOrAlbumScreen(
|
|||||||
authors = (song.authors ?: playlistOrAlbum.authors)?.joinToString("") { it.name },
|
authors = (song.authors ?: playlistOrAlbum.authors)?.joinToString("") { it.name },
|
||||||
durationText = song.durationText,
|
durationText = song.durationText,
|
||||||
onClick = {
|
onClick = {
|
||||||
YoutubePlayer.Radio.reset()
|
player?.mediaController?.let {
|
||||||
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
playlistOrAlbum.items?.mapNotNull { song ->
|
playlistOrAlbum.items?.mapNotNull { song ->
|
||||||
song.toMediaItem(browseId, playlistOrAlbum)
|
song.toMediaItem(browseId, playlistOrAlbum)
|
||||||
}?.let { mediaItems ->
|
}?.let { mediaItems ->
|
||||||
player?.mediaController?.forcePlayAtIndex(mediaItems, index)
|
it.forcePlayAtIndex(mediaItems, index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startContent = {
|
startContent = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -25,6 +26,7 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.valentinilk.shimmer.Shimmer
|
import com.valentinilk.shimmer.Shimmer
|
||||||
import com.valentinilk.shimmer.ShimmerBounds
|
import com.valentinilk.shimmer.ShimmerBounds
|
||||||
@@ -33,6 +35,7 @@ import com.valentinilk.shimmer.shimmer
|
|||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
||||||
|
import it.vfsfitvnm.vimusic.services.StartRadioCommand
|
||||||
import it.vfsfitvnm.vimusic.ui.components.*
|
import it.vfsfitvnm.vimusic.ui.components.*
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
@@ -42,6 +45,7 @@ import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|||||||
import it.vfsfitvnm.vimusic.utils.*
|
import it.vfsfitvnm.vimusic.utils.*
|
||||||
import it.vfsfitvnm.youtubemusic.Outcome
|
import it.vfsfitvnm.youtubemusic.Outcome
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -214,17 +218,13 @@ fun SearchResultScreen(
|
|||||||
is YouTube.Item.Album -> playlistOrAlbumRoute(item.info.endpoint!!.browseId)
|
is YouTube.Item.Album -> playlistOrAlbumRoute(item.info.endpoint!!.browseId)
|
||||||
is YouTube.Item.Artist -> artistRoute(item.info.endpoint!!.browseId)
|
is YouTube.Item.Artist -> artistRoute(item.info.endpoint!!.browseId)
|
||||||
is YouTube.Item.Playlist -> playlistOrAlbumRoute(item.info.endpoint!!.browseId)
|
is YouTube.Item.Playlist -> playlistOrAlbumRoute(item.info.endpoint!!.browseId)
|
||||||
is YouTube.Item.Song -> {
|
is YouTube.Item.Song -> player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlay(item.asMediaItem)
|
it.forcePlay(item.asMediaItem)
|
||||||
item.info.endpoint?.let {
|
it.sendCustomCommand(StartRadioCommand, item.info.endpoint.asBundle)
|
||||||
YoutubePlayer.Radio.setup(it, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is YouTube.Item.Video -> {
|
is YouTube.Item.Video -> player?.mediaController?.let {
|
||||||
player?.mediaController?.forcePlay(item.asMediaItem)
|
it.forcePlay(item.asMediaItem)
|
||||||
item.info.endpoint?.let {
|
it.sendCustomCommand(StartRadioCommand, item.info.endpoint.asBundle)
|
||||||
YoutubePlayer.Radio.setup(it, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,4 +572,14 @@ fun SmallArtistItem(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val NavigationEndpoint.Endpoint.Watch?.asBundle: Bundle
|
||||||
|
get() = this?.let {
|
||||||
|
bundleOf(
|
||||||
|
"videoId" to videoId,
|
||||||
|
"playlistId" to playlistId,
|
||||||
|
"playlistSetVideoId" to playlistSetVideoId,
|
||||||
|
"params" to params,
|
||||||
|
)
|
||||||
|
} ?: Bundle.EMPTY
|
||||||
@@ -157,50 +157,50 @@ fun CurrentPlaylistView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (YoutubePlayer.Radio.isActive && player != null) {
|
// if (YoutubePlayer.Radio.isActive && player != null) {
|
||||||
when (val nextContinuation = YoutubePlayer.Radio.nextContinuation) {
|
// when (val nextContinuation = YoutubePlayer.Radio.nextContinuation) {
|
||||||
is Outcome.Loading, is Outcome.Success<*> -> {
|
// is Outcome.Loading, is Outcome.Success<*> -> {
|
||||||
if (nextContinuation is Outcome.Success<*>) {
|
// if (nextContinuation is Outcome.Success<*>) {
|
||||||
item {
|
// item {
|
||||||
SideEffect {
|
// SideEffect {
|
||||||
coroutineScope.launch {
|
// coroutineScope.launch {
|
||||||
YoutubePlayer.Radio.process(
|
// YoutubePlayer.Radio.process(
|
||||||
player.mediaController,
|
// player.mediaController,
|
||||||
force = true
|
// force = true
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
items(count = 3, key = { it }) { index ->
|
// items(count = 3, key = { it }) { index ->
|
||||||
SmallSongItemShimmer(
|
// SmallSongItemShimmer(
|
||||||
shimmer = shimmer,
|
// shimmer = shimmer,
|
||||||
thumbnailSizeDp = 54.dp,
|
// thumbnailSizeDp = 54.dp,
|
||||||
modifier = Modifier
|
// modifier = Modifier
|
||||||
.alpha(1f - index * 0.125f)
|
// .alpha(1f - index * 0.125f)
|
||||||
.fillMaxWidth()
|
// .fillMaxWidth()
|
||||||
.padding(vertical = 4.dp, horizontal = 16.dp)
|
// .padding(vertical = 4.dp, horizontal = 16.dp)
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
is Outcome.Error -> item {
|
// is Outcome.Error -> item {
|
||||||
Error(
|
// Error(
|
||||||
error = nextContinuation
|
// error = nextContinuation
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
is Outcome.Recovered<*> -> item {
|
// is Outcome.Recovered<*> -> item {
|
||||||
Error(
|
// Error(
|
||||||
error = nextContinuation.error,
|
// error = nextContinuation.error,
|
||||||
onRetry = {
|
// onRetry = {
|
||||||
coroutineScope.launch {
|
// coroutineScope.launch {
|
||||||
YoutubePlayer.Radio.process(player.mediaController, force = true)
|
// YoutubePlayer.Radio.process(player.mediaController, force = true)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
else -> {}
|
// else -> {}
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +1,47 @@
|
|||||||
package it.vfsfitvnm.vimusic.utils
|
package it.vfsfitvnm.vimusic.utils
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.session.MediaController
|
import androidx.media3.session.MediaController
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import it.vfsfitvnm.youtubemusic.Outcome
|
import it.vfsfitvnm.youtubemusic.Outcome
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.guava.await
|
import kotlinx.coroutines.guava.await
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class YoutubePlayer(mediaController: MediaController) : PlayerState(mediaController) {
|
class YoutubePlayer(mediaController: MediaController) : PlayerState(mediaController) {
|
||||||
object Radio {
|
data class Radio(
|
||||||
var isActive by mutableStateOf(false)
|
private val videoId: String? = null,
|
||||||
|
private val playlistId: String? = null,
|
||||||
var listener: Listener? = null
|
private val playlistSetVideoId: String? = null,
|
||||||
|
private val parameters: String? = null
|
||||||
private var videoId: String? = null
|
) {
|
||||||
private var playlistId: String? = null
|
|
||||||
private var playlistSetVideoId: String? = null
|
|
||||||
private var parameters: String? = null
|
|
||||||
|
|
||||||
var nextContinuation by mutableStateOf<Outcome<String?>>(Outcome.Initial)
|
var nextContinuation by mutableStateOf<Outcome<String?>>(Outcome.Initial)
|
||||||
|
|
||||||
fun setup(videoId: String? = null, playlistId: String? = null, playlistSetVideoId: String? = null, parameters: String? = null) {
|
suspend fun process(): List<MediaItem> {
|
||||||
this.videoId = videoId
|
println("process: ${nextContinuation.valueOrNull}")
|
||||||
this.playlistId = playlistId
|
|
||||||
this.playlistSetVideoId = playlistSetVideoId
|
|
||||||
this.parameters = parameters
|
|
||||||
|
|
||||||
isActive = true
|
|
||||||
nextContinuation = Outcome.Initial
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setup(watchEndpoint: NavigationEndpoint.Endpoint.Watch?, play: Boolean = true) {
|
|
||||||
setup(
|
|
||||||
videoId = watchEndpoint?.videoId,
|
|
||||||
playlistId = watchEndpoint?.playlistId,
|
|
||||||
parameters = watchEndpoint?.params,
|
|
||||||
playlistSetVideoId = watchEndpoint?.playlistSetVideoId
|
|
||||||
)
|
|
||||||
|
|
||||||
listener?.process(play)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun process(player: Player, force: Boolean = false, play: Boolean = false) {
|
|
||||||
if (!isActive) return
|
|
||||||
|
|
||||||
if (!force && !play) {
|
|
||||||
val isFirstSong = withContext(Dispatchers.Main) {
|
|
||||||
player.mediaItemCount == 0 || (player.currentMediaItemIndex == 0 && player.mediaItemCount == 1)
|
|
||||||
}
|
|
||||||
val isNearEndSong = withContext(Dispatchers.Main) {
|
|
||||||
player.mediaItemCount - player.currentMediaItemIndex <= 3
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFirstSong && !isNearEndSong) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val token = nextContinuation.valueOrNull
|
val token = nextContinuation.valueOrNull
|
||||||
|
|
||||||
nextContinuation = Outcome.Loading
|
nextContinuation = Outcome.Loading
|
||||||
|
|
||||||
|
var mediaItems: List<MediaItem>? = null
|
||||||
|
|
||||||
nextContinuation = withContext(Dispatchers.IO) {
|
nextContinuation = withContext(Dispatchers.IO) {
|
||||||
YouTube.next(
|
YouTube.next(
|
||||||
videoId = videoId ?: withContext(Dispatchers.Main) {
|
videoId = videoId ?: error("This should not happen"),
|
||||||
player.lastMediaItem?.mediaId ?: error("This should not happen")
|
|
||||||
},
|
|
||||||
playlistId = playlistId,
|
playlistId = playlistId,
|
||||||
params = parameters,
|
params = parameters,
|
||||||
playlistSetVideoId = playlistSetVideoId,
|
playlistSetVideoId = playlistSetVideoId,
|
||||||
continuation = token
|
continuation = token
|
||||||
)
|
)
|
||||||
}.map { nextResult ->
|
}.map { nextResult ->
|
||||||
nextResult.items?.map(it.vfsfitvnm.youtubemusic.YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
|
mediaItems = nextResult.items?.map(YouTube.Item.Song::asMediaItem)
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
if (play) {
|
|
||||||
player.forcePlayFromBeginning(mediaItems)
|
|
||||||
} else {
|
|
||||||
player.addMediaItems(mediaItems.drop(if (token == null) 1 else 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextResult.continuation?.takeUnless { token == nextResult.continuation }
|
nextResult.continuation?.takeUnless { token == nextResult.continuation }
|
||||||
}.recoverWith(token)
|
}.recoverWith(token)
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() {
|
return mediaItems ?: emptyList()
|
||||||
videoId = null
|
|
||||||
playlistId = null
|
|
||||||
playlistSetVideoId = null
|
|
||||||
parameters = null
|
|
||||||
isActive = false
|
|
||||||
nextContinuation = Outcome.Initial
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun process(play: Boolean)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user