Android 媒体路由框架允许制造商通过称为 MediaRouteProvider
的标准化接口在其设备上启用播放。路由提供程序定义了在接收设备上播放媒体的通用接口,使您能够从任何支持媒体路由的 Android 应用程序在您的设备上播放媒体。
本指南讨论如何为接收设备创建媒体路由提供程序,并将其提供给在 Android 上运行的其他媒体播放应用程序。为了使用此 API,您应该熟悉关键类 MediaRouteProvider
、MediaRouteProviderDescriptor
和 RouteController
。
概述
Android 媒体路由框架使媒体应用程序开发人员和媒体播放设备制造商能够通过通用 API 和通用用户界面进行连接。实现 MediaRouter
接口的应用程序开发人员可以连接到框架,并将内容播放到参与媒体路由框架的设备。媒体播放设备制造商可以通过发布 MediaRouteProvider
来参与框架,该提供程序允许其他应用程序连接到接收设备并在其上播放媒体。图 1 说明了应用程序如何通过媒体路由框架连接到接收设备。
在为您的接收设备构建媒体路由提供程序时,该提供程序将用于以下目的
- 描述和发布接收设备的功能,以便其他应用程序可以发现它并使用其播放功能。
- 包装接收设备的编程接口及其通信传输机制,以使设备与媒体路由框架兼容。
路由提供程序的发布
媒体路由提供程序作为 Android 应用程序的一部分发布。您可以通过扩展 MediaRouteProviderService
或使用您自己的服务包装 MediaRouteProvider
的实现并为媒体路由提供程序声明一个意图过滤器来使您的路由提供程序可供其他应用程序使用。这些步骤允许其他应用程序发现并使用您的媒体路由。
注意:包含媒体路由提供程序的应用程序还可以包含指向路由提供程序的 MediaRouter 接口,但这并非必需。
MediaRouter 支持库
媒体路由 API 在 AndroidX MediaRouter 库 中定义。您必须将此库添加到您的应用程序开发项目中。有关将支持库添加到项目的更多信息,请参见 支持库设置。
注意:务必使用 AndroidX
的媒体路由框架实现。请勿使用旧的 android.media
包。
创建提供程序服务
媒体路由框架必须能够发现并连接到您的媒体路由提供程序,以允许其他应用程序使用您的路由。为了做到这一点,媒体路由框架会查找声明媒体路由提供程序意图操作的应用程序。当另一个应用程序想要连接到您的提供程序时,框架必须能够调用并连接到它,因此您的提供程序必须封装在 Service
中。
以下示例代码显示了媒体路由提供程序服务的声明和清单中的意图过滤器,这使得媒体路由框架能够发现和使用它
<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
定义和发布您的媒体路由的功能。本节说明如何使用这些类来发布您的媒体路由的详细信息,以便其他应用程序可以使用。
路由类别
作为媒体路由提供程序的编程描述的一部分,您必须指定您的提供程序是否支持远程播放、辅助输出或两者。这些是媒体路由框架提供的路由类别
CATEGORY_LIVE_AUDIO
— 将音频输出到辅助输出设备,例如无线音乐系统。CATEGORY_LIVE_VIDEO
— 将视频输出到辅助输出设备,例如无线显示设备。CATEGORY_REMOTE_PLAYBACK
— 在单独的设备上播放视频或音频,该设备处理媒体检索、解码和播放,例如 Chromecast 设备。
为了将这些设置包含在您的媒体路由的描述中,您需要将它们插入到 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
意图,您还必须定义您的媒体路由提供程序支持的媒体类型和播放控件。下一节说明如何为您的设备指定这些设置。
媒体类型和协议
远程播放设备的媒体路由提供程序必须指定其支持的媒体类型和传输协议。您可以使用 IntentFilter
类以及该对象的 addDataScheme()
和 addDataType()
方法来指定这些设置。以下代码片段演示了如何定义一个意图过滤器,以支持使用 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); } ... }
有关可用播放控件意图的更多信息,请参见 MediaControlIntent
类。
MediaRouteProviderDescriptor
使用 IntentFilter
对象定义媒体路由的功能后,您可以创建描述符对象以发布到 Android 媒体路由框架。此描述符对象包含媒体路由功能的具体信息,以便其他应用程序可以确定如何与您的媒体路由进行交互。
以下示例代码演示了如何将之前创建的意图过滤器添加到 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 示例展示了如何创建自定义媒体路由提供程序。