Skip to content

Commit

Permalink
feat: album radio
Browse files Browse the repository at this point in the history
  • Loading branch information
z-huang committed Sep 1, 2024
1 parent 7550445 commit bec9f8f
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ class MusicService : MediaLibraryService(),
}
if (initialStatus.items.isEmpty()) return@launch
if (queue.preloadItem != null) {
// add missing songs back, without affecting current playing song
player.addMediaItems(0, initialStatus.items.subList(0, initialStatus.mediaItemIndex))
player.addMediaItems(initialStatus.items.subList(initialStatus.mediaItemIndex + 1, initialStatus.items.size))
} else {
Expand Down Expand Up @@ -543,7 +544,6 @@ class MusicService : MediaLibraryService(),
// Auto load more songs
if (dataStore.get(AutoLoadMoreKey, true) &&
reason != Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT &&
player.playbackState != STATE_IDLE &&
player.mediaItemCount - player.currentMediaItemIndex <= 5 &&
currentQueue.hasNextPage()
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.zionhuang.music.playback.queues

import androidx.media3.common.MediaItem
import com.zionhuang.innertube.YouTube
import com.zionhuang.innertube.models.WatchEndpoint
import com.zionhuang.music.db.entities.AlbumWithSongs
import com.zionhuang.music.extensions.toMediaItem
import com.zionhuang.music.models.MediaMetadata
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext

class LocalAlbumRadio(
private val albumWithSongs: AlbumWithSongs,
private val startIndex: Int = 0,
) : Queue {
override val preloadItem: MediaMetadata? = null

private lateinit var playlistId: String
private val endpoint: WatchEndpoint
get() = WatchEndpoint(
playlistId = playlistId,
params = "wAEB"
)

private var continuation: String? = null
private var firstTimeLoaded: Boolean = false

override suspend fun getInitialStatus(): Queue.Status = withContext(IO) {
Queue.Status(
title = albumWithSongs.album.title,
items = albumWithSongs.songs.map { it.toMediaItem() },
mediaItemIndex = startIndex
)
}

override fun hasNextPage(): Boolean = !firstTimeLoaded || continuation != null

override suspend fun nextPage(): List<MediaItem> = withContext(IO) {
if (!firstTimeLoaded) {
playlistId = YouTube.album(albumWithSongs.album.id).getOrThrow().album.playlistId
val nextResult = YouTube.next(endpoint, continuation).getOrThrow()
continuation = nextResult.continuation
firstTimeLoaded = true
return@withContext nextResult.items.subList(albumWithSongs.songs.size, nextResult.items.size).map { it.toMediaItem() }
}
val nextResult = YouTube.next(endpoint, continuation).getOrThrow()
continuation = nextResult.continuation
nextResult.items.map { it.toMediaItem() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,40 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext

class YouTubeAlbumRadio(
private val playlistId: String,
private var playlistId: String,
) : Queue {
override val preloadItem: MediaMetadata? = null
private val endpoint = WatchEndpoint(
playlistId = playlistId,
params = "wAEB"
)

private val endpoint: WatchEndpoint
get() = WatchEndpoint(
playlistId = playlistId,
params = "wAEB"
)

private var albumSongCount = 0
private var continuation: String? = null
private var firstTimeLoaded: Boolean = false

override suspend fun getInitialStatus(): Queue.Status = withContext(IO) {
val albumSongs = YouTube.albumSongs(playlistId).getOrThrow()
val nextResult = YouTube.next(endpoint, continuation).getOrThrow()
continuation = nextResult.continuation
albumSongCount = albumSongs.size
Queue.Status(
title = nextResult.title,
items = (albumSongs + nextResult.items.subList(albumSongs.size, nextResult.items.size)).map { it.toMediaItem() },
mediaItemIndex = nextResult.currentIndex ?: 0
title = albumSongs.first().album?.name.orEmpty(),
items = albumSongs.map { it.toMediaItem() },
mediaItemIndex = 0
)
}

override fun hasNextPage(): Boolean = continuation != null
override fun hasNextPage(): Boolean = !firstTimeLoaded || continuation != null

override suspend fun nextPage(): List<MediaItem> {
val nextResult = withContext(IO) {
YouTube.next(endpoint, continuation).getOrThrow()
}
override suspend fun nextPage(): List<MediaItem> = withContext(IO) {
val nextResult = YouTube.next(endpoint, continuation).getOrThrow()
continuation = nextResult.continuation
return nextResult.items.map { it.toMediaItem() }
if (!firstTimeLoaded) {
firstTimeLoaded = true
nextResult.items.subList(albumSongCount, nextResult.items.size).map { it.toMediaItem() }
} else {
nextResult.items.map { it.toMediaItem() }
}
}
}
33 changes: 11 additions & 22 deletions app/src/main/java/com/zionhuang/music/ui/component/Items.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ import com.zionhuang.music.db.entities.Album
import com.zionhuang.music.db.entities.Artist
import com.zionhuang.music.db.entities.Playlist
import com.zionhuang.music.db.entities.Song
import com.zionhuang.music.extensions.toMediaItem
import com.zionhuang.music.models.MediaMetadata
import com.zionhuang.music.playback.queues.ListQueue
import com.zionhuang.music.playback.queues.LocalAlbumRadio
import com.zionhuang.music.utils.joinByBullet
import com.zionhuang.music.utils.makeTimeString
import com.zionhuang.music.utils.reportException
Expand Down Expand Up @@ -509,16 +508,11 @@ fun AlbumGridItem(
visible = !isActive,
onClick = {
coroutineScope.launch {
database.albumWithSongs(album.id).first()?.songs
?.map { it.toMediaItem() }
?.let {
playerConnection.playQueue(
ListQueue(
title = album.album.title,
items = it
)
)
}
database.albumWithSongs(album.id).first()?.let { albumWithSongs ->
playerConnection.playQueue(
LocalAlbumRadio(albumWithSongs)
)
}
}
}
)
Expand Down Expand Up @@ -753,26 +747,21 @@ fun YouTubeGridItem(
visible = item is AlbumItem && !isActive,
onClick = {
coroutineScope?.launch(Dispatchers.IO) {
var songs = database
.albumWithSongs(item.id)
.first()?.songs?.map { it.toMediaItem() }
if (songs == null) {
var albumWithSongs = database.albumWithSongs(item.id).first()
if (albumWithSongs?.songs.isNullOrEmpty()) {
YouTube.album(item.id).onSuccess { albumPage ->
database.transaction {
insert(albumPage)
}
songs = albumPage.songs.map { it.toMediaItem() }
albumWithSongs = database.albumWithSongs(item.id).first()
}.onFailure {
reportException(it)
}
}
songs?.let {
albumWithSongs?.let {
withContext(Dispatchers.Main) {
playerConnection.playQueue(
ListQueue(
title = item.title,
items = it
)
LocalAlbumRadio(it)
)
}
}
Expand Down
20 changes: 4 additions & 16 deletions app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,9 @@ import com.zionhuang.music.R
import com.zionhuang.music.constants.AlbumThumbnailSize
import com.zionhuang.music.constants.ThumbnailCornerRadius
import com.zionhuang.music.db.entities.Album
import com.zionhuang.music.db.entities.Song
import com.zionhuang.music.extensions.toMediaItem
import com.zionhuang.music.extensions.togglePlayPause
import com.zionhuang.music.playback.ExoDownloadService
import com.zionhuang.music.playback.queues.ListQueue
import com.zionhuang.music.playback.queues.LocalAlbumRadio
import com.zionhuang.music.ui.component.AutoResizeText
import com.zionhuang.music.ui.component.FontSizeRange
import com.zionhuang.music.ui.component.IconButton
Expand Down Expand Up @@ -339,10 +337,7 @@ fun AlbumScreen(
Button(
onClick = {
playerConnection.playQueue(
ListQueue(
title = albumWithSongs.album.title,
items = albumWithSongs.songs.map(Song::toMediaItem)
)
LocalAlbumRadio(albumWithSongs)
)
},
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
Expand All @@ -362,10 +357,7 @@ fun AlbumScreen(
OutlinedButton(
onClick = {
playerConnection.playQueue(
ListQueue(
title = albumWithSongs.album.title,
items = albumWithSongs.songs.shuffled().map(Song::toMediaItem)
)
LocalAlbumRadio(albumWithSongs.copy(songs = albumWithSongs.songs.shuffled()))
)
},
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
Expand Down Expand Up @@ -436,11 +428,7 @@ fun AlbumScreen(
playerConnection.player.togglePlayPause()
} else {
playerConnection.playQueue(
ListQueue(
title = albumWithSongs.album.title,
items = albumWithSongs.songs.map { it.toMediaItem() },
startIndex = index
)
LocalAlbumRadio(albumWithSongs, startIndex = index)
)
}
},
Expand Down
13 changes: 5 additions & 8 deletions app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ import com.zionhuang.music.db.entities.Artist
import com.zionhuang.music.db.entities.LocalItem
import com.zionhuang.music.db.entities.Playlist
import com.zionhuang.music.db.entities.Song
import com.zionhuang.music.extensions.toMediaItem
import com.zionhuang.music.extensions.togglePlayPause
import com.zionhuang.music.models.toMediaMetadata
import com.zionhuang.music.playback.queues.ListQueue
import com.zionhuang.music.playback.queues.LocalAlbumRadio
import com.zionhuang.music.playback.queues.YouTubeAlbumRadio
import com.zionhuang.music.playback.queues.YouTubeQueue
import com.zionhuang.music.ui.component.AlbumGridItem
Expand Down Expand Up @@ -682,13 +681,11 @@ fun HomeScreen(
is Song -> playerConnection.playQueue(YouTubeQueue.radio(luckyItem.toMediaMetadata()))
is Album -> {
scope.launch(Dispatchers.IO) {
val songs = database.albumSongs(luckyItem.id).first()
playerConnection.playQueue(
ListQueue(
title = luckyItem.title,
items = songs.map(Song::toMediaItem)
database.albumWithSongs(luckyItem.id).first()?.let {
playerConnection.playQueue(
LocalAlbumRadio(it)
)
)
}
}
}
// not possible, already filtered out
Expand Down

0 comments on commit bec9f8f

Please sign in to comment.