Managing Audio Focus (Amazon Fire TV)

Audio playback on Amazon Fire TV is a shared resource. Although apps such as music players can play audio when they are not in the foreground, only one app should be playing audio at any given time.

To manage audio playback across applications, Android provides the concept of audio focus via the AudioManager APIs. This document outlines how to manage audio focus and other related events on Fire TV.

Sample Audio Application Managing Audio Focus

For a relatively simple example of managing audio focus, see the implementation demonstrated in the MediaBrowseService sample app from Google:

This sample application shows all of the relevant Android APIs for handling audio and demonstrates many of the best practices described in more detail in the following sections.

Additionally, see the following topics in the Android documentation for managing audio focus:

Best Practices for Audio-only Applications

If you have an audio-only application, such as a streaming radio player, the following table provides guidelines about how your app should respond to various events when an audio stream is playing:

Event Type AUDIO EVENT or RELATED EVENT Listen to Audio Focus Events? Listen to Media Controller Events? Expected Application behavior/ Customer experience
Home button press (none) Yes Yes Audio stream continues to play. Media controller buttons (for example: play/pause) continue to work.
Voice search button press AUDIOFOCUS_LOSS_TRANSIENT Yes Yes Audio stream pauses. When user exits the Alexa overlay, audio stream resumes. Media controller buttons continue to work.
HDMI cable unplugged ACTION_HDMI_AUDIO_PLUG Yes Yes Audio stream pauses. When user reconnects HDMI cable, audio stream is in paused state. Media controller buttons continue to work. Audio stream resumes when Play button is pressed.
HDMI input switched ACTION_HDMI_AUDIO_PLUG Yes Yes Audio stream pauses. When user switches HDMI input back to Fire TV, audio stream is in paused state. Media controller buttons continue to work. Audio stream resumes when Play button is pressed.
New audio stream is started by an other application AUDIOFOCUS_LOSS No No Audio stream stops. Application stops listening to changes in audio focus and to media controller events. App cleans up resources used to play audio.

Best Practices with Audio Focus

The following are best practices for managing audio focus in an audio-only application:

Each of these best practices is explained in the sections below.

Run Your App as a Foreground Service

Services are used to perform operations in the background when the user may not be directly interacting with your app. Media players, especially those that play music, are ideal for services.

However, services may be halted unexpectedly by the Android operating system if resources run low. To give your service higher priority on the system and prevent it from unexpectedly being killed, make sure you run your app during playback as a foreground service by extending from Service and and starting them using the Service.startForeground() method.

The startForeground() method requires two parameters that indicate a notification: a notification ID you define, and a Notification object. On Android devices, that notification appears in the notification drawer to indicate to the user that a high-priority service is running.

On the Amazon Fire TV platform, heads-up notifications (high priority) are displayed for a few seconds and then fade out. You can view older notifications in the Notifications Center. You can use the Android Notification API to create a heads-up notification:

// define the notification
Notification.Builder builder = new Notification.Builder(getApplicationContext());
builder.setSmallIcon(R.drawable.notification_icon);
builder.setContentTitle(title);
builder.setContentText(text);
builder.setPriority(Notification.[PRIORITY_HIGH][4]);
builder.setType(Notification.Builder.TYPE_MEDIA_INFO);
Notification notification = builder.build();

// start
startForeground(1, notification);

After you’ve stopped playing, remove your service from the foreground state with Service.stopForeground(). The boolean parameter indicates whether to remove the associated notification.

stopForeground(true);

Request Audio Focus

Audio output is a shared resource on the Amazon Fire TV platform — only one audio stream should be playing at any one time. The Android audio manager uses the concept of audio focus to enable applications to share audio playback, and to pause, stop, or lower the volume in response to other apps needing that focus.

An application playing back audio must first request the audio focus with appropriate flags and continue playback only if focus is granted. The app can continue to play audio in the background if and only if it still has audio focus.

Request the audio focus with AudioManager.requestAudioFocus(). The request has three parameters: a focus listener (see Create a Focus Change Listener, below), an audio stream type (such as AudioManager.STREAM_MUSIC), and a duration.

AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int result = am.requestAudioFocus(focusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    am.registerMediaButtonEventReceiver(MediaButtonReceiver);
    // start playback
}

The duration parameter can be one of several options:

  • AUDIOFOCUS_GAIN (permanent focus): Use for when you want to hold the focus and continue playing audio for the foreseeable future, as with a music player.

  • AUDIOFOCUS_GAIN_TRANSIENT (transient focus): Use for when you want to interrupt any existing audio playback for a short period of time, and that other playback should pause while you are holding the focus.

  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK (transient with ducking): Same as transient, but the existing playback is allowed to continue playing audio with a lowered volume. If lowering the volume is not ideal or not implemented, the playback should be paused.

  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE (transient focus, mutes other sounds): Same as transient but mutes all other sounds, including system sounds, in a temporary way. If you’re incorporating voice recognition into your app, or if you have a microphone input, this parameter can be useful.

After audio focus is granted to your app, you should register for handling of media events (such as Next, Fast Forward, Previous, or Play-Pause buttons on headsets, external keyboards, remote controls, etc.) with the AudioManager.registerMediaButtonEventReceiver() method (see the next section) or by setting up a MediaSession using API level >= 21. This is only necessary for continuous audio playback — typically you don’t need to handle the media buttons events for transient (short) playback.

After you’re done playing, release the audio focus using the AudioManager.abandonAudioFocus() method and unregister your media button event receiver (or release your MediaSession on API levels >=21).

// playback complete, give up audio focus
am.abandonAudioFocus(focusListener);

//unregister media buttons
am.unregisterMediaButtonEventReceiver(MediaButtonReceiver);

Register to Receive Media Button Events (for API Level >= 21)

From API levels 21 and higher, use the MediaSession API for interactions with media controllers, volume keys, media buttons (Bluetooth remotes, headsets, etc), and transport controls. The Media Browser Service sample app written by Google demonstrates using these APIs.

Create the instance of the media session at the appropriate place (like in the onCreate() of your service):

private MediaSession mSession = new MediaSession(this, "MusicService");

The session token can be passed to apps by the session owner to allow them to create a MediaController to communicate with the session.

MediaSession.Token token = mSession.getSessionToken();

To receive commands, media keys, and other events, a MediaSession.Callback must be set with setCallback(Callback) and setActive(true) must be called.

mSession.setCallback(new MediaSessionCallback());
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);

//available actions need to be updated dynamically when the player state changes, like "stopping the playback" is only possible while playing. Jumping to next and previous songs is only possible in case there are proper songs in the queue
long actions = PlaybackState.ACTION_PLAY
                 | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID
                 | PlaybackState.ACTION_PLAY_FROM_SEARCH
                 | PlaybackState.ACTION_PLAY_PAUSE;

PlaybackState.Builder stateBuilder = new PlaybackState.Builder().setActions(actions);
long position = getPosition();
int state = mPlayback.getState();

stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());
mSession.setPlaybackState(stateBuilder.build());
mSession.setActive(true);

After finishing playback, you must release the session and stop your service:

mPlayback.stop(true);
stopSelf();
mSession.release();

If you’re targeting older APIs (less than API level 21), follow the instruction here.

To play audio for more than a few moments (that is, you’ve requested non-transient audio focus), register a media button receiver when your audio focus request is granted. The media button receiver enables the user to control your media playback with the media buttons on the remote control, even if your app is not running in the foreground.

Note that registering the media buttons just means your app receives those events before other apps — you must still handle those key events to actually control your media playback.

Your media button receiver extends from BroadcastReceiver and listens for an ACTION_MEDIA_BUTTON intent. Register your receiver in your Android manifest (in this example, the receiver class is MediaButtonReceiver):

<receiver android:name="MediaButtonReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

