Browse Source

Add hack to avoid service destroy when notification is paused

vfsfitvnm 3 years ago
parent
commit
7e42820e7c
1 changed files with 66 additions and 6 deletions
  1. 66 6
      app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt

+ 66 - 6
app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt

@@ -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"