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