MediaRouteProvider 概览

通过 Android 媒体路由器框架,制造商可以通过名为 MediaRouteProvider 的标准化接口在其设备上实现播放功能。路由提供程序定义了用于在接收方设备上播放媒体内容的通用接口,从而可以在设备上通过任何支持媒体路由的 Android 应用播放媒体内容。

本指南讨论了如何为接收方设备创建媒体路由提供程序,并使其可供在 Android 上运行的其他媒体播放应用使用。要使用此 API,您应该熟悉关键类 MediaRouteProviderMediaRouteProviderDescriptorRouteController

概览

媒体应用开发者和媒体播放设备制造商能够通过通用 API 和通用界面连接到 Android 媒体路由器框架。这样一来,实现 MediaRouter 接口的应用开发者便可以连接到该框架,并向参与媒体路由器框架的设备播放内容。要参与该框架,媒体播放设备制造商可以发布 MediaRouteProvider,允许其他应用连接到接收方设备并在上面播放媒体。图 1 说明了应用如何通过媒体路由器框架连接到接收方设备。

图 1. 概要显示媒体路由提供程序类如何提供从媒体应用到接收方设备的通信。

当您为接收方设备构建媒体路由提供程序时,该提供程序将用于以下用途:

  • 描述和发布接收方设备的功能,以便其他应用能够发现它并使用其播放功能。
  • 封装接收方设备的编程接口及其通信传输机制,使该设备能够与媒体路由器框架兼容。

分发路由提供程序

媒体路由提供程序作为 Android 应用的一部分进行分发。通过扩展 MediaRouteProviderService,或者使用您自己的服务封装 MediaRouteProvider 的实现并为该媒体路由提供程序声明一个 Intent 过滤器,可将您的路由提供程序提供给其他应用。采用这些步骤后,其他应用就能发现并利用您的媒体路由。

注意:包含媒体路由提供程序的应用还可以向路由提供程序添加一个 MediaRouter 接口,但这并非强制操作。

媒体路由器库

媒体路由器 API 是在 v7-mediarouter 支持库中定义的。您必须将此库添加到您的应用开发项目中。如需详细了解如何将支持库添加到项目中,请参阅支持库设置

注意:请务必使用媒体路由器框架的 android.support.v7.media 实现。请勿使用旧版 android.media 软件包。

创建提供程序服务

媒体路由器框架必须能够发现并连接到您的媒体路由提供程序,才能允许其他应用使用您的路由。为此,媒体路由器框架会查找声明媒体路由提供程序 Intent 操作的应用。当其他应用想要连接到您的提供程序时,该框架必须能够调用并连接到它,因此您的提供程序必须封装在 Service 中。

以下示例代码显示了清单中的媒体路由提供程序服务的声明和 Intent 过滤器,这些信息可使媒体路由器框架发现并使用该提供程序:

    <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 来定义和发布您的媒体路由的功能。本部分介绍了如何使用这些类来为其他应用发布您的媒体路由的详细信息。

路由类别

在程序化地说明媒体路由提供程序时,您必须指定您的提供程序是否支持远程播放和/或辅助输出。下面是媒体路由器框架提供的路由类别:

要将这些设置添加到媒体路由的说明中,请将它们插入到 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 Intent,则还必须定义您的媒体路由提供程序支持的媒体类型和播放控件。下一部分将介绍如何为您的设备指定这些设置。

媒体类型和协议

远程播放设备的媒体路由提供程序必须指定其支持的媒体类型和传输协议。您可以使用 IntentFilter 类以及该对象的 addDataScheme()addDataType() 方法来指定这些设置。以下代码段演示了如何使用 http、https 和实时流协议 (RTSP) 定义 Intent 过滤器,以支持远程视频播放:

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,从而防止发送方应用互相干扰。

以下代码示例演示了如何构建支持基本的媒体路由播放控件的 Intent 过滤器:

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);
        }
        ...
    }
    

如需详细了解可用的播放控件 Intent,请参阅 MediaControlIntent 类。

MediaRouteProviderDescriptor

使用 IntentFilter 对象定义媒体路由的功能后,您可以创建一个要发布到 Android 媒体路由器框架的描述符对象。此描述符对象包含媒体路由功能的具体信息,以便其他应用可以确定如何与您的媒体路由进行互动。

以下示例代码演示了如何将之前创建的 Intent 过滤器添加到 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 示例展示了如何创建自定义媒体路由提供程序。