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.shuffleModeEnabled = true
browser.prepare() browser.prepare()
browser.play() browser.play()
browser?.sessionActivity?.send() browser.sessionActivity?.send()
} }
findViewById<Button>(R.id.play_button).setOnClickListener { 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.LibraryResult
import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
import androidx.media3.session.SessionCommand import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
@ -32,7 +33,7 @@ import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
/** A [MediaLibraryService.MediaLibrarySession.Callback] implementation. */ /** A [MediaLibraryService.MediaLibrarySession.Callback] implementation. */
open class DemoMediaLibrarySessionCallback(private val context: Context) : open class DemoMediaLibrarySessionCallback(context: Context) :
MediaLibraryService.MediaLibrarySession.Callback { MediaLibraryService.MediaLibrarySession.Callback {
init { init {
@ -155,14 +156,7 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
controller: MediaSession.ControllerInfo, controller: MediaSession.ControllerInfo,
mediaItems: List<MediaItem> mediaItems: List<MediaItem>
): ListenableFuture<List<MediaItem>> { ): ListenableFuture<List<MediaItem>> {
val playlist = mutableListOf<MediaItem>() return Futures.immediateFuture(resolveMediaItems(mediaItems))
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)
} }
@OptIn(UnstableApi::class) // MediaSession.MediaItemsWithStartPosition @OptIn(UnstableApi::class) // MediaSession.MediaItemsWithStartPosition
@ -172,34 +166,57 @@ open class DemoMediaLibrarySessionCallback(private val context: Context) :
mediaItems: List<MediaItem>, mediaItems: List<MediaItem>,
startIndex: Int, startIndex: Int,
startPositionMs: Long startPositionMs: Long
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> { ): ListenableFuture<MediaItemsWithStartPosition> {
if (mediaItems.size == 1) { if (mediaItems.size == 1) {
// Try to expand a single item to a playlist. // Try to expand a single item to a playlist.
val mediaId = mediaItems.first().mediaId maybeExpandSingleItemToPlaylist(mediaItems.first(), startIndex, startPositionMs)?.also {
val mediaItem = MediaItemTree.getItem(mediaId) return Futures.immediateFuture(it)
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)
)
} }
} }
// Let super serve the request if item isn't expanded. return Futures.immediateFuture(
return super.onSetMediaItems(mediaSession, browser, mediaItems, startIndex, startPositionMs) 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( override fun onSearch(

View File

@ -17,9 +17,11 @@ package androidx.media3.demo.session
import android.content.res.AssetManager import android.content.res.AssetManager
import android.net.Uri import android.net.Uri
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.SubtitleConfiguration import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.MediaMetadata import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.UnstableApi
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import java.io.BufferedReader import java.io.BufferedReader
import java.lang.StringBuilder import java.lang.StringBuilder
@ -275,6 +277,18 @@ object MediaItemTree {
return treeNodes[id]?.item 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. * 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() 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 { private fun normalizeSearchText(text: CharSequence?): String {
if (text.isNullOrEmpty() || text.trim().length == 1) { if (text.isNullOrEmpty() || text.trim().length == 1) {
return "" return ""