当用户使用无线技术连接电视,家庭影院和音乐播放器时,他们希望android apps的内容可以在这些大屏幕的设备上播放。启用这种方式的播放,能把一台设备一个用户模式
变为一个乐于分享的、令人激动和鼓舞的多用户模式。
android媒体路由接口被设计成可以在二级设备上呈现和播放媒体。通过使用这些APIs来播放内容有两种实现方式可供使用:
远程播放:当用户手里的一个android设备作为一个远程控制端时,这种方法就利用接收设备来处理检索到的内容数据、解码、和播放。Android apps需要支持Google Cast才能使用该方法。
二级输出:这种方法中,你的app检索、渲染和输出视频或者音频知道到接收设备。Android需要支持无线显示输出才能使用该方法
这篇指导阐明了如何使用这两种方式分别实现你的app传送媒体到二级播放设备。
概述
<?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="android.support.v7.app.MediaRouteActionProvider"
app:showAsAction="always"
/>
</menu>
CATEGORY_LIVE_AUDIO
音频输出到二级输出设备,例如支持无线的音乐系统CATEGORY_LIVE_VIDEO
视频输出的二级输出设备,例如无线显示设备public class MediaRouterPlaybackActivity extends ActionBarActivity {
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_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build();
}
}
@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);
mediaRouteActionProvider.setRouteSelector(mSelector);
// Return true to show the menu.
return true;
}
private final MediaRouter.Callback mMediaRouterCallback =
new MediaRouter.Callback() {
@Override
public void onRouteSelected(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteSelected: route=" + route);
if (route.supportsControlCategory(
MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
// remote playback device
updateRemotePlayer(route);
} else {
// secondary output device
updatePresentation(route);
}
}
@Override
public void onRouteUnselected(MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRouteUnselected: route=" + route);
if (route.supportsControlCategory(
MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
// remote playback device
updateRemotePlayer(route);
} else {
// secondary output device
updatePresentation(route);
}
}
@Override
public void onRoutePresentationDisplayChanged(
MediaRouter router, RouteInfo route) {
Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
if (route.supportsControlCategory(
MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)){
// remote playback device
updateRemotePlayer(route);
} else {
// secondary output device
updatePresentation(route);
}
}
}
public class MediaRouterPlaybackActivity extends ActionBarActivity {
private MediaRouter mMediaRouter;
private MediaRouteSelector mSelector;
private Callback mMediaRouterCallback;
// your app works with so the framework can discover them.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the media router service.
mMediaRouter = MediaRouter.getInstance(this);
...
}
// Add the callback on start to tell the media router what kinds of routes
// your app works with so the framework can discover them.
@Override
public void onStart() {
mMediaRouter.addCallback(mSelector, mMediaRouterCallback,
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() {
mMediaRouter.removeCallback(mMediaRouterCallback);
super.onStop();
}
...
}
你应该只在onStart()和onStopn()生命周期方法中分别添加和移除媒体路由回调对象。不要在onResume()或者onPause()方法中包含这些调用。
private void updateRemotePlayer(RouteInfo route) {
// Changed route: tear down previous client
if (mRoute != null && mRemotePlaybackClient != null) {
mRemotePlaybackClient.release();
mRemotePlaybackClient = null;
}
// Save new route
mRoute = route;
// Attach new playback client
mRemotePlaybackClient = new RemotePlaybackClient(this, mRoute);
// Send file for playback
mRemotePlaybackClient.play(Uri.parse(
"http://archive.org/download/Sintel/sintel-2048-stereo_512kb.mp4"),
"video/mp4", null, 0, null, new ItemActionCallback() {
@Override
public void onResult(Bundle data, String sessionId,
MediaSessionStatus sessionStatus,
String itemId, MediaItemStatus itemStatus) {
logStatus("play: succeeded for item " + itemId);
}
@Override
public void onError(String error, int code, Bundle data) {
logStatus("play: failed - error:"+ code +" - "+ error);
}
});
}
}
RemotePlaybackClient类提供了管理内容播放的另外一些方法。这里是一些RemotePlaybackClient中的关键播放方法:
谷歌电视棒使用Google Cast API的更多信息,请看Google Cast开发文档。
二级输出
二级输出方法发送准备好的媒体内容到连接上的二级输出设备上播放。二级设备可包含电视或无线音频系统,并且通过无线或有线协议可以被连接上,例如一个HDMI线。使用这种方法,你的app有责任处理媒体内容播放(下载、解码、同步音频和视频轨迹),而二级设备只输出最终形式的内容。
注意:通过媒体路由框架使用二级输出显示路由需要只能在android4.2(API水平17)或更高版本上可用的类,尤其展示类。如果你正在开发一个app同时支持远程播放和二级输出设备,你必须包含当低于支持的android版本水平时禁用这些代码的判断。
创建一个展示对象
当通过媒体路由框架使用一个二级输出显示器时,你需要创建一个包含想要呈现在显示器上的内容的展示对象。展示对象是继承自对话框类,所以可以添加布局和视图到该对象上。
你应该明白展示对象有它自己的上下文和资源,独立于创建该对象的app的活动页面。这就需要拥有一个二级上下文,因为展示的内容被绘制在显示器上,它独立于本地android设备上的你的app显示器。特别地,二级显示需要独立上下文,因为它可能需要加载基于它本身特有的屏幕像素的资源。
一下代码示例展示了一个展示对象至少需要实现的类,包含一个GLSurfaceView对象。
public class SamplePresentation extends Presentation {
public SamplePresentation(Context outerContext, Display display) {
super(outerContext, display);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Notice that we get resources from the context of the Presentation
Resources resources = getContext().getResources();
// Inflate a layout.
setContentView(R.layout.presentation_with_media_router_content);
// Add presentation content here:
// Set up a surface view for visual interest
mSurfaceView = (GLSurfaceView)findViewById(R.id.surface_view);
mSurfaceView.setRenderer(new CubeRenderer(false));
}
}
创建一个展示控制
为了显示展示对象,你应该写一个控制层来处理来自MediaRouter.Callback对象接收的消息,并管理该对象的创建和移除。控制层应该也处理关联展示对象到一个选择的显示对象上,该显示对象代表着用户选择的独立的物理显示设备。控制层可以是简单地在活动页面中支持二级显示的一个方法。
下面的代码示例展示了Presentation实现对象的作为一个单独方法的控制层.当一个显示器处理不可选状态或者失去联系时,该方法负责清除无效的展示对象,而在一个显示设备连接时负责创建一个展示对象。
private void updatePresentation(RouteInfo route) {
// Get its Display if a valid route has been selected
Display selectedDisplay = null;
if (route != null) {
selectedDisplay = route.getPresentationDisplay();
}
// Dismiss the current presentation if the display has changed or no new
// route has been selected
if (mPresentation != null && mPresentation.getDisplay() != selectedDisplay) {
mPresentation.dismiss();
mPresentation = null;
}
// Show a new presentation if the previous one has been dismissed and a
// route has been selected.
if (mPresentation == null && selectedDisplay != null) {
// Initialize a new Presentation for the Display
mPresentation = new SamplePresentation(this, selectedDisplay);
mPresentation.setOnDismissListener(
new DialogInterface.OnDismissListener() {
// Listen for presentation dismissal and then remove it
@Override
public void onDismiss(DialogInterface dialog) {
if (dialog == mPresentation) {
mPresentation = null;
}
}
});
// Try to show the presentation, this might fail if the display has
// gone away in the meantime
try {
mPresentation.show();
} catch (WindowManager.InvalidDisplayException ex) {
// Couldn't show presentation - display was already removed
mPresentation = null;
}
}
}
注意:当用户连接一个无线显示器时,媒体路由框架会自动提供一个通知,它展示了显示器的内容在连接的设备上。