playback: improve queue item setup

- Use same media description code
- Make queue removal more reliable
This commit is contained in:
Alexander Capehart 2024-08-29 09:29:46 -06:00
parent 130d30c70d
commit 889713d5e0
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 59 additions and 59 deletions

View file

@ -105,11 +105,8 @@ fun Category.toMediaItem(context: Context): MediaItem {
return MediaItem(description.build(), MediaItem.FLAG_BROWSABLE) return MediaItem(description.build(), MediaItem.FLAG_BROWSABLE)
} }
fun Song.toMediaItem( fun Song.toMediaDescription(context: Context, parent: MusicParent? = null,
context: Context, vararg sugar: Sugar): MediaDescriptionCompat {
parent: MusicParent? = null,
vararg sugar: Sugar
): MediaItem {
val mediaSessionUID = val mediaSessionUID =
if (parent == null) { if (parent == null) {
MediaSessionUID.SingleItem(uid) MediaSessionUID.SingleItem(uid)
@ -117,17 +114,23 @@ fun Song.toMediaItem(
MediaSessionUID.ChildItem(parent.uid, uid) MediaSessionUID.ChildItem(parent.uid, uid)
} }
val extras = Bundle().apply { sugar.forEach { this.it(context) } } val extras = Bundle().apply { sugar.forEach { this.it(context) } }
val description = return MediaDescriptionCompat.Builder()
MediaDescriptionCompat.Builder() .setMediaId(mediaSessionUID.toString())
.setMediaId(mediaSessionUID.toString()) .setTitle(name.resolve(context))
.setTitle(name.resolve(context)) .setSubtitle(artists.resolveNames(context))
.setSubtitle(artists.resolveNames(context)) .setDescription(album.name.resolve(context))
.setDescription(album.name.resolve(context)) .setIconUri(cover.mediaStoreCoverUri)
.setIconUri(album.cover.single.mediaStoreCoverUri) .setMediaUri(uri)
.setMediaUri(uri) .setExtras(extras)
.setExtras(extras) .build()
.build() }
return MediaItem(description, MediaItem.FLAG_PLAYABLE)
fun Song.toMediaItem(
context: Context,
parent: MusicParent? = null,
vararg sugar: Sugar
): MediaItem {
return MediaItem(toMediaDescription(context, parent, *sugar), MediaItem.FLAG_PLAYABLE)
} }
fun Album.toMediaItem( fun Album.toMediaItem(

View file

@ -42,6 +42,8 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.music.service.MediaSessionUID import org.oxycblt.auxio.music.service.MediaSessionUID
import org.oxycblt.auxio.music.service.toMediaDescription
import org.oxycblt.auxio.music.service.toMediaItem
import org.oxycblt.auxio.playback.ActionMode import org.oxycblt.auxio.playback.ActionMode
import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.playback.service.MediaSessionInterface import org.oxycblt.auxio.playback.service.MediaSessionInterface
@ -304,20 +306,7 @@ private constructor(
private fun updateQueue(queue: List<Song>) { private fun updateQueue(queue: List<Song>) {
val queueItems = val queueItems =
queue.mapIndexed { i, song -> queue.mapIndexed { i, song ->
val description = val description = song.toMediaDescription(context, null, { putInt(MediaSessionInterface.KEY_QUEUE_POS, i) })
MediaDescriptionCompat.Builder()
// Media ID should not be the item index but rather the UID,
// as it's used to request a song to be played from the queue.
.setMediaId(song.uid.toString())
.setTitle(song.name.resolve(context))
.setSubtitle(song.artists.resolveNames(context))
// Since we usually have to load many songs into the queue, use the
// MediaStore URI instead of loading a bitmap.
.setIconUri(song.album.cover.single.mediaStoreCoverUri)
.setMediaUri(song.uri)
.setExtras(
Bundle().apply { putInt(MediaSessionInterface.KEY_QUEUE_POS, i) })
.build()
// Store the item index so we can then use the analogous index in the // Store the item index so we can then use the analogous index in the
// playback state. // playback state.
MediaSessionCompat.QueueItem(description, i.toLong()) MediaSessionCompat.QueueItem(description, i.toLong())

View file

@ -93,22 +93,11 @@ constructor(
super.onPlayFromSearch(query, extras) super.onPlayFromSearch(query, extras)
val deviceLibrary = musicRepository.deviceLibrary ?: return val deviceLibrary = musicRepository.deviceLibrary ?: return
val userLibrary = musicRepository.userLibrary ?: return val userLibrary = musicRepository.userLibrary ?: return
val command = expandSearchInfoCommand( val command =
query.ifBlank { null }, expandSearchInfoCommand(query.ifBlank { null }, extras, deviceLibrary, userLibrary)
extras,
deviceLibrary,
userLibrary)
playbackManager.play(requireNotNull(command) { "Invalid playback configuration" }) playbackManager.play(requireNotNull(command) { "Invalid playback configuration" })
} }
data class QueryBundle(
val title: String?,
val album: String?,
val artist: String?,
val genre: String?,
val playlist: String?
)
override fun onAddQueueItem(description: MediaDescriptionCompat) { override fun onAddQueueItem(description: MediaDescriptionCompat) {
super.onAddQueueItem(description) super.onAddQueueItem(description)
val deviceLibrary = musicRepository.deviceLibrary ?: return val deviceLibrary = musicRepository.deviceLibrary ?: return
@ -125,8 +114,22 @@ constructor(
override fun onRemoveQueueItem(description: MediaDescriptionCompat) { override fun onRemoveQueueItem(description: MediaDescriptionCompat) {
super.onRemoveQueueItem(description) super.onRemoveQueueItem(description)
val at = description.extras?.getInt(KEY_QUEUE_POS) ?: return val at = description.extras?.getInt(KEY_QUEUE_POS)
playbackManager.removeQueueItem(at) if (at != null) {
// Direct queue item removal w/preserved extras, we can explicitly remove
// the correct item rather than a duplicate elsewhere.
playbackManager.removeQueueItem(at)
return
}
// Non-queue item or queue item lost it's extras in transit, remove the first item
val uid = MediaSessionUID.fromString(description.mediaId ?: return) ?: return
val songUid = when (uid) {
is MediaSessionUID.SingleItem -> uid.uid
is MediaSessionUID.ChildItem -> uid.childUid
else -> return
}
val firstAt = playbackManager.queue.indexOfFirst { it.uid == songUid }
playbackManager.removeQueueItem(firstAt)
} }
override fun onPlay() { override fun onPlay() {
@ -224,26 +227,28 @@ constructor(
val songQuery = extras.getString(MediaStore.EXTRA_MEDIA_TITLE) val songQuery = extras.getString(MediaStore.EXTRA_MEDIA_TITLE)
val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
val best = deviceLibrary.songs.maxByOrNull { val best =
fuzzy(it.name, songQuery) + fuzzy(it.album.name, albumQuery) + deviceLibrary.songs.maxByOrNull {
fuzzy(it.name, songQuery) +
fuzzy(it.album.name, albumQuery) +
it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) } it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) }
} }
if (best != null) { if (best != null) {
return expandSongIntoCommand(best, null) return expandSongIntoCommand(best, null)
} }
} }
MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE -> { MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE -> {
val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
val best = deviceLibrary.albums.maxByOrNull { val best =
fuzzy(it.name, albumQuery) + it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) } deviceLibrary.albums.maxByOrNull {
} fuzzy(it.name, albumQuery) +
it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) }
}
if (best != null) { if (best != null) {
return commandFactory.album(best, ShuffleMode.OFF) return commandFactory.album(best, ShuffleMode.OFF)
} }
} }
MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE -> { MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE -> {
val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
val best = deviceLibrary.artists.maxByOrNull { fuzzy(it.name, artistQuery) } val best = deviceLibrary.artists.maxByOrNull { fuzzy(it.name, artistQuery) }
@ -251,7 +256,6 @@ constructor(
return commandFactory.artist(best, ShuffleMode.OFF) return commandFactory.artist(best, ShuffleMode.OFF)
} }
} }
MediaStore.Audio.Genres.ENTRY_CONTENT_TYPE -> { MediaStore.Audio.Genres.ENTRY_CONTENT_TYPE -> {
val genreQuery = extras.getString(MediaStore.EXTRA_MEDIA_GENRE) val genreQuery = extras.getString(MediaStore.EXTRA_MEDIA_GENRE)
val best = deviceLibrary.genres.maxByOrNull { fuzzy(it.name, genreQuery) } val best = deviceLibrary.genres.maxByOrNull { fuzzy(it.name, genreQuery) }
@ -259,7 +263,6 @@ constructor(
return commandFactory.genre(best, ShuffleMode.OFF) return commandFactory.genre(best, ShuffleMode.OFF)
} }
} }
MediaStore.Audio.Playlists.ENTRY_CONTENT_TYPE -> { MediaStore.Audio.Playlists.ENTRY_CONTENT_TYPE -> {
val playlistQuery = extras.getString(MediaStore.EXTRA_MEDIA_PLAYLIST) val playlistQuery = extras.getString(MediaStore.EXTRA_MEDIA_PLAYLIST)
val best = userLibrary.playlists.maxByOrNull { fuzzy(it.name, playlistQuery) } val best = userLibrary.playlists.maxByOrNull { fuzzy(it.name, playlistQuery) }
@ -267,14 +270,19 @@ constructor(
return commandFactory.playlist(best, ShuffleMode.OFF) return commandFactory.playlist(best, ShuffleMode.OFF)
} }
} }
else -> {} else -> {}
} }
val bestMusic = (deviceLibrary.songs + deviceLibrary.albums + deviceLibrary.artists + deviceLibrary.genres + userLibrary.playlists) val bestMusic =
.maxByOrNull { fuzzy(it.name, query) } (deviceLibrary.songs +
deviceLibrary.albums +
deviceLibrary.artists +
deviceLibrary.genres +
userLibrary.playlists)
.maxByOrNull { fuzzy(it.name, query) }
// TODO: Error out when we can't correctly resolve the query // TODO: Error out when we can't correctly resolve the query
return bestMusic?.let { expandMusicIntoCommand(it, null) } ?: commandFactory.all(ShuffleMode.ON) return bestMusic?.let { expandMusicIntoCommand(it, null) }
?: commandFactory.all(ShuffleMode.ON)
} }
private fun fuzzy(name: Name, query: String?): Double = private fun fuzzy(name: Name, query: String?): Double =