[Android] Provide playback suggestions within driving mode

Robert Stone git at videolan.org
Mon Dec 13 07:20:18 UTC 2021


vlc-android | branch: master | Robert Stone <rhstone at gmail.com> | Sun Dec  5 22:34:30 2021 -0800| [ec8033e0939ba491bec1cb94e0dd7d8338b86a8b] | committer: Nicolas Pomepuy

Provide playback suggestions within driving mode

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

 .../src/org/videolan/vlc/PlaybackService.kt        | 20 ++++++---
 .../org/videolan/vlc/media/MediaSessionBrowser.kt  | 47 +++++++++++++++++++++-
 2 files changed, 59 insertions(+), 8 deletions(-)

diff --git a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
index 72e6fb4a6..e751394e6 100644
--- a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
+++ b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
@@ -1589,12 +1589,20 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
 
     override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
         AccessControl.logCaller(clientUid, clientPackageName)
-        return if (Permissions.canReadStorage(this at PlaybackService)) {
-            val extras = MediaSessionBrowser.getContentStyle(CONTENT_STYLE_LIST_ITEM_HINT_VALUE, CONTENT_STYLE_LIST_ITEM_HINT_VALUE)
-            extras.putBoolean(TABS_OPT_IN_HINT, true)
-            extras.putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true)
-            BrowserRoot(MediaSessionBrowser.ID_ROOT, extras)
-        } else null
+        if (!Permissions.canReadStorage(this at PlaybackService)) {
+            Log.w(TAG, "Returning null MediaBrowserService root. READ_EXTERNAL_STORAGE permission not granted.")
+            return null
+        }
+        return when {
+            rootHints?.containsKey(BrowserRoot.EXTRA_SUGGESTED) == true -> BrowserRoot(MediaSessionBrowser.ID_SUGGESTED, null)
+            else -> {
+                val extras = MediaSessionBrowser.getContentStyle(CONTENT_STYLE_LIST_ITEM_HINT_VALUE, CONTENT_STYLE_LIST_ITEM_HINT_VALUE).apply {
+                    putBoolean(TABS_OPT_IN_HINT, true)
+                    putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true)
+                }
+                BrowserRoot(MediaSessionBrowser.ID_ROOT, extras)
+            }
+        }
     }
 
     override fun onLoadChildren(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
diff --git a/application/vlc-android/src/org/videolan/vlc/media/MediaSessionBrowser.kt b/application/vlc-android/src/org/videolan/vlc/media/MediaSessionBrowser.kt
index 43e76fe42..59af2a565 100644
--- a/application/vlc-android/src/org/videolan/vlc/media/MediaSessionBrowser.kt
+++ b/application/vlc-android/src/org/videolan/vlc/media/MediaSessionBrowser.kt
@@ -38,6 +38,7 @@ import androidx.annotation.StringRes
 import androidx.annotation.WorkerThread
 import androidx.core.net.toUri
 import org.videolan.medialibrary.interfaces.Medialibrary
+import org.videolan.medialibrary.interfaces.media.Album
 import org.videolan.medialibrary.interfaces.media.MediaWrapper
 import org.videolan.medialibrary.media.MediaLibraryItem
 import org.videolan.resources.*
@@ -153,6 +154,7 @@ class MediaSessionBrowser : ExtensionManagerActivity {
         const val ID_ROOT = "//${BuildConfig.APP_ID}/r"
         const val ID_MEDIA = "$ID_ROOT/media"
         const val ID_SEARCH = "$ID_ROOT/search"
+        const val ID_SUGGESTED = "$ID_ROOT/suggested"
         const val ID_NO_MEDIA = "$ID_ROOT/error/media"
         const val ID_NO_PLAYLIST = "$ID_ROOT/error/playlist"
 
@@ -175,6 +177,7 @@ class MediaSessionBrowser : ExtensionManagerActivity {
         const val MAX_HISTORY_SIZE = 100
         const val MAX_COVER_ART_ITEMS = 50
         private const val MAX_EXTENSION_SIZE = 100
+        private const val MAX_SUGGESTED_SIZE = 15
         const val MAX_RESULT_SIZE = 800
 
         // Extensions management
@@ -399,6 +402,7 @@ class MediaSessionBrowser : ExtensionManagerActivity {
                         limitSize = true
                         list = ml.lastMediaPlayed()?.toList()?.filter { isMediaAudio(it) }?.toTypedArray()
                     }
+                    ID_SUGGESTED -> return buildSuggestions(context, parentId, ml)
                     else -> {
                         val id = ContentUris.parseId(parentIdUri)
                         when (parentIdUri.retrieveParent().toString()) {
@@ -493,6 +497,39 @@ class MediaSessionBrowser : ExtensionManagerActivity {
             return results
         }
 
+        /**
+         * This function constructs a list of suggestions to display in driving mode. A max of fifteen
+         * items are returned to the caller. The first item is always shuffle all.
+         */
+        private fun buildSuggestions(context: Context, parentId: String, ml: Medialibrary): List<MediaBrowserCompat.MediaItem> {
+            val audioCount = ml.audioCount
+            if (audioCount == 0) return emptyList()
+            /* Obtain the most recently played albums from history */
+            val albumNames = mutableSetOf<String>()
+            if (Settings.getInstance(context).getBoolean(PLAYBACK_HISTORY, true)) {
+                val lastMediaPlayed = ml.lastMediaPlayed()?.toList()?.filter { isMediaAudio(it) }
+                if (!lastMediaPlayed.isNullOrEmpty()) for (mw in lastMediaPlayed) mw.album?.let { albumNames.add(it) }
+            }
+            /* Pad the end with recently added albums. We may end up dropping a few due to absent artwork. */
+            val recentAudio = ml.getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MAX_HISTORY_SIZE, 0)
+            if (!recentAudio.isNullOrEmpty()) for (mw in recentAudio) mw.album?.let { albumNames.add(it) }
+            /* Build the list of media items */
+            val results: ArrayList<MediaBrowserCompat.MediaItem> = ArrayList()
+            val shuffleAllPath = Uri.Builder()
+                    .appendPath(ArtworkProvider.SHUFFLE_ALL)
+                    .appendPath(ArtworkProvider.computeExpiration())
+                    .appendPath("$audioCount")
+                    .build()
+            val shuffleAllMediaDesc = getPlayAllBuilder(context.resources, ID_SHUFFLE_ALL, R.string.shuffle_all_title, audioCount, shuffleAllPath).build()
+            results.add(MediaBrowserCompat.MediaItem(shuffleAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
+            /* Query albums by name */
+            val albums = mutableSetOf<Album>()
+            for (albumName in albumNames) ml.searchAlbum(albumName)?.let { albums.addAll(it.toList()) }
+            results.addAll(buildMediaItems(context, parentId, albums.toTypedArray(), null, limitSize = false, suggestionMode = true)
+                    .take((MAX_SUGGESTED_SIZE - results.size).coerceAtLeast(0)))
+            return results
+        }
+
         /**
          * This function constructs a collection of MediaBrowserCompat.MediaItems for each applicable
          * array element in the MediaLibraryItems list passed from either the browse or search methods.
@@ -506,7 +543,8 @@ class MediaSessionBrowser : ExtensionManagerActivity {
          * @param limitSize Limit the number of items returned (default is false)
          * @return List containing fully constructed MediaBrowser MediaItem
          */
-        private fun buildMediaItems(context: Context, parentId: String, list: Array<out MediaLibraryItem>?, groupTitle: String?, limitSize: Boolean = false): List<MediaBrowserCompat.MediaItem> {
+        private fun buildMediaItems(context: Context, parentId: String, list: Array<out MediaLibraryItem>?, groupTitle: String?,
+                                    limitSize: Boolean = false, suggestionMode: Boolean = false): List<MediaBrowserCompat.MediaItem> {
             if (list.isNullOrEmpty()) return emptyList()
             val res = context.resources
             val artworkToUriCache = HashMap<String, Uri>()
@@ -619,10 +657,15 @@ class MediaSessionBrowser : ExtensionManagerActivity {
                         .build()
 
                 /* Set Flags */
-                val flags = when (libraryItem.itemType) {
+                var flags = when (libraryItem.itemType) {
                     MediaLibraryItem.TYPE_MEDIA, MediaLibraryItem.TYPE_PLAYLIST -> MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
                     else -> MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
                 }
+                /* Suggestions must be playable. Skip entries without artwork. */
+                if (suggestionMode) {
+                    flags = MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
+                    if (iconUri == null || iconUri.toString().startsWith(BASE_DRAWABLE_URI)) continue
+                }
                 results.add(MediaBrowserCompat.MediaItem(description, flags))
                 if ((limitSize && results.size == MAX_HISTORY_SIZE) || results.size == MAX_RESULT_SIZE) break
             }



More information about the Android mailing list