Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search functionality not available on Android auto despite passing the necessary extras #645

Closed
yuroyami opened this issue Sep 12, 2023 · 6 comments
Assignees
Labels

Comments

@yuroyami
Copy link

In my MediaLibraryService, here's my implementation of onGetLibraryRoot :

        override fun onGetLibraryRoot(session: MediaLibrarySession, browser: MediaSession.ControllerInfo, params: LibraryParams?): ListenableFuture<LibraryResult<MediaItem>> {
            val rootExtras = Bundle()
            rootExtras.putBoolean(MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
            val libraryParams = LibraryParams.Builder().setExtras(rootExtras).build()
            return Futures.immediateFuture(LibraryResult.ofItem(rootItem, libraryParams))
        }

The code above should make the search functionality available on Android Auto, but the search button doesn't even show up. I tried to replace MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED with "android.media.browse.SEARCH_SUPPORTED" to make sure I am passing the correct key, but no avail.

The same result is met on both the virtual desktop head unit and a real car head unit. Am I doing something wrong ?

@marcbaechinger
Copy link
Contributor

There are a few things that come to mind:

First, I think this is supported out of the box without you having to manually insert the extra. You only have to make sure that the browser has the command COMMAND_CODE_LIBRARY_SEARCH available. If this is the case, Media3 will insert that extra property automatically. This is done here in MediaLibraryServiceLegacyStub.onGetRoot. Vice versa, I would expect if you don't have this command available, Media3 would override your value with false.

So you would have to make sure that the COMMAND_CODE_LIBRARY_SEARCH is available. If you do not override Callback.onConnect() this is included by default. If you do override and give back a different set of commands then please make sure to include this command.

Second, of course, there may be a bug in Media3 that somehow doesn't correctly propagate these extras to the legacy browser client. For this I created a unit test case in MediaBrowserCompatWithMediaLibraryServiceTest to verify whether this works as expected:

  @Test
  public void getRoot_commandSearchAvailable_expectedBrowserRootExtraIsTrue() throws Exception {
    connectAndWait(/* connectionHints= */ Bundle.EMPTY);

    assertThat(
        browserCompat
            .getExtras()
            .getBoolean(BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, /* defaultValue= */ false))
        .isTrue();
  }

This test runs green without me manipulating the available commands in onConnect(), which is the expected result.

I also tested this by using the media controller test app, that uses the MediaBrowserCompat of the legacy media1 library that Automotive is using as well. When I connect with this app to the session demo, I can see the extras in the root keys that I see with browserCompat.getExtras(). This seems to have the expected key in the extras by default without me changing something in the demo app.

image

So from what I see and tested this should work given the COMMAND_CODE_LIBRARY_SEARCH is available which is the case by default.

Can you review your code and let me know whether you provide the required command?

@yuroyami
Copy link
Author

yuroyami commented Sep 16, 2023

@marcbaechinger Note that I've done my testing using an emulator but I also confirm the issue exists on a real car headunit.

Now on to details: I made sure that the required command does indeed exist in the available session commands. Here's my onConnect implementation:

override fun onConnect(session: MediaSession, controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult {
            val superCall = super.onConnect(session, controller)

            val sessionComs = superCall.availableSessionCommands
                .buildUpon()
                .add(CUSTOM_COM_CUSTOM1)
                .add(CUSTOM_COM_CUSTOM2)
                .build()

            //search command code is 50005
            for (comm in sessionComs.commands) {
                Log.e("SessionCommand", "${comm.commandCode} - ${comm.customAction}")
            }
            return MediaSession.ConnectionResult.accept(sessionComs, superCall.availablePlayerCommands)
        }

As expected, all library commands (including the search command) appear in the logcat as well as the two custom commands. But no sign of any search button throughout the lifecycle of the Android Auto interface.

It gets weirder: when I loaded the media3 branch of UAMP (which is using media3-1.0.0 while I am using media3-1.2.0-alpha01) apparently the search functionality did not appear on the car screen either. I assume that the UAMP is supposed to provide the search functionality as well.

It gets even weirder: At some point when I started my media3 development, the search functionality did exist, then at some point it just stopped working. When the search functionality/button used to appear, I had both the onGetSearchResult and onSearch implemented, so it's probably not because of them, but something else. I think there is something else messing with it, probably another MediaLibraryCallback method ? I will keep on testing and see what I can fruit out of it.

Here's my full implementation of the MediaLibraryService just in case:

MediaLibraryService implementation
class MusicPlayerService : MediaLibraryService() {

    lateinit var player: Player
    private var mediaSession: MediaLibrarySession? = null

    //private lateinit var packageValidator: PackageValidator

    private val serviceIOScope = CoroutineScope(Dispatchers.IO)
    private val serviceMainScope = CoroutineScope(Dispatchers.Main)

    /** This is the root item that is parent to our playlist.
     *  It is necessary to have a parent item otherwise there is no "library" */
    val rootItem = MediaItem.Builder()
        .setMediaId(nodeROOT)
        .setMediaMetadata(
            MediaMetadata.Builder()
                .setIsBrowsable(true)
                .setIsPlayable(false)
                .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
                .setTitle("Gmix")
                .build()
        )
        .build()

    lateinit var subroot_TracklistItem: MediaItem

    lateinit var subroot_PlaylistItem: MediaItem

    val rootHierarchy = mutableListOf<MediaItem>()

    var tracklist = mutableListOf<MediaItem>()
    var playlist = mutableListOf<MediaItem>()

    var latestSearchResults = mutableListOf<MediaItem>()

    var tracklistFullyPopulated = false

    /** This will fetch music from the source folder (or the entire device if not specified) */
    private fun queryMusic() {
        val sp = PreferenceManager.getDefaultSharedPreferences(applicationContext)
        val src = sp.getString("music_src_folder", "") ?: ""

        serviceIOScope.launch {
            tracklist.clear()
            scanMusic(
                uri = if (src == "") null else src.toUri(),
                onSongDetected = { song ->
                    tracklist.add(song)
                    serviceMainScope.launch {
                        mediaSession?.notifyChildrenChanged(nodeTRACKLIST, tracklist.size, null)
                    }
                }
            ) {
                tracklistFullyPopulated = true
                tracklist.sortWith(NameComparator())
                serviceMainScope.launch {
                    mediaSession?.notifyChildrenChanged(nodeROOT, 2, null)
                    mediaSession?.notifyChildrenChanged(nodeTRACKLIST, tracklist.size, null)
                    mediaSession?.notifyChildrenChanged(nodeROOT, 2, null)
                }
            }
        }
    }

    override fun onCreate() {
        super.onCreate()

        val icontracklist = getLocalDrawablePath(applicationContext, R.drawable.ic_tracklist)
        val iconplaylist = getLocalDrawablePath(applicationContext, R.drawable.ic_playlist)

        subroot_TracklistItem = MediaItem.Builder()
            .setMediaId(nodeTRACKLIST)
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setIsBrowsable(true)
                    .setIsPlayable(false)
                    .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS)
                    .setTitle("Tracklist")
                    //.setArtworkUri(Uri.parse(icontracklist))
                    /*
                    .setExtras(Bundle().apply {
                        putString("MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI", icontracklist)
                        putString("MediaMetadataCompat.METADATA_KEY_ART_URI", icontracklist)
                        putString("MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI", icontracklist)
                    })

                     */
                    .build()
            )
            .build()

        subroot_PlaylistItem = MediaItem.Builder()
            .setMediaId(nodePLAYLIST)
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setIsBrowsable(true)
                    .setIsPlayable(false)
                    .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS)
                    .setTitle("Playlist")
                    //.setArtworkUri(Uri.parse(iconplaylist))
                    /*
                    .setExtras(Bundle().apply {
                        putString("MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI", iconplaylist)
                        putString("MediaMetadataCompat.METADATA_KEY_ART_URI", iconplaylist)
                        putString("MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI", iconplaylist)
                    })

                     */
                    .build()
            )
            .build()

        rootHierarchy.clear()
        rootHierarchy.add(subroot_TracklistItem)
        rootHierarchy.add(subroot_PlaylistItem)

        queryMusic()

        restorePlaylist {
            playlist.clear()
            playlist.addAll(it)

            mediaSession?.notifyChildrenChanged(nodePLAYLIST, playlist.size, null)
            mediaSession?.notifyChildrenChanged(nodeROOT, 2, null)
        }

        /** Building ExoPlayer to use FFmpeg Audio Renderer and also enable fast-seeking */
        player = ExoPlayer.Builder(applicationContext)
            .setSeekParameters(SeekParameters.CLOSEST_SYNC) /* Enabling fast seeking */
            .setRenderersFactory(
                DefaultRenderersFactory(this).setExtensionRendererMode(
                    DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER /* We prefer extensions, such as FFmpeg */
                )
            )
            .setWakeMode(C.WAKE_MODE_LOCAL) /* Prevent the service from being killed during playback */
            .setHandleAudioBecomingNoisy(true) /* Prevent annoying noise when changing devices */
            .setAudioAttributes(
                AudioAttributes.Builder()
                    .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
                    .setUsage(C.USAGE_MEDIA)
                    .build(),
                true
            )
            .build()

        player.repeatMode = Player.REPEAT_MODE_ALL

        /** Creating our MediaLibrarySession which is an advanced extension of a MediaSession */
        mediaSession = with(MediaLibrarySession.Builder(this, player, SessionLibCallback())) {
            setId(packageName)
            packageManager?.getLaunchIntentForPackage(packageName)?.let { sessionIntent ->
                setSessionActivity(
                    PendingIntent.getActivity(
                        /* context= */ this@MusicPlayerService,
                        /* requestCode= */ 0,
                        sessionIntent,
                        FLAG_IMMUTABLE
                    )
                )
            }
            build()
        }

        //packageValidator = PackageValidator(this, R.xml.allowed_media_browser_callers)

        /** Listening to some player events */
        player.addListener(object : Player.Listener {
            override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
                super.onMediaItemTransition(mediaItem, reason)
                Log.e("ed", mediaItem?.mediaId ?: "Empty Media Id")

                if (mediaItem != null && player.mediaItemCount == 1) {
                    val playlistfootprint = mediaItem.requestMetadata.extras?.getBoolean("isplaylist", false) == true

                    if (playlistfootprint && playlist.isNotEmpty()) {
                        val index = playlist.indexOfFirst { it.mediaId == mediaItem.mediaId }
                        player.setMediaItems(playlist, index, 0)
                        setPlaybackMode(PlayBackMode.PBM_PLAYLIST)
                    }

                    if (!playlistfootprint && tracklist.size > 1) {
                        val index = tracklist.indexOfFirst { it.mediaId == mediaItem.mediaId }
                        player.setMediaItems(tracklist, index, 0)
                        setPlaybackMode(PlayBackMode.PBM_TRACKLIST)
                    }
                }

                updateCustomLayout()
            }

            override fun onPlayerError(error: PlaybackException) {
                error.printStackTrace()

                Log.e("PLAYER", error.stackTraceToString())
            }
        })
    }

    fun retrieveItemFromSearch(q: String): MediaItem? {
        val query =
            if (q.startsWith("play ", ignoreCase = true)) {
                q.drop(5)
            } else {
                q
            }

        if (tracklistFullyPopulated) {
            return tracklist.firstOrNull { item ->
                item.mediaMetadata.title?.contains(query, true) == true
                        ||
                        item.mediaMetadata.artist?.contains(query, true) == true
                        ||
                        item.mediaMetadata.albumTitle?.contains(query, true) == true
                        ||
                        item.mediaMetadata.albumArtist?.contains(query, true) == true
            }?.let { newItem ->
                return@let MediaItem.Builder()
                    .setMediaMetadata(newItem.mediaMetadata)
                    .setMediaId(newItem.mediaId)
                    .setUri(newItem.mediaId)
                    .build()
            }
        } else {
            return null //TODO Use MediaStore to blockingly look for the query
        }
    }

    /** The acutal session callback for Media3 (which is backward-compatible with Media2 and Media1 */
    inner class SessionLibCallback: MediaLibrarySession.Callback {
        override fun onAddMediaItems(mediaSession: MediaSession, controller: MediaSession.ControllerInfo, mediaItems: MutableList<MediaItem>): ListenableFuture<MutableList<MediaItem>> {
            val androidAutoCalled = mediaItems.all { it.localConfiguration == null } && (controller.packageName.isAndroidAuto())


            val newItems = mediaItems.map {
                Log.e("eee", "mediaId: ${it.mediaId}")
                val newItem = it.buildUpon().setUri(it.mediaId).build()

                val query = newItem.requestMetadata.searchQuery?.trim()
                if (query != null) {
                    retrieveItemFromSearch(query) ?: newItem
                } else {
                    newItem
                }
            }.toMutableList()

            val finalItems = if (androidAutoCalled) {
                mutableListOf(newItems.first())
            } else newItems


            return Futures.immediateFuture(finalItems)
        }

        override fun onGetLibraryRoot(session: MediaLibrarySession, browser: MediaSession.ControllerInfo, params: LibraryParams?): ListenableFuture<LibraryResult<MediaItem>> {
            //val isKnownCaller = packageValidator.isKnownCaller(browser.packageName, browser.uid)
            return Futures.immediateFuture(LibraryResult.ofItem(rootItem, params))
        }

        override fun onGetChildren(
            session: MediaLibrarySession, browser: MediaSession.ControllerInfo,
            parentId: String, page: Int, pageSize: Int, params: LibraryParams?
        ): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {

            return Futures.immediateFuture(
                LibraryResult.ofItemList(
                    when (parentId) {
                        nodeROOT -> rootHierarchy
                        nodeTRACKLIST -> tracklist
                        nodePLAYLIST -> playlist
                        else -> ImmutableList.of()
                    },
                    LibraryParams.Builder().build()
                )
            )
        }


        override fun onSubscribe(session: MediaLibrarySession, browser: MediaSession.ControllerInfo, parentId: String, params: LibraryParams?): ListenableFuture<LibraryResult<Void>> {
            session.notifyChildrenChanged(
                parentId,
                when (parentId) {
                    nodeROOT -> 2
                    nodeTRACKLIST -> tracklist.size
                    nodePLAYLIST -> playlist.size
                    else -> 0
                },
                params
            )
            return Futures.immediateFuture(LibraryResult.ofVoid()) //super.onSubscribe(session, browser, parentId, params)
        }

        /** In order to end the service from our media browser side (UI side), we receive
         * our own custom command (which is [CUSTOM_COM_END_SERVICE]). However, the session
         * is not designed to accept foreign weird commands. So we edit the onConnect callback method
         * to make sure it accepts it.
         */
        override fun onConnect(session: MediaSession, controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult {
            val superCall = super.onConnect(session, controller)

            val sessionComs = superCall.availableSessionCommands
                .buildUpon()
                .add(CUSTOM_COM_PLAY_ITEM) //Command executed when an item is requested to play
                .add(CUSTOM_COM_END_SERVICE) //This one is called to end the service manually from the UI
                .add(CUSTOM_COM_PLAYLIST_ADD) //Command used when adding items to playlist
                .add(CUSTOM_COM_PLAYLIST_REMOVE) //Command used when removing items from playlist
                .add(CUSTOM_COM_PLAYLIST_CLEAR) //Command used when clearing all items from playlist
                .add(CUSTOM_COM_SCAN_MUSIC) //Command use to execute a music scan
                .add(CUSTOM_COM_TRACKLIST_FORGET) //Used when an item is to be forgotten (swipe left)
                .add(CUSTOM_COM_SWITCH_REPEAT_MODE) //Used when switching repeat modes
                .build()

            //search command code is 50005
            for (comm in sessionComs.commands) {
                Log.e("SessionCommand", "${comm.commandCode} - ${comm.customAction}")
            }
            return MediaSession.ConnectionResult.accept(sessionComs, superCall.availablePlayerCommands)
        }

        /** Receiving some custom commands such as the command that ends the service.
         * In order to make the player accept newly customized foreign weird commands, we have
         * to edit the onConnect callback method like we did above */
        override fun onCustomCommand(
            session: MediaSession,
            controller: MediaSession.ControllerInfo,
            customCommand: SessionCommand,
            args: Bundle
        ): ListenableFuture<SessionResult> {

            /** When the controller tries to add an item to the playlist */
            if (customCommand == CUSTOM_COM_PLAY_ITEM) {

                args.getString("id")?.let { mediaid ->
                    if (args.getBoolean("playlist", false)) {
                        val i = playlist.indexOfFirst { it.mediaId == mediaid }
                        setPlaybackMode(PlayBackMode.PBM_PLAYLIST)
                        player.setMediaItems(playlist, i, 0)
                    } else {
                        val i = tracklist.indexOfFirst { it.mediaId == mediaid }
                        setPlaybackMode(PlayBackMode.PBM_TRACKLIST)
                        player.setMediaItems(tracklist, i, 0)
                    }

                    player.prepare()
                    player.play()
                    return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))

                }
            }

            /** When the controller (like the app) closes fully, we need to disconnect */
            if (customCommand == CUSTOM_COM_END_SERVICE) {
                session.release()
                player.release()
                this@MusicPlayerService.stopSelf()

                return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))
            }

            /** When the user changes the source folder */
            if (customCommand == CUSTOM_COM_SCAN_MUSIC) {
                queryMusic()

                return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))
            }

            /** When the controller tries to add an item to the playlist */
            if (customCommand == CUSTOM_COM_PLAYLIST_ADD) {
                (args.getString("id", player.currentMediaItem?.mediaId ?: ""))?.let { mediaid ->
                    tracklist.firstOrNull { it.mediaId == mediaid }?.let { itemToAdd ->

                        itemToAdd.playlistFootprint(true)

                        playlist.add(itemToAdd)

                        serviceIOScope.launch {
                            /** notifying UI-end that the playlist has been modified */
                            //mediaSession?.notifyChildrenChanged(nodePLAYLIST, playlist.size, null)

                            //mediaSession?.notifyChildrenChanged(nodeROOT, 2, null)

                            /** Saving the playlist to memory as it is now */
                            snapshotPlaylist(playlist)
                        }

                        updateCustomLayout()

                        return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))
                    }
                }
            }

            /** When the controller tries to remove an item from the playlist */
            if (customCommand == CUSTOM_COM_PLAYLIST_REMOVE) {
                (args.getString("id", player.currentMediaItem?.mediaId ?: ""))?.let { mediaid ->
                    playlist.firstOrNull { it.mediaId == mediaid }?.let { itemToRemove ->
                        playlist.remove(itemToRemove)

                        serviceIOScope.launch {
                            /** notifying UI-end that the playlist has been modified */
                            //mediaSession?.notifyChildrenChanged(nodePLAYLIST, playlist.size, null)

                            /** Saving the playlist to memory as it is now */
                            snapshotPlaylist(playlist)
                        }

                        updateCustomLayout()

                        return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))
                    }
                }
            }

            /** When the controller tries to clear the playlist */
            if (customCommand == CUSTOM_COM_PLAYLIST_CLEAR) {
                playlist.clear()
                mediaSession?.notifyChildrenChanged(nodePLAYLIST, 0, null)

                /** Saving the playlist to memory as it is now */
                snapshotPlaylist(playlist)

                return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))
            }

            if (customCommand == CUSTOM_COM_SWITCH_REPEAT_MODE) {
                if (player.repeatMode == Player.REPEAT_MODE_ALL) {
                    player.repeatMode = Player.REPEAT_MODE_ONE
                } else {
                    player.repeatMode = Player.REPEAT_MODE_ALL
                }

                updateCustomLayout()

                /* notify UI */
                mediaSession?.notifyChildrenChanged(nodeROOT, 2, null)
            }

                /** When the controller tries to forget an item from the tracklist */
            if (customCommand == CUSTOM_COM_TRACKLIST_FORGET) {
                args.getString("id")?.let { mediaid ->
                    tracklist.firstOrNull { it.mediaId == mediaid }?.let { itemToForget ->
                        tracklist.remove(itemToForget)
                        forgetItem(itemToForget)

                        serviceMainScope.launch {
                            /** notifying UI-end that the playlist has been modified */
                            mediaSession?.notifyChildrenChanged(nodeTRACKLIST, tracklist.size, null)
                        }

                        return Futures.immediateFuture(SessionResult(RESULT_SUCCESS))
                    }
                }
            }

            return super.onCustomCommand(session, controller, customCommand, args)
        }

        override fun onSearch(session: MediaLibrarySession, browser: MediaSession.ControllerInfo, query: String, params: LibraryParams?): ListenableFuture<LibraryResult<Void>> {
            latestSearchResults.clear()

            for (item in tracklist) {
                if (item.mediaMetadata.title?.contains(query, true) == true) {
                    latestSearchResults.add(item)
                }
            }

            session.notifySearchResultChanged(browser, query, latestSearchResults.size, params)

            return Futures.immediateFuture(LibraryResult.ofVoid())
        }

        override fun onGetSearchResult(
            session: MediaLibrarySession,
            browser: MediaSession.ControllerInfo,
            query: String,
            page: Int,
            pageSize: Int,
            params: LibraryParams?
        ): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {

            //val fromIndex = max((page - 1) * pageSize, latestSearchResults.size - 1)
            //val toIndex = max(fromIndex + pageSize, latestSearchResults.size - 1)

            return Futures.immediateFuture(
                LibraryResult.ofItemList(
                    if (latestSearchResults.isNotEmpty()) listOf(latestSearchResults.first()) else emptyList(), //.subList(fromIndex, toIndex),
                    LibraryParams.Builder().build()
                )
            )
        }
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? {
        /* return if ("android.media.session.MediaController" == controllerInfo.packageName
            || packageValidator.isKnownCaller(controllerInfo.packageName, controllerInfo.uid)) {
            session
        } else null */
        return mediaSession
    }

    override fun onDestroy() {
        snapshotPlaylist(playlist)
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }

    /** Forgets the mediaitem forever (until a hard app data reset is done) */
    fun forgetItem(mediaItem: MediaItem) {
        /* The mediaid for the MediaItem is the most unique property (since it's the uri that represents it)
           there are no two items on the device that share the same Uri, so we remember this mediaId */
        val sp = PreferenceManager.getDefaultSharedPreferences(applicationContext)
        val forgottenSet =
            sp.getStringSet("forgotten_items", mutableSetOf())?.toMutableSet() ?: mutableSetOf()

        if (!forgottenSet.contains(mediaItem.mediaId)) {
            forgottenSet.add(mediaItem.mediaId)
        }

        sp.edit().putStringSet("forgotten_items", forgottenSet).apply()
    }


    fun updateCustomLayout() {
        val isFavorite = playlist.contains(player.currentMediaItem)

        val addButton =  CommandButton.Builder()
            .setSessionCommand(CUSTOM_COM_PLAYLIST_ADD)
            .setDisplayName("Add to playlist")
            .setIconResId(R.drawable.ic_star_off)
            .setEnabled(true)
            .build()

        val removeButton = CommandButton.Builder()
            .setSessionCommand(CUSTOM_COM_PLAYLIST_REMOVE)
            .setDisplayName("Remove from playlist")
            .setIconResId(R.drawable.ic_star_on)
            .setEnabled(true)
            .build()

        val shouldEnableRepeat = player.repeatMode == Player.REPEAT_MODE_ALL

        val repeatSwitch = CommandButton.Builder()
            .setSessionCommand(CUSTOM_COM_SWITCH_REPEAT_MODE)
            .setDisplayName("Switch repeat mode")
            .setIconResId(if (shouldEnableRepeat) R.drawable.ic_repeat_off else R.drawable.ic_repeat_on)
            .setEnabled(true)
            .build()

        mediaSession?.setCustomLayout(
            listOf(
                if (isFavorite) removeButton else addButton,
                repeatSwitch
            )
        )
    }

    /** Content styling constants */
    companion object {
        private const val phone_android_auto = "com.google.android.projection.gearhead"
        private const val car_android_auto = "com.android.auto"

        fun String.isAndroidAuto(): Boolean {
            return this == phone_android_auto || this == car_android_auto
        }

        private const val TAG = "MusicService"
    }
}

