[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