Leanback トランスポート コントロールを使用する

Leanback AndroidX ライブラリには、ユーザー エクスペリエンスを改善する新しい再生用コントロールが用意されています。動画アプリの場合、トランスポート コントロールは、早送り / 巻き戻しコントロールによる動画スクラブをサポートしています。スクラブ中、ディスプレイには、動画の移動操作に役立つサムネイルが表示されます。

このライブラリには、抽象クラスとすぐに使用できる実装が含まれており、デベロッパーはきめ細かなコントロールを実現できます。ビルド済み実装を利用することで、大量のコーディングを行うことなく、機能豊富なアプリをすばやく構築できます。詳細なカスタマイズが必要な場合は、ライブラリのビルド済みコンポーネントを拡張できます。

コントロールとプレーヤー

Leanback ライブラリは、トランスポート コントロールを備えた UI と、動画を再生するプレーヤーを分離しています。そのために、トランスポート コントロール(および必要に応じて動画)を表示する「再生フラグメント(PlaybackFragment)」と、メディア プレーヤーをカプセル化する「プレーヤー アダプター(PlayerAdapter)」という 2 つのコンポーネントを備えています。

再生フラグメント

アプリの UI アクティビティは、PlaybackFragment または VideoFragment を使用する必要があります。どちらにも、Leanback トランスポート コントロールが含まれています。

  • PlaybackFragment は、トランスポート コントロールをアニメーション化して、必要に応じて非表示 / 表示を切り替えます。
  • VideoFragmentPlaybackFragment を拡張したもので、動画をレンダリングする SurfaceView を備えています。

フラグメントの ObjectAdapter をカスタマイズして UI を強化できます。たとえば、setAdapter() を使用して「関連動画」行を追加できます。

プレーヤー アダプター

PlayerAdapter は、基盤メディア プレーヤーを制御する抽象クラスです。デベロッパーは、ビルド済みの MediaPlayerAdapter 実装を選択することも、このクラスの独自の実装を作成することもできます。

各部分を接着する

再生フラグメントをプレーヤーに接続するには、「コントロール用接着剤」を使用する必要があります。Leanback ライブラリには、2 種類の接着剤が用意されています。

  • PlaybackBannerControlGlue - トランスポート コントロールを再生フラグメント内に「古いスタイル」で描画し、非透過的な背景の内部に配置します(PlaybackBannerControlGlue は、サポートが終了した PlaybackControlGlue の後継です)。
  • PlaybackTransportControlGlue - 透過的な背景の「新しいスタイル」のコントロールを使用します。

Leanback トランスポート コントロール用接着剤

アプリが動画スクラブをサポートするには、PlaybackTransportControlGlue を使用する必要があります。

また、「接着剤ホスト」を指定する必要があります。接着剤ホストは、接着剤を再生フラグメントにバインドし、UI 内にトランスポート コントロールを描画してその状態を維持し、トランスポート コントロール イベントを接着剤に戻します。ホストは、再生フラグメントのタイプに適合する必要があります。PlaybackFragment の場合は PlaybackFragmentGlueHost を使用し、VideoFragment の場合は VideoFragmentGlueHost を使用します。

Leanback トランスポート コントロールの各部分を接着剤でまとめる様子を以下の図に示します。

Leanback トランスポート コントロール用接着剤

アプリを接着するコードは、UI を定義している PlaybackFragment または VideoFragment の内部に存在している必要があります。

下記の例の場合、アプリは PlaybackTransportControlGlue のインスタンスを作成し、playerGlue という名前を付けて、その VideoFragment を、新しく作成された MediaPlayerAdapter に接続しています。VideoFragment であるため、セットアップ コードは setHost() を呼び出して、VideoFragmentGlueHostplayerGlue にアタッチしています。コードは、VideoFragment を拡張するクラスの内部に含まれています。

