深入理解Google Cast开发一个支持Google Cast的sender APP
Posted zhanghui_cuc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Google Cast开发一个支持Google Cast的sender APP相关的知识,希望对你有一定的参考价值。
前言
0.Activities需要继承自ActionBarActivity或FragmentAcitivy
0. cast button需要在所有Actvity中存在,允许用户随时连接,随时断开.
初始化CastContext
首先实现OptionsProvider接口来获得CastOptions,再用CastOptions初始化CastContext
receiver application ID:用于过滤Cast Devices搜索结果并且在Cast会话开始时启动receiver application.
When you develop your own Cast-enabled app, you have to register as a Cast developer and then obtain an application ID for your app.
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.OptionsProvider;
import com.google.android.gms.cast.framework.SessionProvider;
import android.content.Context;
import java.util.List;
public class CastOptionsProvider implements OptionsProvider {
@Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build();
}
@Override
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
return null;
}
}
在androidmanifest.xml中声明
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
在Activity的OnCreate方法中进行CastContext初始化
private CastContext mCastContext;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_browser);
setupActionBar();
mCastContext = CastContext.getSharedInstance(this);
}
添加Cast Button
cast button的逻辑都由MediaRouteButton实现,我们要做的只是把它加到app中
MediaRouteButton
public class MediaRouteButton
extends View
java.lang.Object
↳ android.view.View
↳ android.support.v7.app.MediaRouteButton
________________________________________
The media route button allows the user to select routes and to control the currently selected route.
The application must specify the kinds of routes that the user should be allowed to select by specifying a selector with thesetRouteSelector(MediaRouteSelector) method.
Prerequisites
To use the media route button, the activity must be a subclass of FragmentActivity from the android.support.v4 support library. Refer to support library documentation for details.
首先在res/menu中修改xml
<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"/>
然后在Activity的onCreateOptionMenu方法中作如下修改
private MenuItem mediaRouteMenuItem;
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.browse, menu);
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item);
return true;
}
需要强调的是,在所有的activity中都要添加cast button
这时候再运行app,就能看到cast button了,如果看不到,是因为没有搜索到cast devices.devices搜索的工作由CastContext完成.
cast video content
要在cast devices上播放媒体,需要以下几步
- Create a MediaInfo object that models a media item.
MediaInfo
public final class MediaInfo extends Object
implements SafeParcelable Parcelable
A class that aggregates information about a media item. Use MediaInfo.Builder to build an instance of this class. MediaInfo is used by RemoteMediaPlayer to load media on the receiver application.
- Connect to the Cast device and launch your receiver application.
- Load the MediaInfo object into your receiver and play the content.
- Track the media status.
- Send playback commands to the receiver based on user interactions.
Cast Session
一个Cast Session包含:连接设备,启动,连接到receiver application,初始化media control channel,media control channel用于cast framework和receiver media player之间的消息传递,当用户点击cast button并选择设备之后,一个cast session就开始了,当用户disconnect的时候一个cast session就停止了.
cast session由cast framework的SessionManager管理
CastSession
public class CastSession extends Session
An implementation of Session for managing connections to a Cast receiver device. An instance ofCastSession is automatically created by the SessionManager when the user selects a Cast device from the media route controller dialog. The current active CastSession can be accessed bygetCurrentCastSession().
SessionManager
public class SessionManager extends Object
A class that manages Session instances. The application can attach a SessionManagerListener to be notified of session events.
can be accessed via CastContext.getSessionManager()
session中的事件由SessionManagerListener监听
SessionManagerListener
public interface SessionManagerListener
Known Indirect Subclasses
UIMediaController
A listener interface for monitoring events of a particular type of Session instance.
为Activity添加SessionManager
private CastSession mCastSession;
private SessionManagerListener<CastSession> mSessionManagerListener;
private void setupCastListener() {
mSessionManagerListener = new SessionManagerListener<CastSession>() {
@Override
public void onSessionEnded(CastSession session, int error) {
onApplicationDisconnected();
}
@Override
public void onSessionResumed(CastSession session, boolean wasSuspended) {
onApplicationConnected(session);
}
@Override
public void onSessionResumeFailed(CastSession session, int error) {
onApplicationDisconnected();
}
@Override
public void onSessionStarted(CastSession session, String sessionId) {
onApplicationConnected(session);
}
@Override
public void onSessionStartFailed(CastSession session, int error) {
onApplicationDisconnected();
}
@Override
public void onSessionStarting(CastSession session) {
}
@Override
public void onSessionEnding(CastSession session) {
}
@Override
public void onSessionResuming(CastSession session, String sessionId) {
}
@Override
public void onSessionSuspended(CastSession session, int reason) {
}
private void onApplicationConnected(CastSession castSession) {
mCastSession = castSession;
if (null != mSelectedMedia) {
if (mPlaybackState == PlaybackState.PLAYING) {
mVideoView.pause();
loadRemoteMedia(mSeekbar.getProgress(), true);
return;
} else {
mPlaybackState = PlaybackState.IDLE;
updatePlaybackLocation(PlaybackLocation.REMOTE);
}
}
updatePlayButton(mPlaybackState);
invalidateOptionsMenu();
}
private void onApplicationDisconnected() {
updatePlaybackLocation(PlaybackLocation.LOCAL);
mPlaybackState = PlaybackState.IDLE;
mLocation = PlaybackLocation.LOCAL;
updatePlayButton(mPlaybackState);
invalidateOptionsMenu();
}
};
}
在Activity的onCreate方法中注册SessionManagerListener
...
setupControlsCallbacks();
setupCastListener();
mCastContext = CastContext.getSharedInstance(this);
mCastContext.registerLifecycleCallbacksBeforeIceCreamSandwich(this, savedInstanceState);
mCastSession = mCastContext.getSessionManager().getCurrentCastSession();
Bundle bundle = getIntent().getExtras();
...
} else {
if (mCastSession != null && mCastSession.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE);
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL);
}
mPlaybackState = PlaybackState.IDLE;
updatePlayButton(mPlaybackState);
}
...
将选择的视频cast到receiver
Cast SDK中的RemoteMediaClient用于管理receiver端的media playback
RemoteMediaClient
public class RemoteMediaClient extends Object
implements Cast.MessageReceivedCallback
Class for controlling a media player application running on a receiver.
Some operations, like loading of media or adjusting volume, can be tracked. The corresponding methods return a PendingResult for this purpose. In case of error, such as having lost the connection to the service, the PendingResult will provide a status of FAILED.
对于每一个CastSession,都会由SDK自动创建一个RemoteMediaClient实例,而我们也可以获取到它,在Activity中添加下面的代码来将本地选择的视频cast到receiver
private void loadRemoteMedia(int position, boolean autoPlay) {
if (mCastSession == null) {
return;
}
final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
if (remoteMediaClient == null) {
return;
}
remoteMediaClient.load(buildMediaInfo(), autoPlay, position);
}
private MediaInfo buildMediaInfo() {
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));
return new MediaInfo.Builder(mSelectedMedia.getUrl())
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration(mSelectedMedia.getDuration() * 1000)
.build();
}
现在需要在其他已有的方法里面加入Cast Session的逻辑以以支持remote playback
private void play(int position) {
startControllersTimer();
switch (mLocation) {
case LOCAL:
mVideoView.seekTo(position);
mVideoView.start();
break;
case REMOTE:
mPlaybackState = PlaybackState.BUFFERING;
updatePlayButton(mPlaybackState);
mCastSession.getRemoteMediaClient().seek(position);
break;
default:
break;
}
restartTrickplayTimer();
}
private void togglePlayback() {
...
case IDLE:
...
case REMOTE:
if (mCastSession != null && mCastSession.isConnected()) {
loadRemoteMedia(mSeekbar.getProgress(), true);
}
break;
default:
...
}
private void onPause() {
...
mCastContext.getSessionManager().removeSessionManagerListener(
mSessionManagerListener, CastSession.class);
}
protected void onResume() {
Log.d(TAG, "onResume() was called");
mCastContext.getSessionManager().addSessionManagerListener(
mSessionManagerListener, CastSession.class);
if (mCastSession != null && mCastSession.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE);
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL);
}
super.onResume();
}
现在,打开app,点击cast button,就能将内容cast到receiver上了.
添加mini controller
cast设计规范要求进入用户在离开当前播放内容页面之后,需要提供一个mini controller用于快捷操作,并且提醒用户现在处于cast状态下
SDK也提供了专门的MiniControllerFragment,我们只要把它加到res中即可,CastContext会自动处理用户的交互.controller上最多可以显示三个按键,可以通过xml进行自定义.
MiniControllerFragment
public class MiniControllerFragment extends Fragment
implements ControlButtonsContainer
A fragment that provides remote control functionality. This fragment provides an image for the album art, a ProgressBar for playback progression, and three configurable control buttons. If developers want to use this fragment, they should add it to their layout XML file, and the CastContext will automatically manage its state, and handle any user interactions.
The fragment will be visible when a media session starts, and will be invisible when a media session ends.
将下面的内容加入res/layout中的xml即可
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"/>
通知栏和锁屏界面
设计规范要求sender app在通知栏和锁屏界面都实现media controller,如下
SDK提供了MediaNotificationService来完成这一工作,它是一个service,会通过gradle自动合并到androidmanifest中
MediaNotificationService
public class MediaNotificationService extends Service
A service to provide status bar notifications when casting. This service will start when a media session begins, and will stop when the media session ends. This service builds media-style notification, which includes an album art image, a title and up to 5 action buttons, based on the options provided inNotificationOptions:
我们只需要在CastOptions中做对应的设置即可,如下,修改前面的CastOptionsProvider
public CastOptions getCastOptions(Context context) {
NotificationOptions notificationOptions = new NotificationOptions.Builder()
.setTargetActivityClassName(VideoBrowserActivity.class.getName())
.build();
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build();
return new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build();
}
Expanded Controller
设计规范要求当media被cast的时候,需要提供一个extended controller,就是前面提到的mini controller的全屏版,如下
Cast SDK提供了ExpandedControllerActivity来完成相应的工作,这是一个抽象类,我们只需要继承他,然后再添加cast button
ExpandedControllerActivity
深入理解Google Cast基本概念