androidnotificationsmediaexoplayerplayback

Android Media3: How to add seek buttons to the media notification?


After spending an entire week trying to understand this unbelievably complicated library, I am at my wits' end. I am convinced that there is an out-of-the-box solution for this media notification UI that's in pretty much every major app with media playback these days. It has seek forward and backward buttons, a playback speed button, and a favorite button:enter image description here

I have followed these instructions exactly, and there has been no change to my notification, which looks like this instead. Just a previous track button: enter image description here

I have read this issue, where a developer admits that this is complicated (and promises to document it, lol) but I do not understand the solution he explains.

I am not using a PlayerNotificationManager. Here's what I have so far, which does not work. In my PlaybackService:

    private val back = CommandButton.Builder()
            .setEnabled(true)
            .setIconResId(androidx.media3.session.R.drawable.media3_notification_seek_back)
            .setDisplayName("Seek Back")
            .setPlayerCommand(Player.COMMAND_SEEK_BACK)
            .build()
    private val forward = CommandButton.Builder()
            .setEnabled(true)
            .setIconResId(androidx.media3.session.R.drawable.media3_notification_seek_forward)
            .setDisplayName("Seek Forward")
            .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
            .build()
    private val buttons = ImmutableList.of(back, forward)

    override fun onCreate() {
        super.onCreate()
        val intent = packageManager!!.getLaunchIntentForPackage(packageName)!!
            .let { sessionIntent ->
                PendingIntent.getActivity(this, SESSION_INTENT_REQUEST_CODE, sessionIntent, PendingIntent.FLAG_IMMUTABLE)
            }
        _session = MediaSession.Builder(this, buildPlayer())
            .setSessionActivity(intent)
            .setCustomLayout(buttons)
            .setCallback(CustomMediaSessionCallback())
            .build()
        setListener(PlaybackServiceListener()) // irrelevant to this question
    }

    private inner class CustomMediaSessionCallback: MediaSession.Callback {
      override fun onConnect(
          session: MediaSession,
          controller: MediaSession.ControllerInfo
      ): MediaSession.ConnectionResult {
          val sessionCommands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
              .add(back.playerCommand)
              .add(forward.playerCommand)
              .build()
          return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
              .setCustomLayout(buttons)
              .setAvailablePlayerCommands(sessionCommands)
              .build()
      }
    }

In my ViewModel, I'm creating the SessionToken and MediaController:

private val sessionToken =
        SessionToken(getApplication(), ComponentName(getApplication(), PlaybackService::class.java))
private val controllerFuture = MediaController.Builder(getApplication(), sessionToken).buildAsync()

Once the MediaController is ready, I add the MediaItem and start playback.

with(controller!!) {
    playWhenReady = true
    setMediaItem(mediaItem)
    prepare()
}

To be clear, absolutely nothing changes if I remove .setCustomLayout(buttons) and .setCallback(CustomMediaSessionCallback()) from my MediaSession.Builder. It has no effect.

I do get a warning in logcat: "Couldn't find a unique registered media button receiver in the given context." I searched for this string in the media3 repo, and it does not appear there.

Can anyone help me? Thanks!


Solution

  • I finally found a codebase on GitHub with the notification UI I'm trying to achieve, and it looks like people really are rolling this themselves. Seems completely bonkers to me that this isn't available out of the box, but I'm done digging into the guts of this garbage library for now. Time to build the layout from scratch and move on with my life.

    If I am wrong, please do let me know, and I'll be happy to accept a better answer.