欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

腾讯云互动直播分享屏幕小结

程序员文章站 2022-07-06 20:30:30
...

基于不同的场景提供不同的功能,对于而今最流行的自然是面对面直播,即通过摄像头来形成流视频来进行直播互动。

那么对于在线教育行业,就需要进行屏幕分享了。

一般对于在线教育的场景就是需要显示老师分享的屏幕(大屏幕)以及老师自己的摄像头屏幕(小屏幕)

基于腾讯云随心播开发

场景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);
        }
    }

其他相应处理这里就不多说了,根据相应功能场景定制好相应的文本结构。