Reputation: 27045
I'm making a game and I would like to detect if something else is already playing music (or starts to play music) as my game is running. If that's the case, I'll fade out the music in the game and keep playing the sound effects. I am using Unity and FMOD for audio playback.
As best I can tell Android provides two different ways to do this: AudioManager.isMusicActive and AudioManager.OnAudioFocusChangeListener.
AudioManager.isMusicActive
does not work for my particular case since it seems the way Unity/FMOD registers itself will make this always return true.
AudioManager.OnAudioFocusChangeListener
lets me detect if another App requests the audio focus, but provides no way of detecting if I can safely request it again.
I have no insight into how Unity/FMOD sets up the audio session, but the default is to play sounds regardless of anything else playing in the background. So, if I only knew if something else was playing this would be easy!
Is there a way to tell if something besides my own app is playing music?
Note: When I say music, it may also be an audiobook, podcast or closest equivalent.
There seems to be a possibility to look for any ongoing media playback notifications, but this requires escalated permissions which seems like too big of an ask for a feature this small.
Here's a few canditates for solutions:
AudioFocusRequest.setAcceptsDelayedFocusGain This can be specified when requesting audio focus, but only really means I don't need focus now, later is also fine. However, if anything else is playing audio that isn't "locked" I will steal audio focus, stopping that and giving me no further information in the process.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK initially seemed promising, but as this depends on the other app requesting audio focus to specify itself as transient (ie a notification sound or similar) and provides no benefit to me detecting longer form external audio playback.
BroadcastReceiver/MediaNotifications, here's the Spotify implementation, but as best I can tell this would require specific support inside my app for each player I want to acknowledge, plus the user enabling broadcasts. Might work, but seems brittle.
Upvotes: 27
Views: 2718
Reputation: 2393
To answer just your bold question first, you can try using requestAudioFocus
with the AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
flag. Exclusive would imply nothing else CAN be playing and therefore should return AUDIOFOCUS_REQUEST_FAILED or AUDIOFOCUS_REQUEST_GRANTED which should give you a proxy answer to your question of "is something besides my own app playing music".
You are correct that the listeners are not intended to tell you if you can safely request access, but they can tell you when you can safely start playing.. Just make the request whenever your app is initially loaded or foregrounded (assuming it's not already playing music). If you're GRANTED, play, and don't play if not. Don't worry if you don't get immediate access, because you can have a listener that waits for when access is granted to you later (i.e. a few seconds later at the end of a map voicing out directions, or minutes later when the other long term audio source stops)..
Here's a longer take on the lifecycle as I understand it.
Once your app is launched (or whenever it is foregrounded), ask for AUDIOFOCUS_GAIN. If nothing is playing, you will be GRANTED audio focus and can start playing. If something is playing, you may not get immediate audio focus and should not start playing. If you did NOT start playing, make sure to listen for "AUDIOFOCUS_GAIN" in your FocusChangeListener. Receiving this message means you can start playing!
Don't worry about "locking out" others. As long as you made a reasonable request in asking for AUDIOFOCUS_GAIN (it's reasonable since you plan to use the audio for your game and you plan to use it for longer than 15 seconds) then taking over audio from another app makes sense because your App is being foregrounded and SHOULD take over primary audio. However, in your OnAudioFocusChangeListener you should be a good citizen and listen for LOSS events that may happen if the user switches back to the OTHER app or if other things transiently pop in, where you need to pause/mute or lower-volume/duck yourself accordingly.
In this way you're coding to whether your app is front and center or whether it's being told to be quiet and quiet for how long. That frees you from having to worry about who/what/why/how many other apps are out and about doing something with the audio and instead concentrate on simply coordinating and responding to the Audio Manager's directives until you don't care anymore (i.e. app exit, etc.).
I found these two links immensely useful in understanding the basics and how apps should be respectful of each other.
The last link is for Expo AV Manager. This is not to offer Expo as a solution, it's just I felt the Expo AV code worked well as a fully thought out example of the combinations of conditions one might encounter when navigating Audio management and what code/member variables might be useful to keep track of all of them. There's also a lot of "Expo-ey" framework things going on in that file which can be distracting and ignored, but if you just look at acquireAudioFocus
and onAudioFocusChange
I think there's some good inspiration to be had there.
For some code and a great overview
For the philosophy and rationale behind the lifecycle and how to be a good Audio citizen
Source from Expo AV Manager
Also as a final note, don't forget to be a good citizen and abandon focus when you don't need it. i.e. Backgrounding/Foregrounding, if your user turns off Music in game, etc.
Upvotes: 15