diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt index 72b0f7e7e..cc250c342 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemTranslation.kt @@ -105,11 +105,8 @@ fun Category.toMediaItem(context: Context): MediaItem { return MediaItem(description.build(), MediaItem.FLAG_BROWSABLE) } -fun Song.toMediaItem( - context: Context, - parent: MusicParent? = null, - vararg sugar: Sugar -): MediaItem { +fun Song.toMediaDescription(context: Context, parent: MusicParent? = null, + vararg sugar: Sugar): MediaDescriptionCompat { val mediaSessionUID = if (parent == null) { MediaSessionUID.SingleItem(uid) @@ -117,17 +114,23 @@ fun Song.toMediaItem( MediaSessionUID.ChildItem(parent.uid, uid) } val extras = Bundle().apply { sugar.forEach { this.it(context) } } - val description = - MediaDescriptionCompat.Builder() - .setMediaId(mediaSessionUID.toString()) - .setTitle(name.resolve(context)) - .setSubtitle(artists.resolveNames(context)) - .setDescription(album.name.resolve(context)) - .setIconUri(album.cover.single.mediaStoreCoverUri) - .setMediaUri(uri) - .setExtras(extras) - .build() - return MediaItem(description, MediaItem.FLAG_PLAYABLE) + return MediaDescriptionCompat.Builder() + .setMediaId(mediaSessionUID.toString()) + .setTitle(name.resolve(context)) + .setSubtitle(artists.resolveNames(context)) + .setDescription(album.name.resolve(context)) + .setIconUri(cover.mediaStoreCoverUri) + .setMediaUri(uri) + .setExtras(extras) + .build() +} + +fun Song.toMediaItem( + context: Context, + parent: MusicParent? = null, + vararg sugar: Sugar +): MediaItem { + return MediaItem(toMediaDescription(context, parent, *sugar), MediaItem.FLAG_PLAYABLE) } fun Album.toMediaItem( diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt index f89016f31..a07c2ad30 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionHolder.kt @@ -42,6 +42,8 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.resolveNames 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.PlaybackSettings import org.oxycblt.auxio.playback.service.MediaSessionInterface @@ -304,20 +306,7 @@ private constructor( private fun updateQueue(queue: List) { val queueItems = queue.mapIndexed { i, song -> - val description = - 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() + val description = song.toMediaDescription(context, null, { putInt(MediaSessionInterface.KEY_QUEUE_POS, i) }) // Store the item index so we can then use the analogous index in the // playback state. MediaSessionCompat.QueueItem(description, i.toLong()) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt index dbe726dde..3ab8eaa1e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionInterface.kt @@ -93,22 +93,11 @@ constructor( super.onPlayFromSearch(query, extras) val deviceLibrary = musicRepository.deviceLibrary ?: return val userLibrary = musicRepository.userLibrary ?: return - val command = expandSearchInfoCommand( - query.ifBlank { null }, - extras, - deviceLibrary, - userLibrary) + val command = + expandSearchInfoCommand(query.ifBlank { null }, extras, deviceLibrary, userLibrary) 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) { super.onAddQueueItem(description) val deviceLibrary = musicRepository.deviceLibrary ?: return @@ -125,8 +114,22 @@ constructor( override fun onRemoveQueueItem(description: MediaDescriptionCompat) { super.onRemoveQueueItem(description) - val at = description.extras?.getInt(KEY_QUEUE_POS) ?: return - playbackManager.removeQueueItem(at) + val at = description.extras?.getInt(KEY_QUEUE_POS) + 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() { @@ -224,26 +227,28 @@ constructor( val songQuery = extras.getString(MediaStore.EXTRA_MEDIA_TITLE) val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) - val best = deviceLibrary.songs.maxByOrNull { - fuzzy(it.name, songQuery) + fuzzy(it.album.name, albumQuery) + + val best = + deviceLibrary.songs.maxByOrNull { + fuzzy(it.name, songQuery) + + fuzzy(it.album.name, albumQuery) + it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) } - } + } if (best != null) { return expandSongIntoCommand(best, null) } } - MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE -> { val albumQuery = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) - val best = deviceLibrary.albums.maxByOrNull { - fuzzy(it.name, albumQuery) + it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) } - } + val best = + deviceLibrary.albums.maxByOrNull { + fuzzy(it.name, albumQuery) + + it.artists.maxOf { artist -> fuzzy(artist.name, artistQuery) } + } if (best != null) { return commandFactory.album(best, ShuffleMode.OFF) } } - MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE -> { val artistQuery = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) val best = deviceLibrary.artists.maxByOrNull { fuzzy(it.name, artistQuery) } @@ -251,7 +256,6 @@ constructor( return commandFactory.artist(best, ShuffleMode.OFF) } } - MediaStore.Audio.Genres.ENTRY_CONTENT_TYPE -> { val genreQuery = extras.getString(MediaStore.EXTRA_MEDIA_GENRE) val best = deviceLibrary.genres.maxByOrNull { fuzzy(it.name, genreQuery) } @@ -259,7 +263,6 @@ constructor( return commandFactory.genre(best, ShuffleMode.OFF) } } - MediaStore.Audio.Playlists.ENTRY_CONTENT_TYPE -> { val playlistQuery = extras.getString(MediaStore.EXTRA_MEDIA_PLAYLIST) val best = userLibrary.playlists.maxByOrNull { fuzzy(it.name, playlistQuery) } @@ -267,14 +270,19 @@ constructor( return commandFactory.playlist(best, ShuffleMode.OFF) } } - else -> {} } - val bestMusic = (deviceLibrary.songs + deviceLibrary.albums + deviceLibrary.artists + deviceLibrary.genres + userLibrary.playlists) - .maxByOrNull { fuzzy(it.name, query) } + val bestMusic = + (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 - 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 =