次に、AndroidでのSenderアプリの開発方法について解説します。
まず、Google Cast SDKが外部依存するライブラリを取り込む必要があります。
依存ライブラリの種類と、それぞれの役割は次の通りです。
Receiverアプリとの通信はWebSocketで行いますが、アプリ開発者は上記ライブラリのAPIを使うことでWebSocketを意識することなくプログラミングできます。
Google Cast SDK自体はGoogle Play Services SDKに同梱されているため、個別にインストールする必要はありません。ただし、Google Play Servicesに依存するため、ユーザーが「Google Play 開発者サービス」をアンインストールしたり無効にしている場合はChromecastの機能が使えない可能性があります。
Google MapsやGoogle+、Google Castなど、グーグルが提供するサービスを使ったアプリを開発できる仕組みです。Androidスマートフォンには、アプリ開発者が作ったアプリとは別にGoogle Play Servicesのアプリがインストールされており、バックグラウンドで常駐しています。
Androidの設定画面からアプリ一覧を見ると「Google Play開発者サービス」と表示されているのが、Google Play Servicesのアプリです。アプリ開発者が作ったアプリは、「Google Play開発者サービス」を介することで、これらのサービスを使えるようになります。「Google Play開発者サービス」は自動で更新されるため、アプリ開発者は自身のアプリをアップデートすることなく常に最新バージョンのサービスを使えます。
これらのライブラリの具体的な取り込み手順については、開発環境(Eclipse/Android Studio)によっても異なるため、ここでは説明を省きます。グーグルが公開しているSupport Library SetupやAndroid Sender App Developmentを参照してください。
次に、具体的な実装を見ていきます。
まずはChromecastデバイスの検索・接続・Receiverアプリの起動までを説明します。ユーザーがChromecastと接続するときはCastアイコンを使いますが、Castアイコンの実装方法には次の3通りがあります。
1と2では、Castアイコンをタップした後、標準的なCastメニューが表示される部分も組み込まれています。3は、UIとは独立したベーシックなAPIだけを使って開発する方法で、Castアイコン・Castメニューを含めUI部分を全て自前で作成する必要があります。
公開されているサンプルプログラムMediaRouter-Cast-Button-androidにそれぞれの実装例が記述されています。
本記事では、接続に関する最低限の実装方法を理解してからUI部分について知ってもらうため、始めに3の方法を解説した後、1のMediaRouteActionProviderを使った方法を示します。
中心となるクラスはandroid-support-v7-mediarouterライブラリのMediaRouterクラスで、このクラスがChromecastを含めたマルチメディアの外部出力先の検出をつかさどります。その他、外部出力先を検索する条件を表したMediaRouteSelectorクラスや、検索結果が得られたときなどのコールバックを記述するMediaRouter.Callbackクラスを使います。
まず、それぞれのクラスのインスタンスを取得/生成します(コード1)。
- mMediaRouter = MediaRouter.getInstance(getApplicationContext()); //【1】
- mMediaRouteSelector = new MediaRouteSelector.Builder().addControlCategory(
- CastMediaControlIntent.categoryForCast(APPLICATION_ID)).build();//【2】
- mMediaRouterCallback = new MyMediaRouterCallback();//【3】
【1】でMediaRouterクラスのインスタンスを取得し、【2】【3】でMediaRouteSelector、MediaRouter.Callbackクラスのインスタンスを生成しています。【3】のMyMediaRouterCallbackクラスはMediaRouter.Callbackインターフェースを実装したクラスで、詳細は後に記します。
【2】では、Builder()メソッドを使ってMediaRouteSelectorクラスをインスタンス化する際、「media control category」という外部出力先の種類を示す文字列を指定しています。ここではChromecastデバイスを検出したいので、CastMediaControlIntentクラスのcategoryForCast()メソッドを使ってChromecast用の文字列を生成しています。
具体的には「com.google.android.gms.cast.CATEGORY_CAST/{Application ID}」のようなReceiverアプリのApplication IDを付与した文字列となっています。
注意点として、MediaRouterをimportするパッケージは「android.support.v7.media.MediaRouter」としてください。誤って「android.media.MediaRouter」をimportしてはいけません。
次にMediaRouteSelector、MediaRouter、MediaRouter.Callbackのインスタンスを関連付け、デバイス検出時などにコールバックが呼ばれるようにします。通常はActivityのonResume()メソッドで実装します(コード2)。
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
一方、ActivityのonPause()メソッドでは、次のようにデバイス検出のコールバックを解除する処理を行います(コード3)。
mMediaRouter.removeCallback(mMediaRouterCallback);
MediaRouter.Callbackの実装クラスMyMediaRouterCallbackでは、以下のコールバックメソッドをオーバーライドします(コード4)。
- private class MyMediaRouterCallback extends MediaRouter.Callback {
- @Override
- public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
- // 新たなCastデバイスが見つかったときに呼ばれる
- Log.d(TAG, "onRouteAdded: info=" + info);
- // 見つかったデバイスの情報を保持
- mMediaRoutes.add(info);
- }
- @Override
- public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
- // 見つかっていたCastデバイスを見失ったときに呼ばれる
- Log.d(TAG, "onRouteRemoved: info=" + info);
- // 見つかったデバイスの情報を削除
- mMediaRoutes.remove(info);
- }
- @Override
- public void onRouteSelected(MediaRouter router, RouteInfo info) {
- // 接続先Castデバイスを選択したときに呼ばれる
- Log.d(TAG, "onRouteSelected: info=" + info);
- // 選択したデバイスを保持
- mSelectedDevice = CastDevice.getFromBundle(info.getExtras());
- }
- @Override
- public void onRouteUnselected(MediaRouter router, RouteInfo info) {
- // 接続先Castデバイスの選択を解除したときに呼ばれる
- Log.d(TAG, "onRouteUnselected: info=" + info);
- mSelectedDevice = null;
- }
- }
Castアイコンの表示は、onRouteAdded()メソッドが呼ばれて検出済みのデバイスが一つでも得られたときに行うといいでしょう。
Castメニューで検出したChromecastデバイスの一覧を表示できるよう、onRouteAdded()メソッドの引数に渡されたMediaRouter.RouteInfoインスタンスを保持しておきます。ユーザーがデバイスの一覧をタップして接続先デバイスを選んだときは、次のようにMediaRouterインスタンスに対して選んだ接続先デバイスを教えます(コード5)。
- MediaRouter.RouteInfo info = mMediaRoutes.get(selected); //ユーザーが選択したデバイスに関するRouteInfoを得る
- mMediaRouter.selectRoute(info); //MediaRouterに選択したデバイスを教える
selectRoute()を実行すると、onRouteSelected()メソッドが呼ばれる仕組みです。
以上でMediaRouterに対して接続先デバイスを指定することができました。
次にChromecastデバイスと接続し、Receiverアプリを起動します。
中心となるクラスはgoogle-play-services_libライブラリのGoogleApiClientクラスです。このクラスはChromecastを含むGoogle Play servicesを使うための窓口となるクラスです。
まず、必要なクラスのインスタンスを生成します(コード6)。
- mCastClientListener = new CastListener();
- mConnectionCallbacks = new ConnectionCallbacks();
- mConnectionFailedListener = new ConnectionFailedListener();
- Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(
- mSelectedDevice, mCastClientListener);
- mApiClient = new GoogleApiClient.Builder(this)
- .addApi(Cast.API, apiOptionsBuilder.build())
- .addConnectionCallbacks(mConnectionCallbacks)
- .addOnConnectionFailedListener(mConnectionFailedListener)
- .build();
CastListener、ConnectionCallbacks、ConnectionFailedListenerは、それぞれCast.Listener、GoogleApiClient.ConnectionCallbacks、GoogleApiClient.OnConnectionFailedListener、で定義されているIFを実装した自作クラスで、Chromecastデバイスとの通信結果や、Receiverアプリの状態変化などのコールバックを実装しています。
各コールバックの実装例は次の通りです(コード7)。
- private class CastListener implements Cast.Listener {
- @Override
- public void onApplicationStatusChanged() {
- // Receiverアプリの状態が変わったときに呼ばれる
- Log.d(TAG, "onApplicationStatusChanged");
- }
- @Override
- public void onVolumeChanged() {
- // Receiverアプリの音量が変わったときに呼ばれる
- Log.d(TAG, "onVolumeChanged");
- }
- @Override
- public void onApplicationDisconnected(int errorCode) {
- // Receiverアプリとの接続が切断したときに呼ばれる
- Log.d(TAG, "onApplicationDisconnected");
- }
- }
- private class ConnectionCallbacks implements
- GoogleApiClient.ConnectionCallbacks {
- @Override
- public void onConnected(Bundle connectionHint) {
- // Chromecastデバイスとの接続が成功したときに呼ばれる
- Log.d(TAG, "onConnected");
- }
- @Override
- public void onConnectionSuspended(int cause) {
- // Chromecastデバイスとの接続が途切れたときに呼ばれる
- Log.d(TAG, "onConnectionSuspended");
- }
- }
- private class ConnectionFailedListener implements
- GoogleApiClient.OnConnectionFailedListener {
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- // Chromecastデバイスとの接続に失敗したときに呼ばれる
- Log.d(TAG, "onConnectionFailed");
- }
- }
GoogleApiClientをインスタンス化したら、次のようにしてChromecastデバイスと接続します(コード8)。
mApiClient.connect();
接続の結果によって、コード7のコールバックメソッドのいずれかが呼び出されます。
接続が成功したら、次のようにしてReceiverアプリを起動します(コード9)。
- try {
- Cast.CastApi
- .launchApplication(mApiClient, APPLICATION_ID, false)
- .setResultCallback(
- new ResultCallback<Cast.ApplicationConnectionResult>() {
- @Override
- public void onResult(Cast.ApplicationConnectionResult result) {
- Status status = result.getStatus();
- if (status.isSuccess()) {
- // Receiverアプリ起動成功
- } else {
- // Receiverアプリ起動失敗
- }
- }
- });
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch application", e);
- }
Cast.CastApiのlaunchApplication()メソッドにReceiverアプリのApplication IDを指定して起動します。setResultCallback()メソッドにはReceiverアプリ起動の結果を受け取るコールバックを設定します。
これらのアプリ起動の処理は、コード7のonConnected()メソッドが呼ばれたタイミングで実行すればいいでしょう。
以上がChromecastデバイスの検索からReceiverアプリの起動までの最小限の実装です。上記を応用すればChromecastデバイスとの接続機能を持ったUIを自由に開発できます。