最終更新日:190227原本2015-05-13 

Chromecastアプリ開発入門:
Google Cast SDKを使ったAndroid/iOSアプリの作り方と注意点 (3/6)

MediaRouteActionBarによるCastアイコンの表示

 前節までの実装方法のようにCastアイコンの表示やCastメニューのUIを自作するのは、自由度は高いですが面倒です。冒頭で紹介したMediaRouteActionBarやMediaRouteButtonを使えばUIの作成をライブラリに任せることができます。

 ここではMediaRouteActionBarの使い方を説明します。MediaRouteActionBarを使う場合でも、ここまでに説明した実装は一部を除いて必要です。ここではMediaRouteActionBarを使う場合の追加部分と、実装が一部省略できる部分について述べます。

 MediaRouteActionBarを使うと、Castアイコン表示機能を持ったActionBarを生成できます。

 まず、アプリケーションのメニューを定義するXMLファイルに、MediaRouteActionProviderを追加します(コード10)。

  1. <menu xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:app="http://schemas.android.com/apk/res-auto" >
  3. <item
  4. android:id="@+id/media_route_menu_item"
  5. android:title="@string/media_route_menu_title"
  6. app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
  7. app:showAsAction="always"/>
  8. ...
  9. </menu>
コード10 メニュー定義XMLの変更

 また、MediaRouteActionProviderを適用するActivityはActionBarActivity を継承する必要があります(コード11)。

public class MainActivity extends ActionBarActivity {
    // 省略
}
コード11. Activityの定義

 ActivityのonCreateOptionsMenu()メソッドにて、MediaRouteActonProviderとMediaRouteSelectorの関連付けをします(コード12)。

  1. @Override
  2. public boolean onCreateOptionsMenu(Menu menu) {
  3. getMenuInflater().inflate(R.menu.main, menu);
  4. // MediaRouteActonProviderとMediaRouteSelectorを関連付ける
  5. MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
  6. MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat
  7. .getActionProvider(mediaRouteMenuItem);
  8. mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
  9. return true;
  10. }
コード12 MediaRouteActonProviderとMediaRouteSelectorの関連付け

 以上により、次の機能を組み込んだActionBarが表示されるようになりました。

  • Castアイコンの表示制御
    Receiverを検出したら表示、非検出時は非表示、接続・非接続に応じてアイコン画像を変更するなど
  • Castアイコンをタップしたときに標準的なCastメニューを表示

 このため、UIを自作するときに必要だった次の実装は不要になります。

  • コード5のmMediaRouter.selectRoute(info)メソッドの呼び出し
    この実装がなくても、ユーザーがダイアログでデバイスを選択したら、コード4のMediaRouter.CallbackのonRouteSelected()メソッドがコールバックされる
  • コード4のMediaRouter.CallbackのonRouteAdded()、onRouteRemoved()メソッドの実装
    この実装がなくても、検出済みのChromecastデバイスがCastメニューに表示される

Receiverアプリとの通信

 次にReceiverアプリと通信について説明します。

 Receiverアプリとの通信にはチャンネルを用います。ここで言う「チャンネル」とは、Google Cast SDKが定義するSender-Receiver間の通信経路を指します。チャンネルには次のような特徴があります。

  • チャンネルには、動画再生に関する情報や命令を送受信するチャンネル(メディアチャンネル)と、テキストデータ送受信のためのチャンネル(カスタムチャンネル)がある
  • チャンネルの識別子は「urn:x-cast:」で始まる文字列で表す
  • メディアチャンネルの識別子はあらかじめ決められた「urn:x-cast:com.google.cast.media」である
  • カスタムチャンネルの識別子は「urn:x-cast:」で始まる文字列であれば、アプリ開発者が任意に設定できる(例「urn:x-cast:com.example.custom」)
  • カスタムチャンネルは識別子を変えることで複数作成できる

 今回はメディアチャンネルによる動画再生について実装方法を示します。

メディアチャンネルによる動画再生

 中心となるクラスはRemoteMediaPlayerクラスで、Receiverアプリ上のメディアプレーヤーの操作や状態取得をつかさどります。まずは、RemoteMediaPlayer のインスタンスを生成します(コード13)。

  1. mRemoteMediaPlayer = new RemoteMediaPlayer();
  2. mRemoteMediaPlayer
  3. .setOnStatusUpdatedListener(new RemoteMediaPlayer.OnStatusUpdatedListener() {
  4. @Override
  5. public void onStatusUpdated() {
  6. // メディアプレーヤーの状態が変わったときに呼ばれる
  7. Log.d(TAG, "onStatusUpdated");
  8. // 状態の取得
  9. MediaStatus mediaStatus = mRemoteMediaPlayer
  10. .getMediaStatus();
  11. boolean isPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING;
  12. }
  13. });
  14. mRemoteMediaPlayer
  15. .setOnMetadataUpdatedListener(new RemoteMediaPlayer.OnMetadataUpdatedListener() {
  16. @Override
  17. public void onMetadataUpdated() {
  18. // メディアプレーヤーが保持している動画のメタデータが変わったときに呼ばれる
  19. Log.d(TAG, "onMetadataUpdated");
  20. // メタデータの取得
  21. MediaInfo mediaInfo = mRemoteMediaPlayer.getMediaInfo();
  22. MediaMetadata metadata = mediaInfo.getMetadata();
  23. }
  24. });
