androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.69k stars 404 forks source link

API to allow manual refresh of notification, and making MediaNotification.Provider aware of manual refresh #1833

Open nift4 opened 1 week ago

nift4 commented 1 week ago

Use case description

The OEM MeiZu has implemented so-called "status bar lyrics" in their Flyme OS. It works by setting the lyric text in the Notification using setTicker() and using MeiZu-specific flags in the Notification object to show the ticker as lyrics. They have added two flags:

private const val FLAG_ALWAYS_SHOW_TICKER = 0x01000000
private const val FLAG_ONLY_UPDATE_TICKER = 0x02000000

To enable the lyric mode, I modified MediaNotification.Provider to set the lyric text with setTicker() and apply these flags to the created notification object:

notification.apply {
    // Keep the status bar lyrics scrolling
    flags = flags.or(FLAG_ALWAYS_SHOW_TICKER)
    // Only update the ticker (lyrics), and do not update other properties
    if (onlyUpdateTicker)
        flags = flags.or(FLAG_ONLY_UPDATE_TICKER)
}

To avoid needless animations in the notification, when only the currently active lyric line changes, FLAG_ONLY_UPDATE_TICKER should be set. This requires distigushing updates generated by media3 (where the notification content always changes) and manually generated updates (which are for lyric changes). However, media3 currently does not support manually triggering a notification update. The API for this is package-private, and I had to create a new file in the androidx.media3.session package containing some affordances for updating the notification:

var isManualNotificationUpdate = false
    private set
// onUpdateNotificationInternal is package-private
fun MediaSessionService.doUpdateNotification(session: MediaSession) {
    if (Looper.myLooper() != session.player.applicationLooper)
        throw UnsupportedOperationException("wrong looper for doUpdateNotification")
    isManualNotificationUpdate = true
    onUpdateNotificationInternal(session, false)
    isManualNotificationUpdate = false
}

This is pretty ugly.

Another problem I had was that NotificationCompat.Builder does not have a generic setFlag() method public so these OEM-specific flags had to be set in created Notification object. There however isn't any API in DefaultMediaNotificationProvider to intercept assembled Notification object before it's sent out to callees. I had to create a wrapper MediaNotification.Provider that intercepts the return value of createNotification() and the bitmap loading callback and adds the required flags. (For setting the ticker value, I override addNotificationActions() which provides access to the Builder object. I think that's enough for intercepting the builder.)

Proposed solution

  1. Add a new API to MediaSessionService that allows me to manually trigger an update, akin to onUpdateNotificationInternal(session, false).
  2. Edit MediaNotification.Provider.createNotification() to add a new boolean parameter which is true if the update was triggered manually with the API from point 1.
  3. Expose a way to intercept assembled MediaNotification in DefaultMediaNotificationProvider for both the return value of createNotification() and the value passed to onNotificationChangedCallback.

Alternatives considered

Keep the current pile of hacks :)

marcbaechinger commented 1 week ago

Thanks for the proposal.