Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fun AddToPlaylistDialog(
isVisible: Boolean,
allowSyncing: Boolean = true,
initialTextFieldValue: String? = null,
multiSelectParams: String? = null,
onGetSong: suspend (Playlist) -> List<String>, // list of song ids. Songs should be inserted to database in this function.
onGetSongIds: (suspend () -> List<String>)? = null,
onDismiss: () -> Unit,
Expand Down Expand Up @@ -123,15 +124,34 @@ fun AddToPlaylistDialog(
}

suspend fun addSongsAndSync(targetPlaylist: Playlist, ids: List<String>) {
database.addSongsToPlaylist(targetPlaylist, ids.map { it to null }, prepend = true)
targetPlaylist.playlist.browseId?.let { plist ->
ids.forEach { songId ->
syncUtils.registerPendingAdd(plist, songId)
try {
YouTube.addToPlaylist(plist, songId)
} finally {
syncUtils.unregisterPendingAdd(plist, songId)
}
val localIds = ids.distinct()
database.addSongsToPlaylist(targetPlaylist, localIds.map { it to null }, prepend = true)
val browseId = targetPlaylist.playlist.browseId ?: return
val remoteIds =
if (localIds.size > 1) {
YouTube.getMultiSelectCommand(localIds, multiSelectParams)
.getOrNull()
?.multiSelectCommand
?.addToPlaylistEndpoint
?.videoIds
.orEmpty()
.ifEmpty { localIds }
} else {
localIds
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

remoteIds.forEach { songId ->
syncUtils.registerPendingAdd(browseId, songId)
}
try {
if (remoteIds.size == 1) {
YouTube.addToPlaylist(browseId, remoteIds.first())
} else {
YouTube.addToPlaylist(browseId, remoteIds)
}
} finally {
remoteIds.forEach { songId ->
syncUtils.unregisterPendingAdd(browseId, songId)
}
}
}
Expand Down Expand Up @@ -291,26 +311,29 @@ fun AddToPlaylistDialog(
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "playlistBg"
)
PlaylistListItem(
playlist = playlist,
modifier = Modifier
PlaylistListItem(
playlist = playlist,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 2.dp)
.clip(RoundedCornerShape(16.dp))
.background(rowBg)
.clickable {
selectedPlaylist = playlist
coroutineScope.launch(Dispatchers.IO) {
if (songIds == null) {
songIds = onGetSong(playlist)
} else {
onGetSong(playlist)
}
duplicates = database.playlistDuplicates(playlist.id, songIds!!)
val resolvedSongIds =
if (songIds.isNullOrEmpty()) {
onGetSong(playlist).also { resolved ->
songIds = resolved
}
} else {
songIds!!
}
duplicates = database.playlistDuplicates(playlist.id, resolvedSongIds)
if (duplicates.isNotEmpty()) {
showDuplicateDialog = true
} else {
onDismiss()
addSongsAndSync(playlist, songIds!!)
addSongsAndSync(playlist, resolvedSongIds)
}
}
}
Expand Down
9 changes: 1 addition & 8 deletions app/src/main/kotlin/com/metrolist/music/ui/menu/AlbumMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,7 @@ fun AlbumMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
coroutineScope.launch(Dispatchers.IO) {
playlist.playlist.browseId?.let { playlistId ->
album.album.playlistId?.let { addPlaylistId ->
YouTube.addPlaylistToPlaylist(playlistId, addPlaylistId)
}
}
}
onGetSong = {
songs.map { it.id }
},
onGetSongIds = { songs.map { it.id } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,10 @@ fun PlayerMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
onGetSong = {
database.withTransaction {
insert(mediaMetadata)
}
coroutineScope.launch(Dispatchers.IO) {
playlist.playlist.browseId?.let { YouTube.addToPlaylist(it, mediaMetadata.id) }
}
listOf(mediaMetadata.id)
},
onGetSongIds = { listOf(mediaMetadata.id) },
Expand Down
5 changes: 1 addition & 4 deletions app/src/main/kotlin/com/metrolist/music/ui/menu/QueueMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,10 @@ fun QueueMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
onGetSong = {
database.withTransaction {
insert(mediaMetadata)
}
coroutineScope.launch(Dispatchers.IO) {
playlist.playlist.browseId?.let { YouTube.addToPlaylist(it, mediaMetadata.id) }
}
listOf(mediaMetadata.id)
},
onGetSongIds = { listOf(mediaMetadata.id) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,7 @@ fun SelectionSongMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
coroutineScope.launch(Dispatchers.IO) {
songSelection.forEach { song ->
playlist.playlist.browseId?.let { browseId ->
YouTube.addToPlaylist(browseId, song.id)
}
}
}
onGetSong = {
songSelection.map { it.id }
},
onGetSongIds = { songSelection.map { it.id } },
Expand Down
8 changes: 3 additions & 5 deletions app/src/main/kotlin/com/metrolist/music/ui/menu/SongMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,9 @@ fun SongMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
coroutineScope.launch(Dispatchers.IO) {
playlist.playlist.browseId?.let { browseId ->
YouTube.addToPlaylist(browseId, song.id)
}
onGetSong = {
database.withTransaction {
insert(song.toMediaMetadata())
}
listOf(song.id)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,7 @@ fun YouTubeAlbumMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
coroutineScope.launch(Dispatchers.IO) {
playlist.playlist.browseId?.let { playlistId ->
album?.album?.playlistId?.let { addPlaylistId ->
YouTube.addPlaylistToPlaylist(playlistId, addPlaylistId)
}
}
}
onGetSong = {
album?.songs?.map { it.id }.orEmpty()
},
onGetSongIds = { album?.songs?.map { it.id }.orEmpty() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ fun YouTubePlaylistMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { targetPlaylist ->
onGetSong = {
val allSongs =
songs
.ifEmpty {
YouTube
.playlist(targetPlaylist.id)
.playlist(playlist.id)
.completed()
.getOrNull()
?.songs
Expand All @@ -142,11 +142,6 @@ fun YouTubePlaylistMenu(
database.withTransaction {
allSongs.forEach(::insert)
}
coroutineScope.launch(Dispatchers.IO) {
targetPlaylist.playlist.browseId?.let { playlistId ->
YouTube.addPlaylistToPlaylist(playlistId, targetPlaylist.id)
}
}
allSongs.map { it.id }
},
onGetSongIds = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
Expand Down Expand Up @@ -122,52 +121,16 @@ fun YouTubeSelectionSongMenu(
}
}

AddToPlaylistDialogOnline(
val selectedSongs = songSelection.map { it.toMediaMetadata() }

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
songs =
remember {
songSelection
.map { song ->
// Convert SongItem to Song entity
val metadata = song.toMediaMetadata()
com.metrolist.music.db.entities.Song(
song =
com.metrolist.music.db.entities.SongEntity(
id = metadata.id,
title = metadata.title,
duration = metadata.duration,
thumbnailUrl = metadata.thumbnailUrl,
albumId = metadata.album?.id,
albumName = metadata.album?.title,
liked = metadata.liked,
totalPlayTime = 0,
inLibrary = metadata.inLibrary,
isLocal = false,
libraryAddToken = metadata.libraryAddToken,
libraryRemoveToken = metadata.libraryRemoveToken,
),
artists =
metadata.artists.map { artist ->
com.metrolist.music.db.entities.ArtistEntity(
id = artist.id ?: "",
name = artist.name,
)
},
album =
metadata.album?.let { album ->
com.metrolist.music.db.entities.AlbumEntity(
id = album.id,
title = album.title,
thumbnailUrl = metadata.thumbnailUrl, // Use song's thumbnail as album thumbnail
songCount = 0,
duration = 0,
)
},
)
}.toMutableStateList()
},
onProgressStart = { },
onPercentageChange = { },
onGetSong = {
database.withTransaction {
selectedSongs.forEach(::insert)
}
selectedSongs.map { it.id }
},
onDismiss = {
showChoosePlaylistDialog = false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,10 @@ fun YouTubeSongMenu(

AddToPlaylistDialog(
isVisible = showChoosePlaylistDialog,
onGetSong = { playlist ->
onGetSong = {
database.withTransaction {
insert(song.toMediaMetadata())
}
coroutineScope.launch(Dispatchers.IO) {
playlist.playlist.browseId?.let { browseId ->
YouTube.addToPlaylist(browseId, song.id)
}
}
listOf(song.id)
},
onGetSongIds = { listOf(song.id) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ private fun NewMiniPlayer(
onClick: () -> Unit = {},
) {
val playerConnection = LocalPlayerConnection.current ?: return
val database = LocalDatabase.current
val menuState = LocalMenuState.current

// Theme settings - these rarely change
Expand Down Expand Up @@ -486,7 +487,12 @@ private fun NewMiniPlayer(
menuState.show {
AddToPlaylistDialog(
isVisible = true,
onGetSong = { listOf(metadata.id) },
onGetSong = {
database.withTransaction {
insert(metadata)
}
listOf(metadata.id)
},
onDismiss = menuState::dismiss,
)
}
Expand Down
36 changes: 36 additions & 0 deletions innertube/src/main/kotlin/com/metrolist/innertube/InnerTube.kt
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,42 @@ class InnerTube {
}
}

suspend fun addToPlaylist(
client: YouTubeClient,
playlistId: String,
videoIds: List<String>,
) = withRetry {
httpClient.post("browse/edit_playlist") {
ytClient(client, setLogin = true)
setBody(
EditPlaylistBody(
context = client.toContext(locale, visitorData, dataSyncId),
playlistId = playlistId.removePrefix("VL"),
actions = videoIds.map {
Action.AddVideoAction(addedVideoId = it)
}
)
)
}
}

suspend fun getMultiSelectCommand(
client: YouTubeClient,
selectedItems: List<String>,
multiSelectParams: String? = null,
) = withRetry {
httpClient.post("get_multi_select_command") {
ytClient(client, setLogin = true)
setBody(
GetMultiSelectCommandBody(
context = client.toContext(locale, visitorData, dataSyncId),
selectedItems = selectedItems,
multiSelectParams = multiSelectParams,
)
)
}
}

suspend fun addPlaylistToPlaylist(
client: YouTubeClient,
playlistId: String,
Expand Down
16 changes: 16 additions & 0 deletions innertube/src/main/kotlin/com/metrolist/innertube/YouTube.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.metrolist.innertube.models.response.BrowseResponse
import com.metrolist.innertube.models.response.CreatePlaylistResponse
import com.metrolist.innertube.models.response.EditPlaylistResponse
import com.metrolist.innertube.models.response.FeedbackResponse
import com.metrolist.innertube.models.response.GetMultiSelectCommandResponse
import com.metrolist.innertube.models.response.GetQueueResponse
import com.metrolist.innertube.models.response.GetSearchSuggestionsResponse
import com.metrolist.innertube.models.response.GetTranscriptResponse
Expand Down Expand Up @@ -2589,6 +2590,21 @@ object YouTube {
innerTube.addToPlaylist(WEB_REMIX, playlistId, videoId)
}

suspend fun addToPlaylist(
playlistId: String,
videoIds: List<String>,
) = runCatching {
innerTube.addToPlaylist(WEB_REMIX, playlistId, videoIds)
}

suspend fun getMultiSelectCommand(
selectedItems: List<String>,
multiSelectParams: String? = null,
) = runCatching {
val response = innerTube.getMultiSelectCommand(WEB_REMIX, selectedItems, multiSelectParams).body<GetMultiSelectCommandResponse>()
response
}

suspend fun addPlaylistToPlaylist(
playlistId: String,
addPlaylistId: String,
Expand Down
Loading