From c336a274d85bb6f99dc4af266d9f5591c1c8a7f0 Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Sun, 26 Jun 2022 17:05:20 +0200 Subject: [PATCH] Improve notification management --- .../it/vfsfitvnm/vimusic/MainActivity.kt | 7 +- .../vimusic/services/PlayerService.kt | 111 ++++++++++-------- 2 files changed, 67 insertions(+), 51 deletions(-) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt index 14fb30d..9a8056e 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/MainActivity.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp -import androidx.media3.common.Player import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.valentinilk.shimmer.LocalShimmerTheme import com.valentinilk.shimmer.defaultShimmerTheme @@ -45,7 +44,10 @@ import it.vfsfitvnm.vimusic.ui.screens.HomeScreen import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen import it.vfsfitvnm.vimusic.ui.styling.* import it.vfsfitvnm.vimusic.ui.views.PlayerView -import it.vfsfitvnm.vimusic.utils.* +import it.vfsfitvnm.vimusic.utils.LocalPreferences +import it.vfsfitvnm.vimusic.utils.intent +import it.vfsfitvnm.vimusic.utils.rememberHapticFeedback +import it.vfsfitvnm.vimusic.utils.rememberPreferences class MainActivity : ComponentActivity() { @@ -67,7 +69,6 @@ class MainActivity : ComponentActivity() { override fun onStart() { super.onStart() bindService(intent(), serviceConnection, Context.BIND_AUTO_CREATE) - startService(intent()) } override fun onStop() { diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt index d158b5f..97b2718 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt @@ -1,5 +1,6 @@ package it.vfsfitvnm.vimusic.services +import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service @@ -11,8 +12,10 @@ import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Build import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaControllerCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat +import android.support.v4.media.session.PlaybackStateCompat.* import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import androidx.core.graphics.drawable.toBitmap @@ -52,14 +55,14 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback private lateinit var cache: SimpleCache private lateinit var player: ExoPlayer - private val stateBuilder = PlaybackStateCompat.Builder() + private val stateBuilder = Builder() .setActions( - PlaybackStateCompat.ACTION_PLAY or - PlaybackStateCompat.ACTION_PAUSE or - PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or - PlaybackStateCompat.ACTION_SKIP_TO_NEXT or - PlaybackStateCompat.ACTION_PLAY_PAUSE or - PlaybackStateCompat.ACTION_SEEK_TO + ACTION_PLAY + or ACTION_PAUSE + or ACTION_SKIP_TO_PREVIOUS + or ACTION_SKIP_TO_NEXT + or ACTION_PLAY_PAUSE + or ACTION_SEEK_TO ) private val metadataBuilder = MediaMetadataCompat.Builder() @@ -78,6 +81,29 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback private val songPendingLoudnessDb = mutableMapOf() + private val mediaControllerCallback = object : MediaControllerCompat.Callback() { + override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { + when (state?.state) { + STATE_PLAYING -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(intent()) + } else { + startService(intent()) + } + + startForeground(NotificationId, notification()) + } + STATE_PAUSED -> { + if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) { + stopForeground(false) + notificationManager.notify(NotificationId, notification()) + } + } + else -> {} + } + } + } + override fun onBind(intent: Intent?) = Binder() override fun onCreate() { @@ -117,6 +143,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback mediaSession.setCallback(SessionCallback(player)) mediaSession.setPlaybackState(stateBuilder.build()) mediaSession.isActive = true + mediaSession.controller.registerCallback(mediaControllerCallback) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -137,6 +164,8 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback player.removeListener(this) player.stop() player.release() + + mediaSession.controller.unregisterCallback(mediaControllerCallback) mediaSession.isActive = false mediaSession.release() cache.release() @@ -179,44 +208,41 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback } } + override fun onPlaybackStateChanged(@Player.State playbackState: Int) { + if (playbackState == Player.STATE_READY) { + if (player.duration != C.TIME_UNSET) { + metadataBuilder + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, player.duration) + mediaSession.setMetadata(metadataBuilder.build()) + } + } + } + override fun onPositionDiscontinuity( oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, @Player.DiscontinuityReason reason: Int ) { stateBuilder - .setState(PlaybackStateCompat.STATE_NONE, newPosition.positionMs, 1f) + .setState(STATE_NONE, newPosition.positionMs, 1f) .setBufferedPosition(player.bufferedPosition) - updateNotification() + mediaSession.setPlaybackState(stateBuilder.build()) } override fun onIsPlayingChanged(isPlaying: Boolean) { stateBuilder - .setState( - if (isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, - player.currentPosition, - 1f - ) + .setState(if (isPlaying) STATE_PLAYING else STATE_PAUSED, player.currentPosition, 1f) .setBufferedPosition(player.bufferedPosition) - updateNotification() - } - - private fun updateNotification() { - if (player.duration != C.TIME_UNSET) { - metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, player.duration) - mediaSession.setMetadata(metadataBuilder.build()) - } mediaSession.setPlaybackState(stateBuilder.build()) - createNotification() } - private fun createNotification() { + private fun notification(): Notification { fun NotificationCompat.Builder.addMediaAction( @DrawableRes resId: Int, description: String, - @PlaybackStateCompat.MediaKeyAction command: Long + @MediaKeyAction command: Long ): NotificationCompat.Builder { return addAction( NotificationCompat.Action( @@ -247,20 +273,13 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback .setShowActionsInCompactView(0, 1, 2) .setMediaSession(mediaSession.sessionToken) ) + .addMediaAction(R.drawable.play_skip_back, "Skip back", ACTION_SKIP_TO_PREVIOUS) .addMediaAction( - R.drawable.play_skip_back, - "Skip back", - PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - ).addMediaAction( if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) R.drawable.play else R.drawable.pause, if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) "Play" else "Pause", - if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) PlaybackStateCompat.ACTION_PLAY else PlaybackStateCompat.ACTION_PAUSE - ) - .addMediaAction( - R.drawable.play_skip_forward, - "Skip forward", - PlaybackStateCompat.ACTION_SKIP_TO_NEXT + if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) ACTION_PLAY else ACTION_PAUSE ) + .addMediaAction(R.drawable.play_skip_forward, "Skip forward", ACTION_SKIP_TO_NEXT) if (lastArtworkUri != mediaMetadata.artworkUri) { lastArtworkUri = mediaMetadata.artworkUri @@ -272,28 +291,24 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback onError = { _, _ -> lastBitmap = resources.getDrawable(R.drawable.disc_placeholder, null) ?.toBitmap(notificationThumbnailSize, notificationThumbnailSize) - notificationManager.notify(NotificationId, builder.setLargeIcon(lastBitmap).build()) + notificationManager.notify( + NotificationId, + builder.setLargeIcon(lastBitmap).build() + ) }, onSuccess = { _, result -> lastBitmap = (result.drawable as BitmapDrawable).bitmap - notificationManager.notify(NotificationId, builder.setLargeIcon(lastBitmap).build()) + notificationManager.notify( + NotificationId, + builder.setLargeIcon(lastBitmap).build() + ) } ) .build() ) } - val notificationCompat = builder.build() - startForeground(NotificationId, notificationCompat) - - if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - stopForeground(STOP_FOREGROUND_DETACH) - } else { - stopForeground(false) - } - notificationManager.notify(NotificationId, notificationCompat) - } + return builder.build() } private fun createNotificationChannel() {