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,并导入 软件包 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 构建选择器 调用 onCreate() 方法,如下所示 。请注意,选择器保存在类变量中,允许的路由类型已指定 添加 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 通信 使用自己的 MediaRouter.Callback 实现 方法。每当用户选择、更改或断开路线连接时,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 以进行播放 并从 Service 的生命周期回调调用媒体路由器框架。

控制远程播放路由

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

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

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

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

RemotePlaybackClient 类还支持将 多个媒体项,用于播放和管理媒体队列。

示例代码

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