はじめに

以前 Android で始める Chromecast 対応という記事を書きました。 その時に紹介しきれなかった機能について今回は説明していきたいと思います。

ぜひ前回の記事と合わせて見ていただければと思います。

続・ UI 系の実装

前回は Cast Button と Introduction Overlay についての説明を行いました。 今回は以下の 3 つについて説明をしたいと思います。

  • Mini Controller
  • Expanded Controller
  • Notification & Lock Screen

Mini Controller

キャスト再生中、その現在の再生位置や再生コンテンツのサムネイル、タイトル、サブタイトル等を画面上に表示しておく為の小さいコントローラーがこれになります。 これをタップすると後述するリモコンを表示することも出来ます。

このミニコントローラーを表示しておくことで、ユーザーがリモコンに戻ったり動画を一時停止させるという操作をワンアクションで行えるため非常に便利です。

これも導入自体はとても簡単で、表示したい部分の layout ファイルに以下の記述を加えるだけです。

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.
           MiniControllerFragment" />

ミニコントローラもキャストボタンのときのように、表示非表示の制御を自力でやる必要はありません。 SDK の方で今再生中なのかどうかを確認し、表示非表示を制御してくれます。

もちろんフラグメントなので、表示サイズや位置などは自分で決めるのもありかとは思いますが、 Google の推奨が画面下部へこの形なのでこれを仕様にするのがいいでしょう。

ミニコントローラのカスタマイズ

ミニコントローラはいくつかカスタマイズ可能な部分があります。

  • テーマのカスタマイズ

ミニコントローラーはテーマとして CastMiniController というものを使用しています。 その為、それをオーバーライドして各 UI パーツの設定を変更することで、テーマをある程度カスタマイズすることが可能です。

例としては以下のようになります。

<style name="CustomCastMiniController" parent="CastMiniController">
    <item name="castShowImageThumbnail">true</item>
    <item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
    <item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
    <item name="castBackground">#FFFFFF</item>
    <item name="castProgressBarColor">#FFFFFF</item>
    <item name="castPlayButtonDrawable">@drawable/cast_ic_mini_controller_play</item>
    <item name="castPauseButtonDrawable">@drawable/cast_ic_mini_controller_pause</item>
    <item name="castStopButtonDrawable">@drawable/cast_ic_mini_controller_stop</item>
    <item name="castLargePlayButtonDrawable">@drawable/cast_ic_mini_controller_play_large</item>
    <item name="castLargePauseButtonDrawable">@drawable/cast_ic_mini_controller_pause_large</item>
    <item name="castLargeStopButtonDrawable">@drawable/cast_ic_mini_controller_stop_large</item>
    <item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_mini_controller_skip_prev</item>
    <item name="castSkipNextButtonDrawable">@drawable/cast_ic_mini_controller_skip_next</item>
    <item name="castRewind30ButtonDrawable">@drawable/cast_ic_mini_controller_rewind30</item>
    <item name="castForward30ButtonDrawable">@drawable/cast_ic_mini_controller_forward30</item>
    <item name="castMuteToggleButtonDrawable">@drawable/cast_ic_mini_controller_mute</item>
    <item name="castClosedCaptionsButtonDrawable">@drawable/cast_ic_mini_controller_closed_caption</item>
</style>

これは Google の UI ガイドにも載っているサンプルですが、このような項目を自由に変更することができます。

  • サムネイルを非表示に

ミニコントローラに表示するサムネイルを出さないように設定できます。 これはレイアウトに追加したミニコントローラーの fragment に以下の文を一つ追加することで可能です。

<fragment
    ...
    app:castShowImageThumbnail="false"
    ... >

これは前述したテーマのカスタマイズでも行うことができますのでどちらか片方で設定するのがいいでしょう。

  • 設置するボタンの変更

ミニコントローラーに表示するボタンの種類を決定することができます。 デフォルトでは 再生/一時停止のみになっていますが、ミニコントローラには自由にボタンを設置することの出来るスロットが 3 つ分用意されています。

まずは array としてキャストボタンに置くものを設定しておきます。

<array name="cast_mini_controller_control_buttons">
    <item>@id/cast_button_type_rewind_30_seconds</item>
    <item>@id/cast_button_type_play_pause_toggle</item>
    <item>@id/cast_button_type_forward_30_seconds</item>
</array>

次に設定した name を用いてミニコントローラの方にこのボタンの array を設定します。

<fragment
    android:id="@+id/cast_mini_controller"
    ...
    app:castControlButtons="@array/cast_mini_controller_control_buttons"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment">

これによりミニコントローラに表示されるボタンは自分で出したかったもの 3 つになると思います。

