Add hack to avoid service destroy when notification is paused
This commit is contained in:
parent
79ba4ff993
commit
7e42820e7c
1 changed files with 66 additions and 6 deletions
|
@ -11,6 +11,8 @@ import android.content.res.Configuration
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
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.MediaControllerCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
@ -21,7 +23,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat.startForegroundService
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.media.session.MediaButtonReceiver
|
import androidx.media.session.MediaButtonReceiver
|
||||||
import androidx.media3.common.*
|
import androidx.media3.common.*
|
||||||
|
@ -65,6 +67,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
private lateinit var player: ExoPlayer
|
private lateinit var player: ExoPlayer
|
||||||
|
|
||||||
private val stateBuilder = Builder()
|
private val stateBuilder = Builder()
|
||||||
|
.setState(STATE_NONE, 0, 1f)
|
||||||
.setActions(
|
.setActions(
|
||||||
ACTION_PLAY
|
ACTION_PLAY
|
||||||
or ACTION_PAUSE
|
or ACTION_PAUSE
|
||||||
|
@ -88,17 +91,26 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
|
|
||||||
private val songPendingLoudnessDb = mutableMapOf<String, Float?>()
|
private val songPendingLoudnessDb = mutableMapOf<String, Float?>()
|
||||||
|
|
||||||
|
private var hack: Hack? = null
|
||||||
|
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
private val mediaControllerCallback = object : MediaControllerCompat.Callback() {
|
private val mediaControllerCallback = object : MediaControllerCompat.Callback() {
|
||||||
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
|
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
|
||||||
when (state?.state) {
|
when (state?.state) {
|
||||||
STATE_PLAYING -> {
|
STATE_PLAYING -> {
|
||||||
startForegroundService(this@PlayerService, intent<PlayerService>())
|
ContextCompat.startForegroundService(this@PlayerService, intent<PlayerService>())
|
||||||
startForeground(NotificationId, notification())
|
startForeground(NotificationId, notification())
|
||||||
|
|
||||||
|
hack?.stop()
|
||||||
}
|
}
|
||||||
STATE_PAUSED -> {
|
STATE_PAUSED -> {
|
||||||
if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) {
|
if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) {
|
||||||
stopForeground(false)
|
stopForeground(false)
|
||||||
|
|
||||||
notificationManager.notify(NotificationId, notification())
|
notificationManager.notify(NotificationId, notification())
|
||||||
|
|
||||||
|
hack?.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STATE_NONE -> {
|
STATE_NONE -> {
|
||||||
|
@ -113,7 +125,18 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent?) = Binder()
|
private val binder = Binder()
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): Binder {
|
||||||
|
hack?.stop()
|
||||||
|
hack = null
|
||||||
|
return binder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnbind(intent: Intent?): Boolean {
|
||||||
|
hack = Hack()
|
||||||
|
return super.onUnbind(intent)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
@ -221,6 +244,9 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hack?.stop()
|
||||||
|
hack = null
|
||||||
|
|
||||||
player.removeListener(this)
|
player.removeListener(this)
|
||||||
player.stop()
|
player.stop()
|
||||||
player.release()
|
player.release()
|
||||||
|
@ -229,6 +255,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
mediaSession.isActive = false
|
mediaSession.isActive = false
|
||||||
mediaSession.release()
|
mediaSession.release()
|
||||||
cache.release()
|
cache.release()
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +371,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
.setContentText(mediaMetadata.artist)
|
.setContentText(mediaMetadata.artist)
|
||||||
.setSubText(player.playerError?.message)
|
.setSubText(player.playerError?.message)
|
||||||
.setLargeIcon(bitmapProvider.bitmap)
|
.setLargeIcon(bitmapProvider.bitmap)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(false)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setShowWhen(false)
|
.setShowWhen(false)
|
||||||
.setSmallIcon(player.playerError?.let { R.drawable.alert_circle }
|
.setSmallIcon(player.playerError?.let { R.drawable.alert_circle }
|
||||||
|
@ -354,6 +381,9 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
.setDeleteIntent(broadCastPendingIntent<StopServiceBroadcastReceiver>())
|
.setDeleteIntent(broadCastPendingIntent<StopServiceBroadcastReceiver>())
|
||||||
.setChannelId(NotificationChannelId)
|
.setChannelId(NotificationChannelId)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
|
.setSilent(true)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
|
||||||
.setStyle(
|
.setStyle(
|
||||||
androidx.media.app.NotificationCompat.MediaStyle()
|
androidx.media.app.NotificationCompat.MediaStyle()
|
||||||
.setShowActionsInCompactView(0, 1, 2)
|
.setShowActionsInCompactView(0, 1, 2)
|
||||||
|
@ -385,7 +415,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
NotificationChannelId,
|
NotificationChannelId,
|
||||||
"Now playing",
|
"Now playing",
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -592,7 +622,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SessionCallback(private val player: Player) : MediaSessionCompat.Callback() {
|
private inner class SessionCallback(private val player: Player) : MediaSessionCompat.Callback() {
|
||||||
override fun onPlay() = player.play()
|
override fun onPlay() = player.play()
|
||||||
override fun onPause() = player.pause()
|
override fun onPause() = player.pause()
|
||||||
override fun onSkipToPrevious() = player.seekToPrevious()
|
override fun onSkipToPrevious() = player.seekToPrevious()
|
||||||
|
@ -606,6 +636,36 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/q/53502244/16885569
|
||||||
|
private inner class Hack: Runnable {
|
||||||
|
private var isStarted = false
|
||||||
|
private val intervalMs = 30_000L
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun start() {
|
||||||
|
if (!isStarted) {
|
||||||
|
isStarted = true
|
||||||
|
handler.postDelayed(this, intervalMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun stop() {
|
||||||
|
if (isStarted) {
|
||||||
|
handler.removeCallbacks(this)
|
||||||
|
isStarted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
if (player.playbackState == Player.STATE_ENDED || !player.playWhenReady) {
|
||||||
|
startForeground(NotificationId, notification())
|
||||||
|
stopForeground(false)
|
||||||
|
handler.postDelayed(this, intervalMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NotificationId = 1001
|
private const val NotificationId = 1001
|
||||||
private const val NotificationChannelId = "default_channel_id"
|
private const val NotificationChannelId = "default_channel_id"
|
||||||
|
|
Loading…
Add table
Reference in a new issue