diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaLibrarySessionCallback.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaLibrarySessionCallback.kt index 380cce3f..63c7f5ad 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaLibrarySessionCallback.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaLibrarySessionCallback.kt @@ -91,6 +91,9 @@ private const val MEDIA_BOOKMARK_ITEM = "MEDIA_BOOKMARK_ITEM" private const val MEDIA_PODCAST_ITEM = "MEDIA_PODCAST_ITEM" private const val MEDIA_PODCAST_EPISODE_ITEM = "MEDIA_PODCAST_EPISODE_ITEM" private const val MEDIA_SEARCH_SONG_ITEM = "MEDIA_SEARCH_SONG_ITEM" +private const val MEDIA_GENRE_ID = "MEDIA_GENRE_ID" +private const val MEDIA_GENRE_ITEM = "MEDIA_GENRE_ITEM" +private const val MEDIA_GENRE_SONG_ITEM = "MEDIA_GENRE_SONG_ITEM" // Currently the display limit for long lists is 100 items private const val DISPLAY_LIMIT = 100 @@ -124,6 +127,7 @@ class MediaLibrarySessionCallback( private var starredSongsCache: List? = null private var randomSongsCache: List? = null private var searchSongsCache: List? = null + private var genreSongsCache: List? = null private val musicService get() = musicServiceFactory.getMusicService() private val isOffline get() = activeServerProvider.isOffline() @@ -659,6 +663,10 @@ class MediaLibrarySessionCallback( MEDIA_ARTIST_PLAY_ID -> playAllForArtist(mediaIdParts[1], mediaIdParts[2]) + MEDIA_GENRE_ITEM -> playGenre(mediaIdParts[1]) + + MEDIA_GENRE_SONG_ITEM -> playGenreSong(mediaIdParts[1]) + else -> null } @@ -731,6 +739,10 @@ class MediaLibrarySessionCallback( MEDIA_PODCAST_ITEM -> getPodcastEpisodes(parentIdParts[1]) + MEDIA_GENRE_ID -> getGenres() + + MEDIA_GENRE_ITEM -> getSongsForGenre(parentIdParts[1]) + else -> Futures.immediateFuture(LibraryResult.ofItemList(listOf(), null)) } } @@ -846,6 +858,10 @@ class MediaLibrarySessionCallback( MEDIA_ARTIST_PLAY_ID -> playAllForArtist(mediaIdParts[1], mediaIdParts[2]) + MEDIA_GENRE_ITEM -> playGenre(mediaIdParts[1]) + + MEDIA_GENRE_SONG_ITEM -> playGenreSong(mediaIdParts[1]) + else -> { listOf() } @@ -879,6 +895,72 @@ class MediaLibrarySessionCallback( }.ifEmpty { null } } + private fun getGenres(): ListenableFuture>> { + val mediaItems: MutableList = ArrayList() + + return mainScope.future { + val genres = serviceScope.future { + callWithErrorHandling { musicService.getGenres(false) } + }.await() + + genres?.sortedBy { it.name }?.map { genre -> + mediaItems.add( + genre.name, + listOf(MEDIA_GENRE_ITEM, genre.name).joinToString("|"), + mediaType = MEDIA_TYPE_FOLDER_MIXED, + isBrowsable = true + ) + } + return@future LibraryResult.ofItemList(mediaItems, null) + } + } + + private fun getSongsForGenre( + genre: String + ): ListenableFuture>> { + val mediaItems: MutableList = ArrayList() + + return mainScope.future { + val songs = serviceScope.future { + callWithErrorHandling { musicService.getSongsByGenre(genre, DISPLAY_LIMIT, 0) } + }.await() + + if (songs != null) { + val tracks = songs.getTracks() + genreSongsCache = tracks + + if (tracks.size > 1) { + mediaItems.addPlayAllItem(listOf(MEDIA_GENRE_ITEM, genre).joinToString("|")) + } + + tracks.map { song -> + mediaItems.add( + song.toMediaItem( + listOf(MEDIA_GENRE_SONG_ITEM, song.id).joinToString("|") + ) + ) + } + } + return@future LibraryResult.ofItemList(mediaItems, null) + } + } + + private fun playGenre(genre: String): List? { + if (genreSongsCache == null) { + val content = serviceScope.future { + callWithErrorHandling { musicService.getSongsByGenre(genre, DISPLAY_LIMIT, 0) } + }.get() + genreSongsCache = content?.getTracks() + } + return genreSongsCache + } + + private fun playGenreSong(songId: String): List? { + val song = genreSongsCache?.firstOrNull { it.id == songId } + if (song != null) return listOf(song) + return null + } + private fun getRootItems(): ListenableFuture>> { val mediaItems: MutableList = ArrayList() @@ -922,6 +1004,17 @@ class MediaLibrarySessionCallback( icon = R.drawable.ic_menu_playlists ) + if (!isOffline) { + mediaItems.add( + R.string.main_genres_title, + MEDIA_GENRE_ID, + null, + isBrowsable = true, + mediaType = MEDIA_TYPE_FOLDER_MIXED, + icon = R.drawable.ic_menu_browse + ) + } + return Futures.immediateFuture(LibraryResult.ofItemList(mediaItems, null)) }