MediaRouteProvider 概览

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

本指南介绍了如何为接收方设备创建媒体路由提供程序并使其 适用于在 Android 上运行的其他媒体播放应用。要使用此 API,您需要 您需要熟悉 MediaRouteProvider、 MediaRouteProviderDescriptor和 RouteController。

概览

Android 媒体路由器框架让媒体应用开发者和媒体播放设备能够 可以让制造商通过通用 API 和通用界面进行连接。应用开发者 实现一个 MediaRouter 接口,然后将该接口连接到 框架,并将内容播放到参与媒体路由器框架的设备。媒体 播放设备制造商可以通过以下方式加入该框架:发布一个 MediaRouteProvider,以允许其他应用连接到 在接收设备上播放媒体内容。图 1 说明了应用如何连接到 通过媒体路由器框架连接到设备

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

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

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

分发路由提供程序

媒体路由提供程序作为 Android 应用的一部分进行分发。您的路线提供商可以 扩展为可供其他应用使用 MediaRouteProviderService,或者封装您的 将 MediaRouteProvider 替换为您自己的服务并声明 intent 过滤器。这些步骤可让其他应用发现和利用 媒体路由。

注意:包含媒体路由提供程序的应用还可以包含一个 MediaRouter 接口, 路由提供程序,但这不是必需的。

MediaRouter 支持库

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

注意:请务必使用 AndroidX 媒体路由器框架的实现 请勿使用旧版 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() 方法。通过 以下代码段演示了如何定义 intent 过滤器来支持远程视频 使用 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);
    }
    ...
}

如需详细了解可用的播放控制 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);
}

如需详细了解可用的描述符设置,请参阅参考文档 (针对MediaRouteDescriptor和MediaRouteProviderDescriptor)。

控制路由

当应用连接到您的媒体路由提供程序时,该提供程序会接收播放操作 命令通过媒体路由器框架发送到您的路由。为了处理这些 请求,您必须提供 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 示例展示了如何创建自定义媒体路由提供程序。