playback: sync mediasession and notif

Make notification updates entirely reliant on the MediaSession.

Android 11 and onwards automatically populate the notification with the
MediaSession state. This apparently conflicts with updating the
notification in some cases, resulting in the incorrect song being
shown. Fix this by not populating the notification from Android 11
onward and only posting it when the MediaSession state was set.

Resolves #179.
This commit is contained in:
OxygenCobalt 2022-07-02 14:04:21 -06:00
parent 3663396b97
commit 49a964705b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 99 additions and 120 deletions

View file

@ -26,6 +26,7 @@ finished saving
- Fixed issue where the search filter menu would not display the correct mode - Fixed issue where the search filter menu would not display the correct mode
- Fixed crash when search filter mode was changed - Fixed crash when search filter mode was changed
- Fixed shuffle button appearing below playback bar on Android 10 and lower - Fixed shuffle button appearing below playback bar on Android 10 and lower
- Fixed incorrect song being shown in the notification in some cases [#179]
#### What's Changed #### What's Changed
- Reworked typography and iconography to be more aligned with material design guidelines - Reworked typography and iconography to be more aligned with material design guidelines

View file

@ -25,6 +25,7 @@ import coil.request.Disposable
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Size import coil.size.Size
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.logD
/** /**
* A utility to provide bitmaps in a manner less prone to race conditions. * A utility to provide bitmaps in a manner less prone to race conditions.
@ -53,6 +54,7 @@ class BitmapProvider(private val context: Context) {
// Increment the generation value so that previous requests are invalidated. // Increment the generation value so that previous requests are invalidated.
// This is a second safeguard to mitigate instruction-by-instruction race conditions. // This is a second safeguard to mitigate instruction-by-instruction race conditions.
val generation = synchronized(this) { ++currentGeneration } val generation = synchronized(this) { ++currentGeneration }
logD("new generation for ${song.rawName}: $generation $currentGeneration")
currentRequest?.run { disposable.dispose() } currentRequest?.run { disposable.dispose() }
currentRequest = null currentRequest = null

View file

@ -122,7 +122,6 @@ class PlaybackStateManager private constructor() {
controller.loadSong(song) controller.loadSong(song)
controller.seekTo(positionMs) controller.seekTo(positionMs)
controller.onPlayingChanged(isPlaying) controller.onPlayingChanged(isPlaying)
controller.onRepeatChanged(repeatMode)
controller.onPlayingChanged(isPlaying) controller.onPlayingChanged(isPlaying)
} }
@ -468,14 +467,12 @@ class PlaybackStateManager private constructor() {
} }
private fun notifyRepeatModeChanged() { private fun notifyRepeatModeChanged() {
controller?.onRepeatChanged(repeatMode)
for (callback in callbacks) { for (callback in callbacks) {
callback.onRepeatChanged(repeatMode) callback.onRepeatChanged(repeatMode)
} }
} }
private fun notifyShuffledChanged() { private fun notifyShuffledChanged() {
controller?.onShuffledChanged(isShuffled)
for (callback in callbacks) { for (callback in callbacks) {
callback.onShuffledChanged(isShuffled) callback.onShuffledChanged(isShuffled)
} }
@ -495,11 +492,11 @@ class PlaybackStateManager private constructor() {
/** Called when the playing state is changed. */ /** Called when the playing state is changed. */
fun onPlayingChanged(isPlaying: Boolean) fun onPlayingChanged(isPlaying: Boolean)
/** Called when the repeat mode is changed. */ // /** Called when the repeat mode is changed. */
fun onRepeatChanged(repeatMode: RepeatMode) // fun onRepeatChanged(repeatMode: RepeatMode)
//
/** Called when the shuffled state is changed. */ // /** Called when the shuffled state is changed. */
fun onShuffledChanged(isShuffled: Boolean) // fun onShuffledChanged(isShuffled: Boolean)
} }
/** /**

View file

@ -26,6 +26,7 @@ import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import androidx.media.session.MediaButtonReceiver import androidx.media.session.MediaButtonReceiver
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
@ -43,20 +44,25 @@ import org.oxycblt.auxio.util.logD
* *
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class MediaSessionComponent(private val context: Context, private val player: Player) : class MediaSessionComponent(
private val context: Context,
private val player: Player,
private val callback: Callback
) :
Player.Listener, Player.Listener,
MediaSessionCompat.Callback(), MediaSessionCompat.Callback(),
PlaybackStateManager.Callback, PlaybackStateManager.Callback,
Settings.Callback { Settings.Callback {
interface Callback {
fun onPostNotification(notification: NotificationComponent)
}
private val playbackManager = PlaybackStateManager.getInstance() private val playbackManager = PlaybackStateManager.getInstance()
private val settings = Settings(context, this) private val settings = Settings(context, this)
private val mediaSession = val mediaSession = MediaSessionCompat(context, context.packageName).apply { isActive = true }
MediaSessionCompat(context, context.packageName).apply { isActive = true } private val notification = NotificationComponent(context, mediaSession.sessionToken)
private val provider = BitmapProvider(context) private val provider = BitmapProvider(context)
val token: MediaSessionCompat.Token
get() = mediaSession.sessionToken
init { init {
player.addListener(this) player.addListener(this)
playbackManager.addCallback(this) playbackManager.addCallback(this)
@ -81,15 +87,15 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
// --- PLAYBACKSTATEMANAGER CALLBACKS --- // --- PLAYBACKSTATEMANAGER CALLBACKS ---
override fun onIndexMoved(index: Int) {
updateMediaMetadata(playbackManager.song)
}
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) { override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
updateMediaMetadata(playbackManager.song) updateMediaMetadata(playbackManager.song, parent)
} }
private fun updateMediaMetadata(song: Song?) { override fun onIndexMoved(index: Int) {
updateMediaMetadata(playbackManager.song, playbackManager.parent)
}
private fun updateMediaMetadata(song: Song?, parent: MusicParent?) {
if (song == null) { if (song == null) {
mediaSession.setMetadata(emptyMetadata) mediaSession.setMetadata(emptyMetadata)
return return
@ -97,7 +103,7 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
val title = song.resolveName(context) val title = song.resolveName(context)
val artist = song.resolveIndividualArtistName(context) val artist = song.resolveIndividualArtistName(context)
val metadata = val builder =
MediaMetadataCompat.Builder() MediaMetadataCompat.Builder()
.putText(MediaMetadataCompat.METADATA_KEY_TITLE, title) .putText(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title) .putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
@ -110,18 +116,21 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
.putText(MediaMetadataCompat.METADATA_KEY_COMPOSER, artist) .putText(MediaMetadataCompat.METADATA_KEY_COMPOSER, artist)
.putText(MediaMetadataCompat.METADATA_KEY_WRITER, artist) .putText(MediaMetadataCompat.METADATA_KEY_WRITER, artist)
.putText(MediaMetadataCompat.METADATA_KEY_GENRE, song.genre.resolveName(context)) .putText(MediaMetadataCompat.METADATA_KEY_GENRE, song.genre.resolveName(context))
.putText(
METADATA_KEY_PARENT,
parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs))
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.durationMs) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.durationMs)
if (song.track != null) { if (song.track != null) {
metadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, song.track.toLong()) builder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, song.track.toLong())
} }
if (song.disc != null) { if (song.disc != null) {
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, song.disc.toLong()) builder.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, song.disc.toLong())
} }
if (song.album.year != null) { if (song.album.year != null) {
metadata.putString(MediaMetadataCompat.METADATA_KEY_DATE, song.album.year.toString()) builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, song.album.year.toString())
} }
// Normally, android expects one to provide a URI to the metadata instance instead of // Normally, android expects one to provide a URI to the metadata instance instead of
@ -132,25 +141,30 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
song, song,
object : BitmapProvider.Target { object : BitmapProvider.Target {
override fun onCompleted(bitmap: Bitmap?) { override fun onCompleted(bitmap: Bitmap?) {
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap) builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap)
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap) builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
mediaSession.setMetadata(metadata.build()) val metadata = builder.build()
mediaSession.setMetadata(metadata)
notification.updateMetadata(metadata)
callback.onPostNotification(notification)
} }
}) })
} }
override fun onPlayingChanged(isPlaying: Boolean) { override fun onPlayingChanged(isPlaying: Boolean) {
invalidateSessionState() invalidateSessionState()
invalidateNotificationActions()
} }
override fun onRepeatChanged(repeatMode: RepeatMode) { override fun onRepeatChanged(repeatMode: RepeatMode) {
// TODO: Add the custom actions for Android 13
mediaSession.setRepeatMode( mediaSession.setRepeatMode(
when (repeatMode) { when (repeatMode) {
RepeatMode.NONE -> PlaybackStateCompat.REPEAT_MODE_NONE RepeatMode.NONE -> PlaybackStateCompat.REPEAT_MODE_NONE
RepeatMode.TRACK -> PlaybackStateCompat.REPEAT_MODE_ONE RepeatMode.TRACK -> PlaybackStateCompat.REPEAT_MODE_ONE
RepeatMode.ALL -> PlaybackStateCompat.REPEAT_MODE_ALL RepeatMode.ALL -> PlaybackStateCompat.REPEAT_MODE_ALL
}) })
invalidateNotificationActions()
} }
override fun onShuffledChanged(isShuffled: Boolean) { override fun onShuffledChanged(isShuffled: Boolean) {
@ -160,14 +174,18 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
} else { } else {
PlaybackStateCompat.SHUFFLE_MODE_NONE PlaybackStateCompat.SHUFFLE_MODE_NONE
}) })
invalidateNotificationActions()
} }
// --- SETTINGSMANAGER CALLBACKS --- // --- SETTINGSMANAGER CALLBACKS ---
override fun onSettingChanged(key: String) { override fun onSettingChanged(key: String) {
if (key == context.getString(R.string.set_key_show_covers) || when (key) {
key == context.getString(R.string.set_key_quality_covers)) { context.getString(R.string.set_key_show_covers),
updateMediaMetadata(playbackManager.song) context.getString(R.string.set_key_quality_covers) ->
updateMediaMetadata(playbackManager.song, playbackManager.parent)
context.getString(R.string.set_key_alt_notif_action) -> invalidateNotificationActions()
} }
} }
@ -239,6 +257,7 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
// forces the system to re-poll the position. // forces the system to re-poll the position.
// FIXME: For some reason however, positions just DON'T UPDATE AT ALL when you // FIXME: For some reason however, positions just DON'T UPDATE AT ALL when you
// change from FROM THE APP ONLY WHEN THE PLAYER IS PAUSED. // change from FROM THE APP ONLY WHEN THE PLAYER IS PAUSED.
// TODO: Add the custom actions for Android 13
val state = val state =
PlaybackStateCompat.Builder() PlaybackStateCompat.Builder()
.setActions(ACTIONS) .setActions(ACTIONS)
@ -266,9 +285,25 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
mediaSession.setPlaybackState(state.build()) mediaSession.setPlaybackState(state.build())
} }
private fun invalidateNotificationActions() {
notification.updatePlaying(playbackManager.isPlaying)
if (settings.useAltNotifAction) {
notification.updateShuffled(playbackManager.isShuffled)
} else {
notification.updateRepeatMode(playbackManager.repeatMode)
}
if (!provider.isBusy) {
callback.onPostNotification(notification)
}
}
companion object { companion object {
private val emptyMetadata = MediaMetadataCompat.Builder().build() private val emptyMetadata = MediaMetadataCompat.Builder().build()
const val METADATA_KEY_PARENT = BuildConfig.APPLICATION_ID + ".metadata.PARENT"
private const val ACTIONS = private const val ACTIONS =
PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY or
PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_PAUSE or

View file

@ -21,8 +21,8 @@ import android.annotation.SuppressLint
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -31,12 +31,9 @@ import okhttp3.internal.notify
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.newBroadcastPendingIntent import org.oxycblt.auxio.util.newBroadcastPendingIntent
import org.oxycblt.auxio.util.newMainPendingIntent import org.oxycblt.auxio.util.newMainPendingIntent
@ -47,13 +44,9 @@ import org.oxycblt.auxio.util.newMainPendingIntent
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
class NotificationComponent( class NotificationComponent(private val context: Context, sessionToken: MediaSessionCompat.Token) :
private val context: Context, NotificationCompat.Builder(context, CHANNEL_ID) {
private val callback: Callback,
sessionToken: MediaSessionCompat.Token
) : NotificationCompat.Builder(context, CHANNEL_ID) {
private val notificationManager = context.getSystemServiceSafe(NotificationManager::class) private val notificationManager = context.getSystemServiceSafe(NotificationManager::class)
private val provider = BitmapProvider(context)
init { init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -88,61 +81,41 @@ class NotificationComponent(
notificationManager.notify(IntegerTable.PLAYBACK_NOTIFICATION_CODE, build()) notificationManager.notify(IntegerTable.PLAYBACK_NOTIFICATION_CODE, build())
} }
fun release() {
provider.release()
}
// --- STATE FUNCTIONS --- // --- STATE FUNCTIONS ---
/** Set the metadata of the notification using [song]. */ fun updateMetadata(metadata: MediaMetadataCompat) {
fun updateMetadata(song: Song, parent: MusicParent?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
provider.load( // Notification is automatically filled in by media session, ignore
song, return
object : BitmapProvider.Target { }
override fun onCompleted(bitmap: Bitmap?) {
logD("writing ${song.rawName} to notif")
setContentTitle(song.resolveName(context))
setContentText(song.resolveIndividualArtistName(context))
// Starting in API 24, the subtext field changed semantics from being below the setContentTitle(metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE))
// content text to being above the title. setContentText(metadata.getText(MediaMetadataCompat.METADATA_KEY_ARTIST))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSubText(
parent?.resolveName(context)
?: context.getString(R.string.lbl_all_songs))
} else {
setSubText(song.album.resolveName(context))
}
setLargeIcon(bitmap) // Starting in API 24, the subtext field changed semantics from being below the
// content text to being above the title.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSubText(metadata.getText(MediaSessionComponent.METADATA_KEY_PARENT))
} else {
setSubText(metadata.getText(MediaMetadataCompat.METADATA_KEY_ALBUM))
}
callback.onNotificationChanged(song, this@NotificationComponent) setLargeIcon(metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART))
}
})
} }
/** Set the playing icon on the notification */ /** Set the playing icon on the notification */
fun updatePlaying(isPlaying: Boolean) { fun updatePlaying(isPlaying: Boolean) {
mActions[2] = buildPlayPauseAction(context, isPlaying) mActions[2] = buildPlayPauseAction(context, isPlaying)
if (!provider.isBusy) {
callback.onNotificationChanged(null, this)
}
} }
/** Update the first action to reflect the [repeatMode] given. */ /** Update the first action to reflect the [repeatMode] given. */
fun updateRepeatMode(repeatMode: RepeatMode) { fun updateRepeatMode(repeatMode: RepeatMode) {
mActions[0] = buildRepeatAction(context, repeatMode) mActions[0] = buildRepeatAction(context, repeatMode)
if (!provider.isBusy) {
callback.onNotificationChanged(null, this)
}
} }
/** Update the first action to reflect whether the queue is shuffled or not */ /** Update the first action to reflect whether the queue is shuffled or not */
fun updateShuffled(isShuffled: Boolean) { fun updateShuffled(isShuffled: Boolean) {
mActions[0] = buildShuffleAction(context, isShuffled) mActions[0] = buildShuffleAction(context, isShuffled)
if (!provider.isBusy) {
callback.onNotificationChanged(null, this)
}
} }
// --- NOTIFICATION ACTION BUILDERS --- // --- NOTIFICATION ACTION BUILDERS ---

