androidx / androidx/media

How to handle ForegroundServiceStartNotAllowedException while retrying playback after a network timeout error

Open
#1,342 2 comments 1 reaction 1 assignee View on GitHub

@marcbaechinger is already working on this.

Since May 7, 2024.

question
Dominant language
Java
Stars
3k
Forks
955
Avg merge
12d 14h
Merged PRs (30d)
2

Description

I want to retry playback when a player error is detected (SourceException indicating timeout). However, while attempting to retry, I get this exception. UI (MediaController) is intact (player in idle state). My retry code in the service determines that foreground service is not running (as determined by isServiceRunningInForeground()) in the following code. However, I see it is still running when I use the adb shell command.

adb shell dumpsys activity services com.myapp.MyMediaService | grep app=
    app=ProcessRecord{dd404dc 7272:com.myapp.player/u0a172}

I also see allowStartForeground=PROC_STATE_TOP in the above command output (while playback is going on). And then it changes to DENIED which indicates

  1. Is the foreground service really running? not sure why I get inconsistent observation
  2. how do I make retry work in this case? are there any best practices document that explains how this can be achieved while using Media3?

Here is my code:

    class MyMediaService : MediaSessionService() {
        private var retryJob: Job? = null
        private var mediaSession: MediaSession? = null
        private val retryScope = CoroutineScope(Dispatchers.IO)
    
        override fun onCreate() {
            super.onCreate()
            val customFactory = object : MediaSource.Factory { ... }
            val player = ExoPlayer.Builder(this)
                .setMediaSourceFactory(customFactory)
                .build()
                .apply {
                    addListener(object : Player.Listener {
                        override fun onPlayerError(error: PlaybackException) {
                            super.onPlayerError(error)
                            when (error) {
                                is ExoPlaybackException -> {
                                    if (error.type == ExoPlaybackException.TYPE_SOURCE) {
                                        retryJob?.cancel()
                                        retryJob = retryScope.launch { // Just retry once
                                            delay(5000L)
                                            restartPlayback()
                                        }
                                    }
                                }
                            }
                        }
                    })
                    playWhenReady = true
                }
    
            val intent = packageManager.getLaunchIntentForPackage(packageName)
            val pendingIntent = PendingIntent.getActivity(this, 0, intent, FLAG_IMMUTABLE)
            mediaSession = MediaSession.Builder(this, player)
                .setId("XXX")
                .setSessionActivity(pendingIntent)
                .build()
    
            setListener(object : Listener {
                override fun onForegroundServiceStartNotAllowedException() {
                    super.onForegroundServiceStartNotAllowedException()
                    Timber.d("Overriding onForegroundServiceStartNotAllowedException()")
                }
            })
        }
    
        override fun onTaskRemoved(rootIntent: Intent?) {
            mediaSession?.player?.stop()
            stopForeground(STOP_FOREGROUND_REMOVE)
            stopSelf()
            super.onTaskRemoved(rootIntent)
        }
    
        override fun onDestroy() {
            mediaSession?.run {
                player.release()
                release()
                mediaSession = null
            }
            super.onDestroy()
        }
    
        override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
            return mediaSession
        }
    
        @Suppress("DEPRECATION")
        private fun isServiceRunningInForeground(): Boolean {
            val manager = getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
            val runningServices = manager?.getRunningServices(Int.MAX_VALUE)
            return runningServices?.any {
                it.foreground && it.service.className == MyMediaService::class.java.name
            } ?: false
        }
    
        private fun startForegroundService() {
            val serviceIntent = Intent(this, MyMediaService::class.java)
            try {
                ContextCompat.startForegroundService(this, serviceIntent)
            } catch (e: ForegroundServiceStartNotAllowedException) {
                Timber.e("ForegroundServiceStartNotAllowedException caught: ${e.message}")
            }
        }
    
        private suspend fun restartPlayback() = withContext(Dispatchers.Main) {
            if (!isServiceRunningInForeground()) {
                startForegroundService()
                //startForeground()
                mediaSession?.player?.let { player ->
                    if (player.playbackState == Player.STATE_IDLE || player.playbackState == Player.STATE_ENDED) {
                        player.prepare()
                    } else if (player.playbackState == Player.STATE_READY) {
                        player.play()
                    }
                }
            } else {
                mediaSession?.player?.prepare()
                mediaSession?.player?.play()
            }
        }
    }

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.