MediaRouteProvider の概要

Android メディア ルーター フレームワークでは、メーカーは製造するデバイス上で、MediaRouteProvider と呼ばれる標準インターフェースを使用して再生を行います。 ルート プロバイダは、受信デバイス上でメディアを再生するための共通インターフェースを定義します。これにより、メディアルートをサポートするどの Android アプリからでも、そのデバイスでメディアを再生できます。

このガイドでは、受信デバイス用のメディアルート プロバイダを作成する方法と、それを Android 用の他のメディア再生アプリで使用できるようにする方法について説明します。この API を使用するには、キーとなるクラスである MediaRouteProviderMediaRouteProviderDescriptorRouteController についてよく理解する必要があります。

概要

Android メディア ルーター フレームワークは、メディアアプリのデベロッパーとメディア再生デバイスのメーカーの連携を可能にする、共通の API とユーザー インターフェースを提供します。アプリ デベロッパーは、MediaRouter インターフェースを実装することでメディア ルーター フレームワークに接続でき、それによって、このフレームワークに参加しているデバイス上でコンテンツを再生できます。メディア再生デバイスのメーカーは、他のアプリから受信デバイスに接続してメディアを再生できるようにするための MediaRouteProvider を公開することによって、このフレームワークに参加できます。図 1 は、アプリからメディア ルーター フレームワークを介して受信デバイスに接続する仕組みを示しています。

図 1. メディアルート プロバイダ クラスによってメディアアプリと受信デバイスが接続される仕組みの概要

受信デバイス用のメディアルート プロバイダを構築すると、このプロバイダを次の目的に利用できます。

  • 受信デバイスの機能を説明し公開することで、その再生機能を他のアプリから検出および使用できるようにする。
  • 受信デバイスのプログラミング インターフェースと通信転送メカニズムをラップすることで、そのデバイスにメディア ルーター フレームワークとの互換性を持たせる。

ルート プロバイダの配布

メディアルート プロバイダは、Android アプリの一部として配布されます。ルート プロバイダを他のアプリから使用できるようにするには、MediaRouteProviderService を拡張するか、独自のサービスで MediaRouteProvider の実装をラップし、そのメディアルート プロバイダ用のインテント フィルタを宣言します。この手順により、他のアプリからメディアルートの検出と利用ができるようになります。

注: メディアルート プロバイダを含むアプリには、ルート プロバイダに対する MediaRouter インターフェースを含めることもできますが、これは必須ではありません。

メディア ルーター ライブラリ

メディア ルーター API は、v7-mediarouter サポート ライブラリで定義されています。 このライブラリをアプリ開発プロジェクトに追加する必要があります。 サポート ライブラリをプロジェクトに追加する方法の詳細については、サポート ライブラリのセットアップをご覧ください。

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

プロバイダ サービスの作成

他のアプリからのルートの利用を可能にするためには、メディア ルーター フレームワークによるメディアルート プロバイダの検出と接続が可能でなければなりません。そのために、メディア ルーター フレームワークでは、メディアルート プロバイダのインテント アクションを宣言しているアプリの検索が行われます。別のアプリから接続されるプロバイダは、フレームワークからの呼び出しと接続が可能でなければならないため、Service にカプセル化されている必要があります。

次のサンプルコードは、マニフェスト内でのメディアルート プロバイダ サービスとインテント フィルタの宣言を示しています。これにより、メディア ルーター フレームワークによる検出と使用が可能になります。

    <service android:name=".provider.SampleMediaRouteProviderService"
        android:label="@string/sample_media_route_provider_service"
        android:process=":mrp">
        <intent-filter>
            <action android:name="android.media.MediaRouteProviderService" />
        </intent-filter>
    </service>
    

このマニフェスト例では、実際のメディアルート プロバイダ クラスをラップするサービスを宣言しています。 Android メディア ルーター フレームワークには、メディアルート プロバイダのサービス ラッパーとして使用する MediaRouteProviderService クラスが用意されています。次のサンプルコードは、このラッパークラスの使用方法を示しています。

Kotlin

    class SampleMediaRouteProviderService : MediaRouteProviderService() {

        override fun onCreateMediaRouteProvider(): MediaRouteProvider {
            return SampleMediaRouteProvider(this)
        }
    }
    

Java

    public class SampleMediaRouteProviderService extends MediaRouteProviderService {

        @Override
        public MediaRouteProvider onCreateMediaRouteProvider() {
            return new SampleMediaRouteProvider(this);
        }
    }
    

