Besides responding to your UI controls and media buttons, an audio app should also react to other Android events that can affect its sound. This page describes how to handle three cases:
- Change the volume when the user adjusts a hardware volume control
- Stop playing if the headphones are disconnected while in use
- Stop playing or lower the volume when another app takes over the audio output stream
Honor the volume controls
When a user presses a volume key in a game or music app the volume should change, even if the player is paused between songs or there’s no music for the current game location.
Android uses separate audio streams for playing music, alarms, notifications, the incoming call ringer, system sounds, in-call volume, and DTMF tones. This allows users to control the volume of each stream independently.
By default, pressing the volume control modifies the volume of the active audio stream. If your app isn't currently playing anything, hitting the volume keys adjusts the ringer volume.
Unless your app is a replacement alarm clock, you probably play audio using
the
STREAM_MUSIC
stream.
To ensure that volume controls adjust
the correct stream, you should call
setVolumeControlStream() passing in
AudioManager.STREAM_MUSIC.
setVolumeControlStream(AudioManager.STREAM_MUSIC);
Make this call early in your app’s lifecycle, typically from the onCreate()
method of the activity or fragment that controls your media. This connects
the volume controls to STREAM_MUSIC whenever the target activity or fragment
is visible.
Don't be noisy
Users have a number of alternatives when it comes to enjoying the audio from their Android devices. Most devices have a built-in speaker, headphone jacks for wired headsets, and many also feature Bluetooth connectivity and support for A2DP audio.
When a headset is unplugged or a Bluetooth device disconnected, the audio stream automatically reroutes to the built-in speaker. If you listen to music at a high volume, this can be a noisy surprise.
Luckily the system broadcasts an ACTION_AUDIO_BECOMING_NOISY
intent when this happens. You should create a BroadcastReceiver
that listens for this intent whenever you’re playing audio. In the case of music players, users
typically expect the playback to be paused. For gaming apps, you may choose to significantly
lower the volume instead. Your receiver should look like this:
private class BecomingNoisyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// Pause the playback
}
}
}
Register the receiver when you begin playback, and unregister it when you stop.
If you design your app as we describe in this guide, these calls should appear
in the onPlay() and onStop() media session callbacks.
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver();
MediaSessionCompat.Callback callback = new
MediaSessionCompat.Callback() {
@Override
public void onPlay() {
registerReceiver(myNoisyAudioStreamReceiver, intentFilter);
}
@Override
public void onStop() {
unregisterReceiver(myNoisyAudioStreamReceiver);
}
}
Share the audio focus
Two or more Android apps can play audio to the same output stream simultaneously. The system mixes everything together. While this is technically impressive, it can be very aggravating to a user. To avoid every music app playing at the same time, Android introduces the idea of audio focus. Only one app can hold audio focus at a time.
When your application needs to output audio, it should request audio focus. When it has focus, it can play sound. However, after you acquire audio focus you may not be able to keep it until you’re done playing. Another app can request focus, which preempts your hold on audio focus. If that happens your app should pause playing or lower its volume to allow the new audio source to be heard.
A well-behaved audio app should manage audio focus according to these guidelines:
- For apps that target Android 5.0 (API level 21) and later, use
AudioAttributesto describe the type of audio your app is playing. Apps that play speech should specifyCONTENT_TYPE_SPEECH. - Before starting to play, request audio focus and verify that it has been granted
- When another app gains audio focus, stop playing or duck the volume down
- When playback stops, abandon audio focus
Audio Focus is cooperative. Applications are encouraged to comply with the audio focus guidelines, but the system does not enforce the rules. If an application wants to continue to play loudly even after losing audio focus, nothing can prevent that. This is a bad experience and there's a good chance that users will uninstall an app that misbehaves in this way.
Acquiring and releasing audio focus
Your app should request the audio focus for the stream it uses immediately before playback begins. For example, when the user presses play or the background music for the next game level starts.
Before proceeding to play, call
requestAudioFocus()
and verify that it returned
AUDIOFOCUS_REQUEST_GRANTED. The call to requestAudioFocus()
should be made in the onPlay() callback of a media session if you design your app as we describe in this guide.
When you request audio focus you must specify a duration hint, which may be honored by another app that is currently holding focus and playing:
- Request permanent audio focus (
AUDIOFOCUS_GAIN) when you plan to play audio for the foreseeable future (for example, when playing music) and you expect the previous holder of audio focus to stop playing. - Request transient focus (
AUDIOFOCUS_GAIN_TRANSIENT) when you expect to play audio for only a short time and you expect the previous holder to pause playing. - Request transient focus with ducking
(
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) to indicate that you expect to play audio for only a short time and that it's OK for the previous focus owner to keep playing if it "ducks" (lowers) its audio output. Both audio ouputs are mixed into the audio stream. Ducking is particularly suitable for apps that use the audio stream intermittently, such as for audible driving directions.
The requestAudioFocus() method also requires an AudioManager.OnAudioFocusChangeListener. This listener should be
created in the same activity or service that owns your media session. It
implements the callback onAudioFocusChange() that your app will receive when some other app
acquires or abandons audio focus.
The following snippet requests permanent audio focus on the stream
STREAM_MUSIC and registers an OnAudioFocusChangeListener to handle
subsequent changes in audio focus. ( The change listener is discussed in the
next section.)
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;
...
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback
}
When you finish playback, call
abandonAudioFocus().
// Abandon audio focus when playback complete
am.abandonAudioFocus(afChangeListener);
This notifies the system that you no longer require focus and unregisters the
associated OnAudioFocusChangeListener. If you requested transient focus,
this will notify an app that paused or ducked that it may continue playing or
restore its volume.
Responding to audio focus change
When an app acquires audio focus it must be able to release it when another app
requests audio focus for itself. When this happens your app
receives a call to the
onAudioFocusChange()
method in the AudioFocusChangeListener
that you specified when the app called requestAudioFocus().
The focusChange parameter passed to onAudioFocusChange() indicates the kind of change that's happening. It corresponds
to the duration hint used by the app that's aquiring focus. Your app should
respond appropriately.
- Transient loss of focus
-
If the focus change is transient (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKorAUDIOFOCUS_LOSS_TRANSIENT), your app should duck or pause playing but otherwise maintain the same state.During a transient loss of audio focus you should continue to monitor changes in audio focus and be prepared to resume normal playback when you regain the focus. When the blocking app abandons focus you'll receive a a callback (
AUDIOFOCUS_GAIN) . At this point you can restore the volume to normal level or restart playback. - Permanent loss of focus
-
If the audio focus loss is permanent (
AUDIOFOCUS_LOSS), another application is playing audio. Your app should pause play immediately. At this point your app will never receive anAUDIOFOCUS_GAINcallback. To restart playback the user must take an explicit action, like pressing the play transport control in a notification or app UI.
The following code snippet demonstrates how to implement the
OnAudioFocusChangeListener and its onAudioFocusChange() callback. Notice the
use of a Handler to delay the stop callback on a permanent loss of audio
focus.
private Handler mHandler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// Permanent loss of audio focus
// Pause playback immediately
mediaController.getTransportControls().pause();
// Wait 30 seconds before stopping playback
mHandler.postDelayed(mDelayedStopRunnable,
TimeUnit.SECONDS.toMillis(30));
}
else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
} else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume, keep playing
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Your app has been granted audio focus again
// Raise volume to normal, restart playback if necessary
}
}
};
The handler uses a Runnable that looks like this:
private Runnable mDelayedStopRunnable = new Runnable() {
@Override
public void run() {
mediaController.getTransportControls().stop();
}
};
To ensure the delayed stop does not kick in if the user restarts playback, call
mHandler.removeCallbacks(mDelayedStopRunnable) in response to any state
changes. For example, call removeCallbacks() in your Callback's onPlay(),
onSkipToNext(), etc. You should also call this method in your service's onDestroy()
callback when cleaning up the resources used by your service.