Managing audio focus
Two or more Android applications can simultaneously play audio to the same output stream. The system mixes everything together. While this is technically impressive, it can be very irritating to users. To avoid all music apps playing at the same time, Android introduced the concept of audio focus. Only one app can focus audio 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 get the audio focus, you may not be able to hold it until you’re done playing. Another application can request focus, and it preempts your audio focus. If this happens, your app should pause playback or lower the volume to make it easier for the user to hear the new audio source.
Audio focus is collaborative. Applications are encouraged to follow audio focus guidelines, but the system does not enforce these rules. If an app wants to continue playing loudly after losing sound focus, there’s nothing to stop it. This is a bad experience, and users are likely to uninstall the application that has this bad experience.
A well-performing audio application should manage audio focus according to these general guidelines:
- Called immediately before playback begins
requestAudioFocus()
And verify that the call returnsAUDIOFOCUS_REQUEST_GRANTED. If you design your application as we describe in this guide, it should be in the media sessiononPlay()
Called in a callbackrequestAudioFocus()
. - When another application gains audio focus, stop or pause the playback, or lower the volume.
- When playback stops, drop audio focus.
How audio focus is handled varies from Android version to Android version:
- Starting with Android 2.2 (API Level 8), applications manage audio focus by calling requestAudioFocus() and abandonAudioFocus(). Application must also register a AudioManager. OnAudioFocusChangeListener, to receive callbacks and manage your own audio level.
- For Android 5.0 (API Level 21) or later, audio apps should use AudioAttributes to describe the type of audio your app is playing. For example, an application that plays speech should specify CONTENT_TYPE_SPEECH.
- Apps running Android 8.0 (API level 26) or higher should be used
requestAudioFocus()
Method, it acceptsAudioFocusRequestParameters. AudioFocusRequest contains information about the audio context and application functionality. The system uses this information to automatically manage the capture and loss of audio focus.
Audio focus on Android 8.0 and later
Starting with Android 8.0 (API level 26), when you call requestAudioFocus(), you must provide the AudioFocusRequest argument. To release the audio focus, call the abandonAudioFocusRequest() method, which also takes the AudioFocusRequest as its argument. The same AudioFocusRequest instance should be used when requesting and abandoning focus.
To create an AudioFocusRequest, use audiofocusRequest.Builder. Because a focus request must always specify the type of request, that type is included in the constructor’s constructor. Use the builder’s methods to set the other fields of the request.
The FocusGain field is required; All other fields are optional. Explain the main methods:
- setFocusGain()
This field is required for each request. It has the same value as the durationHint used in calls to requestAudiofococus () prior to Android8.0 :AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, or AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
- setAudioAttributes()
Audioattributes describes the use case for the system application. The system looks at audio when an application gains and loses focus. Attributes replace the concept of stream types. On Android 8.0 (API level 26) and above, any action other than volume control does not support stream types. Use the same properties in the Focus request as in the audio player (as shown in the table below).
Use an AudioAttributes.Builder to first specify the attributes, and then use this method to assign the attributes to the request.
If not specified, AudioAttributes defaults to audioAttributes.usage_media.
- setWillPauseWhenDucked()
When another application requests focus processing using AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, the application with focus usually does not receive the onAudioFocusChange() callback, because the system can do this itself. When you need to suspend play rather than to reduce the volume, call setWillPauseWhenDucked (true), create and set a OnAudioFocusChangeListener, automatic ducking mentioned below.
- setAcceptsDelayedFocusGain()
The request for audio focus may fail when the focus is locked by another application. This approach allows for delayed focus gain: the ability to get focus asynchronously when it is available.
Note that only when you specify a AudioManager. OnAudioFocusChangeListener, delay the focus of the gain (focus gain). Because your application needs to receive a callback to know that focus is allowed to be fetched.
- setOnAudioFocusChangeListener()
Only the specified in the request willPauseWhenDucked (true) or setAcceptsDelayedFocusGain (true), only need OnAudioFocusChangeListener.
There are two ways to set up a listener: one with handler parameters and one without. The handler is the thread in which the listener runs. If no handler is specified, the handler associated with the main Looper is used.
The following example shows how to build an AudioFocusRequest with AudioFocusRequest.Bulder and request and abandon audio focus:
mAudioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
mPlaybackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(mPlaybackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(afChangeListener, mHandler)
.build();
mMediaPlayer = new MediaPlayer();
final Object mFocusLock = new Object();
boolean mPlaybackDelayed = false;
boolean mPlaybackNowAuthorized = false;
// ...
int res = mAudioManager.requestAudioFocus(mFocusRequest);
synchronized(mFocusLock) {
if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
mPlaybackNowAuthorized = false;
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
mPlaybackNowAuthorized = true;
playbackNow();
} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
mPlaybackDelayed = true;
mPlaybackNowAuthorized = false; }} / /... @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) {case AudioManager.AUDIOFOCUS_GAIN:
if (mPlaybackDelayed || mResumeOnFocusGain) {
synchronized(mFocusLock) {
mPlaybackDelayed = false;
mResumeOnFocusGain = false;
}
playbackNow();
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
synchronized(mFocusLock) {
mResumeOnFocusGain = false;
mPlaybackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
synchronized(mFocusLock) {
mResumeOnFocusGain = true;
mPlaybackDelayed = false;
}
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// ... pausing or ducking depends on your app
break; }}}Copy the code
Automatic ducking
In Android 8.0 (API level 26), the system can lower the volume and resume audio playback when another application requests focus using AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, Without calling the application’s onAudioFocusChange() callback.
Automatic volume reduction is acceptable behavior in music and video playback applications, but it is useless when playing spoken content (such as in audio book applications). In this case, the application should be paused.
If you want to make your application asked ducking when the volume of the suspension rather than reduce it, create a OnAudioFocusChangeListener, use a onAudioFocusChange () callback method to achieve the required to suspend/resume. Call setOnAudioFocusChangeListener () to register the listener, and call the setWillPauseWhenDucked (true) to tell the system using your callback, rather than perform automatic ducking operations.
Delayed acquisition of focus
Sometimes the system cannot approve an audio focus request because the focus is “locked” by another application, such as during a phone call. In this case, requestAudioFocus() returns AUDIOFOCUS_REQUEST_FAILED. When this happens, your application should not continue with the audio playback because it does not gain focus.
This method is called setAcceptsDelayedFocusGain (true), it allows the application to focus asynchronous processing the request. When this flag is set, the request made while locked will return AUDIOFOCUS_REQUEST_DELAYED. When the condition to lock the audio focus no longer exists, such as when the call ends, the system grants the pending focus request and calls onAudioFocusChange() to notify the application.
In order to deal with the focus of the delayed gain, you must create a OnAudioFocusChangeListener, it USES a onAudioFocusChange () callback method to achieve the required behavior, And by calling setOnAudioFocusChangeListener () to register the listener.
Audio focus for Android prior to 8.0
When you call requestAudioFocus(), you must specify a Duration hint that may be used by another application that is currently holding focus and playing:
-
Request permanent audio focus (AUDIOFOCUS_GAIN) when you plan to play audio in the foreseeable future (for example, when playing music) and you want the previous audio focus holder to stop playing.
-
Request transient focus (AUDIOFOCUS_GAIN_TRANSIENT) when you want the audio to play for only a short period of time and you want the previous holder to pause it.
-
Request ducking transient focus (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) to indicate that you want the audio to play only for a short period of time, and that it can continue to play if the previous focus owner “ducks” (reduces) its audio output. Both audio outputs are mixed into the audio stream. Ducking is particularly suitable for applications that use audio streams intermittently, such as voice-driven directions.
RequestAudioFocus () method also needs a AudioManager. OnAudioFocusChangeListener. This listener should be created in the same activity or service as your media session. It implements the onAudioFocusChange() callback, which your application receives when other applications gain or lose audio focus.
The following code snippet request flow STREAM_MUSIC permanent audio focus, and register a OnAudioFocusChangeListener to deal with audio focus of the subsequent changes. (Change listeners are discussed in the response to audio focus changes section below.)
AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); AudioManager.OnAudioFocusChangeListener afChangeListener; . // Request audio focusfor 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
}
Copy the code
When you have finished playing, call abandonAudioFocus().
// Abandon audio focus when playback complete
mAudioManager.abandonAudioFocus(afChangeListener);
Copy the code
This system will notify you no longer need to focus, and cancel the relevant OnAudioFocusChangeListener. If you request transient focus, this will notify the paused or duck application that it may resume playing or resume the volume.
Response to changes in audio focus
When an application gains audio focus, it must be able to release it when another application requests its own audio focus. When this happens, your application receives a call to the onAudioFocusChange() method in the AudioFocusChangeListener that was specified when requestAudioFocus() was called.
The focusChange parameter passed to onAudioFocusChange() indicates the changes that are taking place. It corresponds to the duration hint used by the application to get focus. Your application should respond appropriately.
The transient out-of-focus
If the focus change is transient (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK or AUDIOFOCUS_LOSS_TRANSIENT), the application should ducking lower the volume (if you don’t rely on auto ducking) or pause the playback, But keep it the same in other ways.
During the temporary absence of audio focus, you should continue to monitor the changes in audio focus and be ready to resume normal playback when focus is restored. When a blocking application gives up focus, you receive a callback (AUDIOFOCUS_GAIN). At this point, you can restore the volume to normal or restart the playback.
Permanently out of focus
If the audio focus loss is permanent (AUDIOFOCUS_LOSS), another application is playing the audio. Your app should pause playback immediately because it will never receive the AUDIOFOCUS_GAIN callback. To restart playback, the user must take an explicit action, such as pressing the Play transport control in a notification or in the application UI.
The following code snippet demonstrates how to implement OnAudioFocusChangeListener and onAudioFocusChange () callback. Note that a Handler is used to delay the callback of the stop operation in the event of 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 == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
} else if (focusChange == AudioManager.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
}
}
};
Copy the code
The handler uses a Runnable as shown below
private Runnable mDelayedStopRunnable = new Runnable() {
@Override
public void run() { getMediaController().getTransportControls().stop(); }};Copy the code
If the user restarts playback, to ensure that delayed stops don’t work, mHandler.removecallbacks (mDelayedStopRunnable) can be called in response to any state changes. For example, call removeCallbacks() in the onPlay(), onSkipToNext() callback. This method should also be called in the onDestroy() callback of the service when cleaning up resources used by the service.