ルート機能の指定

メディア ルーター フレームワークに接続しようとする他のアプリは、ルートを提供するアプリのマニフェスト宣言を参照することでメディアルートを特定できますが、そのメディアルートの機能も把握する必要があります。メディアルートにはさまざまな種類や機能があるため、他のアプリでルートとの互換性を判断するためには、詳細を把握できなければなりません。

メディア ルーター フレームワークでは、IntentFilter オブジェクト、MediaRouteDescriptor オブジェクト、MediaRouteProviderDescriptor を使用することで、メディアルート機能の定義と公開を行えます。このセクションでは、これらのクラスを使用して、他のアプリにメディアルートの詳細を公開する方法を説明します。

ルートのカテゴリ

メディアルート プロバイダのプログラムでの記述には、そのプロバイダがリモート再生、セカンダリ出力、またはその両方をサポートしているかどうかの情報を含める必要があります。以下は、メディア ルーター フレームワークに用意されているルートのカテゴリです。

  • CATEGORY_LIVE_AUDIO - セカンダリ出力デバイス(ワイヤレス対応の音楽システムなど)への音声出力。
  • CATEGORY_LIVE_VIDEO - セカンダリ出力デバイス(ワイヤレス ディスプレイ デバイスなど)への動画出力
  • CATEGORY_REMOTE_PLAYBACK - メディアの取得、デコード、再生を処理する別のデバイス(Chromecast デバイスなど)での動画や音声の再生。

このような設定をメディアルートの記述に含めるには、次のように設定を IntentFilter オブジェクトに挿入し、それを後で MediaRouteDescriptor オブジェクトに追加します。

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        companion object {
            private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
                addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                arrayListOf(this)
            }
        }
    }
    

Java

    public final class SampleMediaRouteProvider extends MediaRouteProvider {
        private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
        static {
            IntentFilter videoPlayback = new IntentFilter();
            videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
            CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
            CONTROL_FILTERS_BASIC.add(videoPlayback);
        }
    }
    

CATEGORY_REMOTE_PLAYBACK インテントを指定する場合は、メディアルート プロバイダがサポートするメディアタイプと再生コントロールも定義する必要があります。次のセクションでは、デバイスにこれらの設定を指定する方法について説明します。

メディアタイプとプロトコル

リモート再生デバイスのメディアルート プロバイダでは、サポートするメディアタイプと転送プロトコルを指定する必要があります。これらの設定を指定するには、IntentFilter クラス、およびそのオブジェクトの addDataScheme() メソッドと addDataType() メソッドを使用します。次のコード スニペットは、HTTP、HTTPS、リアルタイム ストリーミング プロトコル(RTSP)を使ったリモート動画再生をサポートするインテント フィルタの定義方法を示しています。

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        companion object {

            private fun IntentFilter.addDataTypeUnchecked(type: String) {
                try {
                    addDataType(type)
                } catch (ex: IntentFilter.MalformedMimeTypeException) {
                    throw RuntimeException(ex)
                }
            }

            private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = IntentFilter().run {
                addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                addAction(MediaControlIntent.ACTION_PLAY)
                addDataScheme("http")
                addDataScheme("https")
                addDataScheme("rtsp")
                addDataTypeUnchecked("video/*")
                arrayListOf(this)
            }
        }
        ...
    }
    