ここでボタンについて注意しなくてはいけないことがあります。ミニコントローラに表示するために設定するこの array の数は必ず 3 つでないといけないという点です。 ここで 3 つ以外の数になっていると、 RuntimeException がおこってしまうので気をつけましょう。
もしボタンを設置したくないスロットがある場合には @id/cast_button_type_empty を使用しましょう。

また、3つボタンを設定しても普通だと表示されるボタンは 2 つになっていると思います。 これは単純にミニコントローラ内に表示する場所が足りないため起きている現象です。
この件を解決するには、ミニコントローラに表示するはずのサムネイルを非表示にする必要があります。

以上 2 点に関して気をつけて実装を進めてください。

Expanded Controller

Expanded Controller は、キャストでの再生中に表示しておくリモコン画面のことです。 一時停止や早送り巻き戻し、シークバーの操作など再生中に行いたい機能を置いておく為のものになります。

これは、 SDK の方にクラスとして準備してあるのでそれを継承しカスタマイズして使用することになります。

ここでは、 Expanded Controller の使い方と一緒に Cast へ再生開始の情報を送る方法をまとめたいと思います。

リモコン画面

まずはリモコン画面を表示する方法とそのカスタマイズ方法についてまとめます。

とりあえず最初にすることはリモコン画面へ表示するメニューの準備です。

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

上記のようにキャストボタンのみを内包したメニューを一つ作成します。 これは後々使用するのでリモコン用だとわかるようなファイル名にしておきましょう。

このキャストボタンはリモコン画面上部に表示するようになります。

さて、ここまでの準備ができたのならさっそくリモコン画面用のクラスを作っていきましょう。このときもリモコン画面だと分かりやすいクラス名にしておくと良いでしょう。


public class ExpandedControlsActivity extends ExpandedControllerActivity {

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

上記のように、 ExpandedControllerActivity を継承したクラスを作成します。 とりあえず中身としては先程準備したキャストボタンのみのメニューを表示しておくようにします。

これだけでとりあえずデフォルトのリモコン画面を表示させることが可能です。 特に何かカスタムする予定が無いのであればこのままでも十分使用することができます。

後は新しいアクティビティなので manifest ファイルに以下のように activity の宣言を書いておきましょう。


<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
    <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
</activity>

ここで設定している meta-data は、アプリがバックグラウンドからも消えている時にノーティフィケーションなどから表示された時に親となるアクティビティになります。
リモコンを閉じようとした時に表示されていて欲しい画面を設定しておくと良いでしょう。

後は導入編で少しふれた OptionsProvider に少し設定を加えます。 以前作成している Cast 用の OptionsProvider クラスを開き以下のようにしましょう。


public CastOptions getCastOptions(Context context) {

    //Notification を表示するためのオプション
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    //その他 UI クラスから呼び出される用にする為のオプション
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

以前紹介した部分では AppID のみを設定しただけでしたが、今回で MediaOptions とその中で使用する NotificationOptions を設定しました。

NotificationOptions を設定しておくと Notification が表示されるようになり、 setTargetActivityClassName ではタップ時に表示するクラスを設定することが可能です。

CastMediaOptions は先に作成した NotificationOptions を設定することができます。 また、ミニコントローラやキャストボタンタップ時に表示されるノーティフィケーションの中身をタップした際に表示するクラスを指定することができます。

後は CastOptions.Builder で setCastMediaOptions で先に作成した CastMediaOptions を設定しています。

リモコン画面のカスタマイズ

ミニコントローラ同様にこちらもある程度テーマをカスタマイズすることができます。
CastExpandedController と言うものを利用しているため、それをオーバーライドすることができます。


<style name="CustomCastExpandedController" parent="CastExpandedController">
    <item name="castButtonColor">@null</item>
    <item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
    <item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
    <item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
    <item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_expanded_controller_skip_previous</item>
    <item name="castSkipNextButtonDrawable">@drawable/cast_ic_expanded_controller_skip_next</item>
    <item name="castRewind30ButtonDrawable">@drawable/cast_ic_expanded_controller_rewind30</item>
    <item name="castForward30ButtonDrawable">@drawable/cast_ic_expanded_controller_forward30</item>
</style>

上記スタイルに関しては自由に変更できるので、ボタンの画像の変更等したい場合はここで設定するのが良いでしょう。

また、リモコン画面下部に表示するボタンの種類を変更することも可能です。 その場合は上記で書いているのと同様 CastExpandedController を継承し、以下のように書きます。


<style name="CustomCastExpandedController" parent="CastExpandedController">
    <item name="castControlButtons">
        @array/cast_expanded_controller_control_buttons
    </item>
</style>


後はここで指定している array を作成すればボタンを設定可能です。 array は以下のように作成します。


<array name="cast_expanded_controller_control_buttons">
    <item>@id/cast_button_type_empty</item>
    <item>@id/cast_button_type_rewind_30_seconds</item>
    <item>@id/cast_button_type_forward_30_seconds</item>
    <item>@id/cast_button_type_empty</item>
</array>

これを適当な array を書く xml に追加しておきましょう。 ここで指定することができるボタンの id に関しては公式ページの該当箇所を確認してください。

この時設定できるボタンというのは真ん中にある PLAY/PAUSE ボタンの両脇に置くことの出来る左右の 2 つずつ計 4 つのボタンになります。 PLAY/PAUSE ボタンの表示は必須のためこのボタンの変更には含まれていないようです。

また、これは Minicontroller のときと同様ですが、この array も 4 つでなくてはいけません。 上記のように表示したくない場所には @id/cast_button_type_empty を利用しましょう。

Notification & Lock Screen

Notivication と Lock Screen での cast の操作は Android のみの機能です。そのため、設定する項目自体はそう多くありませんが、きちんと表示されるようにしておきましょう。
ちなみに、今回この記事に沿って実装をしてきた方ならもうすでに表示だけはされるようになっています。

ExpandedController の章で少しふれましたが、 OptionsProvider に NotificationOptions を設定することで表示されるようになります。 後は Notification 部分や Lock Screen でのコントロールにどのボタンを配置するのか設定していきます。


// rewind,paly/pause,forward,stop casting の 4 つをノーティフィケーションに表示するよう設定
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// 小さいビューに表示するときや Lock Screen に表示するボタンを、アレイのどの位置のものか指定する
int[] compatButtonActionsIndicies = new int[]{ 1, 3 };

//skipStepMs で rewind と forward で 30 秒移動する用設定
//setActions で表示するボタンの設定
NotificationOptions notificationOptions = new NotificationOptions.Builder()
        .setActions(buttonActions, compatButtonActionsIndicies)
        .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
        .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

上記のように OptionsProvider を設定しているクラスを編集しましょう。 コメントで書いたとおりの実装をしているので詳しい話は割愛しますが、 ExpandedController の時に編集した物のうち NotificationOptions 部分をこのように上書きすることで Notification に表示するボタン等の設定は完了です。

残りの設定は ExpandedController の部分で説明してる通りで大丈夫です。

再生までの流れ

さて、一通り UI 系の実装も完了したところで実際に Chromecast で再生する時にしなくてはいけない部分を押さえておきましょう。

まずは動画再生に使う値を渡す MediaMetadata を設定します。 ここにはタイトルや、サムネイルを設定することになります。


MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

上記のように MediaMetadata.MEDIATYPEMOVIE を設定し、後はそれぞれ KEY を設定しつつデータを渡すことで MediaMetadata を作成することができます。

後は MediaInfo というクラスを作成し、それを RemoteMediaClient の load メソッドに渡すことで動画の再生を行うことができます。


MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
                .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                .setContentType("videos/mp4")
                .setMetadata(movieMetadata)
                .setStreamDuration(mSelectedMedia.getDuration() * 1000)
                .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(mediaInfo, autoPlay, position);

MediaInfo にはストリームタイプの設定やコンテンツのタイプの設定、先程作成した MediaMetadata などを渡します。 この情報を元に load メソッドを呼ぶことで再生が始まります。

その時に以下のような実装をしておくと、再生が開始したタイミングで自動的にリモコン画面を表示させることができます。

remoteMediaClient.addListener(new RemoteMediaClient.Listener(){

    @Override
    public void onStatusUpdated() {
        Intent intent = new Intent(getActivity(), ExpandedControlesActivity.class);
        getActivity().startActivity(intent);
        remoteMediaClient.removeListener(this);
        }

        @Override
        public void onMetadataUpdated() {}

        @Override
        public void onQueueStatusUpdated() {}

        @Override
        public void onPreloadStatusUpdated() {}

        @Override
        public void onSendingRemoteMediaRequest() {}

        @Override
        public void onAdBreakStatusUpdated() {}
    }
);

自動的にリモコン画面を表示させたいときは、このように RemoteMediaClient.Listener を設定してから load を呼ぶようにしましょう。

この MediaMetadata や、 MediaInfo に渡す値に関してはレシーバーの実装やアプリの実装に関わってくる部分です。
なのでここで上げているのはあくまで一例であり、実際にはレシーバーの仕様に合わせた値を渡してあげることになるでしょう。

おわりに

2回にわけて Chromecast 対応について説明させていただきましたが、いかがでしたでしょうか。

ひとまずこの通り実装すれば Android のセンダー側は作成できるという点を目指して書いてみました。
これから Chromecast 対応を行おうと思っている方の参考になれれば幸いです。