copybara-service bot pushed a commit that referenced this issue Sep 27, 2023
Issue: #561
Issue: #644
Issue: #645
PiperOrigin-RevId: 568948230
@marcbaechinger
Copy link
Contributor

marcbaechinger commented Sep 28, 2023

When I declare media support for Android Auto in the manifest of the session demo app, then I see the search icon and I can search the media library as expected.

To make this work it needs 3 pieces:

  • On connect the Auto controller needs to have COMMAND_CODE_LIBRARY_SEARCH available. This is the case by default.
  • Callback.onSearch(...) is implemented and returns RESULT_SUCCESS and calls session.notifySearchResultChanged to notify the browser about the number of matches.
  • Callback.onGetSearchresult(...) is implemented and returns the actual media items of the search result.

With this in place search worked for me on Android Auto (DHU) and Automotive OS (Emulator with API 29).

microkatz pushed a commit to hugohlln/media that referenced this issue Sep 29, 2023
@yuroyami
Copy link
Author

yuroyami commented Oct 8, 2023

It seems that i haven't yet gotten to a workaround or a solution, the source of the problem remains a mystery. I will fiddle around with my MediaLibraryService and update this thread.

@PawelTypiak
Copy link

I have the opposite problem - using rootExtras.putBoolean(android.media.browse.SEARCH_SUPPORTED, false) should hide the search button, but unfortunately it doesn't work on the 1.2.0 and 1.2.1 version. It works on UAMP Media3 demo app but it has 1.0.0-beta01 version of Media3. Is there any solution to hide the search button?