Kotlin

    class MyVideoFragment : VideoFragment() {

      fun onCreate(savedInstanceState: Bundle) {
          super.onCreate(savedInstanceState)
          val playerGlue = PlaybackTransportControlGlue(getActivity(),
              MediaPlayerAdapter(getActivity()))
          playerGlue.setHost(VideoFragmentGlueHost(this))
          playerGlue.addPlayerCallback(object : PlaybackGlue.PlayerCallback() {
              override fun onPreparedStateChanged(glue: PlaybackGlue) {
                  if (glue.isPrepared()) {
                      playerGlue.seekProvider = MySeekProvider()
                      playerGlue.play()
                  }
              }
          })
          playerGlue.setSubtitle("Leanback artist")
          playerGlue.setTitle("Leanback team at work")
          val uriPath = "android.resource://com.example.android.leanback/raw/video"
          playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath))
      }
    }
    

Java

    public class MyVideoFragment extends VideoFragment {

      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
                  new PlaybackTransportControlGlue(getActivity(),
                          new MediaPlayerAdapter(getActivity()));
          playerGlue.setHost(new VideoFragmentGlueHost(this));
          playerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
              @Override
              public void onPreparedStateChanged(PlaybackGlue glue) {
                  if (glue.isPrepared()) {
                      playerGlue.setSeekProvider(new MySeekProvider());
                      playerGlue.play();
                  }
              }
          });
          playerGlue.setSubtitle("Leanback artist");
          playerGlue.setTitle("Leanback team at work");
          String uriPath = "android.resource://com.example.android.leanback/raw/video";
          playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
      }
    }
    

なお、このセットアップ コードは、メディア プレーヤーから取得したイベントを処理する PlayerAdapter.Callback も定義しています。

UI 用接着剤をカスタマイズする

PlaybackBannerControlGluePlaybackTrabsportControlGlue をカスタマイズして、PlaybackControlsRow を変更することができます。

タイトルと説明をカスタマイズする

再生用コントロールの上部にあるタイトルと説明をカスタマイズするには、onCreateRowPresenter() をオーバーライドします。

Kotlin

    override fun onCreateRowPresenter(): PlaybackRowPresenter {
        return super.onCreateRowPresenter().apply {
            (this as? PlaybackTransportRowPresenter)
                    ?.setDescriptionPresenter(MyCustomDescriptionPresenter())
        }
    }
    

Java

    @Override
    protected PlaybackRowPresenter onCreateRowPresenter() {
      PlaybackTransportRowPresenter presenter = (PlaybackTransportRowPresenter) super.onCreateRowPresenter();
      presenter.setDescriptionPresenter(new MyCustomDescriptionPresenter());
      return presenter;
    }
    

コントロールを追加する

コントロール用接着剤は、PlaybackControlsRow 内のアクションのコントロールを表示します。

PlaybackControlsRow 内のアクションは、プライマリ アクションとセカンダリ アクションという 2 つのグループに分類されます。プライマリ グループのコントロールはシークバーの上部に表示され、セカンダリ グループのコントロールはシークバーの下部に表示されます。最初から存在しているのは、再生 / 一時停止ボタン用のプライマリ アクション 1 つだけで、セカンダリ アクションはありません。

onCreatePrimaryActions()onCreateSecondaryActions() をオーバーライドすることで、プライマリ グループやセカンダリ グループにアクションを追加できます。

Kotlin

    private lateinit var repeatAction: PlaybackControlsRow.RepeatAction
    private lateinit var pipAction: PlaybackControlsRow.PictureInPictureAction
    private lateinit var thumbsUpAction: PlaybackControlsRow.ThumbsUpAction
    private lateinit var thumbsDownAction: PlaybackControlsRow.ThumbsDownAction
    private lateinit var skipPreviousAction: PlaybackControlsRow.SkipPreviousAction
    private lateinit var skipNextAction: PlaybackControlsRow.SkipNextAction
    private lateinit var fastForwardAction: PlaybackControlsRow.FastForwardAction
    private lateinit var rewindAction: PlaybackControlsRow.RewindAction

    override fun onCreatePrimaryActions(primaryActionsAdapter: ArrayObjectAdapter) {
        // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
        // Will display as follows:
        // play/pause, previous, rewind, fast forward, next
        //   > /||      |<        <<        >>         >|
        super.onCreatePrimaryActions(primaryActionsAdapter)
        primaryActionsAdapter.apply {
            add(skipPreviousAction)
            add(rewindAction)
            add(fastForwardAction)
            add(skipNextAction)
        }
    }

    override fun onCreateSecondaryActions(adapter: ArrayObjectAdapter?) {
        super.onCreateSecondaryActions(adapter)
        adapter?.apply {
            add(thumbsDownAction)
            add(thumbsUpAction)
        }
    }
    

