[Android] Add sliding window for the playback queue when in Android Auto mode.

Robert Stone git at videolan.org
Tue Nov 3 15:47:09 CET 2020


vlc-android | branch: 3.3.x | Robert Stone <rhstone at gmail.com> | Sun Oct 25 21:30:17 2020 -0700| [45bb5dcd46ec70e99de667852079724729dd98b4] | committer: Nicolas Pomepuy

Add sliding window for the playback queue when in Android Auto mode.

(cherry picked from commit 603b938609a34afbf299ccd50588bd0a9c121cb9)

> https://code.videolan.org/videolan/vlc-android/commit/45bb5dcd46ec70e99de667852079724729dd98b4
---

 .../src/org/videolan/vlc/PlaybackService.kt        | 69 ++++++++++++++++++----
 1 file changed, 58 insertions(+), 11 deletions(-)

diff --git a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
index 5872b3d4e..2aab4931f 100644
--- a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
+++ b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
@@ -99,6 +99,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
     internal lateinit var settings: SharedPreferences
     private val binder = LocalBinder()
     internal lateinit var medialibrary: Medialibrary
+    private lateinit var artworkMap: MutableMap<String, Uri>
 
     private val callbacks = mutableListOf<Callback>()
     private lateinit var cbActor : SendChannel<CbAction>
@@ -110,6 +111,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
     internal lateinit var mediaSession: MediaSessionCompat
     @Volatile
     private var notificationShowing = false
+    private var prevUpdateInCarMode = true
     private var lastTime = 0L
     private var widget = 0
     /**
@@ -478,6 +480,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
         Util.checkCpuCompatibility(this)
 
         medialibrary = Medialibrary.getInstance()
+        artworkMap = HashMap<String,Uri>()
 
         detectHeadset = settings.getBoolean("enable_headset_detection", true)
 
@@ -917,10 +920,11 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
         mediaSession.setExtras(Bundle().apply {
             putBoolean(PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT, true)
             putBoolean(PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV, true)
-        });
+        })
 
         val mediaIsActive = state != PlaybackStateCompat.STATE_STOPPED
         val update = mediaSession.isActive != mediaIsActive
+        updateMediaQueueSlidingWindow()
         mediaSession.setPlaybackState(pscb.build())
         mediaSession.isActive = mediaIsActive
         mediaSession.setQueueTitle(getString(R.string.music_now_playing))
@@ -1085,26 +1089,69 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
 
     private fun updateMediaQueue() = lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {
         if (!this at PlaybackService::mediaSession.isInitialized) initMediaSession()
+        artworkMap = HashMap<String, Uri>().also {
+            val artworkToUriCache = HashMap<String, Uri>()
+            for (media in playlistManager.getMediaList()) {
+                if (!media.artworkMrl.isNullOrEmpty() && isPathValid(media.artworkMrl)) {
+                    val artworkUri = artworkToUriCache.getOrPut(media.artworkMrl, {getFileUri(media.artworkMrl)})
+                    val key = MediaSessionBrowser.generateMediaId(media)
+                    it[key] = artworkUri
+                }
+            }
+            artworkToUriCache.clear()
+        }
+        updateMediaQueueSlidingWindow(true)
+    }
+
+    /**
+     * Set the mediaSession queue to a sliding window of fifteen tracks max, with the current song
+     * centered in the queue (when possible). Fifteen tracks are used instead of seventeen to
+     * prevent the "Search By Name" bar from appearing on the top of the window.
+     * If Android Auto is exited, set the entire queue on the next update so that Bluetooth
+     * headunits that report the track number show the correct value in the playlist.
+     */
+    private fun updateMediaQueueSlidingWindow(mediaListChanged: Boolean = false) = lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {
+        if (AndroidDevices.isCarMode(this at PlaybackService)) {
+            val mediaList = playlistManager.getMediaList()
+            val halfWindowSize = 7
+            val windowSize = 2 * halfWindowSize + 1
+            val songNum = currentMediaPosition + 1
+            var fromIndex = 0
+            var toIndex = (mediaList.size).coerceAtMost(windowSize)
+            if (songNum > halfWindowSize) {
+                toIndex = (songNum + halfWindowSize).coerceAtMost(mediaList.size)
+                fromIndex = (toIndex - windowSize).coerceAtLeast(0)
+            }
+            buildQueue(mediaList, fromIndex, toIndex)
+            prevUpdateInCarMode = true
+        } else if (mediaListChanged || prevUpdateInCarMode) {
+            buildQueue(playlistManager.getMediaList())
+            prevUpdateInCarMode = false
+        }
+    }
+
+    private fun buildQueue(mediaList: List<MediaWrapper>, fromIndex: Int = 0, toIndex: Int = mediaList.size) = lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {
+        if (!this at PlaybackService.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) return at launch
         val ctx = this at PlaybackService
-        val defaultCoverUri = "android.resource://${BuildConfig.APP_ID}/drawable/${R.drawable.ic_no_song}".toUri()
+        val defaultCoverUri = "android.resource://${BuildConfig.APP_ID}/drawable/${R.drawable.ic_auto_nothumb}".toUri()
         val queue = withContext(Dispatchers.Default) {
-            LinkedList<MediaSessionCompat.QueueItem>().also {
-                for ((position, media) in playlistManager.getMediaList().withIndex()) {
+            ArrayList<MediaSessionCompat.QueueItem>(toIndex - fromIndex).also {
+                for ((position, media) in mediaList.subList(fromIndex, toIndex).withIndex()) {
                     val title: String = media.nowPlaying ?: media.title
-                    val coverUri = if (!media.artworkMrl.isNullOrEmpty() && isPathValid(media.artworkMrl))
-                        getFileUri(media.artworkMrl) else defaultCoverUri
-                    val builder = MediaDescriptionCompat.Builder()
+                    val mediaId = MediaSessionBrowser.generateMediaId(media)
+                    val mediaDesc = MediaDescriptionCompat.Builder()
                             .setTitle(title)
                             .setSubtitle(MediaUtils.getMediaArtist(ctx, media))
                             .setDescription(MediaUtils.getMediaAlbum(ctx, media))
-                            .setIconUri(coverUri)
+                            .setIconUri(artworkMap[mediaId] ?: defaultCoverUri)
                             .setMediaUri(media.uri)
-                            .setMediaId(MediaSessionBrowser.generateMediaId(media))
-                    it.add(MediaSessionCompat.QueueItem(builder.build(), position.toLong()))
+                            .setMediaId(mediaId)
+                            .build()
+                    it.add(MediaSessionCompat.QueueItem(mediaDesc, (fromIndex + position).toLong()))
                 }
             }
         }
-        if (this at PlaybackService.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) mediaSession.setQueue(queue)
+        mediaSession.setQueue(queue)
     }
 
     @MainThread



More information about the Android mailing list