Search code examples
androidandroid-videoviewandroid-tvleanback

Customizing playback controls in a Leanback app


I have a pretty basic task: implementing HLS video streaming using Leanback framework and Exoplayer.

I use a simple single-fragment layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/video_fragment"
        android:name="android.support.v17.leanback.app.VideoFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

Apparently, VideoFragment comes with a default HUD which looks like this:

enter image description here

According to the docs, the recommended approach to bridging media player with playback controls is via PlaybackGlue. For example, Leanback showcase app uses MediaPlayer and thus uses a MediaPlayerGlue. I, for my purposes, have taken ExoPlayerGlue from this project.

private PlaybackFragmentGlueHost glueHost;
private PlaybackControlGlue glue;

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...

    final VideoFragment videoFragment = (VideoFragment) getFragmentManager.findFragmentById(R.id.video_fragment);
    glueHost = new VideoFragmentGlueHost(videoFragment);

    glue = ExoPlayerGlue(this, player)
    glue.setHost(glueHost);
}

This code does the job: it binds clicks to player events, so pause/resume and rewinding/fast-forwarding work as expected.

The tricky part is customizing playback controls. PlaybackGlueHost provides setPlaybackRow(Row row) and setPlaybackRowPresenter(PlaybackRowPresenter presenter) methods. Worth pointing out is that these methods have no effect in onCreate() so I either have to use handler or move them to onPostCreate():

@Override
public void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);

    // Buttons I need in my HUD
    final Action skipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(this); // |<
    final Action rewindAction = PlaybackControlsRow.RewindAction(this); // <<
    final Action playPauseAction = PlaybackControlsRow.PlayPauseAction(this);
    final Action fastForwardAction = PlaybackControlsRow.FastForwardAction(this); // >>
    final Action skipNextAction = PlaybackControlsRow.SkipNextAction(this); // >|
    final Action repeatAction = PlaybackControlsRow.RepeatAction(this);

    final PresenterSelector controlPresenterSelector = ControlButtonPresenterSelector();
    final PlaybackControlsRow playbackControlsRow = PlaybackControlsRow();

    final ArrayObjectAdapter primaryAdapter = ArrayObjectAdapter(controlPresenterSelector);
    final ArrayObjectAdapter secondaryAdapter = ArrayObjectAdapter(controlPresenterSelector);

    // First row
    primaryAdapter.add(skipPreviousAction);
    primaryAdapter.add(rewindAction);
    primaryAdapter.add(playPauseAction);
    primaryAdapter.add(fastForwardAction);
    primaryAdapter.add(skipNextAction);

    // Second row
    secondaryAdapter.add(repeatAction);

    playbackControlsRow.primaryActionsAdapter = primaryAdapter;
    playbackControlsRow.secondaryActionsAdapter = secondaryAdapter;

    // Applying row to glue host
    glueHost.setPlaybackRowPresenter(PlaybackControlsRowPresenter());
    glueHost.setPlaybackRow(playbackControlsRow);
}

Now I have my customized playback row but the player no longer responds to button clicks.

I'm having a hard time trying to figure out how PlaybackGlue actually reacts to events from a PlaybackGlueHost. I know I can set OnActionClickedListener in a row presenter and handle actions manually but it kind of renders glue/host interaction model useless. How did it work in the first place? There is no default playback row I could find... I see that each Action corresponds to a set of key codes but I'm not sure what I can make of it.

Could anyone point me in the right direction? There's surprisingly little information on the subject.

Thanks.


Solution

  • Turned out PlaybackControlGlue had all the necessary methods (setControlsRow(), onCreatePrimaryActions(), onCreateSecondaryActions()) which I totally missed in the implementation I borrowed. The glue takes care of providing them to its host in onAttachedToHost() and then sets neccessary listeners.