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.
- Main Components of the PlaybackOverlayActivity
- The Remote
- Listen to KeyEvents on the Remote
- Trigger the Playback
- Other Features of Leanback-Enabled Projects
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;
...
}
Listeners
: We can add plenty ofListeners
to thePlaybackOverlayActivity
, 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 onlyListener
available on the Leanback template, theOnPlayPauseClickedListener
.VideoView
is the player we'll use to play the video content.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
).MediaSession:
The main task ofMediaSession
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:
- 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. - We check the
LeanbackPlaybackState
. - 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). - 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