Skip to content

Commit

Permalink
fix(android): foreground issues & notification icon (#2048)
Browse files Browse the repository at this point in the history
* fix(android): foreground issues (close #2045)

- move execution of foregrounding and backgrounding to NotificationState.POSTED in order to avoid race condition where notificationId has not been set yet
- avoid comparing to player states that don’t affect foregrounding, by ignoring LOADING, BUFFERING and READY states – all of which can occur both when playing or while paused
- also avoid the initial IDLE state, since we are only interested when the player would become idle (i.e. all tracks were removed)
- ENDED state should also cause foregrounding to stop (without removing notification)

* Fix: Add missing icon to notification (close #2049)

Using `ExoPlayerR.drawable.exo_notification_small_icon` which is exoplayer’s default notification icon.
  • Loading branch information
puckey authored Jul 4, 2023
1 parent 8331979 commit dc4fb1d
Showing 1 changed file with 43 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.doublesymmetry.trackplayer.utils.BundleUtils.setRating
import com.facebook.react.HeadlessJsTaskService
import com.facebook.react.bridge.Arguments
import com.facebook.react.jstasks.HeadlessJsTaskConfig
import com.google.android.exoplayer2.ui.R as ExoPlayerR
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.flow
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -114,6 +115,7 @@ class MusicService : HeadlessJsTaskService() {
val notificationBuilder = NotificationCompat.Builder(this, name)
.setPriority(PRIORITY_LOW)
.setCategory(Notification.CATEGORY_SERVICE)
.setSmallIcon(ExoPlayerR.drawable.exo_notification_small_icon)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
notificationBuilder.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
}
Expand Down Expand Up @@ -491,6 +493,8 @@ class MusicService : HeadlessJsTaskService() {
// Implementation based on https://github.com/Automattic/pocket-casts-android/blob/ee8da0c095560ef64a82d3a31464491b8d713104/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/PlaybackService.kt#L218
var notificationId: Int? = null
var notification: Notification? = null
var stopForegroundWhenNotOngoing = false
var removeNotificationWhenNotOngoing = false

fun startForegroundIfNecessary() {
if (isForegroundService()) {
Expand Down Expand Up @@ -528,34 +532,52 @@ class MusicService : HeadlessJsTaskService() {
}
}

scope.launch {
val BACKGROUNDABLE_STATES = listOf(
AudioPlayerState.IDLE,
AudioPlayerState.ENDED,
AudioPlayerState.STOPPED,
AudioPlayerState.ERROR,
AudioPlayerState.PAUSED
)
val REMOVABLE_STATES = listOf(
AudioPlayerState.IDLE,
AudioPlayerState.STOPPED,
AudioPlayerState.ERROR
)
val LOADING_STATES = listOf(
AudioPlayerState.LOADING,
AudioPlayerState.READY,
AudioPlayerState.BUFFERING
)
var stateCount = 0
event.stateChange.collect {
stateCount++
if (it in LOADING_STATES) return@collect;
// Skip initial idle state, since we are only interested when
// state becomes idle after not being idle
stopForegroundWhenNotOngoing = stateCount > 1 && it in BACKGROUNDABLE_STATES
removeNotificationWhenNotOngoing = stopForegroundWhenNotOngoing && it in REMOVABLE_STATES
}
}

scope.launch {
event.notificationStateChange.collect {
when (it) {
is NotificationState.POSTED -> {
Timber.d("notification posted with id=%s, ongoing=%s", it.notificationId, it.ongoing)
notificationId = it.notificationId;
notification = it.notification;
}
else -> {}
}
}
}
scope.launch {
event.stateChange.collect {
when (it) {
AudioPlayerState.BUFFERING,
AudioPlayerState.PLAYING -> {
startForegroundIfNecessary()
}
AudioPlayerState.IDLE,
AudioPlayerState.STOPPED,
AudioPlayerState.ERROR,
AudioPlayerState.PAUSED -> {
val removeNotification = it != AudioPlayerState.PAUSED && it != AudioPlayerState.ERROR
if (removeNotification || isForegroundService()) {
@Suppress("DEPRECATION")
stopForeground(removeNotification)
Timber.d("stopped foregrounding")
if (it.ongoing) {
if (player.playWhenReady) {
startForegroundIfNecessary()
}
} else if (stopForegroundWhenNotOngoing) {
if (removeNotificationWhenNotOngoing || isForegroundService()) {
@Suppress("DEPRECATION")
stopForeground(removeNotificationWhenNotOngoing)
Timber.d("stopped foregrounding%s", if (removeNotificationWhenNotOngoing) " and removed notification" else "")
}
}
}
else -> {}
Expand Down

0 comments on commit dc4fb1d

Please sign in to comment.