Skip to content

[Bug] When requesting audio focus to the application, the focus is immediately lost #615

@fabiorbap

Description

@fabiorbap

Describe the bug

I have a class which sets a TtsNavigator through

        val ttsNavigator = ttsNavigatorFactory?.createNavigator(
            this,
            initialLocator = locator,
            initialPreferences = preferencesManager.value
        )

When I play it, I request the audio focus because I want to control what happens when there's a gain/loss of audio focus (with incoming messages, calls etc).
However, whenever I run ttsNavigator.play() and request the focus, it's immediately lost. This is the code related to audio focusing:

    private val focusRequest
        @RequiresApi(Build.VERSION_CODES.O)
        get() = AudioFocusRequest.Builder(AUDIOFOCUS_GAIN).run {
            setAudioAttributes(AudioAttributes.Builder().run {
                setUsage(AudioAttributes.USAGE_MEDIA)
                setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                setWillPauseWhenDucked(true)
                build()
            })
            setAcceptsDelayedFocusGain(true)
            setOnAudioFocusChangeListener(afChangeListener)
            build()
        }

    private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
        when (focusChange) {
            AUDIOFOCUS_LOSS -> {
                abandonAudioFocusRequest()
                navigatorNow.value?.pause()
            }
            AUDIOFOCUS_LOSS_TRANSIENT, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> navigatorNow.value?.pause()
            AUDIOFOCUS_GAIN -> navigatorNow.value?.play()
        }
    }

    @Suppress("DEPRECATION")
    private fun abandonAudioFocusRequest() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            audioManager?.abandonAudioFocusRequest(focusRequest)
        } else {
            audioManager?.abandonAudioFocus(afChangeListener)
        }
    }

    @Suppress("DEPRECATION")
    private fun requestAudioFocus(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            audioManager?.requestAudioFocus(focusRequest) == AUDIOFOCUS_REQUEST_GRANTED
        } else {
            audioManager?.requestAudioFocus(
                afChangeListener,
                AudioManager.STREAM_MUSIC,
                AudioManager.AUDIOFOCUS_GAIN
            ) == AUDIOFOCUS_REQUEST_GRANTED
        }
    }

    fun play() {
        navigatorNow.value?.play()
        requestAudioFocus()
    }

As mentioned, when I start playing the TTS, I get AUDIOFOCUS_LOSS here:

        when (focusChange) {
            AUDIOFOCUS_LOSS -> {
                abandonAudioFocusRequest()
                navigatorNow.value?.pause()
            }
            AUDIOFOCUS_LOSS_TRANSIENT, AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> navigatorNow.value?.pause()
            AUDIOFOCUS_GAIN -> navigatorNow.value?.play()
        }
    }

Once I lose the audio focus, I can't do anything with the listener anymore.
One thing that I noticed in the logs is that it seems that Readium "steals" the focus when I hit play. I find this a bit weird, because Readium is being executed in my application, and so it should not steal the focus.
I saw in the Readium code that there is audio focus management.
Here are some logs that I found:

requestAudioFocus() from uid/pid 11799/7267 AA=USAGE_MEDIA/CONTENT_TYPE_SPEECH clientId=android.media.AudioManager@becd5abee.<my class>$$ExternalSyntheticLambda0@a334408 callingPack=<package> req=1 flags=0x3 sdk=34
requestAudioFocus() from uid/pid 11799/7267 AA=USAGE_MEDIA/CONTENT_TYPE_SPEECH clientId=android.media.AudioManager@becd5aborg.readium.navigator.media.tts.session.AudioFocusManager$AudioFocusListener@b212187 callingPack=<package> req=1 flags=0x2 sdk=34
dispatching onAudioFocusChange(-1) to android.media.AudioManager@becd5abee.<my class>$$ExternalSyntheticLambda0@a334408
abandonAudioFocus() from uid/pid 11799/7267 clientId=android.media.AudioManager@becd5abee.<my class>$$ExternalSyntheticLambda0@a334408
abandonAudioFocus, clientId = android.media.AudioManager@becd5abee.<my class>$$ExternalSyntheticLambda0@a334408
dispatching onAudioFocusChange(1) to android.media.AudioManager@becd5aborg.readium.navigator.media.tts.session.AudioFocusManager$AudioFocusListener@b212187

You can see that after my class requests the audio focus, Readium gets it soon after, I don't know why. Once that happens my app never gets the focus back.
It's curious that callingPack for both is my app's package, so I don't understand why my app loses the focus.
I don't know if I'm missing something, or if this is a bug, but please let me know what's the right way to manage audio focus so that I have full control over it while I'm using TTS.

How to reproduce?

  1. Open any book
  2. Initiate the TTS and start playing it
  3. Perform any interruption with sound (notification, message, incoming call)
  4. The TTS will not stop when the interruption happens, and it is not possible to set an audio focus listener for that either

Readium version

3.0.0

Android API version

12 (API 32), 13 (API 33), 14 (API 34)

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions