From 270986215c91b189831c3551864825bbd8912943 Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Tue, 11 Oct 2022 09:08:24 +0200 Subject: [PATCH] Complete android auto support (#47) --- .../kotlin/it/vfsfitvnm/vimusic/Database.kt | 41 +- .../it/vfsfitvnm/vimusic/enums/MediaIDType.kt | 16 - .../service/PlayerMediaBrowserService.kt | 447 +++++++++++------- .../vimusic/service/PlayerService.kt | 43 +- .../vfsfitvnm/vimusic/utils/MediaIDHelper.kt | 77 --- app/src/main/res/drawable/airplane.xml | 2 +- app/src/main/res/drawable/disc.xml | 4 +- app/src/main/res/drawable/disc_white.xml | 12 - app/src/main/res/drawable/heart.xml | 2 +- app/src/main/res/drawable/heart_white.xml | 9 - app/src/main/res/drawable/musical_notes.xml | 2 +- app/src/main/res/drawable/playlist.xml | 14 +- app/src/main/res/drawable/playlist_white.xml | 46 -- 13 files changed, 329 insertions(+), 386 deletions(-) delete mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/MediaIDType.kt delete mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/MediaIDHelper.kt delete mode 100644 app/src/main/res/drawable/disc_white.xml delete mode 100644 app/src/main/res/drawable/heart_white.xml delete mode 100644 app/src/main/res/drawable/playlist_white.xml diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt index a6ab2c4..aa546d0 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt @@ -51,7 +51,6 @@ import it.vfsfitvnm.vimusic.models.SongArtistMap import it.vfsfitvnm.vimusic.models.SongPlaylistMap import it.vfsfitvnm.vimusic.models.SortedSongPlaylistMap import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map @Dao interface Database { @@ -127,10 +126,6 @@ interface Database { @Query("SELECT * FROM Song WHERE id = :id") fun song(id: String): Flow - @Transaction - @Query("SELECT * FROM Song WHERE id = :id") - fun songById(id: String): Flow - @Query("SELECT likedAt FROM Song WHERE id = :songId") fun likedAt(songId: String): Flow @@ -238,28 +233,44 @@ interface Database { @Transaction @Query("SELECT id, name, (SELECT COUNT(*) FROM SongPlaylistMap WHERE playlistId = id) as songCount FROM Playlist ORDER BY name ASC") - fun playlistPreviewsByName(): Flow> + fun playlistPreviewsByNameAsc(): Flow> @Transaction @Query("SELECT id, name, (SELECT COUNT(*) FROM SongPlaylistMap WHERE playlistId = id) as songCount FROM Playlist ORDER BY ROWID ASC") - fun playlistPreviewsByDateAdded(): Flow> + fun playlistPreviewsByDateAddedAsc(): Flow> @Transaction @Query("SELECT id, name, (SELECT COUNT(*) FROM SongPlaylistMap WHERE playlistId = id) as songCount FROM Playlist ORDER BY songCount ASC") - fun playlistPreviewsByDateSongCount(): Flow> + fun playlistPreviewsByDateSongCountAsc(): Flow> + + @Transaction + @Query("SELECT id, name, (SELECT COUNT(*) FROM SongPlaylistMap WHERE playlistId = id) as songCount FROM Playlist ORDER BY name DESC") + fun playlistPreviewsByNameDesc(): Flow> + + @Transaction + @Query("SELECT id, name, (SELECT COUNT(*) FROM SongPlaylistMap WHERE playlistId = id) as songCount FROM Playlist ORDER BY ROWID DESC") + fun playlistPreviewsByDateAddedDesc(): Flow> + + @Transaction + @Query("SELECT id, name, (SELECT COUNT(*) FROM SongPlaylistMap WHERE playlistId = id) as songCount FROM Playlist ORDER BY songCount DESC") + fun playlistPreviewsByDateSongCountDesc(): Flow> fun playlistPreviews( sortBy: PlaylistSortBy, sortOrder: SortOrder ): Flow> { return when (sortBy) { - PlaylistSortBy.Name -> playlistPreviewsByName() - PlaylistSortBy.DateAdded -> playlistPreviewsByDateAdded() - PlaylistSortBy.SongCount -> playlistPreviewsByDateSongCount() - }.map { - when (sortOrder) { - SortOrder.Ascending -> it - SortOrder.Descending -> it.reversed() + PlaylistSortBy.Name -> when (sortOrder) { + SortOrder.Ascending -> playlistPreviewsByNameAsc() + SortOrder.Descending -> playlistPreviewsByNameDesc() + } + PlaylistSortBy.SongCount -> when (sortOrder) { + SortOrder.Ascending -> playlistPreviewsByDateSongCountAsc() + SortOrder.Descending -> playlistPreviewsByDateSongCountDesc() + } + PlaylistSortBy.DateAdded -> when (sortOrder) { + SortOrder.Ascending -> playlistPreviewsByDateAddedAsc() + SortOrder.Descending -> playlistPreviewsByDateAddedDesc() } } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/MediaIDType.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/MediaIDType.kt deleted file mode 100644 index 7d5c04f..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/enums/MediaIDType.kt +++ /dev/null @@ -1,16 +0,0 @@ -package it.vfsfitvnm.vimusic.enums - -enum class MediaIDType { - Playlist, - RandomFavorites, - RandomSongs, - Song; - - val prefix: String - get() = when (this) { - Song -> "VIMUSIC_SONG_ID_" - Playlist -> "VIMUSIC_PLAYLIST_ID_" - RandomSongs -> "VIMUSIC_RANDOM_SONGS" - RandomFavorites -> "VIMUSIC_RANDOM_FAVORITES" - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt index ec93f5b..eb489ef 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt @@ -1,206 +1,307 @@ package it.vfsfitvnm.vimusic.service +import android.media.MediaDescription as BrowserMediaDescription +import android.media.browse.MediaBrowser.MediaItem as BrowserMediaItem import android.content.ComponentName +import android.content.ContentResolver import android.content.Context -import android.content.Intent import android.content.ServiceConnection -import android.media.MediaDescription -import android.media.browse.MediaBrowser.MediaItem +import android.media.session.MediaSession import android.net.Uri import android.os.Bundle import android.os.IBinder import android.os.Process import android.service.media.MediaBrowserService -import it.vfsfitvnm.vimusic.BuildConfig +import androidx.annotation.DrawableRes +import androidx.core.net.toUri +import androidx.core.os.bundleOf +import androidx.media3.common.Player +import androidx.media3.datasource.cache.Cache import it.vfsfitvnm.vimusic.Database -import it.vfsfitvnm.vimusic.enums.PlaylistSortBy -import it.vfsfitvnm.vimusic.enums.SongSortBy -import it.vfsfitvnm.vimusic.enums.SortOrder -import it.vfsfitvnm.vimusic.utils.MediaIDHelper +import it.vfsfitvnm.vimusic.R +import it.vfsfitvnm.vimusic.models.Album +import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.PlaylistPreview +import it.vfsfitvnm.vimusic.utils.asMediaItem +import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex +import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning +import it.vfsfitvnm.vimusic.utils.forceSeekToNext +import it.vfsfitvnm.vimusic.utils.forceSeekToPrevious +import it.vfsfitvnm.vimusic.utils.intent +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext -class PlayerMediaBrowserService : MediaBrowserService() { +class PlayerMediaBrowserService : MediaBrowserService(), ServiceConnection { + private val coroutineScope = CoroutineScope(Dispatchers.IO) + private var lastSongs = emptyList() - var playerServiceBinder: PlayerService.Binder? = null - var isBound = false + private var bound = false - override fun onCreate() { - super.onCreate() - val intent = Intent(this, PlayerService::class.java) - bindService(intent, playerConnection, Context.BIND_AUTO_CREATE) + override fun onDestroy() { + if (bound) { + unbindService(this) + } + super.onDestroy() } + override fun onServiceConnected(className: ComponentName, service: IBinder) { + if (service is PlayerService.Binder) { + bound = true + sessionToken = service.mediaSession.sessionToken + service.mediaSession.setCallback(SessionCallback(service.player, service.cache)) + } + } + + override fun onServiceDisconnected(name: ComponentName) = Unit + override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? { - if (!isCallerAllowed(clientPackageName, clientUid)) { - return null - } - val extras = Bundle() - extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE) - return BrowserRoot(MEDIA_ROOT_ID, extras) - } - - override fun onLoadChildren( - parentId: String, - result: Result> - ) { - when (parentId) { - MEDIA_ROOT_ID -> result.sendResult(createMenuMediaItem()) - MEDIA_PLAYLISTS_ID -> result.sendResult(createPlaylistsMediaItem()) - MEDIA_FAVORITES_ID -> result.sendResult(createFavoritesMediaItem()) - MEDIA_SONGS_ID -> result.sendResult(createSongsMediaItem()) - } - } - - private fun createFavoritesMediaItem(): MutableList { - val favorites = runBlocking(Dispatchers.IO) { - Database.favorites().first() - }.map { entry -> - MediaItem( - MediaDescription.Builder() - .setMediaId(MediaIDHelper.createMediaIdForSong(entry.id)) - .setTitle(entry.title) - .setSubtitle(entry.artistsText) - .setIconUri( - Uri.parse(entry.thumbnailUrl) - ) - .build(), MediaItem.FLAG_PLAYABLE - ) - }.toCollection(mutableListOf()) - if (favorites.isNotEmpty()) { - favorites.add( - 0, MediaItem( - MediaDescription.Builder() - .setMediaId(MediaIDHelper.createMediaIdForRandomFavorites()) - .setTitle("Play all random") - .setIconUri( - Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/drawable/shuffle") - ) - .build(), MediaItem.FLAG_PLAYABLE - ) - ) - } - return favorites - } - - private fun createSongsMediaItem(): MutableList { - val songs = runBlocking(Dispatchers.IO) { - Database.songs(SongSortBy.DateAdded, SortOrder.Descending).first() - }.map { entry -> - MediaItem( - MediaDescription.Builder() - .setMediaId(MediaIDHelper.createMediaIdForSong(entry.id)) - .setTitle(entry.title) - .setSubtitle(entry.artistsText) - .setIconUri( - Uri.parse(entry.thumbnailUrl) - ) - .build(), MediaItem.FLAG_PLAYABLE - ) - }.toCollection(mutableListOf()) - if (songs.isNotEmpty()) { - songs.add( - 0, MediaItem( - MediaDescription.Builder() - .setMediaId(MediaIDHelper.createMediaIdForRandomSongs()) - .setTitle("Play all random") - .setIconUri( - Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/drawable/shuffle") - ) - .build(), MediaItem.FLAG_PLAYABLE - ) - ) - } - return songs - } - - private fun createPlaylistsMediaItem(): MutableList { - return runBlocking(Dispatchers.IO) { - Database.playlistPreviews(PlaylistSortBy.DateAdded, SortOrder.Descending).first() - }.map { entry -> - MediaItem( - MediaDescription.Builder() - .setMediaId(MediaIDHelper.createMediaIdForPlaylist(entry.playlist.id)) - .setTitle(entry.playlist.name) - .setSubtitle("${entry.songCount} songs") - .setIconUri( - Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/drawable/playlist") - ) - .build(), MediaItem.FLAG_PLAYABLE - ) - }.toCollection(mutableListOf()) - } - - private fun createMenuMediaItem(): MutableList { - return mutableListOf( - MediaItem( - MediaDescription.Builder() - .setMediaId(MEDIA_PLAYLISTS_ID) - .setTitle("Playlists") - .setIconUri( - Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/drawable/playlist_white") - ) - .build(), MediaItem.FLAG_BROWSABLE - ), MediaItem( - MediaDescription.Builder() - .setMediaId(MEDIA_FAVORITES_ID) - .setTitle("Favorites") - .setIconUri( - Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/drawable/heart_white") - ) - .build(), MediaItem.FLAG_BROWSABLE - ), MediaItem( - MediaDescription.Builder() - .setMediaId(MEDIA_SONGS_ID) - .setTitle("Songs") - .setIconUri( - Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/drawable/disc_white") - ) - .build(), MediaItem.FLAG_BROWSABLE - ) - ) - } - - private val playerConnection = object : ServiceConnection { - override fun onServiceConnected( - className: ComponentName, - service: IBinder + return if (clientUid == Process.myUid() + || clientUid == Process.SYSTEM_UID + || clientPackageName == "com.google.android.projection.gearhead" ) { - playerServiceBinder = service as PlayerService.Binder - isBound = true - sessionToken = playerServiceBinder?.mediaSession?.sessionToken - } - - override fun onServiceDisconnected(name: ComponentName) { - isBound = false + bindService(intent(), this, Context.BIND_AUTO_CREATE) + BrowserRoot( + MediaId.root, + bundleOf("android.media.browse.CONTENT_STYLE_BROWSABLE_HINT" to 1) + ) + } else { + null } } - private fun isCallerAllowed( - clientPackageName: String, - clientUid: Int - ): Boolean { - return when { - clientUid == Process.myUid() -> true - clientUid == Process.SYSTEM_UID -> true - ANDROID_AUTO_PACKAGE_NAME == clientPackageName -> true - else -> false + override fun onLoadChildren(parentId: String, result: Result>) { + runBlocking(Dispatchers.IO) { + result.sendResult( + when (parentId) { + MediaId.root -> mutableListOf( + songsBrowserMediaItem, + playlistsBrowserMediaItem, + albumsBrowserMediaItem + ) + + MediaId.songs -> Database + .songsByPlayTimeDesc() + .first() + .take(30) + .also { lastSongs = it } + .map { it.asBrowserMediaItem } + .toMutableList() + .apply { + if (isNotEmpty()) add(0, shuffleBrowserMediaItem) + } + + MediaId.playlists -> Database + .playlistPreviewsByDateAddedDesc() + .first() + .map { it.asBrowserMediaItem } + .toMutableList() + .apply { + add(0, favoritesBrowserMediaItem) + add(1, offlineBrowserMediaItem) + } + + MediaId.albums -> Database + .albumsByRowIdDesc() + .first() + .map { it.asBrowserMediaItem } + .toMutableList() + + else -> mutableListOf() + } + ) } } - companion object { - const val ANDROID_AUTO_PACKAGE_NAME = "com.google.android.projection.gearhead" - const val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT" - const val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1 - const val MEDIA_ROOT_ID = "VIMUSIC_MEDIA_ROOT_ID" - const val MEDIA_PLAYLISTS_ID = "VIMUSIC_MEDIA_PLAYLISTS_ID" - const val MEDIA_FAVORITES_ID = "VIMUSIC_MEDIA_FAVORITES_ID" - const val MEDIA_SONGS_ID = "VIMUSIC_MEDIA_SONGS_ID" + private fun uriFor(@DrawableRes id: Int) = Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(resources.getResourcePackageName(id)) + .appendPath(resources.getResourceTypeName(id)) + .appendPath(resources.getResourceEntryName(id)) + .build() + + private val shuffleBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.shuffle) + .setTitle("Shuffle") + .setIconUri(uriFor(R.drawable.shuffle)) + .build(), + BrowserMediaItem.FLAG_PLAYABLE + ) + + private val songsBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.songs) + .setTitle("Songs") + .setIconUri(uriFor(R.drawable.musical_notes)) + .build(), + BrowserMediaItem.FLAG_BROWSABLE + ) + + + private val playlistsBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.playlists) + .setTitle("Playlists") + .setIconUri(uriFor(R.drawable.playlist)) + .build(), + BrowserMediaItem.FLAG_BROWSABLE + ) + + private val albumsBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.albums) + .setTitle("Albums") + .setIconUri(uriFor(R.drawable.disc)) + .build(), + BrowserMediaItem.FLAG_BROWSABLE + ) + + private val favoritesBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.favorites) + .setTitle("Favorites") + .setIconUri(uriFor(R.drawable.heart)) + .build(), + BrowserMediaItem.FLAG_PLAYABLE + ) + + private val offlineBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.offline) + .setTitle("Offline") + .setIconUri(uriFor(R.drawable.airplane)) + .build(), + BrowserMediaItem.FLAG_PLAYABLE + ) + + private val DetailedSong.asBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.forSong(id)) + .setTitle(title) + .setSubtitle(artistsText) + .setIconUri(thumbnailUrl?.toUri()) + .build(), + BrowserMediaItem.FLAG_PLAYABLE + ) + + private val PlaylistPreview.asBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.forPlaylist(playlist.id)) + .setTitle(playlist.name) + .setSubtitle("$songCount songs") + .setIconUri(uriFor(R.drawable.playlist)) + .build(), + BrowserMediaItem.FLAG_PLAYABLE + ) + + private val Album.asBrowserMediaItem + inline get() = BrowserMediaItem( + BrowserMediaDescription.Builder() + .setMediaId(MediaId.forAlbum(id)) + .setTitle(title) + .setSubtitle(authorsText) + .setIconUri(thumbnailUrl?.toUri()) + .build(), + BrowserMediaItem.FLAG_PLAYABLE + ) + + private inner class SessionCallback(private val player: Player, private val cache: Cache) : + MediaSession.Callback() { + override fun onPlay() = player.play() + override fun onPause() = player.pause() + override fun onSkipToPrevious() = player.forceSeekToPrevious() + override fun onSkipToNext() = player.forceSeekToNext() + override fun onSeekTo(pos: Long) = player.seekTo(pos) + override fun onSkipToQueueItem(id: Long) = player.seekToDefaultPosition(id.toInt()) + + override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { + val data = mediaId?.split('/') ?: return + + coroutineScope.launch { + val mediaItems = when (data.getOrNull(0)) { + MediaId.shuffle -> lastSongs + + MediaId.songs -> data + .getOrNull(1) + ?.let { songId -> + val index = lastSongs.indexOfFirst { it.id == songId } + + if (index != -1) { + val mediaItems = lastSongs.map(DetailedSong::asMediaItem) + + withContext(Dispatchers.Main) { + player.forcePlayAtIndex(mediaItems, index) + } + return@launch + } + + emptyList() + } ?: emptyList() + + MediaId.favorites -> Database + .favorites() + .first() + + MediaId.offline -> Database + .songsWithContentLength() + .first() + .filter { song -> + song.contentLength?.let { + cache.isCached(song.id, 0, song.contentLength) + } ?: false + } + + MediaId.playlists -> data + .getOrNull(1) + ?.toLongOrNull() + ?.let { playlistId -> + Database.playlistWithSongs(playlistId).first()?.songs + } ?: emptyList() + + MediaId.albums -> data + .getOrNull(1) + ?.let { albumId -> + Database.albumSongs(albumId).first() + } ?: emptyList() + + else -> emptyList() + }.map(DetailedSong::asMediaItem).shuffled() + + withContext(Dispatchers.Main) { + player.forcePlayFromBeginning(mediaItems) + } + } + } } -} \ No newline at end of file + private object MediaId { + const val root = "root" + const val songs = "songs" + const val playlists = "playlists" + const val albums = "albums" + + const val favorites = "favorites" + const val offline = "offline" + const val shuffle = "shuffle" + + fun forSong(id: String) = "songs/$id" + fun forPlaylist(id: Long) = "playlists/$id" + fun forAlbum(id: String) = "albums/$id" + } +} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt index 6d4c37d..eb6ec5c 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt @@ -13,14 +13,12 @@ import android.content.SharedPreferences import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color -import android.media.MediaDescription import android.media.MediaMetadata import android.media.audiofx.AudioEffect import android.media.session.MediaSession import android.media.session.PlaybackState import android.net.Uri import android.os.Build -import android.os.Bundle import android.os.Handler import android.text.format.DateUtils import androidx.compose.runtime.getValue @@ -72,7 +70,6 @@ import it.vfsfitvnm.vimusic.models.Event import it.vfsfitvnm.vimusic.models.QueuedMediaItem import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.utils.InvincibleService -import it.vfsfitvnm.vimusic.utils.MediaIDHelper import it.vfsfitvnm.vimusic.utils.RingBuffer import it.vfsfitvnm.vimusic.utils.TimerJob import it.vfsfitvnm.vimusic.utils.YouTubeRadio @@ -327,18 +324,18 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene } // On playlist changed, we refresh the mediaSession queue - if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) { - mediaSession.setQueue(player.currentTimeline.mediaItems.mapIndexed { index, it -> - MediaSession.QueueItem( - MediaDescription.Builder() - .setMediaId(it.mediaId) - .setTitle(it.mediaMetadata.title) - .setSubtitle(it.mediaMetadata.artist) - .setIconUri(it.mediaMetadata.artworkUri) - .build(), index.toLong() - ) - }) - } +// if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) { +// mediaSession.setQueue(player.currentTimeline.mediaItems.mapIndexed { index, it -> +// MediaSession.QueueItem( +// MediaDescription.Builder() +// .setMediaId(it.mediaId) +// .setTitle(it.mediaMetadata.title) +// .setSubtitle(it.mediaMetadata.artist) +// .setIconUri(it.mediaMetadata.artworkUri) +// .build(), index.toLong() +// ) +// }) +// } } private fun maybeRecoverPlaybackError() { @@ -486,10 +483,10 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene MediaMetadata.METADATA_KEY_ALBUM, player.currentMediaItem?.mediaMetadata?.albumTitle ) - .putBitmap( - MediaMetadata.METADATA_KEY_ALBUM_ART, - if (isShowingThumbnailInLockscreen) bitmapProvider.bitmap else null - ) +// .putBitmap( +// MediaMetadata.METADATA_KEY_ALBUM_ART, +// if (isShowingThumbnailInLockscreen) bitmapProvider.bitmap else null +// ) .putLong(MediaMetadata.METADATA_KEY_DURATION, player.duration) .build().let(mediaSession::setMetadata) } @@ -555,7 +552,6 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene } } - override fun notification(): Notification? { if (player.currentMediaItem == null) return null @@ -780,7 +776,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene val cache: Cache get() = this@PlayerService.cache - val mediaSession: MediaSession + val mediaSession get() = this@PlayerService.mediaSession val sleepTimerMillisLeft: StateFlow? @@ -862,11 +858,6 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene override fun onSkipToPrevious() = player.forceSeekToPrevious() override fun onSkipToNext() = player.forceSeekToNext() override fun onSeekTo(pos: Long) = player.seekTo(pos) - override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { - player.forcePlayFromBeginning(MediaIDHelper.extractMusicQueueFromMediaId(mediaId)) - } - - override fun onSkipToQueueItem(id: Long) = player.seekToDefaultPosition(id.toInt()) } private class NotificationActionReceiver(private val player: Player) : BroadcastReceiver() { diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/MediaIDHelper.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/MediaIDHelper.kt deleted file mode 100644 index 4084b9c..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/MediaIDHelper.kt +++ /dev/null @@ -1,77 +0,0 @@ -package it.vfsfitvnm.vimusic.utils - -import androidx.media3.common.MediaItem -import it.vfsfitvnm.vimusic.Database -import it.vfsfitvnm.vimusic.enums.MediaIDType -import it.vfsfitvnm.vimusic.enums.SongSortBy -import it.vfsfitvnm.vimusic.enums.SortOrder -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking - -class MediaIDHelper { - - companion object { - - fun createMediaIdForSong(id: String): String { - return MediaIDType.Song.prefix.plus(id) - } - - fun createMediaIdForPlaylist(id: Long): String { - return MediaIDType.Playlist.prefix.plus(id) - } - - fun createMediaIdForRandomFavorites(): String { - return MediaIDType.RandomFavorites.prefix - } - - fun createMediaIdForRandomSongs(): String { - return MediaIDType.RandomSongs.prefix - } - - fun extractMusicQueueFromMediaId(mediaID: String?): List { - val result = mutableListOf() - mediaID?.apply { - with(mediaID) { - when { - startsWith(MediaIDType.Song.prefix) -> { - val id = mediaID.removePrefix(MediaIDType.Song.prefix) - val song = runBlocking(Dispatchers.IO) { - Database.songById(id).first() - } - song?.apply { - result.add(song.asMediaItem) - } - } - startsWith(MediaIDType.Playlist.prefix) -> { - val id = mediaID.removePrefix(MediaIDType.Playlist.prefix).toLong() - val playlist = runBlocking(Dispatchers.IO) { - Database.playlistWithSongs(id).first() - } - playlist?.apply { - if (playlist.songs.isNotEmpty()) { - playlist.songs.map { it.asMediaItem }.forEach(result::add) - } - } - } - startsWith(MediaIDType.RandomFavorites.prefix) -> { - val favorites = runBlocking(Dispatchers.IO) { - Database.favorites().first() - } - favorites.map { it.asMediaItem }.forEach(result::add) - result.shuffle() - } - startsWith(MediaIDType.RandomSongs.prefix) -> { - val favorites = runBlocking(Dispatchers.IO) { - Database.songs(SongSortBy.DateAdded, SortOrder.Descending).first() - } - favorites.map { it.asMediaItem }.forEach(result::add) - result.shuffle() - } - } - } - } - return result - } - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/airplane.xml b/app/src/main/res/drawable/airplane.xml index ad97c02..cee47b7 100644 --- a/app/src/main/res/drawable/airplane.xml +++ b/app/src/main/res/drawable/airplane.xml @@ -4,6 +4,6 @@ android:viewportWidth="512" android:viewportHeight="512"> diff --git a/app/src/main/res/drawable/disc.xml b/app/src/main/res/drawable/disc.xml index 6649e6f..fd3f2a8 100644 --- a/app/src/main/res/drawable/disc.xml +++ b/app/src/main/res/drawable/disc.xml @@ -4,9 +4,9 @@ android:viewportWidth="512" android:viewportHeight="512"> diff --git a/app/src/main/res/drawable/disc_white.xml b/app/src/main/res/drawable/disc_white.xml deleted file mode 100644 index fd3f2a8..0000000 --- a/app/src/main/res/drawable/disc_white.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/heart.xml b/app/src/main/res/drawable/heart.xml index cbf1527..2a71a1e 100644 --- a/app/src/main/res/drawable/heart.xml +++ b/app/src/main/res/drawable/heart.xml @@ -4,6 +4,6 @@ android:viewportWidth="512" android:viewportHeight="512"> diff --git a/app/src/main/res/drawable/heart_white.xml b/app/src/main/res/drawable/heart_white.xml deleted file mode 100644 index 2a71a1e..0000000 --- a/app/src/main/res/drawable/heart_white.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/musical_notes.xml b/app/src/main/res/drawable/musical_notes.xml index ac341fb..8d3d474 100644 --- a/app/src/main/res/drawable/musical_notes.xml +++ b/app/src/main/res/drawable/musical_notes.xml @@ -4,6 +4,6 @@ android:viewportWidth="512" android:viewportHeight="512"> diff --git a/app/src/main/res/drawable/playlist.xml b/app/src/main/res/drawable/playlist.xml index 923e8d6..05a33e6 100644 --- a/app/src/main/res/drawable/playlist.xml +++ b/app/src/main/res/drawable/playlist.xml @@ -8,39 +8,39 @@ android:strokeLineJoin="round" android:strokeWidth="48" android:fillColor="#00000000" - android:strokeColor="#000000" + android:strokeColor="#FFFFFF" android:strokeLineCap="round"/> + android:fillColor="#FFFFFF"/> + android:fillColor="#FFFFFF"/> diff --git a/app/src/main/res/drawable/playlist_white.xml b/app/src/main/res/drawable/playlist_white.xml deleted file mode 100644 index ac99221..0000000 --- a/app/src/main/res/drawable/playlist_white.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - -