androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.64k stars 389 forks source link

Android 15 AudioFocus #1815

Open Tolriq opened 1 day ago

Tolriq commented 1 day ago

I completely missed a change in Android 15 about audio focus : https://developer.android.com/about/versions/15/behavior-changes-15#audio-focus

With a link to the doc page https://developer.android.com/media/optimize/audio-focus that absolutely does not cover this new major restriction.

We can no more request focus if the app is not in foreground and of course we can no more go to foreground from the background.

This breaks features like auto play on headset plug, auto play on bt connection, any kind of automations via a published API and a few other cases.

Is there some things planned around that or documentation or are we really supposed to ask the user to exclude the app from battery restriction and by doing so generate the complete opposite of what all those limitations are supposed to do?

Media3 already catch the many ways the startForeground can be refused, so is there something planned to ensure we can request the focus and play music or we are all out of luck?

tonihei commented 1 day ago

This breaks features like auto play on headset plug, auto play on bt connection, any kind of automations via a published API and a few other cases.

Were you actually able to confirm this somewhere? I tested this recently to ensure the Media3 implementation is compatible with this change and couldn't observe any problems.

Tolriq commented 1 day ago

Media3 does not offer auto play on bt connection or headset connection or an API to automate pausing / playing based on external events. It just react to the buttons that arrive via media session and will work most of the time if the startforeground happens quickly enough, but there's cases where the startForeground will fails hence why it's catched in the session code and then audio focus will fails inside media3 too in those cases. (I also wonder how session works when receiving a next command since it seems to only go foreground on a play command I assume a next command when paused does not start playback ? Else it would fail now, probably a case to test if it's supposed to start playback)

And this still leaves the issue for all the other use case where the user want autoplay without clicking on buttons.

But yes I've confirmed in my app that I can't go foreground from an intent receiver for bt/headset connection events (or my api) and that then the audio focus request fails with a rejection.

I can ask the users to exclude the app from battery restriction to be able to go foreground as a workaround, but this is music playing they should not have to do that.

tonihei commented 1 day ago

I can't go foreground from an intent receiver for bt/headset connection events (or my api) and that then the audio focus request fails with a rejection

That part is interesting and may be a signal that it works as intended. If you don't get an exemption to start your app into foreground, then it's consistent to also not get the audio focus. Getting audio focus without foreground permission may actually be quite confusing because it would allow playback to start for a bit, but then the system could just kill your app mid-playback. All the cases I tested were where the app is not yet in foreground and requests audio focus before requesting the foreground state. This seems to work as long as the app is already exempted from the foreground restriction and allowed to start into foreground anyway. So I think this is all WAI.

Media3 does not offer auto play on bt connection or headset connection or an API to automate pausing / playing based on external events.

This is actually part of why foreground service restrictions exist really. The system wants to avoid apps starting services long after the user stopped interacting with them. The interesting part about this setup is that I think your app has only been paused for a relatively short period of time? There is some pending work in Media3 and the Android platform (for next year) to make this overall experiences more consistent and robust (~mostly avoiding this situation in the first 10 minutes after going into the paused state). This is unrelated to audio focus though as audio focus just follows the general foreground service logic here to be consistent.

Tolriq commented 1 day ago

The need is not just after the first 10 minutes for those kinds of user requests.

They unplug the headset (disconnect BT) the app is then killed when it wants then later (like the next morning) they plug / connect BT and expect the music to play as per the settings they enabled.

Those BT / headset events are music event they are allowed to start the app in background, but we then can't start playback now this is IMO not WAI and hard to explain to the users. Especially with the required number of clicks as not a valid case to request direct exemption and all the warnings about more battery usage.

So if you have access to the platform team maybe expose that need? Most music players offers it due to very high user requests. (probably just need an exception for those events to startforeground or a specific new broadcast or whatever) but this is really highly requested feature.

but then the system could just kill your app mid-playback.

Well when you have apps that reach all the funny OEMs this happens also when requesting the foreground permission and we need to deal with all those cases :)

