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 方法

本节描述前四个步骤。下一节将描述回调方法。

使用 AppCompatActivity

当您在 activity 中使用媒体路由器框架时,您应该从 AppCompatActivity 扩展 activity 并导入包 androidx.appcompat.app。您必须将 androidx.appcompat:appcompatandroidx.mediarouter:mediarouter 支持库添加到您的应用开发项目中。有关将支持库添加到项目的更多信息,请参阅 Android Jetpack 入门

警告:请确保使用 androidx 实现的媒体路由器框架。请勿使用较旧的 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="androidx.mediarouter.app.MediaRouteActionProvider"
        app:showAsAction="always"
    />
</menu>

创建 MediaRouteSelector

出现在媒体路由按钮菜单中的路由由 MediaRouteSelector 确定。从 AppCompatActivity 扩展您的 activity,并在 activity 创建时调用 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 是共享资源,因此您的应用需要响应通常的 activity 生命周期回调来管理其 MediaRouter 回调。

  • 当创建 activity (onCreate(Bundle)) 时,获取指向 MediaRouter 的指针,并在应用的生命周期内保留它。
  • 当 activity 变得可见 (onStart()) 时将回调附加到 MediaRouter,并在其隐藏 (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();
    }
    ...
}

MediaRouter 框架还提供了一个 MediaRouteDiscoveryFragment 类,该类负责为 activity 添加和删除回调。

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

控制远程播放路由

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

RemotePlaybackClient 类提供了用于管理内容播放的其他方法。以下是 RemotePlaybackClient 类中一些关键的播放方法:

  • play() — 播放特定媒体文件,由 Uri 指定。
  • pause() — 暂停当前播放的媒体曲目。
  • resume() — 在暂停命令后继续播放当前曲目。
  • seek() — 移动到当前曲目中的特定位置。
  • release() — 拆除应用与远程播放设备之间的连接。

您可以使用这些方法将操作附加到您在应用中提供的播放控件。这些方法中的大多数还允许您包含回调对象,以便您可以监视播放任务或控制请求的进度。

RemotePlaybackClient 类还支持对多个媒体项目进行排队播放和媒体队列管理。

示例代码

Android BasicMediaRouterMediaRouter 示例进一步演示了 MediaRouter API 的用法。