Improve notification management

This commit is contained in:
vfsfitvnm
2022-06-26 17:05:20 +02:00
parent 308808a363
commit c336a274d8
2 changed files with 67 additions and 51 deletions

View File

@@ -31,7 +31,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.valentinilk.shimmer.LocalShimmerTheme import com.valentinilk.shimmer.LocalShimmerTheme
import com.valentinilk.shimmer.defaultShimmerTheme 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.screens.IntentUriScreen
import it.vfsfitvnm.vimusic.ui.styling.* import it.vfsfitvnm.vimusic.ui.styling.*
import it.vfsfitvnm.vimusic.ui.views.PlayerView 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() { class MainActivity : ComponentActivity() {
@@ -67,7 +69,6 @@ class MainActivity : ComponentActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
bindService(intent<PlayerService>(), serviceConnection, Context.BIND_AUTO_CREATE) bindService(intent<PlayerService>(), serviceConnection, Context.BIND_AUTO_CREATE)
startService(intent<PlayerService>())
} }
override fun onStop() { override fun onStop() {

View File

@@ -1,5 +1,6 @@
package it.vfsfitvnm.vimusic.services package it.vfsfitvnm.vimusic.services
import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.Service import android.app.Service
@@ -11,8 +12,10 @@ import android.graphics.drawable.BitmapDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.support.v4.media.MediaMetadataCompat 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.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat import android.support.v4.media.session.PlaybackStateCompat
import android.support.v4.media.session.PlaybackStateCompat.*
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.toBitmap 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 cache: SimpleCache
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private val stateBuilder = PlaybackStateCompat.Builder() private val stateBuilder = Builder()
.setActions( .setActions(
PlaybackStateCompat.ACTION_PLAY or ACTION_PLAY
PlaybackStateCompat.ACTION_PAUSE or or ACTION_PAUSE
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or or ACTION_SKIP_TO_PREVIOUS
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or or ACTION_SKIP_TO_NEXT
PlaybackStateCompat.ACTION_PLAY_PAUSE or or ACTION_PLAY_PAUSE
PlaybackStateCompat.ACTION_SEEK_TO or ACTION_SEEK_TO
) )
private val metadataBuilder = MediaMetadataCompat.Builder() private val metadataBuilder = MediaMetadataCompat.Builder()
@@ -78,6 +81,29 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
private val songPendingLoudnessDb = mutableMapOf<String, Float?>() private val songPendingLoudnessDb = mutableMapOf<String, Float?>()
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<PlayerService>())
} else {
startService(intent<PlayerService>())
}
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 onBind(intent: Intent?) = Binder()
override fun onCreate() { override fun onCreate() {
@@ -117,6 +143,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
mediaSession.setCallback(SessionCallback(player)) mediaSession.setCallback(SessionCallback(player))
mediaSession.setPlaybackState(stateBuilder.build()) mediaSession.setPlaybackState(stateBuilder.build())
mediaSession.isActive = true mediaSession.isActive = true
mediaSession.controller.registerCallback(mediaControllerCallback)
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 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.removeListener(this)
player.stop() player.stop()
player.release() player.release()
mediaSession.controller.unregisterCallback(mediaControllerCallback)
mediaSession.isActive = false mediaSession.isActive = false
mediaSession.release() mediaSession.release()
cache.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( override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo, oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo, newPosition: Player.PositionInfo,
@Player.DiscontinuityReason reason: Int @Player.DiscontinuityReason reason: Int
) { ) {
stateBuilder stateBuilder
.setState(PlaybackStateCompat.STATE_NONE, newPosition.positionMs, 1f) .setState(STATE_NONE, newPosition.positionMs, 1f)
.setBufferedPosition(player.bufferedPosition) .setBufferedPosition(player.bufferedPosition)
updateNotification() mediaSession.setPlaybackState(stateBuilder.build())
} }
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
stateBuilder stateBuilder
.setState( .setState(if (isPlaying) STATE_PLAYING else STATE_PAUSED, player.currentPosition, 1f)
if (isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED,
player.currentPosition,
1f
)
.setBufferedPosition(player.bufferedPosition) .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()) mediaSession.setPlaybackState(stateBuilder.build())
createNotification()
} }
private fun createNotification() { private fun notification(): Notification {
fun NotificationCompat.Builder.addMediaAction( fun NotificationCompat.Builder.addMediaAction(
@DrawableRes resId: Int, @DrawableRes resId: Int,
description: String, description: String,
@PlaybackStateCompat.MediaKeyAction command: Long @MediaKeyAction command: Long
): NotificationCompat.Builder { ): NotificationCompat.Builder {
return addAction( return addAction(
NotificationCompat.Action( NotificationCompat.Action(
@@ -247,20 +273,13 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
.setShowActionsInCompactView(0, 1, 2) .setShowActionsInCompactView(0, 1, 2)
.setMediaSession(mediaSession.sessionToken) .setMediaSession(mediaSession.sessionToken)
) )
.addMediaAction(R.drawable.play_skip_back, "Skip back", ACTION_SKIP_TO_PREVIOUS)
.addMediaAction( .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) 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) "Play" else "Pause",
if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) PlaybackStateCompat.ACTION_PLAY else PlaybackStateCompat.ACTION_PAUSE if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) ACTION_PLAY else ACTION_PAUSE
)
.addMediaAction(
R.drawable.play_skip_forward,
"Skip forward",
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
) )
.addMediaAction(R.drawable.play_skip_forward, "Skip forward", ACTION_SKIP_TO_NEXT)
if (lastArtworkUri != mediaMetadata.artworkUri) { if (lastArtworkUri != mediaMetadata.artworkUri) {
lastArtworkUri = mediaMetadata.artworkUri lastArtworkUri = mediaMetadata.artworkUri
@@ -272,28 +291,24 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
onError = { _, _ -> onError = { _, _ ->
lastBitmap = resources.getDrawable(R.drawable.disc_placeholder, null) lastBitmap = resources.getDrawable(R.drawable.disc_placeholder, null)
?.toBitmap(notificationThumbnailSize, notificationThumbnailSize) ?.toBitmap(notificationThumbnailSize, notificationThumbnailSize)
notificationManager.notify(NotificationId, builder.setLargeIcon(lastBitmap).build()) notificationManager.notify(
NotificationId,
builder.setLargeIcon(lastBitmap).build()
)
}, },
onSuccess = { _, result -> onSuccess = { _, result ->
lastBitmap = (result.drawable as BitmapDrawable).bitmap lastBitmap = (result.drawable as BitmapDrawable).bitmap
notificationManager.notify(NotificationId, builder.setLargeIcon(lastBitmap).build()) notificationManager.notify(
NotificationId,
builder.setLargeIcon(lastBitmap).build()
)
} }
) )
.build() .build()
) )
} }
val notificationCompat = builder.build() return 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)
}
} }
private fun createNotificationChannel() { private fun createNotificationChannel() {