コード13 RemoteMediaPlayerインスタンス生成

 RemoteMediaPlayerには、状態が変わったときの通知を受け取るOnStatusUpdatedListener、動画のメタデータが変わった時の通知を受け取るOnMetadataUpdatedListenerをセットします。

 RemoteMediaPlayerのインスタンスを生成したら、メディアチャンネルを確立します(コード14)。

  1. try {
  2. Cast.CastApi.setMessageReceivedCallbacks(mApiClient,
  3. mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer);
  4. } catch (IOException e) {
  5. Log.e(TAG, "Exception while creating media channel", e);
  6. }
コード14 メディアチャンネル確立

 チャンネル確立ときにmRemoteMediaPlayer.getNamespace()で得た文字列を渡していますが、この文字列がメディアチャンネルの識別子である「urn:x-cast:com.google.cast.media」となっています。

 チャンネルを確立したら、まずはRemoteMediaPlayerのrequestStatus()メソッド を呼び出して現在の状態を取得します。後にコード13のonStatusUpdated()メソッドが呼ばれるのでそのタイミングで現在の状態を取得できます(コード15)。

  1. mRemoteMediaPlayer.requestStatus(mApiClient).setResultCallback(
  2. new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
  3. @Override
  4. public void onResult(MediaChannelResult result) {
  5. if (!result.getStatus().isSuccess()) {
  6. Log.e(TAG, "Failed to request status.");
  7. }
  8. }
  9. });
コード15 現在の状態取得

 以上でReceiverアプリのメディアプレーヤーを制御する準備ができました。動画をロードして再生を開始するには、次のように実装します(コード16)。

  1. MediaMetadata mediaMetadata = new MediaMetadata(
  2. MediaMetadata.MEDIA_TYPE_MOVIE);
  3. mediaMetadata.putString(MediaMetadata.KEY_TITLE, "My video");
  4. MediaInfo mediaInfo = new MediaInfo.Builder(
  5. "http://your.server.com/video.mp4").setContentType("video/mp4")
  6. .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
  7. .setMetadata(mediaMetadata).setCustomData(customDataJson)
  8. .build();
  9. try {
  10. mRemoteMediaPlayer
  11. .load(mApiClient, mediaInfo, true)
  12. .setResultCallback(
  13. new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
  14. @Override
  15. public void onResult(MediaChannelResult result) {
  16. if (result.getStatus().isSuccess()) {
  17. Log.d(TAG, "Media loaded successfully");
  18. }
  19. }
  20. });
  21. } catch (IllegalStateException e) {
  22. Log.e(TAG, "Problem occurred with media during loading", e);
  23. } catch (Exception e) {
  24. Log.e(TAG, "Problem opening media during loading", e);
  25. }
コード16 動画の再生開始

 ロードするコンテンツに関する情報をセットしたMediaInfoインスタンスを生成し、RemoteMediaPlayerのload()メソッドを呼び出します。メタデータに関する情報はMediaMetadataクラスのインスタンスに設定します。コンテンツのURLはMediaInfoのBuilder()メソッドの引数に指定します。また、setCustomData()メソッドでアプリに固有な付加情報を設定し、Receiverアプリに受け渡すことも可能です。

 なお、カスタムデータを送信する際の注意点として、送信できるデータサイズは64KBまでという制約があります。それを超えるデータはメディアチャンネルではなくカスタムチャンネルを使って送信する必要がありますが、カスタムチャンネルも一度に送信できるデータサイズは64KBまでの制約があるため、いくつかに分割して送信するようプログラミングしなければなりません。

 動画の再生が始まると、RemoteMediaPlayerのインスタンスを使用して一時停止やシークといったメディアプレーヤーの操作ができるようになります(コード17)。

  1. // 一時停止
  2. mRemoteMediaPlayer.pause(mApiClient).setResultCallback(
  3. new ResultCallback<MediaChannelResult>() {
  4. @Override
  5. public void onResult(MediaChannelResult result) {
  6. // 省略
  7. }
  8. });
  9. // シーク
  10. mRemoteMediaPlayer.seek(mApiClient, seekPosition).setResultCallback(
  11. new ResultCallback<MediaChannelResult>() {
  12. @Override
  13. public void onResult(MediaChannelResult result) {
  14. // 省略
  15. }
  16. });
コード17 一時停止とシーク

 メディアプレーヤーの操作が行われると、Receiverアプリのメディアプレーヤーの状態が変わり、コード13のOnStatusUpdatedListenerクラスのメソッドが呼ばれます。なお、コード13のOnStatusUpdatedListenerクラスのメソッドは、同じChromecastデバイスにつながっている他のSenderアプリが操作した場合や、Receiverアプリ自身の制御により状態が変わったときにも呼ばれます。