第二个 Android 11 开发者预览版现已推出,快来测试并分享您的反馈吧

MediaRouter 概览

要在您的应用中使用 MediaRouter 框架,您必须获取 MediaRouter 对象的实例并附加一个 MediaRouter.Callback 对象来监听路由事件。通过媒体路由发送的内容将会通过该路由的关联 MediaRouteProvider(少数特殊情况除外,例如蓝牙输出设备)。图 1 概要显示了用于在设备之间路由内容的类。

图 1. 应用使用的主要媒体路由器类的概览。

注意:如果您希望自己的应用支持 Google Cast 设备,则您应使用 Cast SDK 并将您的应用编译为 Cast 发送端。请按照 Cast 文档中的说明操作,而非直接使用 MediaRouter 框架。

媒体路由按钮

Android 应用应使用媒体路由按钮来控制媒体路由。MediaRouter 框架为该按钮提供了标准接口,有助于用户识别和使用路由(如果有)。媒体路由按钮通常位于应用操作栏的右侧,如图 2 所示。

图 2. 操作栏中的媒体路由按钮。

当用户按下媒体路由按钮时,可用的媒体路由会显示在列表中,如图 3 所示。

图 3. 按下媒体路由按钮后显示的可用媒体路由列表。

要创建媒体路由按钮,请按以下步骤操作:

  1. 使用 AppCompatActivity
  2. 定义媒体路由按钮菜单项
  3. 创建 MediaRouteSelector
  4. 将媒体路由按钮添加到操作栏
  5. 在 Activity 的生命周期内创建并管理 MediaRouter.Callback 方法

本部分介绍了前四个步骤。下一部分介绍了 Callback 方法。

使用 AppCompatActivity

当您在 Activity 中使用媒体路由器框架时,应从 AppCompatActivity 扩展 Activity 并导入软件包 android.support.v7.media。您必须将 v7-appcompatv7-mediarouter 支持库添加到应用开发项目中。如需详细了解如何将支持库添加到项目中,请参阅支持库设置

注意:请务必使用媒体路由器框架的 android.support.v7.media 实现。请勿使用旧版 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="android.support.v7.app.MediaRouteActionProvider"
            app:showAsAction="always"
        />
    </menu>
    

创建 MediaRouteSelector

媒体路由按钮菜单中出现的路由由 MediaRouteSelector 确定。从 AppCompatActivity 扩展您的 Activity,并在创建 Activity(通过从 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。此路由类型会将运行您的应用的设备视为遥控器。连接的接收方设备会处理所有内容数据的检索、解码和播放事宜。这便是支持 Google Cast 的应用(例如,Chromecast)的工作原理。

少数制造商支持称为“辅助输出”的特殊路由选项。借助该路由,您的媒体应用可以检索、渲染视频或音乐,或者直接将视频或音乐流式传输到所选的远程接收方设备上的屏幕和/或音响设备中。使用辅助输出将内容发送到支持无线功能的音乐系统或视频显示屏。要发现并选择这些设备,您需要将 CATEGORY_LIVE_AUDIOCATEGORY_LIVE_VIDEO 控件类别添加到 MediaRouteSelector 中。您还需要创建和处理自己的 Presentation 对话框。

将媒体路由按钮添加到操作栏

媒体路由菜单和 MediaRouteSelector 已定义,您现在可以将媒体路由按钮添加到 Activity 中。为每个 Activity 替换 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 回调

在同一设备上运行的所有应用共用一个 MediaRouter 实例及其路由(通过应用的 MediaRouteSelector 针对每个应用进行过滤)。每个 Activity 都使用自己的 MediaRouter.Callback 方法实现与 MediaRouter 进行通信。当用户选择、更改路由,或与路由断开连接时,MediaRouter 便会调用这些回调方法。

您可以替换回调中的多个方法,以接收有关路由事件的信息。MediaRouter.Callback 类的实现应至少替换 onRouteSelected()onRouteUnselected()

由于 MediaRouter 是共用资源,因此您的应用需要管理其 MediaRouter 回调以响应常规的 Activity 生命周期回调:

  • 创建 Activity (onCreate(Bundle)) 后,抓取指向 MediaRouter 的指针,并在应用的生命周期内持有它。
  • 当 Activity 变为可见 (onStart()) 时,将回调附加到 MediaRouter;当 Activity 处于隐藏状态 (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 类,该类负责添加和移除 Activity 的回调。

注意:如果您正在编写一款音乐播放应用,并且希望该应用在后台时播放音乐,则您必须编译一个用于播放的 Service 服务,并从该服务的生命周期回调中调用媒体路由器框架。

控制远程播放路由

在选择远程播放路由时,您的应用会充当遥控器。路由另一端的设备会处理所有内容数据的检索、解码和播放功能。应用界面中的控件使用 RemotePlaybackClient 对象与接收方设备进行通信。

RemotePlaybackClient 类提供了用于管理内容播放的其他方法。以下是一些来自 RemotePlaybackClient 类的主要播放方法:

  • play() - 播放由 Uri 指定的特定媒体文件。
  • pause() - 暂停当前正在播放的媒体曲目。
  • resume() - 执行暂停命令后继续播放当前曲目。
  • seek() - 移至当前曲目中的特定位置。
  • release() - 断开应用与远程播放设备之间的连接。

您可以使用这些方法将操作附加到您在应用中提供的播放控件。其中的大部分方法还可让您添加回调对象,以便监控播放任务或控制请求的进度。

RemotePlaybackClient 类还支持将多个媒体项加入队列,以便播放和管理媒体队列。

示例代码

Android BasicMediaRouterMediaRouter 示例进一步演示了如何使用 MediaRouter API。