android-mediaplayerexoplayerandroid-mediasessionandroid-media3exoplayer-media-item

Media3 notification: How to update title dynamically during playback


I need to modify the title shown in the media notification based on the current playback position. According to the documentation, I can use MediaNotification.Provider however the document also says this

Note: Starting with API 33 the System UI notification is populated from the data in the session.
Accordingly, customizations of the MediaNotification.Provider have effect before API 33 only.

For 33 and above, I can update metadata but cannot call setMediaItem on the player again as that will disturb the current playback session (plays from the beginning).

Question 1: Using media3 default notification, how do I update title on the fly?

If my only option is build custom notification,

Question 2: How to disable default mediastyle notification? (currently I see two notifications)

Question 3: How to create my own layout and show / update data on that?

Here is the service:

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null
    private lateinit var playerNotificationManager: PlayerNotificationManager
    private lateinit var notificationManager: NotificationManagerCompat

    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this)
            .setMediaSourceFactory(...)
            .build()
            .apply {
                addListener(object : Player.Listener {
                    ...
                })
                playWhenReady = true
            }

        mediaSession = MediaSession.Builder(this, player)
            .build()

        notificationManager = NotificationManagerCompat.from(this)
        createChannel()

        playerNotificationManager = PlayerNotificationManager.Builder(this, NOTIFICATION_ID, NOTIFICATION_CHANNEL)
            .setNotificationListener(object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
                    // Handle notification being dismissed by the user
                }

                override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
                    // Handle notification being posted
                    startForeground(notificationId, notification)
                }
            })
            .setMediaDescriptionAdapter(object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun getCurrentContentTitle(player: Player): CharSequence {
                    return "My Title"
                }

                override fun createCurrentContentIntent(player: Player): PendingIntent? {
                    return null
                }

                override fun getCurrentContentText(player: Player): CharSequence {
                    return "My Description"
                }

                override fun getCurrentLargeIcon(player: Player, callback: PlayerNotificationManager.BitmapCallback): Bitmap? {
                    return null
                }
            })
            .build()
        playerNotificationManager.setPlayer(player)
        ///playerNotificationManager.setMediaSessionToken(mediaSession.token) <-- not sure if session token needs to be set
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        mediaSession?.player?.stop()
        stopForeground(STOP_FOREGROUND_REMOVE)
        stopSelf()
        super.onTaskRemoved(rootIntent)
    }

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

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        return mediaSession
    }

    @TargetApi(Build.VERSION_CODES.O)
    private fun createChannel() {
        val channel = NotificationChannel(
            NOTIFICATION_CHANNEL,
            this.getString(R.string.playback_notification_channel),
            NotificationManager.IMPORTANCE_LOW)
        channel.enableLights(false)
        channel.enableVibration(false)
        channel.setShowBadge(false)
        channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
        notificationManager.createNotificationChannel(channel)
    }

    companion object {
        private const val NOTIFICATION_CHANNEL = "player_channel"
        private const val NOTIFICATION_ID = 100
    }
}

Solution

  • Instead of creating PlayerNotificationManager, all we need to do is to override this method in MediaNotification.Provider

    fun getNotificationContentText(MediaMetadata metadata): CharSequence
    

    Once the above object is created, from MediaSessionService, need to call

    setMediaNotificationProvider(provider)