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 示例演示了如何创建自定义媒体路由提供程序。