The system wants to avoid apps starting services long after the user stopped interacting with them.

And yet the events will start the app and consume a lot of battery but then won't allow the app to do what the user want unless they enable the setting to consume event more battery when not needed.

Anyway I'll do more tests about requesting the focus with the unrestricted battery without going foreground first to see what happens as it would avoid the need to completely reverse my handling of going foreground when the app can actually play and not before if the focus is refused.

tonihei commented 1 day ago

Those BT / headset events are music event they are allowed to start the app in background, but we then can't start playback now this is IMO not WAI

Can you point me to the exact API entry point for this? That's definitely worth checking with the relevant team.

Tolriq commented 1 day ago
        <receiver
            android:name="app.symfonik.core.playback.receiver.HeadsetReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.HEADSET_PLUG" />
            </intent-filter>
        </receiver>
        <receiver
            android:name="app.symfonik.core.playback.receiver.BtHeadsetReceiver"
            android:enabled="false"
            android:exported="true">
            <intent-filter>
                <action android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
            </intent-filter>
        </receiver>

Those are the 2 commonly used intents.

Then in the onReceive we check if it's a connection or disconnect and react accordingly.

For example:

override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED && dynamicPreferenceRepository.autoPlayBluetooth.get()) {
            logVerbose(PlayerService.TAG) { "Manifest ACTION_CONNECTION_STATE_CHANGED received" }
            val newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED)
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                if (context?.audioManager?.isMusicActive != true) {
                    playbackController.changeToLocalRenderer(true)
                } else {
                    logVerbose(PlayerService.TAG) { "Music is active skipping BT auto play" }
                }
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED && playbackController.currentRendererInfo.value.type == RendererInfo.Type.LOCAL) {
                playbackController.pause()
            }
        }
    }

Note that the receiver is disabled by default and enabled by code if the user wants the feature to reduce battery usage and unwanted app start.

Tolriq commented 1 day ago

And I've just tested despite having the battery restriction disabled on Android 15 the audio focus is still refused if we are not in the foreground.

So no other choice than a complete internals rewrite to request foreground before the focus despite in my case being in 2 different modules completely unaware of the others :(

Tolriq commented 1 day ago

One last case I forget about as less requested, but some apps (including mine) have an option to auto pause when volume reach 0 then resume when volume is back again and it was paused due to volume mute.

But that case would probably be covered by the future 10 mins delay after pause, even if far from perfect I guess most would be under 10.

Tolriq commented 1 day ago

Ok so @tonihei the docs are actually wrong and the situation is problematic.

Having a foreground service is actually not enough, we need to have the app opened or an user interaction from the media session or a widget (The know user interaction gives a delay to go foreground)

I've just made a million tests and can confirm that if the app is in background with battery set to unrestricted then: 1) If the app is visible or if the commands comes from media session or a widget with the small delay to go foreground then we can request focus with or without having a foreground service. 2) If the app is not visible and the command does not come from one of the exceptions to go foreground via user interaction, then even if we have a foreground service running (and triple checked that the service is indeed foreground, added delays, ...) then the audio focus will be DENIED.

So basically we have no way to automate the playback start even with unrestricted battery.


An easy way to test and confirm is to use the testapp-controller from media 3 with a small modification. Tweak the code testapp-controller to wait 20 seconds before sending commands from the control screen.

Start an app that target Android 15 with a media session put the app in background and confirm it's seen as such. (App with battery unrestricted) Start the controller, connect to the session press play and put the controller app in the background quickly.

Wait the 20 seconds, the controller is no more foreground when the command is sent to the media session.

The audio focus request is denied even if the app have it's service foreground and playback does not start.

This is not what the doc suggest and this prevent all possible workarounds.

Confirmed on

I hope you can reach the platform team and confirm it's not intended and can be fixed or that there's a solution / workaround possible.