MediaRouter の概要

アプリ内で MediaRouter フレームワークを使用するには、MediaRouter オブジェクトのインスタンスを取得し、MediaRouter.Callback オブジェクトをアタッチしてルーティング イベントをリッスンする必要があります。 メディアルート経由で送信されるコンテンツは、そのルートに関連付けられた MediaRouteProvider を通過します(Bluetooth 出力デバイスなどのいくつかの特別な場合を除く)。図 1 は、デバイス間でのコンテンツのルーティングに使用されるクラスの概要を示しています。

図 1. アプリで使用する主要なメディア ルーター クラスの概要。

注: アプリで Google Cast デバイスをサポートする場合は、Cast SDK を使用して、アプリを Cast センダーとしてビルドする必要があります。直接 MediaRouter フレームワークを使用せず、Cast ドキュメントの手順を行ってください。

メディアルート ボタン

Android アプリでは、メディアルート ボタンを使用してメディアのルーティングを制御します。MediaRouter フレームワークには、このボタン用の標準インターフェースが用意されています。これにより、ユーザーはルーティングが利用可能な場合にそれを認識して使用できます。メディアルート ボタンは、通常、アプリのアクションバーの右端に配置されます(図 2 参照)。

図 2. アクションバーのメディアルート ボタン

ユーザーがメディアルート ボタンを選択すると、利用可能なメディアルートのリストが表示されます(図 3 参照)。

図 3. メディアルート ボタンを選択することで表示される利用可能なメディアルートのリスト

メディアルート ボタンの作成手順は次のとおりです。

  1. AppCompatActivity を使用する
  2. メディアルート ボタンのメニュー項目を定義する
  3. MediaRouteSelector を作成する
  4. メディアルート ボタンをアクションバーに追加する
  5. アクティビティのライフサイクルで MediaRouter.Callback メソッドを作成し管理する

このセクションでは、最初の 4 つの手順について説明します。Callback メソッドについては、その次のセクションで説明します。

AppCompatActivity を使用する

アクティビティでメディア ルーター フレームワークを使用するには、アクティビティを AppCompatActivity から拡張し、パッケージ android.support.v7.media をインポートする必要があります。また、サポート ライブラリ v7-appcompatv7-mediarouter を、アプリ開発プロジェクトに追加する必要があります。 サポート ライブラリをプロジェクトに追加する方法の詳細については、サポート ライブラリのセットアップをご覧ください。

注意: 必ずメディア ルーター フレームワークの android.support.v7.media の実装を使用してください。 古い android.media パッケージを使用しないでください。

メディアルート ボタンのメニュー項目を定義する XML ファイルを作成します。 項目のアクションは、MediaRouteActionProvider クラスにします。 ファイルの例を以下に示します。

    // myMediaRouteButtonMenuItem.xml
    <?xml version="1.0" encoding="utf-8"?>
    <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>
    

MediaRouteSelector を作成する

メディアルート ボタンのメニューに表示されるルートは、MediaRouteSelector によって決まります。 以下のコードサンプルに示すように、AppCompatActivity からアクティビティを拡張し、アクティビティを作成する際に onCreate() メソッドから MediaRouteSelector.Builder を呼び出してセレクタを作成します。なお、セレクタはクラス変数に保存され、使用できるルートタイプを指定するには、MediaControlIntent オブジェクトを追加します。

Kotlin

    class MediaRouterPlaybackActivity : AppCompatActivity() {

        private var mSelector: MediaRouteSelector? = null

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // Create a route selector for the type of routes your app supports.
            mSelector = MediaRouteSelector.Builder()
                    // These are the framework-supported intents
                    .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                    .build()
        }
    }
    

Java

    public class MediaRouterPlaybackActivity extends AppCompatActivity {
        private MediaRouteSelector mSelector;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // Create a route selector for the type of routes your app supports.
            mSelector = new MediaRouteSelector.Builder()
                    // These are the framework-supported intents
                    .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                    .build();
        }
    }
    

ほとんどのアプリでは、必要なルートタイプは CATEGORY_REMOTE_PLAYBACK のみです。このルートタイプでは、アプリを実行しているデバイスはリモコンとして扱われます。 コンテンツ データの取得、デコード、再生のすべては、接続された受信デバイスにより処理されます。 Chromecast など、Google Cast をサポートするアプリはこの仕組みで動作します。

メーカーによっては、「セカンダリ出力」と呼ばれる特別なルーティング オプションをサポートしています。このルーティングを使用すると、メディアアプリでは、選択したリモート受信デバイスの画面やスピーカーに直接、動画や音楽を取得、レンダリング、ストリーミングできます。 セカンダリ出力を使用すれば、ワイヤレス対応の音楽システムやビデオ ディスプレイにコンテンツを送信できます。このようなデバイスの検出と選択を可能にするには、コントロール カテゴリ CATEGORY_LIVE_AUDIO または CATEGORY_LIVE_VIDEO を MediaRouteSelector に追加する必要があります。さらに、独自の Presentation ダイアログを作成して処理する必要があります。

メディアルート ボタンをアクションバーに追加する

メディアルート メニューと MediaRouteSelector を定義したら、メディアルート ボタンをアクティビティに追加できます。 オプション メニューを追加するには、アクティビティごとに onCreateOptionsMenu() メソッドをオーバーライドします。

Kotlin

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)

        // Inflate the menu and configure the media router action provider.
        menuInflater.inflate(R.menu.sample_media_router_menu, menu)

        // Attach the MediaRouteSelector to the menu item
        val mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item)
        val mediaRouteActionProvider =
                MenuItemCompat.getActionProvider(mediaRouteMenuItem) as MediaRouteActionProvider

        // Attach the MediaRouteSelector that you built in onCreate()
        selector?.also(mediaRouteActionProvider::setRouteSelector)

        // Return true to show the menu.
        return true
    }
    

Java

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // Inflate the menu and configure the media router action provider.
        getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);

        // Attach the MediaRouteSelector to the menu item
        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
        MediaRouteActionProvider mediaRouteActionProvider =
                (MediaRouteActionProvider)MenuItemCompat.getActionProvider(
                mediaRouteMenuItem);
        // Attach the MediaRouteSelector that you built in onCreate()
        mediaRouteActionProvider.setRouteSelector(selector);

        // Return true to show the menu.
        return true;
    }
    

アプリにアクションバーを実装する方法の詳細については、アクションバー デベロッパー ガイドをご覧ください。

メディアルート ボタンを MediaRouteButton として任意のビューに追加することもできます。setRouteSelector() メソッドを使用して、MediaRouteSelector をボタンにアタッチする必要があります。アプリにメディアルート ボタンを組み込む際のガイドラインについては、Google Cast 設計チェックリストをご覧ください。

MediaRouter コールバック

同じデバイスで実行されるすべてのアプリは、1 つの MediaRouter インスタンスとそのルート(アプリの MediaRouteSelector によってアプリごとにフィルタされる)を共有します。各アクティビティと MediaRouter との通信には、MediaRouter.Callback メソッドのアクティビティごとの実装が使用されます。ユーザーがルートを選択、変更、切断するたびに、MediaRouter によりこのコールバック メソッドが呼び出されます。

このコールバックには、ルーティング イベントに関する情報を受け取るためにオーバーライドできるメソッドがいくつかあります。MediaRouter.Callback クラスの実装では、少なくとも onRouteSelected()onRouteUnselected() をオーバーライドします。

MediaRouter は共有リソースであるため、アプリは通常のアクティビティ ライフサイクルにおけるコールバックへの応答として、次のように MediaRouter コールバックに対応する必要があります。

  • アクティビティが作成されたとき(onCreate(Bundle))に MediaRouter へのポインタを取得し、アプリの有効期間中はそのポインタを保持する。
  • アクティビティが表示されたとき(onStart())にコールバックを MediaRouter にアタッチし、非表示になったとき(onStop())にデタッチする。

以下のコードサンプルは、コールバック オブジェクトを作成および保存する方法、MediaRouter インスタンスを取得する方法、コールバックを管理する方法を示しています。 onStart() でコールバックをアタッチするとき、CALLBACK_FLAG_REQUEST_DISCOVERY フラグを使用していることに注意してください。これにより、メディアルート ボタンの使用可能ルートのリストが、MediaRouteSelector によって更新されます。

Kotlin

    class MediaRouterPlaybackActivity : AppCompatActivity() {

        private var mediaRouter: MediaRouter? = null
        private var mSelector: MediaRouteSelector? = null

        // Variables to hold the currently selected route and its playback client
        private var mRoute: MediaRouter.RouteInfo? = null
        private var remotePlaybackClient: RemotePlaybackClient? = null

        // Define the Callback object and its methods, save the object in a class variable
        private val mediaRouterCallback = object : MediaRouter.Callback() {

            override fun onRouteSelected(router: MediaRouter, route: MediaRouter.RouteInfo) {
                Log.d(TAG, "onRouteSelected: route=$route")
                if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                    // Stop local playback (if necessary)
                    // ...

                    // Save the new route
                    mRoute = route

                    // Attach a new playback client
                    remotePlaybackClient =
                        RemotePlaybackClient(this@MediaRouterPlaybackActivity, mRoute)

                    // Start remote playback (if necessary)
                    // ...
                }
            }

            override fun onRouteUnselected(
                    router: MediaRouter,
                    route: MediaRouter.RouteInfo,
                    reason: Int
            ) {
                Log.d(TAG, "onRouteUnselected: route=$route")
                if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {

                    // Changed route: tear down previous client
                    mRoute?.also {
                        remotePlaybackClient?.release()
                        remotePlaybackClient = null
                    }

                    // Save the new route
                    mRoute = route

                    when (reason) {
                        MediaRouter.UNSELECT_REASON_ROUTE_CHANGED -> {
                            // Resume local playback (if necessary)
                            // ...
                        }
                    }
                }
            }
        }

        // Retain a pointer to the MediaRouter
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // Get the media router service.
            mediaRouter = MediaRouter.getInstance(this)
            ...
        }

        // Use this callback to run your MediaRouteSelector to generate the
        // list of available media routes
        override fun onStart() {
            mSelector?.also { selector ->
                mediaRouter?.addCallback(selector, mediaRouterCallback,
                        MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY)
            }
            super.onStart()
        }

        // Remove the selector on stop to tell the media router that it no longer
        // needs to discover routes for your app.
        override fun onStop() {
            mediaRouter?.removeCallback(mediaRouterCallback)
            super.onStop()
        }
        ...
    }
    

Java

    public class MediaRouterPlaybackActivity extends AppCompatActivity {
        private MediaRouter mediaRouter;
        private MediaRouteSelector mSelector;

        // Variables to hold the currently selected route and its playback client
        private MediaRouter.RouteInfo mRoute;
        private RemotePlaybackClient remotePlaybackClient;

        // Define the Callback object and its methods, save the object in a class variable
        private final MediaRouter.Callback mediaRouterCallback =
                new MediaRouter.Callback() {

            @Override
            public void onRouteSelected(MediaRouter router, RouteInfo route) {
                Log.d(TAG, "onRouteSelected: route=" + route);

                if (route.supportsControlCategory(
                    MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
                    // Stop local playback (if necessary)
                    // ...

                    // Save the new route
                    mRoute = route;

                    // Attach a new playback client
                    remotePlaybackClient = new RemotePlaybackClient(this, mRoute);

                    // Start remote playback (if necessary)
                    // ...
                }
            }

            @Override
            public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
                Log.d(TAG, "onRouteUnselected: route=" + route);

                if (route.supportsControlCategory(
                    MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){

                    // Changed route: tear down previous client
                    if (mRoute != null && remotePlaybackClient != null) {
                        remotePlaybackClient.release();
                        remotePlaybackClient = null;
                    }

                    // Save the new route
                    mRoute = route;

                    if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
                        // Resume local playback  (if necessary)
                        // ...
                    }
                }
            }
        }

        // Retain a pointer to the MediaRouter
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // Get the media router service.
            mediaRouter = MediaRouter.getInstance(this);
            ...
        }

        // Use this callback to run your MediaRouteSelector to generate the list of available media routes
        @Override
        public void onStart() {
            mediaRouter.addCallback(mSelector, mediaRouterCallback,
                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
            super.onStart();
        }

        // Remove the selector on stop to tell the media router that it no longer
        // needs to discover routes for your app.
        @Override
        public void onStop() {
            mediaRouter.removeCallback(mediaRouterCallback);
            super.onStop();
        }
        ...
    }
    

メディア ルーター フレームワークには、アクティビティに対するコールバックの追加と削除を処理する MediaRouteDiscoveryFragment クラスも用意されています。

注: バックグラウンドに置かれた状態で音楽を再生する音楽再生アプリを開発する場合は、再生用の Service を作成し、そのサービスのライフサイクル コールバックからメディア ルーター フレームワークを呼び出す必要があります。

リモート再生ルートの制御

リモート再生ルートを選択した場合は、アプリはリモコンとして機能します。ルートの反対側のデバイスは、コンテンツ データの取得、デコード、再生の機能をすべて処理します。アプリの UI のコントロールは、RemotePlaybackClient オブジェクトを使用して受信デバイスと通信します。

RemotePlaybackClient クラスには、コンテンツの再生を管理するための追加のメソッドが用意されています。以下に、RemotePlaybackClient クラスの主要な再生メソッドをいくつか示します。

  • play() - Uri で指定された特定のメディア ファイルを再生します。
  • pause() - 現在再生中のメディア トラックを一時停止します。
  • resume() - 一時停止コマンドの後、現在のトラックの続きを再生します。
  • seek() - 現在のトラックの特定の位置に移動します。
  • release() - アプリからリモート再生デバイスへの接続を解除します。

これらのメソッドを使用して、アプリで提供する再生コントロールにアクションをアタッチできます。このうちのほとんどはコールバック オブジェクトを含めることもできるため、再生タスクやコントロール リクエストの進行状況を監視できます。

RemotePlaybackClient クラスは、複数のメディアを再生用のキューに入れたり、そのメディアキューを管理したりする機能もサポートしています。

サンプルコード

Android BasicMediaRouter サンプルと MediaRouter サンプルには、MediaRouter API のその他の使用方法が示されています。