Java

    private PlaybackControlsRow.RepeatAction repeatAction;
    private PlaybackControlsRow.PictureInPictureAction pipAction;
    private PlaybackControlsRow.ThumbsUpAction thumbsUpAction;
    private PlaybackControlsRow.ThumbsDownAction thumbsDownAction;
    private PlaybackControlsRow.SkipPreviousAction skipPreviousAction;
    private PlaybackControlsRow.SkipNextAction skipNextAction;
    private PlaybackControlsRow.FastForwardAction fastForwardAction;
    private PlaybackControlsRow.RewindAction rewindAction;

    @Override
    protected void onCreatePrimaryActions(ArrayObjectAdapter primaryActionsAdapter) {
        // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
        // Will display as follows:
        // play/pause, previous, rewind, fast forward, next
        //   > /||      |<        <<        >>         >|
        super.onCreatePrimaryActions(primaryActionsAdapter);
        primaryActionsAdapter.add(skipPreviousAction);
        primaryActionsAdapter.add(rewindAction);
        primaryActionsAdapter.add(fastForwardAction);
        primaryActionsAdapter.add(skipNextAction);
    }

    @Override
    protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
        super.onCreateSecondaryActions(adapter);
        adapter.add(thumbsDownAction);
        adapter.add(thumbsUpAction);
    }
    

新しいアクションを処理するには、onActionClicked() をオーバーライドする必要があります。

Kotlin

    override fun onActionClicked(action: Action) {
        when(action) {
            rewindAction -> {
                // Handle Rewind
            }
            fastForwardAction -> {
                // Handle FastForward
            }
            thumbsDownAction -> {
                // Handle ThumbsDown
            }
            thumbsUpAction -> {
                // Handle ThumbsUp
            }
            else ->
                // The superclass handles play/pause and delegates next/previous actions to abstract methods,
                // so those two methods should be overridden rather than handling the actions here.
                super.onActionClicked(action)
        }
    }

    override fun next() {
        // Skip to next item in playlist.
    }

    override fun previous() {
        // Skip to previous item in playlist.
    }
    

Java

    @Override
    public void onActionClicked(Action action) {
        if (action == rewindAction) {
            // Handle Rewind
        } else if (action == fastForwardAction ) {
            // Handle FastForward
        } else if (action == thumbsDownAction) {
            // Handle ThumbsDown
        } else if (action == thumbsUpAction) {
            // Handle ThumbsUp
        } else {
            // The superclass handles play/pause and delegates next/previous actions to abstract methods,
            // so those two methods should be overridden rather than handling the actions here.
            super.onActionClicked(action);
        }
    }

    @Override
    public void next() {
        // Skip to next item in playlist.
    }

    @Override
    public void previous() {
        // Skip to previous item in playlist.
    }
    

必要に応じて、独自の PlaybackTransportRowPresenter を実装してカスタム コントロールをレンダリングし、PlaybackSeekUi を使用してシーク アクションに応答することができます。

動画スクラブをサポートする

VideoFragment を使用しているアプリの場合、動画スクラブをサポートすることをおすすめします。

スクラブ

PlaybackSeekDataProvider の実装を提供する必要があります。このコンポーネントは、スクロール時に使用される動画サムネイルを提供します。PlaybackSeekDataProvider を拡張して、独自のプロバイダを実装する必要があります。詳細については、Android TV GitHub リポジトリにある Android Leanback Showcase サンプルアプリの例をご覧ください。