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 からアクティビティを拡張し、パッケージ androidx.appcompat.app をインポートする必要があります。サポート ライブラリ androidx.appcompat:appcompatandroidx.mediarouter:mediarouter をアプリ開発プロジェクトに追加する必要があります。プロジェクトにサポート ライブラリを追加する方法について詳しくは、Android Jetpack スタートガイドをご覧ください。

注意: 必ずメディア ルーター フレームワークの androidx 実装を使用してください。古い 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="androidx.mediarouter.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.Callback メソッドの独自の実装を使用して MediaRouter と通信します。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 BasicMediaRouterMediaRouter のサンプルでは、MediaRouter API の使用方法が詳しく説明されています。