guoen21 added a commit to DiceTechnology/androidx-media that referenced this issue Feb 20, 2024
* Remove some debugging lines in `AtomParsers`

These were accidentally added in https://github.com/androidx/media/commit/885ddb167e3ac164e2ee6dfcf3886c703a45bc38

PiperOrigin-RevId: 565035337

* Compositor: Move input source javadoc to class-level.

As discussed offline, this is important for users of the class, and not all users may choose to read method javadoc, so best to make sure it's visible by leaving it at the class-level.

PiperOrigin-RevId: 565057677

* Change SubtitleParser interface to support incremental output

This change introduces two new types of method to `SubtitleParser`:
1. `parse()` methods that take a `Consumer<CuesWithTiming>` and return `void`
2. `parseToLegacySubtitle` method that returns `Subtitle`

(1) ensures that in the new 'parse before SampleQueue' world we can
write cues to the `SampleQueue` as soon as they're ready - this is
especially important when parsing monolithic text files, e.g. for a
whole movie.

(2) ensures that during the transition, the legacy 'parse after
SampleQueue' behaviour doesn't see any regressions in 'time to first
cue being shown'. Previously we had a single implementation to convert
from `List<CuesWithTiming>` to `Subtitle`, but this relies on the
complete list of cues being available, which can take a long time for
large files in some formats (with ExoPlayer's current parsing logic).
By allowing implementations to customise the way they create a
`Subtitle`, we can directly re-use the existing logic, so that the
'time to first cue being shown' should stay the same.

This change migrates all **usages** to the new methods, but doesn't
migrate any **implementations**. I will migrate the implementations in
follow-up CLs before deleting the old list-returning `parse()` methods.

PiperOrigin-RevId: 565057945

* Add overflow tests for sample count to duration conversion methods

These methods were updated to use the new overflow-resistant
`scaleLargeValue` method in https://github.com/androidx/media/commit/885ddb167e3ac164e2ee6dfcf3886c703a45bc38.

PiperOrigin-RevId: 565059609

* Add javadoc to `SubtitleParser.OutputOptions`

PiperOrigin-RevId: 565066243

* Rollback of https://github.com/androidx/media/commit/e2882c051b35c8b3f71fb63992680c84ae048668

PiperOrigin-RevId: 565069442

* Add test for MultiInputVideoGraph

This test composites the first frame from two video inputs.

PiperOrigin-RevId: 565090338

* Move listener methods into private method for readability

The logic that handles components' boundaries are grouped together in private
methods, like handling VideoCompositor's output textures.

PiperOrigin-RevId: 565131579

* Move bitmap loader tests in test

These tests were under androidTest because we needed a functional
BitmapFactory. Robolectric now supports decoding bitmaps so moving them
under tests.

PiperOrigin-RevId: 565181239

* Compositor: Document VideoCompositor interface thread safety.

PiperOrigin-RevId: 565388982

* Return TIME_UNSET for last sync sample if there is no video track

PiperOrigin-RevId: 565395263

* Upgrade Guava dependency to 32.1.2

Android's version of Guava was upgraded in http://r.android.com/2731599

PiperOrigin-RevId: 565396612

* Read muxedPartialVideo only in MUXER_MODE_MUX_PARTIAL_VIDEO mode

When we switch from MUXER_MODE_MUX_PARTIAL_VIDEO to MUXER_MODE_APPEND_VIDEO
`muxedPartialVideo` will already be `true` so `endTrack` method will pass
through this `if(muxedPartialVideo)` check which is incorrect.

PiperOrigin-RevId: 565398117

* Compositor: Move Settings to DefaultVideoCompositor.

This previously was in the VideoCompositor class, but wasn't
referenced at all from that interface.

PiperOrigin-RevId: 565409646

* Support multiple streams in the ImageRenderer

PiperOrigin-RevId: 565410924

* Compositor: Split out OpenGL drawing logic into inner class.

Organize logic a bit by splitting logic about how we draw using OpenGL onto a
texture, out from the larger class, which has lots of logic discussing how we select
frames and streams.

No functional change intended, but a few method calls are shuffled around to
simplify things.

PiperOrigin-RevId: 565426225

* Fix flaky test becasue ExtTexMgr is not using emulator settings

When running on emulators, ExternalTextureManager needs a longer timeout for
forcing EOS, but we didn't catch a device name running on blaze: `generic_x86`

PiperOrigin-RevId: 565513152

* Open progress conditionVariable when quitting internal thread.

If getProgress is blocking whilst the internal thread calls endInternal
(for error or success), the condition is never opened. Related to this,
onCompleted and onError are therefore never surfaced to the app.

progressState is accessed from application and internal threads, so
should be marked volatile to prevent a thread caching the value.

PiperOrigin-RevId: 565720184

* Change UI module documentation link

PiperOrigin-RevId: 566257915

* Rollback of https://github.com/androidx/media/commit/d58f5fdf7d9421850d4059dda4d3777f5fe03b85

PiperOrigin-RevId: 566258299

* Add QuickTime (Classic) support to `Mp4Extractor`

PiperOrigin-RevId: 566272132

* Compositor: Remove obsolete TODO

PiperOrigin-RevId: 566327798

* Fix ErrorProne lint actionable items

PiperOrigin-RevId: 566571653

* Decouple output size listener and setting output surface

This is because `onOutputSizeChanged()` should in theory be called on the
listener executor.

PiperOrigin-RevId: 566591784

* Update `WebvttParser` to implement new partial-output method

This requires adapting the 'to `CuesWithTiming` list' logic to work with
the new partial-output API, and that needs a private method so it's no
longer a good fit for a default method on `Subtitle` - hence moving it
to a new utility class.

Also update the implementation to never return `UNSET` duration (this is
an equivalent change to the `SsaParser` change in https://github.com/androidx/media/commit/9631923440b8be2239225a2099842410548c7ebd).

PiperOrigin-RevId: 566598094

* Switch `WebvttParserTest` to use partial-output method

PiperOrigin-RevId: 566598735

* Move setAudioAttributes from ExoPlayer to Player

PiperOrigin-RevId: 566607528

* Add two helpful methods to FakeMediaSource for testing

PiperOrigin-RevId: 566636545

* Update `external/guava/METADATA` link from `master` to `main` branch

PiperOrigin-RevId: 566645434

* Remove using Consumer of ExportException in VideoGraphs

PiperOrigin-RevId: 566651267

* Don't call `MediaSession.Callback.onPostConnect` if connecting failed

I spotted that this looked wrong while investigating test various
session test failures. However, changing this logic doesn't seem to
affect the tests so I don't know if the change is an improvement or not.

PiperOrigin-RevId: 566655318

* Re-land: `TtmlParser` implementation - moved from `TtmlDecoder`

This change uses the new incremental overloads of `SubtitleParser` to
avoid introducing the performance regression caused by the original
change requiring all cues to be fully parsed before the first could be
shown on screen.

`TtmlDecoder` which used to be `SimpleSubtitleDecoder` will now be
called `TtmlParser` and implement `SubtitleParser` interface. For
backwards compatibility, we will have the same functionality provided
by `DelegatingSubtitleDecoder` backed-up by a new `TtmlParser`
instance.

PiperOrigin-RevId: 566671398

* Deprecate `Util.getAudioContentTypeForStreamType`

This method isn't used by the library (since <unknown commit>).

It doesn't really work well (e.g. arbitrarily defaults to `MUSIC` when
`UNKNOWN` would be a better default). There's no suggested replacement.

PiperOrigin-RevId: 566676744

* Use a longer timeout for running Transformer on emulators

PiperOrigin-RevId: 566688502

* Create `AudioOffloadPreferences` class

Move audio offload mode related interfaces and definitions from `TrackSelectionParameters` to a new `AudioOffloadModePreferences` class.

PiperOrigin-RevId: 566905017

* TTML: Remove unused `CellResolution.columns` and collapse to `int rows`

PiperOrigin-RevId: 566908824

* Add MimeTypes.EXTERNALLY_LOADED_IMAGE

PiperOrigin-RevId: 566915171

* Fix not setting videoEffect in CompositingVSP

The current code only set the videoEffects when CVSP is initialized, which
happens after `player.prepare()`. But it's valid that videoEffects are set
before calling `prepare()`.

PiperOrigin-RevId: 566941216

* Extract the VideoSinkProvider interface

PiperOrigin-RevId: 566972998

* Add performance playback test for video effects

PiperOrigin-RevId: 567000714

* Move GlProgram.loadAsset to Util and make it public.

PiperOrigin-RevId: 567025091

* Add new setIntsUniform for setting ivec uniforms in shaders.

PiperOrigin-RevId: 567102956

* Move DefaultImageDecoderTest in tests

Move DefaultImageDecoderTest in tests since we can decode images with
Robolectric's native graphics mode.

PiperOrigin-RevId: 567259458

* HDR: Update HdrMode fallback to use OpenGL tone-mapping.

OpenGL tone-mapping is more reliable and widely supported than
MediaCodec tone-mapping.

PiperOrigin-RevId: 567267389

* Bump IMA SDK version to 3.31.0

#minor-change

PiperOrigin-RevId: 567282094

* Session tests: Add missing `CountdownLatch.await()`

This helps deflake this test.

PiperOrigin-RevId: 567288892

* Compositor: Clarify javadoc, to mention VideoCompositorSettings.

Clarify that we use OverlaySettings to place frames over one another,
as passed in via VideoCompositorSettings

PiperOrigin-RevId: 567321828

* Adjust externally loaded image URI MIME type definition

Adjust the Javadoc to highlight that data of this MIME type just contains a URI, not the actual image content.

And also remove the superfluous "key" part of the MIME type string that doesn't
really add information (and it's also really just an URI, not an URI key).

PiperOrigin-RevId: 567560238

* Split VideoGraph interface and move VideoGraph to common

PiperOrigin-RevId: 567599249

* Cross-reference per-stream volume methods from device volume methods

The per-stream methods are generally preferred and having a reference
to them from the device-wide methods may help with discoverability.

Issue: google/ExoPlayer#11295
PiperOrigin-RevId: 567604785

* Use proxy controller to maintain platform session and notification

With this change, the notification controller that is connected by
`MediaNotificationManager`, is used as a proxy controller of the
System UI controller. An app can use the proxy at connection time
and during the lifetime of the session for configuration of the
platform session and the media notification on all API levels.

This includes using custom layout and available player and session
commands of the proxy to maintain the platform session (actions,
custom actions, session extras) and the `MediaNotification.Provider`.

The legacy System UI controller is hidden from the public API,
instead the app interacts with the Media3 proxy:

- System UI is hidden from `MediaSession.getConnectedControllers()`.
- Calls from System UI to methods of `MediaSession.Callback`/
  `MediaLibrarySession.Callback` are mapped to the `ControllerInfo`
  of the proxy controller.
- When `getControllerForCurrentRequest()` is called during an operation of
  System UI the proxy `ControllerInfo` is returned.

PiperOrigin-RevId: 567606117

* Add missing VideoGraph javadoc param

PiperOrigin-RevId: 567621861

* Compositor: Add VideoCompositorSettings to Composition.

This allows apps using Transformer to customize how a Composition is used.

PiperOrigin-RevId: 567633129

* Test: Use timestamp iterator in texture output test.

Simplify tests, before we add some similar tests.

PiperOrigin-RevId: 567666340

* Test: Rename getBitmap to getBitmapAtPresentationTimeUs

PiperOrigin-RevId: 567683139

* Remove static initializer block from `MediaSessionKeyEventTest`

PiperOrigin-RevId: 568149422

* Allow custom methods in Rtsp Options response public header

ExoPlayer will not fail playback if an RTSP server responds to the Options request with an unknown RTSP method request type. ExoPlayer will parse the response and just not call methods it does not know how to use.

Issue: androidx/media#613
PiperOrigin-RevId: 568152076

* Fix 'unused return value' error in `SubtitleViewUtilsTest`

PiperOrigin-RevId: 568170342

* Pass down ControllerInfo from service to session when connecting

With this change, the `ControllerInfo` passed to
`MediaSessionService.onGetSession(ControllerInfo)`
is the same instance that is passed later to all
callback methods of `MediaSession.Callback`.

PiperOrigin-RevId: 568216855

* Resolve and dispatch media button events within Media3

Before this change, media button events are routed from `onStartCommand`
of the `MediaSessionService` to  the `MediaSessionCompat`, resolved by
the legacy library to a session command called on
`MediaSessionCompat.Callback` from where the command is delegated back
to the Media3 session.

With this change the keycode is resolved directly to a Media3 command
that is sent to the session through the media notification controller
of the session.

After this change, a playback or custom command sent to the session
from a notification, either as a pending intent (before API 33) or as
a legacy session command, look the same and the caller is the
media notification controller on all API levels.

PiperOrigin-RevId: 568224123

* Update VideoSink `queueBitmap()` to match VideoFrameProcessor

PiperOrigin-RevId: 568226567

* Update decode-only flag logic in non-MediaCodec-renderers

MediaCodecRenderer has already been updated to not rely on the
input stream to mark its samples as decode-only and instead use
a simple time-based comparison to achieve the same effect.

This change makes the same update for all other renderers that
either use the flag directly or forward to a "decoder" instance.

PiperOrigin-RevId: 568232212

* Set SurfaceView lifecycle to follow attachment in PlayerView

This avoids destroying the surface if PlayerView is hidden in the
view hierarchy.

PiperOrigin-RevId: 568501459

* Verify a thread is alive before sending a message to it.

PiperOrigin-RevId: 568515736

* Add new APIs to ExoMediaDrm

Changes
---
- Added `removeOfflineLicense(byte[])` and `getOfflineLicenseKeySetIds` and consumed them in their implementations

Background
---
- These APIs will help in addressing an increasing amount of `java.lang.IllegalArgumentException: Failed to restore keys: BAD_VALUE` which is our top playback error in our app
	- Based on our discussion with Widevine team and [this exoplayer issue](https://github.com/google/ExoPlayer/issues/11202#issuecomment-1708792594)
		- TL;DR: The failure occurs on startup if the user has 200+ offline licenses, we would like to add the functionality to remove offline licenses

**Note: Why we want these APIs in ExoMediaDrm and not in OfflineLicenseHelper**
	- As per the issue above, we would like to access these 2 public APIs in MediaDrm that don’t exist in `OfflineLicenseHelper` or `ExoMediaDrm`
		- APIs interested in:
			- [MediaDrm#removeOfflineLicense()](https://developer.android.com/reference/android/media/MediaDrm#removeOfflineLicense(byte%5B%5D)): To remove offline license
			- [MediaDrm#getOfflineLicenseKeySetIds()](https://developer.android.com/reference/android/media/MediaDrm#getOfflineLicenseKeySetIds()): To see number of offline licenses on startup

		- We use `OfflineLicenseHelper` to download license for L1 and we don't interact with `ExoMediaDrm` directly. But for the alternate Widevine integration, we directly depend on `ExoMediaDrm` APIs to override and call CDM Native APIs.
		- We would like to have the functionality of removing offline licenses for both integration which would need access to above APIs in `ExoMediaDrm`.

Links
---
- https://github.com/androidx/media/issues/659

* Reformat some javadoc and use Guava empty list

* Change the DASH playback dump for webvtt in mp4 to include subtitles

#minor-release

PiperOrigin-RevId: 568567703

* Limit SequenceAssetLoader variable scope.

All usages of these variables are within the SampleConsumerWrapper,
so limit them to that scope.

PiperOrigin-RevId: 568582645

* Update dumpfile tests to only print ColorInfo fields that are set

PiperOrigin-RevId: 568789489

* Throw Exception if posting to application handler fails.

PiperOrigin-RevId: 568799683

* Mark HEIF decoding as only supported on API 26+

https://developer.android.com/guide/topics/media/platform/supported-formats#image-formats

#minor-release

PiperOrigin-RevId: 568864219

* Add position interpolation to MediaControllerImplLegacy

Without this, the position won't udpate until the session sends
a new playback state.

PiperOrigin-RevId: 568889286

* Test: Update HDR GL tone-map fallback string.

PiperOrigin-RevId: 568920716

* Add default implementation of Callback.onSubscribe

The library already maintains the subscribed controllers internally. This
change adds `MediaLibrarySession.getSubscribedControllers(mediaId)` to
access subscribed controllers for a given media ID.

To accept a subscription, `MediaLibraryService.Callback.onSubscribe` is
required to return `RESULT_SUCCESS`. So far, this isn't the case for the
default implementation of the library.

This change implements `Callback.onSubscribe` to conditionally
provide `RESULT_SUCCESS`. The default calls `Callback.onGetItem(mediaId)` to
assess the availability of the media item. If the app retruns `RESULT_SUCCESS`
with a browsable item, the subscription is accepted. If receiving a valid item
fails, the subscription is rejected.

Issue: androidx/media#561
PiperOrigin-RevId: 568925079

* Add isAutomotiveController and isAutoCompanionController

Issue: androidx/media#561
Issue: androidx/media#644
Issue: androidx/media#645
PiperOrigin-RevId: 568948230

* Split demo service into its own module for reuse

To support Automotive with the session demo, we need a separate app module.
To do this we need to split the service into its own module and make it
usable from different modules.

PiperOrigin-RevId: 568975271

* Update release notes for 1.2.0-alpha02

PiperOrigin-RevId: 569161165

* Suppress lint in `PlayerControlView` and `TrackSelectionDialogBuilder`

PiperOrigin-RevId: 569165562

* Fix review comments

* Move Single/MultiVideoGraph impl to effect

PiperOrigin-RevId: 569188658

* Mark `DefaultImageDecoder.BitmapDecoder` as `@VisibleForTesting`

It seems likely we will define a new "image decoder" interface that
returns `ListenableFuture<Bitmap>`, and naming that will be
hard/annoying if we need to keep this interface working too.

It's also not really clear what a non-test implementation of this
interface would be expected to do, since `DefaultImageDecoder` is
documented to always decode using `BitmapFactory`.

#minor-release

PiperOrigin-RevId: 569206325

* Bump Media3 version numbers for 1.2.0-alpha02

PiperOrigin-RevId: 569269992

* Add demos/session-automotive module

This change also enables Android Auto support for the
session demo.

PiperOrigin-RevId: 569448376

* ExportTest: make 8K asset and trim

Move remote 8K file to local and trim to 320ms.

Trim done with ffmpeg:

`ffmpeg -i {remote_file} -t 0.3 -c:v copy -c:a copy 8k24fps_300ms.mp4`

PiperOrigin-RevId: 569449962

* Add previewing specific video graph.

PiperOrigin-RevId: 569473178

* Rename `DefaultImageDecoder` to `BitmapFactoryImageDecoder`

This reflects the documented behaviour of this class.

#minor-release

PiperOrigin-RevId: 569475137

* Remove FfmpegVideoRenderer from Media3 1.2.0 release

* Update playlist UI when playlist is updated

When changing the playlist on Android Auto the UI of the
activity needs to be kept in sync.

PiperOrigin-RevId: 569528785
(cherry picked from commit 52d9fbff73cddd5c0881f6fd53a6ba35220d0ed1)

* Mark test_session_current support app as MultiDexApplication

PiperOrigin-RevId: 570015354
(cherry picked from commit e9bf41ca044368f6833984a38b0f7804ce9b1960)

* MediaCodeVideoRenderer: flush video sink before codec

When seeking, we must first flush the video sink so it stops
using any SurfaceTextures before flushing MediaCodec.

#minor-release

PiperOrigin-RevId: 570015998
(cherry picked from commit 144bd7223626a2936368cbcb3bf3f7004ebe5e45)

* Merge pull request #574 from hugohlln:main

PiperOrigin-RevId: 570037211
(cherry picked from commit b06d82323865870e5c3572e867d3cc165200e497)

* Fix `Util.scaleLargeValue/Timestamp` to handle negative numbers

#minor-release

PiperOrigin-RevId: 570337535
(cherry picked from commit 9edbfa974aeab851065655e09e8c1accf51a009c)

* Deprecate experimental keepAudioTrackOnSeek methods.

#minor-release

PiperOrigin-RevId: 570340714
(cherry picked from commit 1bb501ab5046cfb7c9e21302cd3a0d73c176512c)

* Explicitly mark DecoderOutputBuffer as shouldBeSkipped if needed

In some cases, SimpleDecoder output needs to be skipped for rendering
because the decoder produces no data. This is one of the remaining
usages of BUFFER_FLAG_DECODE_ONLY at the moment and can be more
directly solved without using the flag. SimpleDecoder still needs to
check the flag though for backwards compatbility with custom decoders
while the flag is not completely removed.

PiperOrigin-RevId: 570345233
(cherry picked from commit c8aac24ffd8bfe708d68a251a9f28b3b48bed50c)

* Add Dumper support for outputting multiline strings

PiperOrigin-RevId: 570348425
(cherry picked from commit b83f12c4ba5f7adac388f003596214b03d1d9358)

* Add DashPlayback test with sideloaded TTML subtitles

The test is hidden behind the Ignore annotation due to some flakiness just like `webvttInMp4`. However, it will be removed when the subtitle parsing is moved to a pre-sample-queue architecture.

#minor-release

PiperOrigin-RevId: 570376275
(cherry picked from commit bd5a3920b85423f1c1b625369fd606a9f36e1248)

* Disable offload scheduling at set up for track transition

While sleeping for offload, position is estimated based on time playing. If asleep and AudioTrack is reused, then the position will keep incrementing as the subsequent item plays. That is until wakeup when playing position is updated to the timestamp of the second item. Offload scheduling should be disabled until track transitions fully.

PiperOrigin-RevId: 570397140
(cherry picked from commit da06bf057a230293c8fb7f06cbfba71df8c4b5b1)

* Move decode-only handling out of MetadataDecoder interface logic

The interface requires the implementation to return null if the
decode-only flag is set. So instead of setting the flag and returning
null, we can simply not call the method and assume it's null.

The only reason where this wouldn't work is if the metadata format
has keyframe-like logic and requires previous metadata to decode
the next one. This is not something we came across before and it seems
ignorable. If that feature is needed in the future, we should instead
add a method to MetadataDecoder to set the first output timestamp.

#minor-release

PiperOrigin-RevId: 570399838
(cherry picked from commit 796781d4c365e31a196c96e0588e4ff5ff0e3bf0)

* Add nullness annotations to `MediaCodecRenderer`

#fixit

PiperOrigin-RevId: 570403923
(cherry picked from commit 7a91474af9f84595743655b18f4164e193bf2fc1)

* Add nullness annotations to `DecoderVideoRenderer`

Also fixed a bug where format queue was polled with wrong timestamp value.

#fixit

PiperOrigin-RevId: 570420304
(cherry picked from commit a879bae1ee2c684e8100f50bcff39655d46a2e8d)

* Add onAudioTrackInitialized/Released events

This is useful for analytics and understanding player behavior
during transitions.

#minor-release

PiperOrigin-RevId: 570623227
(cherry picked from commit 8e2bf21011c63e2ca2fc58c4353cd66930b621e3)

* Update getName of BitmapFactoryImageDecoder

cleanup from https://github.com/androidx/media/commit/8f5835c51c0d0e4221a1de68b6638c910b911264

#minor-release

PiperOrigin-RevId: 570663437
(cherry picked from commit 572fb4676cd8ce5b40c8ab044aeb2250390f1c17)

* Replace ENCODING_DTS_UHD_P2 value by reference to platform constant

#minor-release

PiperOrigin-RevId: 570696505
(cherry picked from commit 9ef1c20e7af4cc483e3ab408218545b182c1a28e)

* Remove wrong Javadoc

The corresponding logic has been removed in https://github.com/androidx/media/commit/796781d4c365e31a196c96e0588e4ff5ff0e3bf0

#minor-release

PiperOrigin-RevId: 570729509
(cherry picked from commit 64fe863f315bbc870b347c4d9ea8009fb7713773)

* Add Decoder.setOutputStartTimeUs and use it in extension decoders

This gets rid of the reliance on the decode only flag that is still
set on input buffers to the decoder if they are less than the start
time.

We still need to set and check the decode-only flag in SimpleDecoder
to ensure compatbility with custom decoders that use the flag while
it's not fully removed.

PiperOrigin-RevId: 570736692
(cherry picked from commit a03e20fe6c423389d54eb08d3b1f1d19499a0d9a)

* Add `CuesWithTiming.endTimeUs`

In most cases this is more useful than `durationUs`.

We will keep `durationUs`, and the constructor will continue to take
`startTimeUs` and `durationUs`, to allow for use-cases where we don't
know the start time but still want to indicate a duration (this will be
used to implement CEA-608 timeout).

#minor-release

PiperOrigin-RevId: 570944449
(cherry picked from commit bf7b91e57e477f71644d5e585e0e999deadf7fa3)

* Use RTSP Setup response timeout value in KeepAliveMonitor intervalMs

Set KeepAliveMonitor to send a keep-alive message at half the timeout value, if provided, by the RTSP Setup response.

Issue: androidx/media#662
PiperOrigin-RevId: 570946237
(cherry picked from commit 42c1846984fc8ebca5cdbdcf6df8d2dca44eea96)

* Remove experimental keepAudioTrackOnSeek.

PiperOrigin-RevId: 570966027
(cherry picked from commit 068d420ba2d27847c5c581d851ff6a5e1ec45611)

* Update documentation wrongly referencing the decode-only flag

#minor-release

PiperOrigin-RevId: 570973457
(cherry picked from commit 87f1b4252ec2d342a153e49aac19455cb91655d1)

* Change equalTo check in ImagePlaybackTest to atLeast

The aim of this test is to make sure the image is onscreen for the right amount of time, so to drive down flakes from the decoder taking too long, change this to an atLeast check

#minor-release

PiperOrigin-RevId: 570988044
(cherry picked from commit 9cc75ca52e49792bed43e4d8fbf67b9a0576fdc0)

* Add tests for `CuesWithTiming.endTimeUs`

#minor-release

PiperOrigin-RevId: 570988195
(cherry picked from commit 6057b59723ce6e45ec8a68007eff6687956a9a73)

* Remove release notes lines added by merge conflict

#minor-release

PiperOrigin-RevId: 571005643
(cherry picked from commit 49b1e0bbc2a82f3b9c6ba0a1696ee0b1e53673ec)

* Deprecate decode-only flag.

The flag is no longer used by our components and only set and checked
in a few places to guarantee compatiblity with existing renderers and
decoders that still use it.

The flag will be removed in the future due to its design limitations.

#minor-release

PiperOrigin-RevId: 571291168
(cherry picked from commit 89d01981bc4cf9218e73bcce1b52c7afe29fbecd)

* Add MEDIA_PLAY_FROM_SEARCH to manifest of session demo app

This is required to make GMS send voice commands to the app.

#minor-release

PiperOrigin-RevId: 571326122
(cherry picked from commit 78f403aa7b795e0e3bcd2f4682bef171f545d4fa)

* Allow pause if in offload mode after writing all buffers

In offload mode, `AudioTrack#stop()` will put the track in `PLAYSTATE_STOPPING` rather than `PLAYSTATE_STOPPED`. The difference in state means that `AudioTrack` can be paused and played during this 'stopping' period.

Currently, if `AudioTrackPositionTracker#handleEndOfStream()` has been called then `DefaultAudioSink` in `pause()` won't call `AudioTrack#pause()`. `AudioTrack#pause()` should be called in this case if in offload mode.

#minor-release

PiperOrigin-RevId: 571335108
(cherry picked from commit ab42d64d6d5f1859f1c45aebfe26060978b864bd)

* Update cached playbackHeadPosition when pausing after AudioTrack.stop()

In some streaming scenarios, like offload, the sink may finish writing buffers a bit before playback reaches the end of the track. In this case a player may pause while in this 'stopping' state.

The AudioTrackPositionTracker needs to update the cached values it uses to calculate position in the `PLAYSTATE_STOPPED`/`PLAYSTATE_STOPPING` states if pause/play are called during this period.

PiperOrigin-RevId: 571345914
(cherry picked from commit a789db5b41d9d7a671e83a488b3dec372eaa8b3d)

* Add multidex Gradle dependency to test-session-current

#minor-release

PiperOrigin-RevId: 571347856
(cherry picked from commit e63be0317f2152398174bb01c3ea134a0883da10)

* Deflake RTSP keep-alive monitor test

Alters RTSP KeepAlive monitor test to just make sure that keep-alive message is sent.

The test was added in https://github.com/androidx/media/commit/42c1846984fc8ebca5cdbdcf6df8d2dca44eea96

#minor-release

PiperOrigin-RevId: 571349013
(cherry picked from commit 417970f7132ab9fd539ba692309e29050b7001d5)

* Bump Media3 version numbers for 1.2.0-beta01 release

#minor-release

PiperOrigin-RevId: 572003628
(cherry picked from commit 97645a200d6abcdbacd1805ef149b5f2b02943c0)

* Update RELEASENOTES.md for 1.2.0-beta01 release

PiperOrigin-RevId: 571941830
(cherry picked from commit 62ad1dfdf5699b72f5f218c9cabcf73fb1a9f070)

* Rollback of https://github.com/androidx/media/commit/64bd3bcad3fa4b0e433b16d583456920afad3ce2

PiperOrigin-RevId: 574766164
(cherry picked from commit f0cab4d03ecfa7fbd48262c332d85329736224af)

* Do not hide System UI when app rejects connection

If an app rejects the connection of the internal media notification manager
the session should behave like without the the media notification controller.
The legacy System UI controller should not be hidden or even rejected to
connect in such a case.

#minor-release

PiperOrigin-RevId: 574807901
(cherry picked from commit 54d5810fc353a9e7133ef929ab2f822d921070b1)

* Merge pull request #728 from lawadr:audio-capabilities-fix

PiperOrigin-RevId: 574829263
(cherry picked from commit 5f80a4708165ffe977ce37400f7c8eae01142e2d)

* Add missing command checks to playback resumption flow

Player methods shouldn't be called if they are not available and the
entry point to the playback resumption flow only checks
COMMAND_PLAY_PAUSE.

#minor-release

PiperOrigin-RevId: 574834148
(cherry picked from commit bfd1a2724c660de0df3c13f8394238ac6aa26e68)

* Remove CompositionPlayer activity from the transformer demo app

The CompositionPlayer is not ready yet.

Issue: androidx/media#741
PiperOrigin-RevId: 574859927
(cherry picked from commit beb1711d4cfad51b88016bbb2b1e0f1a7945ed84)

* Update translations in the ui module

#minor-release

PiperOrigin-RevId: 575161190
(cherry picked from commit a8ab9e2c70c98ee65e3b3d71806da6c9fc5c42e3)

* Use MediaSessionImpl.onMediaButtonEvent() to dispatch key events

This change moves the handling of any media button event into
`MediaSessionImpl.onMediaButtonEvent(intent)`. This includes
the double click handling from `MediaSessionLegacyStub`.

The advantage is that everything is in one place which allows
to offer `MediaSession.Callback.onMediaButtonEvent` with which
an app can override the default implementation and handle media
buttons in a custom way.

Media button events can originate from various places:

- Delivered to `MediaSessionService.onStartCommand(Intent)`
  - A `PendingIntent` from the notification below API 33
  - An `Intent` sent to the `MediaButtonReceiver` by the system dispatched
    to the service
- Delivered to `MediaSessionCompat.Callback.onMediaButtonEvent(Intent)`
  implemented by `MediaSessionLegacyStub` during the session is active
  - Bluetooth (headset/remote control)
  - Apps/system using `AudioManager.dispatchKeyEvent(KeyEvent)`
  - Apps/system using `MediaControllerCompat.dispatchKeyEvent(keyEvent)`

Issue: androidx/media#12
Issue: androidx/media#159
Issue: androidx/media#216
Issue: androidx/media#249

#minor-release

PiperOrigin-RevId: 575231251
(cherry picked from commit a79d44edc5c7fdc81120dbc9b2c89b9799b14031)

* Update `TextRenderer` to handle `CuesWithTiming` instances directly

The existing `Subtitle` handling code is left intact to support the
legacy post-`SampleQueue` decoding path for now.

This also includes full support for merging overlapping `CuesWithTiming`
instances, which explains the test dump file changes, and which should
resolve the following issues (if used with the
decoder-before-`SampleQueue` subtitle logic added in
https://github.com/androidx/media/commit/5d453fcf37b52a9ea4182f266d60f3bf8e3318c2):

* Issue: google/ExoPlayer#10295
* Issue: google/ExoPlayer#4794

It should also help resolve Issue: androidx/media#288, but that will also require
some changes in the DASH module to enable pre-`SampleQueue` subtitle
parsing (which should happen soon).

PiperOrigin-RevId: 571021417
(cherry picked from commit 002ee0555dc35dce9570f1a991b33ec92743db10)

* Return true from `CuesResolver.addCues` if the output changed

This belongs in the resolver, because it depends on the resolution
algorithm (and therefore the logic can't live in `TextRenderer`).

This also fixes a bug in `TextRenderer` where we were doing arithmetic
with `cues.durationUs` without checking if it was `TIME_UNSET` first.

#minor-release

PiperOrigin-RevId: 571332750
(cherry picked from commit 272428734b79ac6857a4333ede2b12563f8b78de)

* Report dropped frames from the VideoSink

After https://github.com/androidx/media/commit/4fad529433011d280f1e5ebd4465808ef60c2d77, MediaCodecVideoRenderer does not report if frames
are dropped from the VideoSink. This commit fixes this.

#minor-release

PiperOrigin-RevId: 571905721
(cherry picked from commit 05b17b543060c1f32ae7af212e5e8b33203bdadd)

* Fix the asset and dump file names for the standalone TTML DASH test

#minor-release

PiperOrigin-RevId: 571941997
(cherry picked from commit 33c151eb5b5ce85e554215af0c1d860b66c66fab)

* Use more targeted listening in session PlayerActivity

The current metadata updates are triggered by item transitions,
but depending on the speed of loading the playlist, the first
metadata may only be known later via metadata-change callbacks.

Slow playlist loading also means the UI stays empty and it's
beneficial to show a placeholder to avoid the impressions the
UI hangs.

Finally, clean-up by removing unused string constants and merging
all listeners into onEvents

#minor-release

PiperOrigin-RevId: 571951529
(cherry picked from commit fd81c904e11c47dcd7694e9b2f610914d4cf2596)

* ...Update metalava library and Reformat api.txt...

PiperOrigin-RevId: 572013340
(cherry picked from commit da49a02b44365d6f85d5191ccb9c6df05d01fd3c)

* Fix the resumption of playback when suitable device is connected.

With this change the playback will resume as soon as the suitable device is connected and suppression reason is cleared (within set time out).

#minor-release

PiperOrigin-RevId: 572140309
(cherry picked from commit dc859eae82767598c43bbb182e81228be55f030b)

* Add missing Future cancellation checks

Future.isDone and getDone doesn't imply the Future was successful
and it may have been cancelled or failed.

In case where we handle failure, we should also handle cancellation
to avoid CancellationException to bubble up unchecked.

In demo app code where we use isDone for field initialization, we
want to crash in the failure case (usually security exception where
the connection is disallowed), but we want to gracefully handle
cancellation. Cancellation of these variables usually happens in
Activity.onDestroy/onStop, but methods may be called after this point.

#minor-release

PiperOrigin-RevId: 572178018
(cherry picked from commit fe7c62afe0b39f8d6617cf610dbdccc9e6adcfb4)

* Make BundleListRetriever local Binder aware

When used within the same process, we don't have to go via the
onTransact method (which includes marshalling and unmarhsalling
the data), but can directly return the list.

#minor-release

PiperOrigin-RevId: 572179846
(cherry picked from commit 0bddd06938fb5dc97a99a0cb3a444815a47be41c)

* Use package-level `@OptIn` for demo apps

This demonstrates that `@OptIn` can now be used at the package-level
(since [`androidx.annotation:annotation-experimental:1.3.0`](https://developer.android.com/jetpack/androidx/releases/annotation#annotation-experimental-1.3.0)).

PiperOrigin-RevId: 572187729
(cherry picked from commit d60596cfca2926f851881be871117c7772e7096c)

* Align audio adaptive support checks with video

In particular:
 - Add allowAudioNonSeamlessAdaptiveness parameter (default true, same
   as video and as already implemented by default)
 - Forward mixedMimeTypeAdaptation support to AudioTrackScore
   (as for VideoTrackScore) and adapt mixed MIME type adaptive
   support accordingly
 - Check adaptive support when deciding whether a track is allowed for
   adaptation (also same check as for video). This takes the new
   parameter into account.

PiperOrigin-RevId: 572191308
(cherry picked from commit f20d18e6cae136f8109380d69be76178008cdc17)

* Remove unneccessary method parameter

The value already exists as a class field.

#minor-release

PiperOrigin-RevId: 572200771
(cherry picked from commit e5fa0c2ce98930e4b679576290b5c0bebd37ad21)

* Add `SubtitleParser.Factory.getCueReplacementBehavior()`

This gives access to the replacement behavior for a particular subtitle
format without needing to instantiate a `SubtitleParser`.

#minor-release

PiperOrigin-RevId: 572226084
(cherry picked from commit e366c3d419f487beb567e360c21400c31add477f)

* Update `@UnstableApi` docs to include a `package-info.java` example

#minor-release

PiperOrigin-RevId: 572229092
(cherry picked from commit 7009c53c799171c4f8e418af5fdb31a6a5544ab9)

* Do not interrupt controller thread without a good reason

Interrupting the main thread in particular may be dangerous
as the flag is not cleared after handling the current message.

#minor-release

PiperOrigin-RevId: 572259422
(cherry picked from commit 846117399ff87dc025c355639444de2e54430b18)

* Add experimental opt-in to parse DASH subtitles during extraction

This currently only applies to subtitles muxed into mp4 segments, and
not standalone text files linked directly from the manifest.

Issue: androidx/media#288

#minor-release

PiperOrigin-RevId: 572263764
(cherry picked from commit 66fa5919590789b384506a4e604fe02a5a5e0877)

* Add MediaSession.Builder().setPeriodicPositionUpdateEnabled()

This allows to disable periodic position updates when building
the session.

#minor-release

PiperOrigin-RevId: 572531837
(cherry picked from commit 4dc3db4da3da486b9c9ec1780aa595da8de5330c)

* Request notification permission when starting session demo app

#minor-release

PiperOrigin-RevId: 572556101
(cherry picked from commit c7a091a97373d3009074dba7ec0eeaaae79b1a12)

* Update release notes to mention AudioOffloadPreference class changes

Issue: androidx/media#721
PiperOrigin-RevId: 572565009
(cherry picked from commit cef85be40f11f129f38bb19438721236c164c9bf)

* Check whether a session is still managed before removing

When the controller of the `MediaNotificationManager` is disconnected,
the session is removed from the service without checking whether the
session hasn't already been removed. This caused flakiness in `MediaSessionServiceTest.addSession()`.

Because there is a public API `MediaSessionService.removeSession()`,
the controller can't make an assumption whether the session is still
contained in the service when being disconnected.

#minor-release

PiperOrigin-RevId: 572568350
(cherry picked from commit 7fdc5b22bac1af6fd074df38bb6b98c921e713a1)

* Split available command filtering and bundling

A few methods in PlayerInfo and related classes combine filtering
information with bundling in one method. This makes it impossible
to use just the filtering for example and it's also easier to reason
about than two dedicated methods. This change splits these methods
into two parts accordingly.

PiperOrigin-RevId: 572592458
(cherry picked from commit 4ebe630a80296cbb4437336a50abccb39da978f7)

* Avoid bundling PlayerInfo for in-process calls

PlayerInfo bundling is costly and we can add a shortcut for
in-process binder calls where we store the direct object
reference in a live Binder object that can be written to the
Bundle instead of the individual data fields.

#minor-release

PiperOrigin-RevId: 572816784
(cherry picked from commit d1fc15f2075dd5c130a12420889fd83bd6517a08)

* Migrate `SubtitleParser` tests to incremental `parse()` methods

All the production code is already calling these new incremental
methods, migrating the tests allows us to remove the old
`List`-returning methods in a follow-up change.

#minor-release

PiperOrigin-RevId: 572822828
(cherry picked from commit a12bde4f57002a9adf5da6c01b2f601a6edf92e9)

* Merge pull request #650 from cedricxperi:dts-lbr-buffer-underflow-fix

PiperOrigin-RevId: 572864175
(cherry picked from commit 2421ba4d8fec6ef805f2765f522d4bf0027a08c9)

* Change `LegacySubtitleUtil` handling of `SubtitleParser.OutputOptions`

If the `Subtitle` has 'active' cues at `OutputOptions.startTimeUs`, this
change ensures these are emitted in a `CuesWithTiming` with
`CuesWithTiming.startTimeUs = OutputOptions.startTimeUs`. If
`OutputOptions.outputAllCues` is also set, then another `CuesWithTiming`
is emitted at the end that covers the 'first part' of the active cues,
and  ends at `OutputOptions.startTimeUs`.

As well as adding some more tests to `LegacySubtitleUtilWebvttTest`,
this change also adds more tests for `TtmlParser` handling of
`OutputOptions`, which transitively tests the behaviour of
`LegacySubtitleUtil`.

#minor-release

PiperOrigin-RevId: 573151016
(cherry picked from commit f9ece88a25b449ab1e59ec0f6a67b71d7a2dc8ce)

* Remove the 'super speed' SmoothStreaming PlayReady stream from demo

This content is no longer available, the manifest is returning a 404.

Issue: google/ExoPlayer#11309

#minor-release

PiperOrigin-RevId: 573202175
(cherry picked from commit a19f577976fc670c47e837d521c48170ab900ea0)

* Migrate `SubtitleParser` implementations to incremental `parse()`

All production and test callers of the non-incremental methods are
already migrated, so we can remove them in this change too.

#minor-release

PiperOrigin-RevId: 573207318
(cherry picked from commit ecd24646cb1fd4b06a27cfe87ec0df47e9db87ed)

* Test more URI forms in `RawResourceDataSourceContractTest`

PiperOrigin-RevId: 573220915
(cherry picked from commit 40459f72123cfc3bbead5dd42ce2aa3a824155b2)

* Remove deprecated `DownloadNotificationHelper.buildProgressNotification`

Use a non deprecated method that takes a `notMetRequirements` parameter
instead.

PiperOrigin-RevId: 573252909
(cherry picked from commit 8b7ebc70320e66b6360df37c36d4cfc2fb71aa98)

* Calculate HLS live playlist refresh interval accurately

Previously, we calculated the next playlist reload time by adding the target duration (or half of it, depending on whether there is a real update in the new playlist snapshot) from the last load completion time, which makes the reload interval as long as `targetDuration(or half of it) + lastLoadDuration`. While still complying to the standard that "the client MUST wait for at least the target duration before attempting to reload the Playlist file again", this could cause buffering when the playback position is close to the end of live window. This change is to calculate the reload interval accurately by not adding the term `lastLoadDuration`.

Issue: androidx/media#663

#minor-release

PiperOrigin-RevId: 573300009
(cherry picked from commit 58a63c82aa0b59e86a656cf6644781a1c4690c82)

* 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
(cherry picked from commit 00425dbe80dc9da38766f7235052c434d79724d1)

* Only set the queue when COMMAND_GET_TIMELINE is available

Android Auto shows a queue button when the queue is not empty.
Apps were able to remove this queue button with the legacy API
by not setting the queue of the session.

After this change, removing `COMMAND_GET_TIMELINE` from the commands
of the media notification controller or the session player sets the
queue in the platform session to null.

#minor-release
Issue: androidx/media#339
PiperOrigin-RevId: 573813558
(cherry picked from commit f53e1bc6f63caba7774c35aeb663b9178941faf5)

* Send `ConnectionState` as in-process bundle if possible

#minor-release

PiperOrigin-RevId: 573849858
(cherry picked from commit d5f093f43cc2bda763436d4ecf32c38c76b9418e)

* Send media button events from service directly using `MediaSessionImpl`

Media button event coming from the `MediaSessionService` are delegated
to the `MediaSessionImpl` and then sent to the session by using the
`MediaSessionStub` directly instead of using the `MediaController`
API.

Splitting the `MediaController.Listener` and `Player.Listener` in
`MediaNotificationManager` got reverted, and both listener are set to the
controller as before. This reverts the change that introduced a
different timing behaviour. It still holds, that a listener
registered on a `MediaController` that calls a method like `play()` is
called immediately and before the call has arrived at the player. This
change works around this behaviour from the library side by calling
`MediaSessionStub` directly with a `ControllerInfo`.

#minor-release

PiperOrigin-RevId: 573918850
(cherry picked from commit 64bd3bcad3fa4b0e433b16d583456920afad3ce2)

* Move DASH subtitle parsing release note to correct section

#minor-release

PiperOrigin-RevId: 574090381
(cherry picked from commit df19097e220f7b4599830bc2a802b0951bc71cfb)

* Merge pull request #491 from v-novaltd:dsparano-exo128

PiperOrigin-RevId: 574129451
(cherry picked from commit 009d48a75e932c9e8e94a28ca2b92970cf5fe357)

* Publish MIDI decoder module on Maven repository

Issue: androidx/media#734

#minor-release

PiperOrigin-RevId: 574182702
(cherry picked from commit 61770f8a61312cacf596536b614eeb49f6abab6e)

* Rollback of https://github.com/androidx/media/commit/64bd3bcad3fa4b0e433b16d583456920afad3ce2

PiperOrigin-RevId: 574290408
(cherry picked from commit 1a43aa3602075cc88c902de293cb025ff3d619cc)

* Rollback of https://github.com/androidx/media/commit/4ebe630a80296cbb4437336a50abccb39da978f7

PiperOrigin-RevId: 574308136
(cherry picked from commit ff330bd8e9e925987396597cfa25f5455e1d4048)

* Fix MIDI decoder build.gradle

Issue: androidx/media#734

#minor-release

PiperOrigin-RevId: 574425269
(cherry picked from commit ff4ff76990b52718f8c1e4acd9075c5c06ebee0e)

* Use DataSourceBitmapLoader by default

This replaces the SimpleBitmapLoader that can now be deprecated
as it's fully unused and doesn't provide any additional functionality.

#minor-release

PiperOrigin-RevId: 574454636
(cherry picked from commit db86932781b4a5f377d1f4c1414c3d6a74ede174)

* Send decode-only Opus samples in bypass mode for seekPreRoll skip

As Opus decoders skip some bytes prior to playback during a seek, the renderer for bypass playback should send samples to the decoder even if they would be decode-only.

#minor-release

PiperOrigin-RevId: 574494666
(cherry picked from commit 00193e0304a5ea2c20012fabf77f82f29e218372)

* Add formatting to `scheme` list in `DefaultDataSource` javadoc

The current formatting makes the 'scheme' part of the list blend into
the definition, especially when the definition is multi-line.

https://developer.android.com/reference/androidx/media3/datasource/DefaultDataSource

I considered adding another level of nesting, but I think bold will
help distinguish the structure of the list without adding too much HTML
or visual whitespace.

#minor-release

PiperOrigin-RevId: 574514208
(cherry picked from commit aec6db77faba617dc2ab225b72c9bc3350c5b5c3)

* Rollback of https://github.com/androidx/media/commit/4ebe630a80296cbb4437336a50abccb39da978f7

PiperOrigin-RevId: 574530273
(cherry picked from commit c0759a4e62466bbc0816737e28adf1b4a513016c)

* Rollback of https://github.com/androidx/media/commit/eafe2e35f0f343d95b95769dc273d016c20fe3c6

PiperOrigin-RevId: 574755143
(cherry picked from commit cf3733765c2ca59ef2261b2e59b21fae5e7546eb)

* Update RELEASENOTES for 1.2.0-rc01 release

PiperOrigin-RevId: 575795800
(cherry picked from commit 7202f5d4de6a51b3fe23994c49ad8c15350d260c)

* Bump Media3 version numbers for 1.2.0-rc01

#minor-release

PiperOrigin-RevId: 575805495
(cherry picked from commit 9d3d7abdc6460bcc6f01145bcb3ce1e854b86a1a)

* Reorder RELEASENOTES to move unreleased changes to correct section

PiperOrigin-RevId: 575807109
(cherry picked from commit 105bdf57d80e32d8c0d8c4e2598b9c43c9461870)

* Add `com.github.philburk:jsyn` to JAR list

#minor-release

PiperOrigin-RevId: 576148893
(cherry picked from commit 00943a0a734e55a1dbdf584c1bc139ed4db67e64)

* Rollback of https://github.com/androidx/media/commit/a19f577976fc670c47e837d521c48170ab900ea0

PiperOrigin-RevId: 577139027
(cherry picked from commit dd6306e1ba5e860d7a267a0ad3a16eb028e8fd7a)

* Bump media3 versions to 1.2.0 (stable)

#minor-release

PiperOrigin-RevId: 580856330
(cherry picked from commit 3918d3620052138f0d7718cd8076e7389222572d)

* Merge release notes for media3 1.2.0 stable release

PiperOrigin-RevId: 580923121
(cherry picked from commit 7ee07a5ff583e8d56e34783dc4ecfdb7d9a65ef5)

* Add `@OptIn` to fields in demo `PlayerActivity` now this is supported

This is possible now we use `annotation-experimental:1.3.1`.

#minor-release

PiperOrigin-RevId: 582315579
(cherry picked from commit 8d83d491f198fbe3a21181df235dfff314252929)

* Remove recommendation to pin `annotation-experimental` to version 1.2.0

This was intended to avoid bringing in a transitive dependency on the
Kotlin standard library, but Gradle no longer flags lint errors on
`@RequiresOptIn` violations with `annotation-experimental:1.2.0` (1.3.0
is needed), making this recommendation dangerous. See also
https://issuetracker.google.com/310651921.

PiperOrigin-RevId: 582276430
(cherry picked from commit aa1ec981a3eea59f8be9d44104d0573a60816436)

* Clean-up multi-line strings in YAML issue templates

* If we don't want any newlines in the result, it's better to use `>`
* If we want newlines (e.g. for markdown) then we should ensure the
  string **only** contains the newlines we want, because GitHub (unlike
  other markdown renderers) preserves single newlines in the output,
  leading to ugly newlines dictated by the source.

Also remove a markdown-style link that isn't renderered as markdown.

PiperOrigin-RevId: 590309749
(cherry picked from commit 6aeaad26addee2d5793a624526d1811b8973c4ce)

* Fallback to legacy sizerate check for H264 if CDD PerfPoint check fails

Some devices supporting Performance Points for decoder coverage are missing coverage over the CDD requirements for H264. For these cases ExoPlayer should fall back to legacy resolution and frame rate support checks. If there is an H264 stream evaluated as a `PerformancePointCoverageResult` of `COVERAGE_RESULT_NO`, then ExoPlayer checks for coverage of the [720p CDD requirement](https://source.android.com/docs/compatibility/10/android-10-cdd#5_3_4_h_264).

Issue: google/ExoPlayer#10898

Issue: androidx/media#693
PiperOrigin-RevId: 575768836
(cherry picked from commit 4515a0c3f24706a43b3247b558b14d98f2b0fce2)

* Bump okhttp dependency to 4.12

Issue: androidx/media#768
PiperOrigin-RevId: 577208115
(cherry picked from commit e8cca688ad39590a9537c940ed0db4ca805f0fb8)

* Remove stray parentheses from release notes

PiperOrigin-RevId: 577809964
(cherry picked from commit db1ab1dcf37a484370c33b399c52f4e9569c793d)

* Put the custom keys in MediaMetadataCompat to MediaMetadata.extras

PiperOrigin-RevId: 578473874
(cherry picked from commit 84022eacc560b90cf34253b2470aabdf4a4b767d)

* Add warning log if DASH manifest contains incomplete ClearKey info

Unfortunately we can't fail any more obviously at this point, because
manifests often contain config for multiple DRM schemes, and when
parsing the manifest we don't know which scheme is going to be used for
playback. It would be unreasonable to fail playback due to incomplete
ClearKey config if playback was otherwise going to succeed using e.g.
Widevine.

* Issue: androidx/media#777
* Issue: androidx/media#563
* Issue: google/ExoPlayer#9169

#minor-release

PiperOrigin-RevId: 578491484
(cherry picked from commit d42c23706b615d1987f988fe219ab0fe61d44ac6)

* Split media1/media3 conversion methods out of `MediaUtils`

Android Studio removed some nested imports, but I think the extra
qualification at the usage site is actually mostly helpful, so I'm
leaving it as-is.

PiperOrigin-RevId: 578518880
(cherry picked from commit 72b7019578f3051e1bec826cf0ac401a86d818dc)

* Fix access to stale ByteBuffer in FfmpegAudioDecoder

The native code can now reallocate the buffer if it needs to grow
its size, so we have to reacquire a reference in the Java code to
avoid accessing a stale instance.

This fixes a bug introduced by https://github.com/androidx/media/pull/746/commits/8750ed8de6469dc818007f2eb254df9ddbd52cc5.

PiperOrigin-RevId: 578799862
(cherry picked from commit ae6f83d298424bd405372803bb8b206fc95a2d0f)

* Fix proguard rule to also keep referenced class name

PiperOrigin-RevId: 579234050
(cherry picked from commit bce82bdc752a8da1d7c1f78bdfb417414407849b)

* Remove old pre-releases from the github bug template

PiperOrigin-RevId: 580554963
(cherry picked from commit 508582d56c9f8e6219d2e93f0f80ea180b4ad272)

* Don't crash when receiving a bad playback state

PiperOrigin-RevId: 580942377
(cherry picked from commit e79809616cd0ecb6f39cbeffdaaf143c260f64e6)

* Parse "f800" as channel count of 5 for Dolby in DASH manifest

Issue: androidx/media#688
PiperOrigin-RevId: 581908905
(cherry picked from commit 79711ebd3f8626d9ec31f7ac18434625caeac28f)

* Expand frame drop workaround to Realme C11

Based on on-device testing, this device seems to have the same issue as Moto G (20) where frames are dropped despite configuring the decoder not to drop frames.

PiperOrigin-RevId: 581943805
(cherry picked from commit 330713f687d4ebaec9ee8e9aaf39db503f299ce3)

* Update recommended way to suppress `@UnstableApi` errors in `lint.xml`

#minor-release

PiperOrigin-RevId: 582599098
(cherry picked from commit bd7615c0b83df8187daaf7e8d91eb1ce9bb35240)

* Workaround layout problems with Material Design

In some contexts (e.g. BottomSheetDialogFrament), Material Design
themes will override the default of singleLine=false to true. This
causes layout problems because the forward/rewind buttons are no
longer visible with singleLine=true.

This problem can be avoided by explicitly requesting the default
value of false in our layout files.

Issue: androidx/media#511

#minor-release

PiperOrigin-RevId: 582604131
(cherry picked from commit 310e2edccac75b1ed30eb69520224cb48d1cc190)

* Populate `MediaMetadata.extras` to `MediaMetadataCompat`

Ensures backward compatibility.

Issue: androidx/media#802
PiperOrigin-RevId: 583425114
(cherry picked from commit 6df240877c30aedb271c2bc74c54fc2bab0e4cbf)

* Use `.test` top level domain for test URI

PiperOrigin-RevId: 583951327
(cherry picked from commit ffbaa090aa24ef138d38b16b9e6e45560b5c8dd3)

* Return empty timeline when media info is null

Issue: androidx/media#708
PiperOrigin-RevId: 584054624
(cherry picked from commit 167f50a9ca8b8cbd80bc5ff4c7afd4c6a1db7dc2)

* Add test case to test position conversion when POSITION_UNKNOWN

PiperOrigin-RevId: 584261559
(cherry picked from commit ec478138baf58ed5c1a4c5117d5f28e5b40c94eb)

* Avoid clipping live offset override to min/max offsets

The live offset override is used to replace the media-defined
live offset after user seeks to ensure the live adjustment adjusts
to the new user-provided live offset and doesn't go back to the
original one.

However, the code currently clips the override to the min/max
live offsets defined in LiveConfiguration. This is useful to
clip the default value (in case of inconsistent values in the media),
but the clipping shouldn't be applied to user overrides as
the player will then adjust the position back to the min/max
and doesn't stay at the desired user position.

See https://github.com/androidx/media/commit/2416d9985718accfcc00ddc951afa217c261f7ae#r132871601

PiperOrigin-RevId: 584311004
(cherry picked from commit af0282b9db62a8c5de1dbcc49794872d0157c1ab)

* MidiExtractor: mark only the first sample as key-frame

This change fixes a bug with seeking forward in MIDI. When seeking forward,
the progressive media period attempts to seek within the sample queue, if a
key-frame exists before the seeking position. With MIDI, however, we can
only skip Note-On and Note-Off samples and all other samples must be sent
to the MIDI decoder.

When seeking outside the sample queue, the MidiExtractor already
instructs the player to start from the beginning of the MIDI input. With
this change, only the first output sample is a key-frame, thus the
progressive media period can no longer seek within the sample queue and
is forced to seek from the MIDI input start always.

Issue: androidx/media#704

PiperOrigin-RevId: 584321443
(cherry picked from commit ec08db458e6cedcb79a42f10eaac7f8da7e7bcdb)

* Add session extras to the state of the controller

This change adds `MediaController.getSessionExtras()` through
which a controller can access the session extras.

The session extras can be set for the entire session when
building the session. This can be overridden for specific
controllers in `MediaSession.Callback.onConnect`.

PiperOrigin-RevId: 584430419
(cherry picked from commit a063d137b4307348a140ec6a2b6d254db294395e)

* Merge pull request #707 from equeim:ffmpeg-6.0

PiperOrigin-RevId: 584893190
(cherry picked from commit 45b51d8c972f957e02097c5ecff2261ffe23e397)

* Remove redundant ) in Javadoc

PiperOrigin-RevId: 584910697
(cherry picked from commit 85a54e2e190b705367b0d2dd4d7fcd71900b3fdd)

* Fix typo in `DashManifestParser`

PiperOrigin-RevId: 585017285
(cherry picked from commit 479344d74e1e63d83cd94ed14517e27030c85e6a)

* Avoid value close to overflow for `KEY_OPERATING_RATE`

Using `Integer.MAX_VALUE` risks causing arithmetic overflow in the codec
implementation.

Issue: androidx/media#810

PiperOrigin-RevId: 585104621
(cherry picked from commit ad40db448943fef579879c9c2a988f514b254109)

* Work around codec frame rate issues in Redmi Note 9 Pro

The decoder and encoder won't accept high values for frame rate, so avoid
setting the key when configuring the decoder, and set a default value for the
encoder (where the key is required).

Also skip SSIM calculation for 4k, where the device lacks concurrent decoding
support.

PiperOrigin-RevId: 585604976
(cherry picked from commit 8b38b34b9f02c3648d2988c4cdaca005fbf8f1cd)

* Restrict operating rate workaround to SM8550

PiperOrigin-RevId: 585613041
(cherry picked from commit e84a13fb54ab0d325b928bf7ec0d8632ccbc8ca3)

* Merge pull request #837 from superjohan:fix/android-14-clearkey

PiperOrigin-RevId: 585639025
(cherry picked from commit 5f27b1821027d4cd086b87242e9e756a301b8e9a)

* Update emulator device names

PiperOrigin-RevId: 585682881
(cherry picked from commit b598c96c2f604af731d43e5bd021e5198dab0af1)

* Exit early progressive loads if the load task is canceled

Add an check when loading progressive media in case the load
is canceled. If the player is released very early, the progressive
media period may carry on with the initial loading unnecessarily.

PiperOrigin-RevId: 586288385
(cherry picked from commit 3d1d8f4439f194029d1522137b7f428e96bf61b3)

* Don't include null text or bitmaps in `Cue.toBundle()`

`fromBundle` doesn't distinguish between `FIELD_BITMAP` and `FIELD_TEXT`
being present with a null value, or being absent, so we might as well
avoid including them when the value is null.

I've separated this from a later change to add
`Cue.toSerializableBundle` which will also skip setting a bitmap value
into the `Bundle` if `this.bitmap == null`. This is partly because it
results in changes to a lot of extractor test dump files, and it's
easier to review that as a separate change.

PiperOrigin-RevId: 586626141
(cherry picked from commit 28c210686f54d52610fe2f9560ad1dfa73753ccd)

* MCR: Ensure `mediaCrypto` and `codec` are atomically non-null

`mediaCrypto` is initialized before `codec` in
`maybeInitCodecOrBypass`. Before this change, it was possible for
`maybeInitCodecOrBypass` to complete with `mediaCrypto != null` and
`codec == null`, in particular if it was run as part of clearing the
player surface (since in that case, no video codec is initialized).
This inconsistent state then causes issues during a later invocation of
`maybeInitCodecOrBypass`, when `mediaCrypto` is still non-null, and
`mediaCryptoRequiresSecureDecoder = true`, but the
content has been changed to unencrypted with no associated DRM session.
This results in a playback error, because a secure decoder is
initialized but there's no DRM session available to work with it.

This change ensures that when `maybeInitCodecOrBypass` completes,
either both `mediaCrypto != null` and `codec != null` (i.e. codec
initialization was completed) or `mediaCrypto == null` and
`codec == null` (i.e. codec initialization was not completed). We also
ensure that when nulling out `mediaCrypto` we also set
`maybeInitCodecOrBypass = false`. A later change should be able to
demote `maybeInitCodecOrBypass` from a field to a local in order to
remove any risk of that part of state becoming out of sync. This
resolves the issue, because during the second invocation of
`maybeInitCodecOrBypass` an insecure decoder is now (correctly)
initialized and the unencrypted content is successfully played.

#minor-release

PiperOrigin-RevId: 587713911
(cherry picked from commit 913f6da08305b36798c84d5134d19b2d11affdd2)

* Map VORBIS channel layout to Android layout

Both the extension OPUS decoder and the OMX/C2 MediaCodec
implementations for OPUS and VORBIS decode into the channel
layout defined by VORBIS. See
https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-140001.2.3

While this is technically correct for a stand-alone OPUS or VORBIS
decoder, it doesn't match the channel layout expected by Android.
See https://developer.android.com/reference/android/media/AudioFormat#channelMask

The fix is to apply the channel mapping after decoding if needed.
Also add e2e tests with audio dumps for the extension renderer,
including a new 5.1 channel test file.

Issue: google/ExoPlayer#8396

PiperOrigin-RevId: 588004832
(cherry picked from commit b1541b096f9dc4d1f9ca71b6743c836f6bd4de89)

* Limit processing Opus decode-only frames by seek-preroll in offload

As Opus decoders skip some bytes prior to playback during a seek, the renderer for bypass playback should send samples to the decoder even if they would be decode-only. However, the renderer should not send samples with time preceding that range. This change adds that constraint.

#minor-release

PiperOrigin-RevId: 588014983
(cherry picked from commit d1e38abf93353af1bc3fb2d9a0dfbac01e387f3e)

* Fix nullability issue in MediaControllerImplLegacy

PiperOrigin-RevId: 588035411
(cherry picked from commit e0f1783a54867ee81d6f9fb93ffdcee8451f2118)

* Add Robolectric e2e test support for HEVC content

PiperOrigin-RevId: 588055594
(cherry picked from commit d4fe3fe318ef5961911b828aa7a42b124acfc186)

* Add extractor and playback tests for Pixel JPEG motion photo…
@marcbaechinger
Copy link
Contributor

I'm closing this after testing again with tot that will be released with 1.4.1.

The search icon is available on AAOS emulator for API 29 and API 33 if COMMAND_CODE_LIBRARY_SEARCH is included in the available commands in MediaSession.Callback.onConnect(MediaSession, ControllerInfo, ...) when the controller of Automotive identified with MediaSession.isAutomotiveController(controllerInfo) connects:

Disabling the search for Auto and Automotive (removing the search command from the default command set):

override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult {
  if (session.isAutomotiveController(controller) ||
      session.isAutoCompanionController(controller)) {
    return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
      .setAvailableSessionCommands(mediaNotificationSessionCommands)
      .build()
  }
 // all other controllers
 return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
}

@androidx androidx locked and limited conversation to collaborators Jul 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants