Developer Console

Step 6: Play Video Content with the PlaybackOverlayFragment (Fire TV)

We have followed the journey on Fire TV from browsing and content discovery to reading the details of specific content and performing an action. Now, we will dig into the last part: how to play the video.

In a Leanback-enabled project, playing video content is performed within the PlaybackOverlayActivity. The UI of the PlaybackOverlayActivity is simple: we have a full-screen video player that is responsible for playing the content. On top of the video player is the PlaybackOverlayFragment, which is responsible for displaying all the media controls and managing the underlying content play back.

There are many different video players that can be used, but when you first deploy the Leanback-enabled project, the default video player is VideoView.

VideoView is a very basic video player that is perfect if you just want to display non-encrypted video files in an easy way. Most developers prefer to opt for a more powerful and feature-rich player, and for Fire TV, the usual choice is the Amazon-customized version of ExoPlayer. For the sake of simplicity, we'll stick with what we find in the default Leanback template, VideoView.

When we analyze the xml file of the UI in the PlaybackOverlayActivity, we'll see that there's nothing more than these two components:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
/>
    <fragment
        android:id="@+id/playback_controls_fragment"
        android:name="PlaybackOverlayFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
/>
</FrameLayout>

Main Components of the PlaybackOverlayActivity

The basic setup of the PlaybackOverlayActivity is quite simple, and focuses on four main components:

public class PlaybackOverlayActivity extends Activity implements
        PlaybackOverlayFragment.OnPlayPauseClickedListener {

    private VideoView mVideoView;
    private LeanbackPlaybackState mPlaybackState;
    private MediaSession mSession;

    ...

}
  1. Listeners: We can add plenty of Listeners to the PlaybackOverlayActivity, which is useful for adding callbacks for all the actions performed by the user on their remote control (play, pause, rewind, etc). For simplicity, we'll review the only Listener available on the Leanback template, the OnPlayPauseClickedListener.
  2. VideoView is the player we'll use to play the video content.
  3. LeanbackPlaybackState is just a flag that is used to keep track of what the status of the app is (e.g. LeanbackPlaybackState.PLAYING, LeanbackPlaybackState.PAUSED).
  4. MediaSession: The main task of MediaSession is to talk to the underlying Android framework and manage the ownership of the actions performed with the remote.

The Remote

One of the main differences between Fire TV and other Android devices is that the only way to interact with Fire TV is through its remote.

Because Fire OS 5 is based on Android Lollipop, the KeyEvents generated on the Fire TV remote are the same KeyEvents that are generated on a classic Android device, but with physical buttons.

First of all, we need to make sure that our application is in control of the remote. We can easily do that by setting a couple flags in the MediaSession.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.playback_controls);
    ...

    mSession = new MediaSession(this, "LeanbackSampleApp");
    mSession.setCallback(new MediaSessionCallback());
    mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
            | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
    );

    mSession.setActive(true);

}

When MediaSession.setFlags() is invoked, our application takes control of the actions performed on the buttons of the remote. In particular, the FLAG_HANDLES_MEDIA_BUTTONS allows users to take control of the buttons on the bottom of the remote (rewind, play/pause, and forward), and FLAG_HANDLES_TRANSPORT_CONTROLS allows our app to listen for the navigation buttons that control the movement inside the views of the app (up, right, down, left).

Listen to KeyEvents on the Remote

Now that we have set the MediaSession of our app, we can start listening for actions performed on the remote.

To do this, we need to react to KeyEvents:

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    PlaybackOverlayFragment playbackOverlayFragment =
		findFragmentById(R.id.playback_controls_fragment);
    switch (keyCode) {
        ...
        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
            if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
                playbackOverlayFragment.togglePlayback(false);
            } else {
                playbackOverlayFragment.togglePlayback(true);
            }
            return true;
        ...
    }
}

We need to override onKeyUp(), as this is the event that is triggered when users click on a button and lifts their finger (single click/action concluded).

In this case, we demonstrate how to react when the user presses the button play/pause. We react to the KeyEvent: KEYCODE_MEDIA_PLAY_PAUSE, and then check the current LeanbackPlaybackState. If we are PLAYING, we toggle playback to false on the PlaybackOverlayFragment (to stop playing). Otherwise, we set it to true, triggering the callback on the underlying fragment.

Trigger the Playback

Finally, we need to trigger playback on the VideoView. To do this, we need to implement the callback provided by the ClickListener in the activity (in our case, PlaybackOverlayFragment.OnPlayPauseClickedListener).

public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) {
   mVideoView.setVideoPath(movie.getVideoUrl());
    ...
    if (mPlaybackState != LeanbackPlaybackState.PLAYING) {
        	mPlaybackState = LeanbackPlaybackState.PLAYING;
        	if (position > 0) {
            mVideoView.seekTo(position);
            mVideoView.start();
        }
    ...
 }
}

Here's a step-by-step view of what happens:

  1. Set the URL of the video to play. To do this, we use VideoView.setVideoPath(), bypassing the URL of the video. It could be a simple video file on our cloud repository or an embedded video in the app. The important bit is that the URL needs to point to a video file.
  2. We check the LeanbackPlaybackState.
  3. If we need to start playing the content, we use seekTo() to set the position in the video where we want it to start playing (in milliseconds, typically 0).
  4. Call VideoView.start() to finally make the video play.

Other Features of Leanback-Enabled Projects

The Leanback template is quite complex, but allows the creation of rich TV experiences for users. For example, by default, you can define recommended videos when a piece of content is displayed. Also, developers can implement the SearchFragment to create custom search experiences.


Last updated: Oct 29, 2020