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.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<PlayerService>(), serviceConnection, Context.BIND_AUTO_CREATE)
startService(intent<PlayerService>())
}
override fun onStop() {

View File

@@ -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<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 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() {