Implement the onReceive() callback in your receiver class to receive and handle media button intents:

public void onReceive(Context context, Intent intent) {
    if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
        KeyEvent key = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
        int keycode = key.getKeyCode();

        if (keycode == KeyEvent.KEYCODE_MEDIA_NEXT) {
            // next
        } else if (keycode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {
           // previous
        } else if (keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
           // play or pause
        }
    }
}

After you’re done playing, or if you lose the audio focus, unregister the media buttons so that other apps can control media playback when needed.

Several Java code samples are available that demonstrate how to handle audio focus for older APIs. The code samples are as follows:

  • AudioCapabilities.java
  • AudioDeviceEventReceiver.java
  • AudioEventManager.java
  • AudioEventManagerCallbackListener.java

You can download the code samples here.

Implement your Audio Focus Change Listener

Audio focus is a cooperative resource. Once you’ve been granted audio focus, it is not guaranteed to stay yours until you are done with it. If other apps request the audio focus, you must respond to those requests to pause playback or stop playing altogether. See Android’s Audio Focus topic for more details.

Listen for audio focus changes with AudioManager.OnAudioFocusChangeListener and the onAudioFocusChange() method. Subscribe to the Audio Focus events with your listener.

OnAudioFocusChangeListener focusListener = new OnAudioFocusChangeListener() {
	public void onAudioFocusChange(int focus) {
		switch (focus) {
		case AudioManager.AUDIOFOCUS_GAIN:
			// continue playback and raise volume (if it was previously lowered)
			// ...
			break;

		case AudioManager.AUDIOFOCUS_LOSS:
			// stop playback, de-register buttons, clean up
			// ...
			break;

      case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            // pause playback
      case AudioManager.AUDIOFOCUS_LOSS_TRANIENT_CAN_DUCK:
      			// lower audio volume
			// ...
			break;
		}
	}
}

The argument for onAudioFocusChange() is an integer that may be one of the following:

  • AUDIOFOCUS_GAIN: Your app has gained or regained the audio focus. Continue playback and restore the volume in case it was lowered previously.

  • AUDIOFOCUS_LOSS: Your app has lost the audio focus (permanently) and as an other application started playback, you should not expect getting it back. Halt playback, de-register media button and event receivers, abandon audiofocus and release resrouces.

  • AUDIOFOCUS_LOSS_TRANSIENT: Your app has lost audio focus for a short amount of time. Pause playback.

  • AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: You may lower the audio volume or you must pause your playback. A reasonable volume level should not interfere with other applications, voice search responses etc - usually between 30-50% of the original volume.

Handling Audio Focus with Voice Interactions

When users press the Microphone button, system-wide voice capabilities start and cannot be overridden. In general, your app should pause (implement the onPause() activity lifecycle method) in response to voice interactions. If you need search functionality within your app, you must implement it yourself and provide your own user interface for that function.

For applications playing audio (when your application has audio focus), initiating voice search will also cause an audio focus change callback with the parameter AUDIOFOCUS_LOSS_TRANSIENT (without ducking – that is, lowering the volume). As such, you must handle the AUDIOFOCUS_LOSS_TRANSIENT event to accommodate voice search.

In general your application should handle all other audio focus change events as well (AUDIOFOCUS_LOSS and AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK), since any application (not just voice search) might request other kinds of audio focus. In short, all audio focus use cases should be handled by your application correctly.

For backwards compatibility with older applications on the Amazon Fire TV platform, we send out both an AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK and an AUDIOFOCUS_LOSS_TRANSIENT when a voice search is initiated. Your applications should handle this use case by pausing playback.

See the Android documentation on Audio Focus for details on audio focus and audio ducking. For more information about voice search and the results returned from the Fire TV catalog, see Implementing Search on Fire TV.

For video apps, your app should pause playback when voice capabilities are invoked (in your OnPause() method). When your app resumes, playback should remain paused until the user presses the PLAY button on the remote.