Java

    public final class SampleMediaRouteProvider extends MediaRouteProvider {

        private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;

        static {
            IntentFilter videoPlayback = new IntentFilter();
            videoPlayback.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
            videoPlayback.addAction(MediaControlIntent.ACTION_PLAY);
            videoPlayback.addDataScheme("http");
            videoPlayback.addDataScheme("https");
            videoPlayback.addDataScheme("rtsp");
            addDataTypeUnchecked(videoPlayback, "video/*");
            CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
            CONTROL_FILTERS_BASIC.add(videoPlayback);
        }
        ...

        private static void addDataTypeUnchecked(IntentFilter filter, String type) {
            try {
                filter.addDataType(type);
            } catch (MalformedMimeTypeException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    

再生コントロール

リモート再生を提供するメディアルート プロバイダでは、サポートするメディア コントロールの種類を指定する必要があります。メディアルートで利用できる一般的なコントロールのタイプは次のとおりです。

  • 再生コントロール - 再生、一時停止、巻き戻し、早送りなど。
  • キュー機能 - これにより、受信デバイス上の再生リストに対して、送信アプリからのアイテムの追加や削除が可能になります。
  • セッション機能 - これにより、受信デバイスから要求元アプリにセッション ID が提供され、その後の各再生コントロール リクエストではその ID がチェックされることで、送信アプリ間での相互干渉が回避されます。

次のコード例は、基本的なメディアルート再生コントロールをサポートするインテント フィルタの作成方法を示しています。

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        companion object {
            ...
            private val CONTROL_FILTERS_BASIC: ArrayList<IntentFilter> = run {
                val videoPlayback: IntentFilter = ...
                ...
                val playControls = IntentFilter().apply {
                    addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
                    addAction(MediaControlIntent.ACTION_SEEK)
                    addAction(MediaControlIntent.ACTION_GET_STATUS)
                    addAction(MediaControlIntent.ACTION_PAUSE)
                    addAction(MediaControlIntent.ACTION_RESUME)
                    addAction(MediaControlIntent.ACTION_STOP)
                }
                arrayListOf(videoPlayback, playControls)
            }
        }
        ...
    }
    

Java

    public final class SampleMediaRouteProvider extends MediaRouteProvider {
        private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
        static {
            ...
            IntentFilter playControls = new IntentFilter();
            playControls.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
            playControls.addAction(MediaControlIntent.ACTION_SEEK);
            playControls.addAction(MediaControlIntent.ACTION_GET_STATUS);
            playControls.addAction(MediaControlIntent.ACTION_PAUSE);
            playControls.addAction(MediaControlIntent.ACTION_RESUME);
            playControls.addAction(MediaControlIntent.ACTION_STOP);
            CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
            CONTROL_FILTERS_BASIC.add(videoPlayback);
            CONTROL_FILTERS_BASIC.add(playControls);
        }
        ...
    }
    

利用可能な再生コントロール インテントの詳細については、MediaControlIntent クラスをご覧ください。

MediaRouteProviderDescriptor

IntentFilter オブジェクトを使用してメディアルートの機能を定義したら、Android メディア ルーター フレームワークに公開するための記述子オブジェクトを作成できます。この記述子オブジェクトには、メディアルートの機能の詳細が含まれているため、他のアプリでそのメディアルートの操作方法を判断することが可能になります。

次のコード例は、前に作成したインテント フィルタを MediaRouteProviderDescriptor に追加し、この記述子をメディア ルーター フレームワークで使用できるように設定する方法を示しています。

Kotlin

    class SampleMediaRouteProvider(context: Context) : MediaRouteProvider(context) {

        init {
            publishRoutes()
        }

        private fun publishRoutes() {
            val resources = context.resources
            val routeName: String = resources.getString(R.string.variable_volume_basic_route_name)
            val routeDescription: String = resources.getString(R.string.sample_route_description)
            // Create a route descriptor using previously created IntentFilters
            val routeDescriptor: MediaRouteDescriptor =
                    MediaRouteDescriptor.Builder(VARIABLE_VOLUME_BASIC_ROUTE_ID, routeName)
                            .setDescription(routeDescription)
                            .addControlFilters(CONTROL_FILTERS_BASIC)
                            .setPlaybackStream(AudioManager.STREAM_MUSIC)
                            .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
                            .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
                            .setVolumeMax(VOLUME_MAX)
                            .setVolume(mVolume)
                            .build()
            // Add the route descriptor to the provider descriptor
            val providerDescriptor: MediaRouteProviderDescriptor =
                    MediaRouteProviderDescriptor.Builder()
                            .addRoute(routeDescriptor)
                            .build()

            // Publish the descriptor to the framework
            descriptor = providerDescriptor
        }
        ...
    }
    

Java

    public SampleMediaRouteProvider(Context context) {
        super(context);
        publishRoutes();
    }

    private void publishRoutes() {
        Resources r = getContext().getResources();
        // Create a route descriptor using previously created IntentFilters
        MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
                VARIABLE_VOLUME_BASIC_ROUTE_ID,
                r.getString(R.string.variable_volume_basic_route_name))
                .setDescription(r.getString(R.string.sample_route_description))
                .addControlFilters(CONTROL_FILTERS_BASIC)
                .setPlaybackStream(AudioManager.STREAM_MUSIC)
                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
                .setVolumeMax(VOLUME_MAX)
                .setVolume(mVolume)
                .build();
        // Add the route descriptor to the provider descriptor
        MediaRouteProviderDescriptor providerDescriptor =
                new MediaRouteProviderDescriptor.Builder()
                .addRoute(routeDescriptor)
                .build();

        // Publish the descriptor to the framework
        setDescriptor(providerDescriptor);
    }
    

使用可能な記述子設定の詳細については、MediaRouteDescriptorMediaRouteProviderDescriptor のリファレンス ドキュメントをご覧ください。

ルートのコントロール

あるアプリからメディアルート プロバイダへの接続がなされると、プロバイダは他のアプリからルートに送信された再生コマンドをメディア ルーター フレームワークを介して受信します。このリクエストを処理するには、MediaRouteProvider.RouteController クラスを実装しておく必要があります。このクラスにより、コマンドが処理され、受信デバイスとの実際の通信がなされます。

メディア ルーター フレームワークにより、ルート プロバイダの onCreateRouteController() メソッドが呼び出されてそのクラスのインスタンスが作成され、そこにリクエストがルーティングされます。 以下は、MediaRouteProvider.RouteController クラスの主要なメソッドで、メディアルート プロバイダに実装する必要があるものです。

  • onSelect() - アプリで再生のルートが選択されたときに呼び出されます。このメソッドを使用して、メディアの再生を開始する前に必要な準備作業を行います。
  • onControlRequest() - 特定の再生コマンドを受信デバイスに送信します。
  • onSetVolume() - 受信デバイスにリクエストを送信して、再生音量を特定の値に設定します。
  • onUpdateVolume() - 受信デバイスにリクエストを送信して、指定の量だけ再生音量を変更します。
  • onUnselect() - アプリでルートの選択が解除されたときに呼び出されます。
  • onRelease() - フレームワークでルートが不要になったときに呼び出されます。これにより、ルートのリソースが解放されます。

音量変更を除くすべての再生コントロール リクエストは、onControlRequest() メソッドに送られます。このメソッドの実装では、コントロール リクエストを適切にパースし、応答する必要があります。以下に、このメソッドの実装でリモート再生メディアルートのコマンドを処理する例を示します。

Kotlin

    private class SampleRouteController : MediaRouteProvider.RouteController() {
        ...

        override fun onControlRequest(
                intent: Intent,
                callback: MediaRouter.ControlRequestCallback?
        ): Boolean {
            return if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                val action = intent.action
                when (action) {
                    MediaControlIntent.ACTION_PLAY -> handlePlay(intent, callback)
                    MediaControlIntent.ACTION_ENQUEUE -> handleEnqueue(intent, callback)
                    MediaControlIntent.ACTION_REMOVE -> handleRemove(intent, callback)
                    MediaControlIntent.ACTION_SEEK -> handleSeek(intent, callback)
                    MediaControlIntent.ACTION_GET_STATUS -> handleGetStatus(intent, callback)
                    MediaControlIntent.ACTION_PAUSE -> handlePause(intent, callback)
                    MediaControlIntent.ACTION_RESUME -> handleResume(intent, callback)
                    MediaControlIntent.ACTION_STOP -> handleStop(intent, callback)
                    MediaControlIntent.ACTION_START_SESSION -> handleStartSession(intent, callback)
                    MediaControlIntent.ACTION_GET_SESSION_STATUS ->
                        handleGetSessionStatus(intent, callback)
                    MediaControlIntent.ACTION_END_SESSION -> handleEndSession(intent, callback)
                    else -> false
                }.also {
                    Log.d(TAG, sessionManager.toString())
                }
            } else {
                false
            }
        }
        ...
    }
    

Java

    private final class SampleRouteController extends
            MediaRouteProvider.RouteController {
        ...

        @Override
        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {

            String action = intent.getAction();

            if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                boolean success = false;
                if (action.equals(MediaControlIntent.ACTION_PLAY)) {
                    success = handlePlay(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
                    success = handleEnqueue(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
                    success = handleRemove(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
                    success = handleSeek(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
                    success = handleGetStatus(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
                    success = handlePause(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
                    success = handleResume(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
                    success = handleStop(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
                    success = handleStartSession(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
                    success = handleGetSessionStatus(intent, callback);
                } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
                    success = handleEndSession(intent, callback);
                }

                Log.d(TAG, sessionManager.toString());
                return success;
            }
            return false;
        }
        ...
    }
    

ここで重要なのは、MediaRouteProvider.RouteController クラスは、メディア再生デバイスに対する API のラッパーとして機能させるためのものだということです。このクラスのメソッドの実装は、受信デバイスに用意されたプログラム インターフェースによってまったく異なります。

サンプルコード

MediaRouter サンプルには、カスタム メディアルート プロバイダを作成する方法が示されています。