Initial commit
This commit is contained in:
44
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Player.kt
Normal file
44
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Player.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
|
||||
|
||||
fun Player.forcePlay(mediaItem: MediaItem) {
|
||||
setMediaItem(mediaItem, true)
|
||||
playWhenReady = true
|
||||
prepare()
|
||||
}
|
||||
|
||||
fun Player.forcePlayAtIndex(mediaItems: List<MediaItem>, mediaItemIndex: Int) {
|
||||
if (mediaItems.isEmpty()) return
|
||||
|
||||
setMediaItems(mediaItems, true)
|
||||
playWhenReady = true
|
||||
seekToDefaultPosition(mediaItemIndex)
|
||||
prepare()
|
||||
}
|
||||
|
||||
fun Player.forcePlayFromBeginning(mediaItems: List<MediaItem>) =
|
||||
forcePlayAtIndex(mediaItems, 0)
|
||||
|
||||
val Player.lastMediaItem: MediaItem?
|
||||
get() = mediaItemCount.takeIf { it > 0 }?.let { it - 1 }?.let(::getMediaItemAt)
|
||||
|
||||
val Timeline.mediaItems: List<MediaItem>
|
||||
get() = (0 until windowCount).map { index ->
|
||||
getWindow(index, Timeline.Window()).mediaItem
|
||||
}
|
||||
|
||||
fun Player.addNext(mediaItem: MediaItem) {
|
||||
addMediaItem(currentMediaItemIndex + 1, mediaItem)
|
||||
}
|
||||
|
||||
fun Player.enqueue(mediaItem: MediaItem) {
|
||||
addMediaItem(mediaItemCount, mediaItem)
|
||||
}
|
||||
|
||||
fun Player.enqueue(mediaItems: List<MediaItem>) {
|
||||
addMediaItems(mediaItemCount, mediaItems)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.media3.common.*
|
||||
import androidx.media3.session.MediaController
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
open class PlayerState(val mediaController: MediaController) : Player.Listener {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
var currentPosition by mutableStateOf(mediaController.currentPosition)
|
||||
|
||||
var duration by mutableStateOf(mediaController.duration)
|
||||
private set
|
||||
|
||||
val progress: Float
|
||||
get() = currentPosition.toFloat() / duration.absoluteValue
|
||||
|
||||
var playbackState by mutableStateOf(mediaController.playbackState)
|
||||
private set
|
||||
|
||||
var mediaItemIndex by mutableStateOf(mediaController.currentMediaItemIndex)
|
||||
private set
|
||||
|
||||
var mediaItem by mutableStateOf(mediaController.currentMediaItem)
|
||||
private set
|
||||
|
||||
var mediaMetadata by mutableStateOf(mediaController.mediaMetadata)
|
||||
private set
|
||||
|
||||
var isPlaying by mutableStateOf(mediaController.isPlaying)
|
||||
private set
|
||||
|
||||
var playWhenReady by mutableStateOf(mediaController.playWhenReady)
|
||||
private set
|
||||
|
||||
var repeatMode by mutableStateOf(mediaController.repeatMode)
|
||||
private set
|
||||
|
||||
var error by mutableStateOf(mediaController.playerError)
|
||||
|
||||
var mediaItems by mutableStateOf(mediaController.currentTimeline.mediaItems)
|
||||
private set
|
||||
|
||||
init {
|
||||
handler.post(object : Runnable {
|
||||
override fun run() {
|
||||
duration = mediaController.duration
|
||||
currentPosition = mediaController.currentPosition
|
||||
handler.postDelayed(this, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
this.playbackState = playbackState
|
||||
}
|
||||
|
||||
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
|
||||
this.mediaMetadata = mediaMetadata
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
this.isPlaying = isPlaying
|
||||
}
|
||||
|
||||
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
||||
this.playWhenReady = playWhenReady
|
||||
}
|
||||
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
this.mediaItem = mediaItem
|
||||
mediaItemIndex = mediaController.currentMediaItemIndex
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
this.repeatMode = repeatMode
|
||||
}
|
||||
|
||||
override fun onPlayerError(playbackException: PlaybackException) {
|
||||
error = playbackException
|
||||
}
|
||||
|
||||
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
||||
mediaItems = timeline.mediaItems
|
||||
mediaItemIndex = mediaController.currentMediaItemIndex
|
||||
}
|
||||
}
|
||||
108
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt
Normal file
108
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Preferences.kt
Normal file
@@ -0,0 +1,108 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.edit
|
||||
import androidx.media3.common.Player
|
||||
import it.vfsfitvnm.vimusic.enums.SongCollection
|
||||
import it.vfsfitvnm.youtubemusic.YouTube
|
||||
|
||||
@Stable
|
||||
class Preferences(holder: SharedPreferences) : SharedPreferences by holder {
|
||||
var searchFilter by preference("searchFilter", YouTube.Item.Song.Filter.value)
|
||||
var repeatMode by preference("repeatMode", Player.REPEAT_MODE_OFF)
|
||||
var homePageSongCollection by preference("homePageSongCollection", SongCollection.MostPlayed)
|
||||
}
|
||||
|
||||
val LocalPreferences = staticCompositionLocalOf<Preferences> { TODO() }
|
||||
|
||||
@Composable
|
||||
fun rememberPreferences(): Preferences {
|
||||
val context = LocalContext.current
|
||||
return remember {
|
||||
Preferences(context.getSharedPreferences("preferences", Context.MODE_PRIVATE))
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: Boolean) =
|
||||
mutableStateOf(value = getBoolean(key, defaultValue)) {
|
||||
edit {
|
||||
putBoolean(key, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: Int) =
|
||||
mutableStateOf(value = getInt(key, defaultValue)) {
|
||||
edit {
|
||||
putInt(key, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: Long) =
|
||||
mutableStateOf(value = getLong(key, defaultValue)) {
|
||||
edit {
|
||||
putLong(key, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: Float) =
|
||||
mutableStateOf(value = getFloat(key, defaultValue)) {
|
||||
edit {
|
||||
putFloat(key, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: String) =
|
||||
mutableStateOf(value = getString(key, defaultValue)!!) {
|
||||
edit {
|
||||
putString(key, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: Set<String>) =
|
||||
mutableStateOf(value = getStringSet(key, defaultValue)!!) {
|
||||
edit {
|
||||
putStringSet(key, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: Dp) =
|
||||
mutableStateOf(value = getFloat(key, defaultValue.value).dp) {
|
||||
edit {
|
||||
putFloat(key, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.preference(key: String, defaultValue: TextUnit) =
|
||||
mutableStateOf(value = getFloat(key, defaultValue.value).sp) {
|
||||
edit {
|
||||
putFloat(key, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T : Enum<T>> SharedPreferences.preference(
|
||||
key: String,
|
||||
defaultValue: T
|
||||
) =
|
||||
mutableStateOf(value = enumValueOf<T>(getString(key, defaultValue.name)!!)) {
|
||||
edit {
|
||||
putString(key, it.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> mutableStateOf(value: T, onStructuralInequality: (newValue: T) -> Unit) =
|
||||
mutableStateOf(
|
||||
value = value,
|
||||
policy = object : SnapshotMutationPolicy<T> {
|
||||
override fun equivalent(a: T, b: T): Boolean {
|
||||
val areEquals = a == b
|
||||
if (!areEquals) onStructuralInequality(b)
|
||||
return areEquals
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@file:OptIn(InternalComposeApi::class)
|
||||
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun relaunchableEffect(
|
||||
key1: Any?,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
): () -> Unit {
|
||||
val applyContext = currentComposer.applyCoroutineContext
|
||||
val launchedEffect = remember(key1) { LaunchedEffectImpl(applyContext, block) }
|
||||
return launchedEffect::onRemembered
|
||||
}
|
||||
11
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RingBuffer.kt
Normal file
11
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RingBuffer.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
class RingBuffer<T>(val size: Int, init: (index: Int) -> T) {
|
||||
private val list = MutableList(2, init)
|
||||
|
||||
private var index = 0
|
||||
|
||||
fun getOrNull(index: Int): T? = list.getOrNull(index)
|
||||
|
||||
fun append(element: T) = list.set(index++ % size, element)
|
||||
}
|
||||
43
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/TextStyle.kt
Normal file
43
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/TextStyle.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
||||
|
||||
fun TextStyle.style(style: FontStyle) = copy(fontStyle = style)
|
||||
|
||||
fun TextStyle.weight(weight: FontWeight) = copy(fontWeight = weight)
|
||||
|
||||
fun TextStyle.align(align: TextAlign) = copy(textAlign = align)
|
||||
|
||||
fun TextStyle.color(color: Color) = copy(color = color)
|
||||
|
||||
inline val TextStyle.italic: TextStyle
|
||||
get() = style(FontStyle.Italic)
|
||||
|
||||
inline val TextStyle.medium: TextStyle
|
||||
get() = weight(FontWeight.Medium)
|
||||
|
||||
inline val TextStyle.semiBold: TextStyle
|
||||
get() = weight(FontWeight.SemiBold)
|
||||
|
||||
inline val TextStyle.bold: TextStyle
|
||||
get() = weight(FontWeight.Bold)
|
||||
|
||||
inline val TextStyle.center: TextStyle
|
||||
get() = align(TextAlign.Center)
|
||||
|
||||
inline val TextStyle.secondary: TextStyle
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = color(LocalColorPalette.current.textSecondary)
|
||||
|
||||
inline val TextStyle.disabled: TextStyle
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = color(LocalColorPalette.current.textDisabled)
|
||||
129
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/YoutubePlayer.kt
Normal file
129
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/YoutubePlayer.kt
Normal file
@@ -0,0 +1,129 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.session.MediaController
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import it.vfsfitvnm.youtubemusic.Outcome
|
||||
import it.vfsfitvnm.youtubemusic.YouTube
|
||||
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
class YoutubePlayer(mediaController: MediaController) : PlayerState(mediaController) {
|
||||
object Radio {
|
||||
var isActive by mutableStateOf(false)
|
||||
|
||||
var listener: Listener? = 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)
|
||||
|
||||
fun setup(videoId: String? = null, playlistId: String? = null, playlistSetVideoId: String? = null, parameters: String? = null) {
|
||||
this.videoId = videoId
|
||||
this.playlistId = playlistId
|
||||
this.playlistSetVideoId = playlistSetVideoId
|
||||
this.parameters = parameters
|
||||
|
||||
isActive = true
|
||||
nextContinuation = Outcome.Initial
|
||||
}
|
||||
|
||||
fun setup(watchEndpoint: NavigationEndpoint.Endpoint.Watch?) {
|
||||
setup(
|
||||
videoId = watchEndpoint?.videoId,
|
||||
playlistId = watchEndpoint?.playlistId,
|
||||
parameters = watchEndpoint?.params,
|
||||
playlistSetVideoId = watchEndpoint?.playlistSetVideoId
|
||||
)
|
||||
|
||||
listener?.process(true)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
nextContinuation = Outcome.Loading
|
||||
|
||||
nextContinuation = withContext(Dispatchers.IO) {
|
||||
YouTube.next(
|
||||
videoId = videoId ?: withContext(Dispatchers.Main) {
|
||||
player.lastMediaItem?.mediaId ?: error("This should not happen")
|
||||
},
|
||||
playlistId = playlistId,
|
||||
params = parameters,
|
||||
playlistSetVideoId = playlistSetVideoId,
|
||||
continuation = token
|
||||
)
|
||||
}.map { nextResult ->
|
||||
nextResult.items?.map(it.vfsfitvnm.youtubemusic.YouTube.Item.Song::asMediaItem)?.let { mediaItems ->
|
||||
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 }
|
||||
}.recoverWith(token)
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
videoId = null
|
||||
playlistId = null
|
||||
playlistSetVideoId = null
|
||||
parameters = null
|
||||
isActive = false
|
||||
nextContinuation = Outcome.Initial
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun process(play: Boolean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val LocalYoutubePlayer = compositionLocalOf<YoutubePlayer?> { null }
|
||||
|
||||
@Composable
|
||||
fun rememberYoutubePlayer(
|
||||
mediaControllerFuture: ListenableFuture<MediaController>,
|
||||
repeatMode: Int
|
||||
): YoutubePlayer? {
|
||||
val mediaController by produceState<MediaController?>(initialValue = null) {
|
||||
value = mediaControllerFuture.await().also {
|
||||
it.repeatMode = repeatMode
|
||||
}
|
||||
}
|
||||
|
||||
val playerState = remember(mediaController) {
|
||||
YoutubePlayer(mediaController ?: return@remember null).also {
|
||||
// TODO: should we remove the listener later on?
|
||||
mediaController?.addListener(it)
|
||||
}
|
||||
}
|
||||
|
||||
return playerState
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
@Composable
|
||||
fun rememberHapticFeedback(): HapticFeedback {
|
||||
val view = LocalView.current
|
||||
|
||||
return remember {
|
||||
object : HapticFeedback {
|
||||
override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
|
||||
view.performHapticFeedback(
|
||||
HapticFeedbackConstants.LONG_PRESS,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
172
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/utils.kt
Normal file
172
app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/utils.kt
Normal file
@@ -0,0 +1,172 @@
|
||||
package it.vfsfitvnm.vimusic.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import it.vfsfitvnm.vimusic.Database
|
||||
import it.vfsfitvnm.vimusic.internal
|
||||
import it.vfsfitvnm.vimusic.models.Info
|
||||
import it.vfsfitvnm.vimusic.models.Song
|
||||
import it.vfsfitvnm.vimusic.models.SongWithAuthors
|
||||
import it.vfsfitvnm.vimusic.models.SongWithInfo
|
||||
import it.vfsfitvnm.youtubemusic.YouTube
|
||||
|
||||
fun Context.shareAsYouTubeSong(mediaItem: MediaItem) {
|
||||
val sendIntent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, "https://music.youtube.com/watch?v=${mediaItem.mediaId}")
|
||||
}
|
||||
|
||||
startActivity(Intent.createChooser(sendIntent, null))
|
||||
}
|
||||
|
||||
fun Database.insert(mediaItem: MediaItem): Song {
|
||||
return internal.runInTransaction<Song> {
|
||||
Database.song(mediaItem.mediaId)?.let {
|
||||
return@runInTransaction it
|
||||
}
|
||||
|
||||
val albumInfo = mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId ->
|
||||
Info(
|
||||
text = mediaItem.mediaMetadata.albumTitle!!.toString(),
|
||||
browseId = albumId
|
||||
)
|
||||
}
|
||||
|
||||
val albumInfoId = albumInfo?.let { insert(it) }
|
||||
|
||||
val authorsInfo =
|
||||
mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")?.let { artistNames ->
|
||||
mediaItem.mediaMetadata.extras!!.getStringArrayList("artistIds")?.let { artistIds ->
|
||||
artistNames.mapIndexed { index, artistName ->
|
||||
Info(
|
||||
text = artistName,
|
||||
browseId = artistIds.getOrNull(index)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val song = Song(
|
||||
id = mediaItem.mediaId,
|
||||
title = mediaItem.mediaMetadata.title!!.toString(),
|
||||
albumInfoId = albumInfoId,
|
||||
durationText = mediaItem.mediaMetadata.extras?.getString("durationText")!!,
|
||||
thumbnailUrl = mediaItem.mediaMetadata.artworkUri!!.toString()
|
||||
)
|
||||
|
||||
insert(song)
|
||||
|
||||
val authorsInfoId = authorsInfo?.let { insert(authorsInfo) }
|
||||
|
||||
authorsInfoId?.forEach { authorInfoId ->
|
||||
insert(
|
||||
SongWithAuthors(
|
||||
songId = mediaItem.mediaId,
|
||||
authorInfoId = authorInfoId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return@runInTransaction song
|
||||
}
|
||||
}
|
||||
|
||||
val YouTube.Item.Song.asMediaItem: MediaItem
|
||||
get() = MediaItem.Builder()
|
||||
.setMediaId(info.endpoint!!.videoId)
|
||||
.setUri(info.endpoint!!.videoId)
|
||||
.setCustomCacheKey(info.endpoint!!.videoId)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(info.name)
|
||||
.setArtist(authors.joinToString("") { it.name })
|
||||
.setAlbumTitle(album?.name)
|
||||
.setArtworkUri(thumbnail.url.toUri())
|
||||
.setExtras(
|
||||
bundleOf(
|
||||
"videoId" to info.endpoint!!.videoId,
|
||||
"albumId" to album?.endpoint?.browseId,
|
||||
"durationText" to durationText,
|
||||
"artistNames" to authors.map { it.name },
|
||||
"artistIds" to authors.map { it.endpoint?.browseId },
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
val YouTube.Item.Video.asMediaItem: MediaItem
|
||||
get() = MediaItem.Builder()
|
||||
.setMediaId(info.endpoint!!.videoId)
|
||||
.setUri(info.endpoint!!.videoId)
|
||||
.setCustomCacheKey(info.endpoint!!.videoId)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(info.name)
|
||||
.setArtist(authors.joinToString("") { it.name })
|
||||
.setArtworkUri(thumbnail.url.toUri())
|
||||
.setExtras(
|
||||
bundleOf(
|
||||
"videoId" to info.endpoint!!.videoId,
|
||||
"durationText" to durationText,
|
||||
"artistNames" to if (isOfficialMusicVideo) authors.map { it.name } else null,
|
||||
"artistIds" to if (isOfficialMusicVideo) authors.map { it.endpoint?.browseId } else null,
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
val SongWithInfo.asMediaItem: MediaItem
|
||||
get() = MediaItem.Builder()
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(song.title)
|
||||
.setArtist(authors?.joinToString("") { it.text })
|
||||
.setAlbumTitle(album?.text)
|
||||
.setArtworkUri(song.thumbnailUrl?.toUri())
|
||||
.setExtras(
|
||||
bundleOf(
|
||||
"videoId" to song.id,
|
||||
"albumId" to album?.browseId,
|
||||
"artistNames" to authors?.map { it.text },
|
||||
"artistIds" to authors?.map { it.browseId },
|
||||
"durationText" to song.durationText
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.setMediaId(song.id)
|
||||
.build()
|
||||
|
||||
fun YouTube.AlbumItem.toMediaItem(
|
||||
albumId: String,
|
||||
album: YouTube.Album
|
||||
): MediaItem? {
|
||||
return MediaItem.Builder()
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(info.name)
|
||||
.setArtist((authors ?: album.authors).joinToString("") { it.name })
|
||||
.setAlbumTitle(album.title)
|
||||
.setArtworkUri(album.thumbnail.url.toUri())
|
||||
.setExtras(
|
||||
bundleOf(
|
||||
"videoId" to info.endpoint?.videoId,
|
||||
"playlistId" to info.endpoint?.playlistId,
|
||||
"albumId" to albumId,
|
||||
"durationText" to durationText,
|
||||
"artistNames" to (authors ?: album.authors).map { it.name },
|
||||
"artistIds" to (authors ?: album.authors).map { it.endpoint?.browseId }
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.setMediaId(info.endpoint?.videoId ?: return null)
|
||||
.build()
|
||||
}
|
||||
Reference in New Issue
Block a user