MediaRouteProvider の概要

Android メディア ルーター フレームワークでは、メーカーは MediaRouteProvider と呼ばれる標準化されたインターフェースを介してデバイスでの再生を有効にできます。ルート プロバイダは、レシーバー デバイスでメディアを再生するための共通のインターフェースを定義することで、メディアルートをサポートする Android アプリから機器でメディアを再生できるようにします。

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

概要

Android メディア ルーター フレームワークを使用すると、メディアアプリのデベロッパーとメディア再生デバイス メーカーは、共通の API と共通のユーザー インターフェースを介して接続できます。MediaRouter インターフェースを実装するアプリ デベロッパーは、フレームワークに接続して、メディア ルーター フレームワークに参加しているデバイスでコンテンツを再生できます。メディア再生デバイス メーカーは、他のアプリがレシーバー デバイスに接続してメディアを再生できるようにする MediaRouteProvider を公開することで、フレームワークに参加できます。図 1 は、アプリがメディア ルーター フレームワークを介して受信デバイスに接続する方法を示しています。

図 1. メディアルート プロバイダ クラスがメディアアプリから受信デバイスへの通信を提供する仕組みの概要。

受信デバイスのメディアルート プロバイダを作成すると、プロバイダは次の目的を果たします。

  • レシーバー デバイスの機能を記述して公開し、他のアプリがそのデバイスを検出して再生機能を使用できるようにする。
  • 受信デバイスのプログラミング インターフェースとその通信トランスポート メカニズムをラップして、メディア ルーター フレームワークとの互換性を持たせます。

ルート プロバイダの配布

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

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

MediaRouter サポート ライブラリ

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

注意: 必ずメディア ルーター フレームワークの AndroidX 実装を使用してください。 古い 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 サンプルは、カスタム メディアルート プロバイダを作成する方法を示しています。