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

Compose でビルドを改善する
Android TV OS 用の Jetpack Compose を使用して、最小限のコードで美しい UI を作成します。

Leanback UI ツールキットには、ユーザー エクスペリエンスを向上させる再生コントロールが用意されています。動画アプリの場合、トランスポート コントロールは、前方コントロールと後方コントロールを使用した動画スクラブをサポートしています。ディスプレイをスクラブすると、動画内を移動しやすいようにサムネイルが表示されます。

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

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

Leanback UI ツールキットでは、動画を再生するプレーヤーからトランスポート コントロールの UI を分離しています。これは、トランスポート コントロール(および必要に応じて動画)を表示する再生サポート フラグメントと、メディア プレーヤーをカプセル化するプレーヤー アダプターの 2 つのコンポーネントによって実現されます。

再生フラグメント

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

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

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

PlayerAdapter

PlayerAdapter は、基盤となるメディア プレーヤーを制御する抽象クラスです。デベロッパーは、事前構築済みの MediaPlayerAdapter 実装を選択するか、このクラスの独自の実装を記述できます。

フラグメントを接着する

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

  • PlaybackBannerControlGlue は、トランスポート コントロールを再生フラグメント内に「以前のスタイル」で描画して、不透明な背景内に配置します。(PlaybackBannerControlGlue はサポートが終了した PlaybackControlGlue に代わるものです)。
  • PlaybackTransportControlGlue は、透明な背景の「新しいスタイル」コントロールを使用します。

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

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

また、グルーを再生フラグメントにバインドし、UI にトランスポート コントロールを描画してその状態を維持し、トランスポート コントロール イベントをグルーに戻す「グルーホスト」を指定する必要があります。ホストは、再生フラグメントのタイプに適合する必要があります。PlaybackSupportFragmentGlueHostPlaybackFragment とともに、VideoSupportFragmentGlueHostVideoFragment と一緒に使用します。

以下の図は、Leanback トランスポート コントロールの各要素がどのように組み合わされているかを示しています。

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

アプリを接着するコードは、UI を定義する PlaybackSupportFragment または VideoSupportFragment の内部に配置する必要があります。

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

Kotlin

class MyVideoFragment : VideoSupportFragment() {

  fun onCreate(savedInstanceState: Bundle) {
      super.onCreate(savedInstanceState)
      val playerGlue = PlaybackTransportControlGlue(getActivity(),
          MediaPlayerAdapter(getActivity()))
      playerGlue.setHost(VideoSupportFragmentGlueHost(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 VideoSupportFragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
              new PlaybackTransportControlGlue(getActivity(),
                      new MediaPlayerAdapter(getActivity()));
      playerGlue.setHost(new VideoSupportFragmentGlueHost(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 用接着剤をカスタマイズする

PlaybackBannerControlGluePlaybackTransportControlGlue をカスタマイズして、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 を使用してシーク アクションに応答することをおすすめします。

動画スクラブ

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

スクラブ

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