[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