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.73k stars 416 forks source link

Android 15 AudioFocus #1815

Open Tolriq opened 1 month ago

Tolriq commented 1 month 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 month 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 month 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 month 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 month 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 month 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 month 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 month 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 month 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 month 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.

Tolriq commented 1 month ago

Seems others confirm this https://issuetracker.google.com/issues/375228130 would be really really nice if you could help this being triaged (as there's no direct access to those teams) so we get an official answer as this is highly impacting.

tonihei commented 1 month ago

An easy way to test and confirm is to use the testapp-controller from media 3 with a small modification.

Could you spell out these steps in more detail? I tried various combinations of what I think you described, but could not get into a state where the audio focus is denied.

Tolriq commented 1 month ago

The repro is simply to have the media session calls made while the caller is in background state.

So in the test app for the controller screen instead of directly sending the the command, start a thread wait at least 20 seconds then send the command.

Then after clicking the play command put the controller app in background quickly. The os should see it as backgrounded and then the player app is also seen as backgrounded despite the bind.

Then the player app can't request the focus even if it have a foreground service.

This is a way to show that even via media session the issue arise and why I pointed it.

But you can also repro in a simple app. Add the app to battery exclusions.

Have an app that start a thread, wait 20 seconds, start the foreground service then try to get the audio focus.

And same process start the app, go in background, and the focus will fail.

See the linked tracker issue it have a repro for a similar use cases.

tonihei commented 1 month ago

Hmm, can't quite tell why it doesn't reproduce for me. What I've done in detail is:

controller: MediaController -> run {
                  Thread {
                      Thread.sleep(20000)
                      Handler(Looper.getMainLooper()).post { controller.play() }
                  }.start()
              }
          }

The system logs confirm the foreground service start was allowed due to selecting unrestricted battery:

Background started FGS: Allowed [callingPackage: androidx.media3.demo.session; callingUid: 10467; uidState: SVC ; uidBFSL: n/a; intent: Intent { cmp=androidx.media3.demo.session/.PlaybackService }; code:TEMP_ALLOWED_WHILE_IN_USE; tempAllowListReason:<,reasonCode:SYSTEM_ALLOW_LISTED,duration:9223372036854775807,callingUid:-1>; targetSdkVersion:35; callerTargetSdkVersion:35; startForegroundCount:4; bindFromPackage:null: isBindService:false]

... but no mention of audio focus issues anywhere.

Tolriq commented 1 month ago

I'm testing to reproduce what you write with 1.5.0 beta 1 and do not observe the same issue at all.

When pausing and putting the media session app in background the audio focus is not released, so you probably need to request it by another app to ensure the session app request it again.

But when using your code, pressing play does not work, from the logs I can see that the controller.play() is called, but the play does not start, it only starts when I return to the test app or the session app.

Maybe increase the delays and check you are correctly API 35? (Edge to edge should bug for those 2 apps)

With both the media session paused long enough, then having the test app in background long enough to send the command it gives:

2024-10-25 14:05:17.978  1686-4535  AS.HardeningEnforcer    system_server                        I  Focus request DENIED for uid:11059 clientId:android.media.AudioManager@c22d63androidx.media3.exoplayer.AudioFocusManager$AudioFocusListener@a13ad60 req:1 procState:10
2024-10-25 14:05:17.978  1686-4535  AS.AudioService         system_server                        W  Audio focus request blocked by hardening
SimonMarquis commented 2 weeks ago

I have a very similar issue on my own app, which is now broken on Android 15. Broken because stopping the media playback is the main (and only) feature provided by this app. Even with a started foreground service, requesting audio focus always leads to AUDIOFOCUS_REQUEST_FAILED on Android 15.

Here is my current setup:

And the PR migrating to a foreground service

Tolriq commented 2 weeks ago

Yes it's currently broken for sure.

https://issuetracker.google.com/issues/375228130 is supposed to be affected but from experience with AOSP reports this means nothing and it will be closed in 2 years as obsolete as always.

Here we have 2 cases: 1) The doc is wrong and a foreground service is not enough and we are doomed for Android 15. 2) The doc is correct and it's a bug and we are doomed too because even if they fix in AOSP it will probably still be broken on many devices in the wild.

In all cases we need to have this reaching the AOSP team to confirm what it is. As if it's 1) We need to be able to at least try to have them change their mind for Android 16. And if it's 2) we need them to be aware for them to fix.

Currently the hope is that @tonihei reach them for an official answer as this is a real big deal and problem to target Android 15 that we'll be forced to in a couple of months.

In all cases the target Android 15 will be pain for many that will be hard to explain to users why they lose features.