View file

@ -24,7 +24,7 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.media.AudioManager import android.media.AudioManager
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationCompat import android.support.v4.media.MediaMetadataCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayer
@ -75,15 +75,14 @@ import org.oxycblt.auxio.widgets.WidgetProvider
class PlaybackService : class PlaybackService :
Service(), Service(),
Player.Listener, Player.Listener,
NotificationComponent.Callback,
PlaybackStateManager.Controller, PlaybackStateManager.Controller,
MediaSessionComponent.Callback,
Settings.Callback { Settings.Callback {
// Player components // Player components
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private lateinit var replayGainProcessor: ReplayGainAudioProcessor private lateinit var replayGainProcessor: ReplayGainAudioProcessor
// System backend components // System backend components
private lateinit var notificationComponent: NotificationComponent
private lateinit var mediaSessionComponent: MediaSessionComponent private lateinit var mediaSessionComponent: MediaSessionComponent
private lateinit var widgetComponent: WidgetComponent private lateinit var widgetComponent: WidgetComponent
private val systemReceiver = PlaybackReceiver() private val systemReceiver = PlaybackReceiver()
@ -123,8 +122,7 @@ class PlaybackService :
// --- SYSTEM SETUP --- // --- SYSTEM SETUP ---
widgetComponent = WidgetComponent(this) widgetComponent = WidgetComponent(this)
mediaSessionComponent = MediaSessionComponent(this, player) mediaSessionComponent = MediaSessionComponent(this, player, this)
notificationComponent = NotificationComponent(this, this, mediaSessionComponent.token)
IntentFilter().apply { IntentFilter().apply {
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY) addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
@ -177,7 +175,6 @@ class PlaybackService :
widgetComponent.release() widgetComponent.release()
mediaSessionComponent.release() mediaSessionComponent.release()
notificationComponent.release()
player.release() player.release()
logD("Service destroyed") logD("Service destroyed")
@ -260,7 +257,6 @@ class PlaybackService :
logD("Loading ${song.rawName}") logD("Loading ${song.rawName}")
player.setMediaItem(MediaItem.fromUri(song.uri)) player.setMediaItem(MediaItem.fromUri(song.uri))
player.prepare() player.prepare()
notificationComponent.updateMetadata(song, playbackManager.parent)
} }
override fun seekTo(positionMs: Long) { override fun seekTo(positionMs: Long) {
@ -273,18 +269,21 @@ class PlaybackService :
override fun onPlayingChanged(isPlaying: Boolean) { override fun onPlayingChanged(isPlaying: Boolean) {
player.playWhenReady = isPlaying player.playWhenReady = isPlaying
notificationComponent.updatePlaying(isPlaying)
} }
override fun onRepeatChanged(repeatMode: RepeatMode) { override fun onPostNotification(notification: NotificationComponent) {
if (!settings.useAltNotifAction) { if (hasPlayed && playbackManager.song != null) {
notificationComponent.updateRepeatMode(repeatMode) logD(
} mediaSessionComponent.mediaSession.controller.metadata.getText(
} MediaMetadataCompat.METADATA_KEY_TITLE))
override fun onShuffledChanged(isShuffled: Boolean) { if (!isForeground) {
if (settings.useAltNotifAction) { startForeground(IntegerTable.PLAYBACK_NOTIFICATION_CODE, notification.build())
notificationComponent.updateShuffled(isShuffled) isForeground = true
} else {
// If we are already in foreground just update the notification
notification.renotify()
}
} }
} }
@ -295,34 +294,6 @@ class PlaybackService :
getString(R.string.set_key_replay_gain), getString(R.string.set_key_replay_gain),
getString(R.string.set_key_pre_amp_with), getString(R.string.set_key_pre_amp_with),
getString(R.string.set_key_pre_amp_without) -> onTracksChanged(player.currentTracks) getString(R.string.set_key_pre_amp_without) -> onTracksChanged(player.currentTracks)
getString(R.string.set_key_show_covers),
getString(R.string.set_key_quality_covers) ->
playbackManager.song?.let { song ->
notificationComponent.updateMetadata(song, playbackManager.parent)
}
getString(R.string.set_key_alt_notif_action) ->
if (settings.useAltNotifAction) {
onShuffledChanged(playbackManager.isShuffled)
} else {
onRepeatChanged(playbackManager.repeatMode)
}
}
}
// --- NOTIFICATION CALLBACKS ---
override fun onNotificationChanged(song: Song?, component: NotificationComponent) {
if (hasPlayed && playbackManager.song != null) {
logD("Starting foreground/notifying: ${song?.rawName}")
logD(NotificationCompat.getContentTitle(component.build()))
if (!isForeground) {
startForeground(IntegerTable.PLAYBACK_NOTIFICATION_CODE, component.build())
isForeground = true
} else {
// If we are already in foreground just update the notification
notificationComponent.renotify()
}
} }
} }