为了在您的应用中使用 MediaRouter 框架,您必须获取一个 MediaRouter
对象的实例,并附加一个 MediaRouter.Callback
对象来监听路由事件。通过媒体路由发送的内容会经过路由关联的 MediaRouteProvider
(除了少数特殊情况,例如蓝牙输出设备)。图 1 提供了用于在设备之间路由内容的类的概览。
注意: 如果您希望您的应用支持 Google Cast 设备,您应该使用 Cast SDK 并将您的应用构建为 Cast 发送方。请按照 Cast 文档 中的说明操作,而不是直接使用 MediaRouter 框架。
媒体路由按钮
Android 应用应该使用媒体路由按钮来控制媒体路由。MediaRouter 框架为按钮提供了一个标准界面,这有助于用户识别和使用路由(如果可用)。媒体路由按钮通常放置在应用操作栏的右侧,如图 2 所示。
当用户按下媒体路由按钮时,可用的媒体路由会以列表形式显示,如图 3 所示。
请按照以下步骤创建媒体路由按钮
- 使用 AppCompatActivity
- 定义媒体路由按钮菜单项
- 创建 MediaRouteSelector
- 将媒体路由按钮添加到操作栏
- 在您的活动生命周期中创建和管理 MediaRouter.Callback 方法
本部分介绍前四个步骤。下一部分将介绍 Callback 方法。
使用 AppCompatActivity
当您在活动中使用媒体路由框架时,您应该从 AppCompatActivity
扩展活动,并导入包 androidx.appcompat.app
。您必须将 androidx.appcompat:appcompat 和 androidx.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
扩展您的活动,并在活动创建时通过从 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_AUDIO
或 CATEGORY_LIVE_VIDEO
控制类别添加到 MediaRouteSelector。您还需要创建和处理您自己的 Presentation
对话框。
将媒体路由按钮添加到操作栏
定义了媒体路由菜单和 MediaRouteSelector 后,您现在可以将媒体路由按钮添加到活动。为您的每个活动覆盖 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 过滤)。每个活动使用其自己的 MediaRouter.Callback
方法实现与 MediaRouter 进行通信。每当用户选择、更改或断开连接路由时,MediaRouter 都会调用回调方法。
回调中包含多个方法,您可以覆盖这些方法以接收有关路由事件的信息。至少,您对 MediaRouter.Callback
类的实现应该覆盖 onRouteSelected()
和 onRouteUnselected()
。
由于 MediaRouter 是一个共享资源,因此您的应用需要根据通常的活动生命周期回调来管理其 MediaRouter 回调
- 当活动创建时 (
onCreate(Bundle)
),获取指向MediaRouter
的指针,并在应用的整个生命周期内保留它。 - 当活动变为可见时 (
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
类,它负责为活动添加和删除回调。
注意: 如果您正在编写音乐播放应用,并且希望应用在后台播放音乐,那么您必须构建一个 Service
用于播放,并从 Service 的生命周期回调中调用媒体路由框架。
控制远程播放路由
当您选择远程播放路由时,您的应用将充当远程控制。路由另一端处的设备处理所有内容数据检索、解码和播放功能。您应用 UI 中的控件使用 RemotePlaybackClient
对象与接收器设备进行通信。
RemotePlaybackClient
类提供了用于管理内容播放的其他方法。以下是一些来自 RemotePlaybackClient
类的关键播放方法
play()
— 播放特定媒体文件,由Uri
指定。pause()
— 暂停当前正在播放的媒体轨道。resume()
— 暂停命令后继续播放当前轨道。seek()
— 移动到当前轨道中的特定位置。release()
— 拆除从您的应用到远程播放设备的连接。
您可以使用这些方法将操作附加到您在应用中提供的播放控件。大多数这些方法还允许您包含一个回调对象,以便您可以监视播放任务或控制请求的进度。
RemotePlaybackClient
类还支持将多个媒体项目排队以进行播放,以及管理媒体队列。
示例代码
Android BasicMediaRouter 和 MediaRouter 示例进一步演示了 MediaRouter API 的使用。