Expand MediaItems in session demo instead of just replacing them

When MediaItems are added from the controller, we currently completely
replace the item with the one from our database, overriding any
potential additional information the controller may have set.

Also forward the onAddMediaItems/onSetMediaItems callbacks to common
helper methods instead of redirecting them through super methods

#minor-release
Issue: androidx/media#706
PiperOrigin-RevId: 573799351
This commit is contained in:
tonihei 2023-10-16 07:02:22 -07:00 committed by Copybara-Service
parent 37c86f3c15
commit 00425dbe80
3 changed files with 65 additions and 47 deletions

View File

@ -88,7 +88,7 @@ class PlayableFolderActivity : AppCompatActivity() {
browser.shuffleModeEnabled = true
browser.prepare()
browser.play()
browser?.sessionActivity?.send()
browser.sessionActivity?.send()
}
findViewById<Button>(R.id.play_button).setOnClickListener {

View File

@ -25,6 +25,7 @@ import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList
@ -32,7 +33,7 @@ import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
/** A [MediaLibraryService.MediaLibrarySession.Callback] implementation. */
open class DemoMediaLibrarySessionCallback(private val context: Context) :
open class DemoMediaLibrarySessionCallback(context: Context) :
MediaLibraryService.MediaLibrarySession.Callback {
init {
@ -155,14 +156,7 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
controller: MediaSession.ControllerInfo,
mediaItems: List<MediaItem>
): ListenableFuture<List<MediaItem>> {
val playlist = mutableListOf<MediaItem>()
mediaItems.forEach { mediaItem ->
when (mediaItem.requestMetadata.searchQuery) {
null -> MediaItemTree.getItem(mediaItem.mediaId)?.let { playlist.add(it) }
else -> playlist.addAll(MediaItemTree.search(mediaItem.requestMetadata.searchQuery!!))
}
}
return Futures.immediateFuture(playlist)
return Futures.immediateFuture(resolveMediaItems(mediaItems))
}
@OptIn(UnstableApi::class) // MediaSession.MediaItemsWithStartPosition
@ -172,34 +166,57 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
mediaItems: List<MediaItem>,
startIndex: Int,
startPositionMs: Long
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
): ListenableFuture<MediaItemsWithStartPosition> {
if (mediaItems.size == 1) {
// Try to expand a single item to a playlist.
val mediaId = mediaItems.first().mediaId
val mediaItem = MediaItemTree.getItem(mediaId)
val playlist = mutableListOf<MediaItem>()
var indexInPlaylist = startIndex
mediaItem?.apply {
if (mediaMetadata.isBrowsable == true) {
// Get children browsable item.
playlist.addAll(MediaItemTree.getChildren(mediaId))
} else if (requestMetadata.searchQuery == null) {
// Try to get the parent and its children.
MediaItemTree.getParentId(mediaId)?.let {
playlist.addAll(MediaItemTree.getChildren(it))
indexInPlaylist = MediaItemTree.getIndexInMediaItems(mediaId, playlist)
}
}
}
if (playlist.isNotEmpty()) {
// Return the expanded playlist to be set on the player of the session.
return Futures.immediateFuture(
MediaSession.MediaItemsWithStartPosition(playlist, indexInPlaylist, startPositionMs)
)
maybeExpandSingleItemToPlaylist(mediaItems.first(), startIndex, startPositionMs)?.also {
return Futures.immediateFuture(it)
}
}
// Let super serve the request if item isn't expanded.
return super.onSetMediaItems(mediaSession, browser, mediaItems, startIndex, startPositionMs)
return Futures.immediateFuture(
MediaItemsWithStartPosition(resolveMediaItems(mediaItems), startIndex, startPositionMs)
)
}
private fun resolveMediaItems(mediaItems: List<MediaItem>): List<MediaItem> {
val playlist = mutableListOf<MediaItem>()
mediaItems.forEach { mediaItem ->
if (mediaItem.mediaId.isNotEmpty()) {
MediaItemTree.expandItem(mediaItem)?.let { playlist.add(it) }
} else if (mediaItem.requestMetadata.searchQuery != null) {
playlist.addAll(MediaItemTree.search(mediaItem.requestMetadata.searchQuery!!))
}
}
return playlist
}
@OptIn(UnstableApi::class) // MediaSession.MediaItemsWithStartPosition
private fun maybeExpandSingleItemToPlaylist(
mediaItem: MediaItem,
startIndex: Int,
startPositionMs: Long
): MediaItemsWithStartPosition? {
var playlist = listOf<MediaItem>()
var indexInPlaylist = startIndex
MediaItemTree.getItem(mediaItem.mediaId)?.apply {
if (mediaMetadata.isBrowsable == true) {
// Get children browsable item.
playlist = MediaItemTree.getChildren(mediaId)
} else if (requestMetadata.searchQuery == null) {
// Try to get the parent and its children.
MediaItemTree.getParentId(mediaId)?.let {
playlist =
MediaItemTree.getChildren(it).map { mediaItem ->
if (mediaItem.mediaId == mediaId) MediaItemTree.expandItem(mediaItem)!! else mediaItem
}
indexInPlaylist = MediaItemTree.getIndexInMediaItems(mediaId, playlist)
}
}
}
if (playlist.isNotEmpty()) {
return MediaItemsWithStartPosition(playlist, indexInPlaylist, startPositionMs)
}
return null
}
override fun onSearch(

View File

@ -17,9 +17,11 @@ package androidx.media3.demo.session
import android.content.res.AssetManager
import android.net.Uri
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import com.google.common.collect.ImmutableList
import java.io.BufferedReader
import java.lang.StringBuilder
@ -275,6 +277,18 @@ object MediaItemTree {
return treeNodes[id]?.item
}
fun expandItem(item: MediaItem): MediaItem? {
val treeItem = getItem(item.mediaId) ?: return null
@OptIn(UnstableApi::class) // MediaMetadata.populate
val metadata = treeItem.mediaMetadata.buildUpon().populate(item.mediaMetadata).build()
return item
.buildUpon()
.setMediaMetadata(metadata)
.setSubtitleConfigurations(treeItem.localConfiguration?.subtitleConfigurations ?: listOf())
.setUri(treeItem.localConfiguration?.uri)
.build()
}
/**
* Returns the media ID of the parent of the given media ID, or null if the media ID wasn't found.
*
@ -342,19 +356,6 @@ object MediaItemTree {
return treeNodes[id]?.getChildren() ?: listOf()
}
fun getRandomItem(): MediaItem {
var curRoot = getRootItem()
while (curRoot.mediaMetadata.isBrowsable == true) {
val children = getChildren(curRoot.mediaId)
curRoot = children.random()
}
return curRoot
}
fun getItemFromTitle(title: String): MediaItem? {
return titleMap[title]?.item
}
private fun normalizeSearchText(text: CharSequence?): String {
if (text.isNullOrEmpty() || text.trim().length == 1) {
return ""