腾讯云互动直播分享屏幕小结
基于不同的场景提供不同的功能,对于而今最流行的自然是面对面直播,即通过摄像头来形成流视频来进行直播互动。
那么对于在线教育行业,就需要进行屏幕分享了。
一般对于在线教育的场景就是需要显示老师分享的屏幕(大屏幕)以及老师自己的摄像头屏幕(小屏幕)
基于腾讯云随心播开发
场景1:
分享屏幕以及摄像头屏幕
首先,在activity注册广播事件
private void registerReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Constants.ACTION_SURFACE_CREATED);
intentFilter.addAction(Constants.ACTION_HOST_ENTER);
intentFilter.addAction(Constants.ACTION_CAMERA_OPEN_IN_LIVE);
intentFilter.addAction(Constants.ACTION_CAMERA_CLOSE_IN_LIVE);
intentFilter.addAction(Constants.ACTION_SWITCH_VIDEO);
intentFilter.addAction(Constants.ACTION_HOST_LEAVE);
intentFilter.addAction(Constants.ACTION_SCREEN_SHARE_IN_LIVE);
registerReceiver(mBroadcastReceiver, intentFilter);
}
其实通过翻译还是很好理解,我觉得我还是解释一下各个事件的含义。
ACTION_SURFACE_CREATED:AvSurfaceView 初始化成功
ACTION_HOST_ENTER:主播进入房间
ACTION_CAMERA_OPEN_IN_LIVE:有人打开摄像头
ACTION_CAMERA_CLOSE_IN_LIVE:有人关闭摄像头
ACTION_SWITCH_VIDEO:点击成员回调
ACTION_HOST_LEAVE:主播结束直播
ACTION_SCREEN_SHARE_IN_LIVE:有人分享屏幕
针对不同的事件做不同的处理,很多功能基本上就ok了。
回归正题,当接收到分享屏幕消息时
if (action.equals(Constants.ACTION_SCREEN_SHARE_IN_LIVE)) {//有屏幕分享
ArrayList<String> ids = intent.getStringArrayListExtra("ids");
//如果是自己本地直接渲染
for (String id : ids) {
if (!mRenderUserList.contains(id)) {
mRenderUserList.add(id);
}
updateHostLeaveLayout();
if (id.equals(MySelfInfo.getInstance().getId())) {
showVideoView(true, id);
return;
}
}
//其他人一并获取
int requestCount = CurLiveInfo.getCurrentRequestCount();
mLiveHelper.requestScreenViewList(ids);
isScreenShare = true;
requestCount = requestCount + ids.size();
CurLiveInfo.setCurrentRequestCount(requestCount);
}
这里先初始化一个用户列表mRenderUserList,将用户添加进去,这个用户指的是主播和互动成员。如果不包含就添加,如果当前动作是主播,则 showVideoView(true, id);如果不是主播就去请求主播数据。
mLiveHelper.requestScreenViewList(ids);
看看请求主播数据中如何处理
/**
* AVSDK 请求主播数据
*
* @param identifiers 主播ID
*/
public void requestScreenViewList(ArrayList<String> identifiers) {
SxbLog.i(TAG, "requestViewList " + identifiers);
if (identifiers.size() == 0) return;
AVEndpoint endpoint = ((AVRoomMulti) QavsdkControl.getInstance().getAVContext().getRoom()).getEndpointById(identifiers.get(0));
SxbLog.d(TAG, "requestViewList hostIdentifier " + identifiers + " endpoint " + endpoint);
if (endpoint != null) {
ArrayList<String> alreadyIds = QavsdkControl.getInstance().getRemoteVideoIds();//已经存在的IDs
SxbLog.i(TAG, "requestViewList identifiers : " + identifiers.size());
SxbLog.i(TAG, "requestViewList alreadyIds : " + alreadyIds.size());
for (String id : identifiers) {//把新加入的添加到后面
if (!alreadyIds.contains(id)) {
alreadyIds.add(id);
}
}
int viewindex = 0;
for (String id : alreadyIds) {//一并请求
if (viewindex >= 4) break;
AVView view = new AVView();
view.videoSrcType = AVView.VIDEO_SRC_TYPE_SCREEN;
view.viewSizeType = AVView.VIEW_SIZE_TYPE_BIG;
//界面数
mRequestViewList[viewindex] = view;
mRequestIdentifierList[viewindex] = id;
viewindex++;
}
QavsdkControl.getInstance().getAvRoomMulti().requestViewList(mRequestIdentifierList, mRequestViewList, viewindex, mRequestViewListCompleteCallback);
} else {
if (null != mContext) {
Toast.makeText(mContext, "Wrong Room!!!! Live maybe close already!", Toast.LENGTH_SHORT).show();
}
}
}
这里就是请求主播分享屏幕数据,回调在mRequestViewListCompleteCallback,支持最多四个,类型为
view.videoSrcType = AVView.VIDEO_SRC_TYPE_SCREEN;
看看回调
private AVRoomMulti.RequestViewListCompleteCallback mRequestViewListCompleteCallback = new AVRoomMulti.RequestViewListCompleteCallback() {
public void OnComplete(String identifierList[], AVView viewList[], int count, int result,String s) {
String ids = "";
for (String id : identifierList) {
mLiveView.showVideoView(REMOTE, id);
ids = ids + " " + id;
}
SxbLog.d(TAG, LogConstants.ACTION_VIEWER_SHOW + LogConstants.DIV + MySelfInfo.getInstance().getId() + LogConstants.DIV + "get stream data"
+ LogConstants.DIV + LogConstants.STATUS.SUCCEED + LogConstants.DIV + "ids " + ids);
// TODO
SxbLog.d(TAG, "RequestViewListCompleteCallback.OnComplete");
}
};
主要是走了 mLiveView.showVideoView(REMOTE, id);,在这里面打开相应窗口
/**
* 加载视频数据
*
* @param isLocal 是否是本地数据
* @param id 身份
*/
@Override
public void showVideoView(boolean isLocal, final String id) {
Log.i(TAG, "showVideoView " + id + "isLocal" + isLocal);
//渲染本地Camera
if (isLocal == true) {
QavsdkControl.getInstance().setSelfId(id);
QavsdkControl.getInstance().setLocalHasVideo(true, id);
//主播通知用户服务器
Log.i(TAG, "showVideoView host :" + id);
} else {
Log.i(TAG, "showVideoView :" + id);
if (isScreenShare) {
QavsdkControl.getInstance().setRemoteHasVideo(true, id, AVView.VIDEO_SRC_TYPE_SCREEN);
isScreenShare = false;
} else {
QavsdkControl.getInstance().setRemoteHasVideo(true, id, AVView.VIDEO_SRC_TYPE_CAMERA);
}
}
}
至此整个流程结束。然而事实并没有那么简单。
显示摄像头小窗口,同上,然而我们点击进去随心播setRemoteHasVideo看一下
public void setRemoteHasVideo(boolean isRemoteHasVideo, String identifier, int videoSrcType) {
SxbLog.i(TAG, "setRemoteHasVideo : " + identifier);
if (null != mAVUIControl) {
mAVUIControl.setSmallVideoViewLayout(isRemoteHasVideo, identifier, videoSrcType);
}
}
我滴天,竟然是小窗口布局,分享屏幕要全屏啊,是不是很溜。别着急,我们在找找,然后找到了
public void setRemoteHasVideo(String identifier, int videoSrcType, boolean isRemoteHasVideo) {
if (null != mAVUIControl) {
mAVUIControl.setRemoteHasVideo(identifier, videoSrcType, isRemoteHasVideo, false, false);
}
}
所以方法需要改一下,毕竟参数位置不一样,修改后的调用
if (isScreenShare) {
QavsdkControl.getInstance().setRemoteHasVideo(id, AVView.VIDEO_SRC_TYPE_SCREEN, true);
isScreenShare = false;
} else {
QavsdkControl.getInstance().setRemoteHasVideo(true, id, AVView.VIDEO_SRC_TYPE_CAMERA);
}
分享屏幕调用大屏,打开摄像头需要调用小屏。
然而事实并没有什么卵用,不对,有用,但是在某些场景下需要调整。
比如,学生进入直播的时候老师已经打开摄像头以及分享了屏幕。那么学生进入需要几乎是同时处理打开摄像头,这样会造成页面卡顿,或者说两个页面会有一个视屏流不动了。大家做项目的时候可以亲测一下,为了解决这个问题,我在接收分享屏幕的广播后面增加一个延迟。
if (action.equals(AVConstants.ACTION_SCREEN_SHARE_IN_LIVE)) {//有屏幕分享
LogUtils.e("有屏幕分享");
isBegin=true;
HandlerUtils.postDelayed(new Runnable() {
@Override
public void run() {
ArrayList<String> ids = intent.getStringArrayListExtra("ids");
//如果是自己本地直接渲染
String identifier = Constants.TEST_T + UserManager.getInstance().getUserModel().getId();
for (String id : ids) {
if (!mRenderUserList.contains(id)) {
mRenderUserList.add(id);
}
if (id.equals(identifier)) {
showVideoView(true, id);
return;
}
}
//其他人一并获取
int requestCount = CurLiveInfo.getCurrentRequestCount();
mLiveHelper.requestScreenViewList(ids);
isScreenShare = true;
requestCount = requestCount + ids.size();
CurLiveInfo.setCurrentRequestCount(requestCount);
}
}, 1000);
}
这里代码肯定要写在主线程的,不用多想,这样解决同时请求两路数据,会造成一路卡屏的情况。
如果你以为这样就结束了,那就太小看直播的集成难度了。
在学生进入直播间,老师未分享屏幕的情况下,如果老师点击分享屏幕还是会造成卡顿,就类似与如果分开去拿AVSDK数据,在打开相应窗口显示就会遇到这种情况。
那么如何解决?杀死一万脑细胞之后是这么做的。
/**
* AVSDK 请求主播数据
*
* @param identifiers 主播ID
*/
public void requestScreenViewList(ArrayList<String> identifiers) {
Log.i(TAG, "requestViewList " + identifiers);
if (identifiers.size() == 0) return;
AVEndpoint endpoint = QavsdkControl.getInstance().getAVContext().getRoom().getEndpointById(identifiers.get(0));
Log.d(TAG, "requestViewList hostIdentifier " + identifiers + " endpoint " + endpoint);
if (endpoint != null) {
ArrayList<String> alreadyIds = QavsdkControl.getInstance().getRemoteVideoIds();//已经存在的IDs
Log.i(TAG, "requestViewList identifiers : " + identifiers.size());
Log.i(TAG, "requestViewList alreadyIds : " + alreadyIds.size());
for (String id : identifiers) {//把新加入的添加到后面
if (!alreadyIds.contains(id)) {
alreadyIds.add(id);
}
}
int viewindex = 0;
for (String id : alreadyIds) {//一并请求
if (viewindex >= 4) break;
AVView view2 = new AVView();
view2.videoSrcType = AVView.VIDEO_SRC_TYPE_CAMERA;
view2.viewSizeType = AVView.VIEW_SIZE_TYPE_BIG;
//界面数
mRequestViewList[viewindex] = view2;
mRequestIdentifierList[viewindex] = id;
viewindex++;
AVView view = new AVView();
view.videoSrcType = AVView.VIDEO_SRC_TYPE_SCREEN;
view.viewSizeType = AVView.VIEW_SIZE_TYPE_BIG;
//界面数
mRequestViewList[viewindex] = view;
mRequestIdentifierList[viewindex] = id;
viewindex++;
}
QavsdkControl.getInstance().getAvRoomMulti().requestViewList(mRequestIdentifierList, mRequestViewList, viewindex, mRequestViewListCompleteCallback);
} else {
if (null != mContext) {
Toast.makeText(mContext, "Wrong Room!!!! Live maybe close already!", Toast.LENGTH_SHORT).show();
}
}
}
在去请求分享屏幕的主播数据的时候,同时请求摄像头的主播数据,就类似于请求分享屏幕的数据时刷新摄像头的数据,这样就解决了分享屏幕造成的卡顿问题。
基本上到这,分享屏幕以及打开摄像头就能共存运行了,来一张项目效果图吧
当然还有一些其他的处理问题,比如当你没有收到分享信息该怎么办,你在老师上课之后进入如何保证收到监听信息。
这些都需要进行心跳连接,通过心跳连接接收主播端实时数据,进行相应处理。
比如学生进入直播间会给老师发生进入直播消息,相应老师会通过心跳发送直播状态信息,学生根据信息做相应处理,就增加了直播的稳定性。
/**
* 摄像头开闭
*
* @param b
*/
private void toggleCameraViewOfTeacher(final boolean b) {
String identifier = Constants.TEST_T + CurLiveInfo.getHostID();
if (b) {
QavsdkControl.getInstance().setRemoteHasVideo(true, identifier, AVView.VIDEO_SRC_TYPE_CAMERA);
} else {
QavsdkControl.getInstance().setRemoteHasVideo(false, identifier, AVView.VIDEO_SRC_TYPE_CAMERA);
}
}
/**
* 屏幕分享开闭
*
* @param b
*/
private void toggleShareViewOfTeacher(final boolean b) {
String identifier = Constants.TEST_T + CurLiveInfo.getHostID();
LogUtils.e("toggleShareViewOfTeacher identifier:" + identifier + " b:" + b);
if (!b) {
QavsdkControl.getInstance().setRemoteHasVideo(identifier, AVView.VIDEO_SRC_TYPE_SCREEN, false);
} else {
QavsdkControl.getInstance().setRemoteHasVideo(identifier, AVView.VIDEO_SRC_TYPE_SCREEN, true);
}
}
其他相应处理这里就不多说了,根据相应功能场景定制好相应的文本结构。