6
3

More than 5 years have passed since last update.

PlaybackTransportControlGlueを理解する

Posted at

PlaybackTransportControlGlueというクラスをご存知でしょうか?
最近AndroidTV用のアプリを開発したことがある方なら知っているかもしれません。

AndroidTVではcom.android.support:leanback-v17というライブラリを使用するのですが、そのライブラリのv27.0.0から追加された、比較的新しいクラスです。

現在このクラスを使用して開発しているのですが、あまりに記事が少ないため実装して得た知見をまとめておこうと思います。

PlaybackTransportControlGlueとは

glue_side_by_side.png

右側の透過されたコントローラを表現するクラスです。
左側のやつはAndroidTVを持っている人なら一度は見たことあるかもしれません。一般的なコントローラです。

できること

  • Titleを設定できる
  • Subtitleを設定できる
  • 再生・停止・リピート・言語設定等のアクションを設定できる(PrimaryAction)
  • PIP・Goodボタン・Badボタン等を設定できる(SecondaryAction)
  • シークが簡単に実装できる
  • 関連するビデオ一覧を設定できる

0*EdN-twJCF4O-kSqF..png

できないこと

  • 上記できる枠を超えたことをしたい場合、苦労する。(どうしてもやりたい場合は覚悟が必要)

そもそもGlueってなに?

Glue = のり
今回はfragmentとplayerをつなぐもの

なんのためにあるの?

再生に必要なアクションを管理するため。

実装方法

下記の通りにUIとPlayerをGlueを使ってつなげる。

leanback-control-glue.png

インスタンス作成

AdapterをGlueに渡して、Hostを設定する。

mPlayerGlue = new VideoPlayerGlue(getActivity(), mPlayerAdapter, mPlaylistActionListener);
mPlayerGlue.setHost(new VideoFragmentGlueHost(this));

Titleを設定する

playerGlue.setTitle("Leanback team at work");

Descriptionを設定する

playerGlue.setSubtitle("Leanback artist");

再生・停止・リピート・言語設定等のアクションを設定する(PrimaryAction)

Glueクラスのコンストラクタでインスタンス化したActionをonCreatePrimaryActions()内でadapterにセットする

   @Override
   protected void onCreatePrimaryActions(ArrayObjectAdapter adapter) {
       // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
       // Will display as follows:
       // play/pause, previous, rewind, fast forward, next
       //   > /||      |<        <<        >>         >|
       super.onCreatePrimaryActions(adapter);
       adapter.add(mSkipPreviousAction);
       adapter.add(mRewindAction);
       adapter.add(mFastForwardAction);
       adapter.add(mSkipNextAction);
   }

クリックイベントの受け取り

GlueクラスのonActionClicked()で受け取る(SecondaryActionも同じ方法)

   @Override
   public void onActionClicked(Action action) {
       if (shouldDispatchAction(action)) {
           dispatchAction(action);
           return;
       }
       // Super class handles play/pause and delegates to abstract methods next()/previous().
       super.onActionClicked(action);
   }

PIP・Goodボタン・Badボタン等を設定する(SecondaryAction)

Glueクラスのコンストラクタでインスタンス化したActionをonCreateSecondaryActions()内でadapterにセットする

   @Override
   protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
       super.onCreateSecondaryActions(adapter);
       adapter.add(mThumbsDownAction);
       adapter.add(mThumbsUpAction);
       adapter.add(mRepeatAction);
   }

設定可能なアクション一覧

こちらを参照
https://developer.android.com/reference/android/support/v17/leanback/widget/Action.html

シークが簡単に実装できる

1*dtGGBNKg6fQ6ov3UPld01A.gif

上記のようなサムネイル付き画像付きシークが簡単に実装できる。

実装方法

PlaybackSeekDataProviderを作成して、Glueにセットする

public class PlaybackSeekDiskDataProvider extends PlaybackSeekAsyncDataProvider {

    final Paint mPaint;
    final String mPathPattern;
    PlaybackSeekDiskDataProvider(long duration, long interval, String pathPattern) {
        mPathPattern = pathPattern;
        int size = (int) (duration / interval) + 1;
        long[] pos = new long[size];
        for (int i = 0; i < pos.length; i++) {
            pos[i] = i * duration / pos.length;
        }
        setSeekPositions(pos);  //Seek可能な幅を設定する
        mPaint = new Paint();
        mPaint.setTextSize(16);
        mPaint.setColor(Color.BLUE);
    }

    protected Bitmap doInBackground(Object task, int index, long position) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException ex) {
            // Thread might be interrupted by cancel() call.
        }
        if (isCancelled(task)) {
            return null;
        }
        String path = String.format(mPathPattern, (index + 1));
        if (new File(path).exists()) {
            return BitmapFactory.decodeFile(path);
        } else {
            Bitmap bmp = Bitmap.createBitmap(160, 160, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bmp);
            canvas.drawColor(Color.YELLOW);
            canvas.drawText(path, 10, 80, mPaint);
            canvas.drawText(Integer.toString(index), 10, 150, mPaint);
            return bmp;
        }
    }

シークの幅で画像が決まっている以上、動的に幅を変更することはできなそう。
例えば、リモコンのロングタップで早送りの幅を変えるような実装のこと。(再設定は可能)

関連するビデオ一覧を設定できる

Glueクラスのコントローラに設定するRowAdapterを作成、アイテムをセットする。

private ArrayObjectAdapter initializeRelatedVideosRow() {
       ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
       presenterSelector.addClassPresenter(
               mPlayerGlue.getControlsRow().getClass(), mPlayerGlue.getPlaybackRowPresenter());
       presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());

       ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(presenterSelector);

       rowsAdapter.add(mPlayerGlue.getControlsRow());

       HeaderItem header = new HeaderItem(getString(R.string.related_movies));
       ListRow row = new ListRow(header, mVideoCursorAdapter);
       rowsAdapter.add(row);

       setOnItemViewClickedListener(new ItemViewClickedListener());

       return rowsAdapter;
   }

辛いところ

00:00にシークすることができない

指摘したところSupportライブラリチームが修正してくれたので、まもなく公開されるはず。。。

簡単にシークバーを出したり消したりできない

adb shell dumpsys activity topを実行してビューの構成を確かめた後、findViewByIdで参照を取得する必要がある

とりあえずこんな感じシークバーの表示・非表示を制御できる。

view?.let { 
           it.findViewById<View>(R.id.playback_progress)?.apply { this.visibility = visibility }
           it.findViewById<View>(R.id.current_time)?.apply { this.visibility = visibility }
           it.findViewById<View>(R.id.separate_time)?.apply { this.visibility = visibility }
           it.findViewById<View>(R.id.total_time)?.apply { this.visibility = visibility }
           }
      }

PlaybackFragmentを呼び出した時点で配信パスが決定していないとUIが崩れる

詳細画面から配信パスを渡して再生するような設計になっている模様。
プレイヤー画面を起動してからAPI等で配信パスを受け取りたい場合はonCreateViewで空のAdapterと空のGlueを設定後、再度設定することで回避できる。

終わりに

いかがでしょうか?PlaybackTransportControlGlueはAndroidTVのアプリでもまだ導入しているアプリが少ないので狙い目だと思います。
ぜひこの機会に使ってくれる人が増えて、もっと使いやすいライブラリになっていけばと思います。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3