Skip to content

maxim-top/floo-rtc-ucloud-android

Repository files navigation

蓝莺IM RTC SDK(UCloud版),安卓本地库

蓝莺IM,是由美信拓扑团队研发的新一代即时通讯云服务,SDK设计简单集成方便,服务采用云原生技术和多云架构,私有云也可按月付费。

蓝莺IM RTC SDK(UCloud版),则是在UCloud URTC基础上封装的实时音视频SDK。目前的版本提供一对一的视频通话和语音通话功能。

Scc Count Badge Scc Count Badge

介绍

为最大程度减少用户的开发成本,降低开发难度,我们将复杂的RTC业务逻辑隐藏在RTCManager之下。

整个SDK分为两大部分:

  1. RTCManager

RTCManager负责管理BMXRTCEngine(即UCloudEngine)、BMXRTCEngineListener、BMXRTCService和BMXRTCServiceListener。

其中: BMXRTCEngine用于提供RTC相关业务功能,比如进入房间、退出房间、发布流、订阅流等; BMXRTCEngineListener用于接收RTC相关业务事件,比如发布流成功、退出房间成功等; BMXRTCService用于发送RTC基本类型的消息,有发起呼叫、接听呼叫、结束通话; BMXRTCServiceListener用于接收RTC基本类型的消息。如果您还有其它类型的自定义消息,可以通过floo-android提供的消息机制创建自定义消息,发送和接收。

  1. BMXRtcRenderView

BMXRtcRenderView提供了视频画面渲染功能,本地和远程视频画面都可以使用。

开发

配置UCloud环境

  1. AndroidManifest.xml中application字段增加meta-data字段,设置UCloud RTC环境,例如
<meta-data
    android:name="UCloudProperties"
    android:value="urtc.properties" />
  1. 在项目assets目录新建properties文件,比如urtc.properties,例如
AppId=urtc-xxxxxx
AppKey=xxxxxx
WriteToLogCat=true
LogReport=true
UCloudRtcSdkPushEncode=UCLOUD_RTC_PUSH_ENCODE_MODE_H264
UCloudRtcSdkLogLevel=UCLOUD_RTC_SDK_LogLevelInfo
UCloudRtcSdkMode=UCLOUD_RTC_SDK_MODE_TRIAL
ReConnectTimes=60
UCloudRtcSdkVideoOutputOrientationMode=UCLOUD_RTC_VIDEO_OUTPUT_FIXED_PORTRAIT_MODE

创建用户界面

  1. 导入视频画面类依赖
import top.maxim.rtc.view.BMXRtcRenderView;
import top.maxim.rtc.view.RTCRenderView;
  1. 在通话界面创建两个画面的容器布局(本例中为大画面全屏,小画面居右上)
    <FrameLayout
        android:id="@+id/video_view_container_large"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/color_black" />

    <FrameLayout
        android:id="@+id/video_view_container_small"
        android:layout_width="120dp"
        android:layout_height="212dp"
        android:background="@color/color_black"
        android:layout_gravity="right"
        android:visibility="gone" />

  1. 添加本地画面到小画面容器布局
        ViewGroup smallViewGroup = mVideoContainer.findViewById(R.id.video_view_container_small);
        //呼叫过程中,对方画面为空,则将本地画面全屏
        if (mRemoteView == null){
            ViewGroup.LayoutParams layoutParams = smallViewGroup.getLayoutParams();
            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
            smallViewGroup.setLayoutParams(layoutParams);
        }
        smallViewGroup.setVisibility(View.VISIBLE);
        mLocalView = new RTCRenderView(this);
        mLocalView.init();
        mLocalView.setScalingType(BMXRtcRenderView.ScalingType.SCALE_ASPECT_FILL);
        mLocalView.getSurfaceView().setZOrderMediaOverlay(true);
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
        smallViewGroup.addView(mLocalView, layoutParams);
  1. 添加对方画面到大画面容器布局
        //将原本全屏的本地画面还原为小尺寸
        if (mLocalView != null){
            ViewGroup smallViewGroup = mVideoContainer.findViewById(R.id.video_view_container_small);
            ViewGroup.LayoutParams layoutParams = smallViewGroup.getLayoutParams();
            layoutParams.width = getPixelsFromDp(120);
            layoutParams.height = getPixelsFromDp(212);
            smallViewGroup.setLayoutParams(layoutParams);
            mLocalView.setScalingType(BMXRtcRenderView.ScalingType.SCALE_ASPECT_FILL);
        }

        ViewGroup largeViewGroup = mVideoContainer.findViewById(R.id.video_view_container_large);
        largeViewGroup.setVisibility(View.VISIBLE);
        mRemoteView = new RTCRenderView(this);
        mRemoteView.init();
        mRemoteView.setScalingType(BMXRtcRenderView.ScalingType.SCALE_ASPECT_FILL);

        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.gravity = Gravity.CENTER;
        largeViewGroup.addView(mRemoteView, layoutParams);

音视频通话业务逻辑

  1. 导入RTCManager
import top.maxim.rtc.RTCManager;
  1. 添加事件监听
mEngine = RTCManager.getInstance().getRTCEngine();
mEngine.addRTCEngineListener(mListener = new BMXRTCEngineListener() {

    @Override
    public void onJoinRoom(String info, long roomId, BMXErrorCode error) {
        super.onJoinRoom(info, roomId, error);
        mRoomId = roomId;
        if (BaseManager.bmxFinish(error)) {
            mEngine.publish(BMXVideoMediaType.Camera, mHasVideo, true);
            Log.e(TAG, "加入房间成功 开启发布本地流, roomId= " + roomId + "msg = " + info);
        } else {
            Log.e(TAG, "加入房间失败 roomId= " + roomId + "msg = " + info);
        }
    }

    @Override
    public void onLeaveRoom(String info, long roomId, BMXErrorCode error, String reason) {
        super.onLeaveRoom(info, roomId, error, reason);
        if (BaseManager.bmxFinish(error)) {
            Log.e(TAG, "离开房间成功 roomId= " + roomId + "msg = " + reason);
        }else{
            Log.e(TAG, "离开房间失败 roomId= " + roomId + "msg = " + reason);
        }
    }

    @Override
    public void onMemberJoined(long roomId, long usedId) {
        super.onMemberJoined(roomId, usedId);
        Log.e(TAG, "远端用户加入 uid= " + usedId);
    }

    @Override
    public void onMemberExited(long roomId, long usedId, String reason) {
        super.onMemberExited(roomId, usedId, reason);
        Log.e(TAG, "远端用户离开 uid= " + usedId);
        //回收界面
        onRemoteLeave();
    }

    @Override
    public void onLocalPublish(BMXStream stream, String info, BMXErrorCode error) {
        super.onLocalPublish(stream, info, error);
        if (BaseManager.bmxFinish(error)) {
            //打开本地视频画面,发送呼叫消息
            onUserJoin(stream);
            Log.e(TAG, "发布本地流成功 开启预览 msg = " + info);
        }else{
            Log.e(TAG, "发布本地流失败 msg = " + info);
        }
    }

    @Override
    public void onLocalUnPublish(BMXStream stream, String info, BMXErrorCode error) {
        super.onLocalUnPublish(stream, info, error);
        if (BaseManager.bmxFinish(error)) {
            Log.e(TAG, "停止发布本地流成功 msg = " + info);
        }else{
            Log.e(TAG, "停止发布本地流失败 msg = " + info);
        }
    }

    @Override
    public void onRemotePublish(BMXStream stream, String info, BMXErrorCode error) {
        super.onRemotePublish(stream, info, error);
        if (BaseManager.bmxFinish(error)) {
            mEngine.subscribe(stream);
            Log.e(TAG, "远端发布流 开启订阅");
        }else{
            Log.e(TAG, "远端发布流失败 msg = " + info);
        }
    }

    @Override
    public void onRemoteUnPublish(BMXStream stream, String info, BMXErrorCode error) {
        super.onRemoteUnPublish(stream, info, error);
        if (BaseManager.bmxFinish(error)) {
            Log.e(TAG, "远端取消发布流");
            BMXVideoCanvas canvas = new BMXVideoCanvas();
            canvas.setMStream(stream);
            mEngine.stopRemoteView(canvas);
            //停止订阅
            mEngine.unSubscribe(stream);
            //回收界面
            onRemoteLeave();
        }else{
            Log.e(TAG, "远端取消发布流失败 msg = " + info);
        }
    }

    @Override
    public void onSubscribe(BMXStream stream, String info, BMXErrorCode error) {
        super.onSubscribe(stream, info, error);
        if (BaseManager.bmxFinish(error)) {
            //展示对方视频画面
            onRemoteJoin(stream);
            mPickupTimestamp = getTimeStamp();
            Log.e(TAG, "订阅远端流成功 msg = " + info);
        } else {
            Log.e(TAG, "订阅远端流失败 msg = " + info);
        }
    }

    @Override
    public void onUnSubscribe(BMXStream stream, String info, BMXErrorCode error) {
        super.onUnSubscribe(stream, info, error);
        if (BaseManager.bmxFinish(error)) {
            Log.e(TAG, "取消订阅远端流成功, 开启预览 msg = " + info);
        } else {
            Log.e(TAG, "取消订阅远端流失败 msg = " + info);
        }
    }
});
  1. 加入房间
    //设置视频分辨率
    BMXVideoConfig config = new BMXVideoConfig();
    config.setProfile(EngineConfig.VIDEO_PROFILE);
    mEngine.setVideoProfile(config);

    //设置用户ID、pin密码和房间ID
    BMXRoomAuth auth = new BMXRoomAuth();
    auth.setMUserId(mUserId);
    auth.setMRoomId(mRoomId);//主叫方无须设置roomId,房间创建成功事件会返回系统分配的roomId;被叫方需要设置与主叫方一样的roomId
    auth.setMToken(mPin);//房间pin密码,建议随机生成高强度密码
    mEngine.joinRoom(auth);
  1. 挂断通话
    public void onCallHangup(View view){
        //发送挂断消息
        sendRTCHangupMessage();
        leaveRoom();
        finish();
    }
  1. 回收界面
    private void onRemoteLeave() {
        removeRemoteView();
        mHandler.removeAll();
        finish();
    }
  1. 打开本地画面,发送呼叫消息
    private void onUserJoin(BMXStream info){
        if(info == null){
            return;
        }
        if (mHasVideo) {
            runOnUiThread(() -> {
                addLocalView();
                BMXVideoCanvas canvas = new BMXVideoCanvas();
                canvas.setMView(mLocalView.getObtainView());
                canvas.setMStream(info);
                mEngine.startPreview(canvas);
            });
        } else {

        }
        if (mIsInitiator) {
            //用户加入放入房间 发送给对方信息
            sendRTCCallMessage();
        }
    }
  1. 打开对方画面
    private void onRemoteJoin(BMXStream info) {
        if(info == null){
            return;
        }
        runOnUiThread(() -> {
            if (mHasVideo) {
                addRemoteView();
                BMXVideoCanvas canvas = new BMXVideoCanvas();
                canvas.setMView(mRemoteView.getObtainView());
                canvas.setMUserId(info.getMUserId());
                canvas.setMStream(info);
                mEngine.startRemoteView(canvas);
            }
        });
    }
  1. 通话结束时回收资源
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mEngine != null) {
            mEngine.removeRTCEngineListener(mListener);
            mListener = null;
        }
        RTCManager.getInstance().removeRTCServiceListener(mRTCListener);
        mRTCListener = null;
    }

使用rtcService实现音视频通话信令

  1. 添加事件监听

private BMXRTCServiceListener mRTCListener = new BMXRTCServiceListener(){

public void onRTCCallMessageReceive(BMXMessage msg) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //暂停以处理稍后收到的对应通话的挂断消息(mHungupCalls),
                // 这样可以避免弹出已结束的通话
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String callId = msg.config().getRTCCallId();
            if (mHungupCalls.contains(callId)){
                mHungupCalls.remove(callId);
                ackMessage(msg);
                return;
            }
            long roomId = msg.config().getRTCRoomId();
            long chatId = msg.config().getRTCInitiator();
            long myId = SharePreferenceUtils.getInstance().getUserId();
            if (myId == chatId){
                return;
            }
            //如果已在通话中,则发送忙线消息给对方
            if (RTCManager.getInstance().getRTCEngine().isOnCall){
                replyBusy(callId, myId, chatId);
                return;
            }
            String pin = msg.config().getRTCPin();
            if(mActivityRef != null && mActivityRef.get() != null){
                Context context = mActivityRef.get();
                if (msg.type() == BMXMessage.MessageType.Single) {
                    //打开通话界面(呼入中)
                    SingleVideoCallActivity.openVideoCall(context, chatId, roomId, callId,
                            false, msg.config().getRTCCallType(), pin, msg.msgId());
                }
            }
        }
    }, "onRTCCallMessageReceive").start();
}

public void onRTCPickupMessageReceive(BMXMessage msg) {
    if (msg.config().getRTCCallId().equals(mCallId) && msg.fromId() == mUserId){
        leaveRoom();
        ackMessage(msg);
    }
}

public void onRTCHangupMessageReceive(BMXMessage msg) {
    long otherId = mEngine.otherId;
    if (msg.config().getRTCCallId().equals(mCallId) &&
            (msg.fromId()==otherId
            || msg.content().equals("busy")
            || msg.content().equals("rejected")
            || msg.content().equals("canceled")
            || msg.content().equals("timeout")
            || !mEngine.isOnCall)){
        leaveRoom();
        ackMessage(msg);
    }
}

}; RTCManager.getInstance().addRTCServiceListener(mRTCListener);

  1. 发送呼叫消息
public String sendRTCCallMessage(BMXMessageConfig.RTCCallType type, long roomId, long from, long to,
                                    String pin) {
    BMXMessageConfig con = BMXMessageConfig.createMessageConfig(false);
    con.setRTCCallInfo(type, roomId, from, BMXMessageConfig.RTCRoomType.Broadcast, pin);
    con.setIOSConfig("{\"mutable_content\":true,\"loc-key\":\"call_in\"}");
    BMXMessage msg = BMXMessage.createRTCMessage(from, to, BMXMessage.MessageType.Single, to, "");
    msg.setConfig(con);
    msg.setExtension("{\"rtc\":\"call\"}");
    handlerMessage(msg);
    return con.getRTCCallId();
}
  1. 发送接听消息
public void sendRTCPickupMessage(long from, long to, String callId) {
    BMXMessageConfig con = BMXMessageConfig.createMessageConfig(false);
    con.setRTCPickupInfo(callId);
    BMXMessage msg = BMXMessage.createRTCMessage(from, to, BMXMessage.MessageType.Single, to, "");
    msg.setConfig(con);
    handlerMessage(msg);
}
  1. 发送挂断消息
public void sendRTCHangupMessage(long from, long to, String callId, String content, String iosConfig) {
    BMXMessageConfig con = BMXMessageConfig.createMessageConfig(false);
    con.setRTCHangupInfo(callId);
    BMXMessage msg = BMXMessage.createRTCMessage(from, to, BMXMessage.MessageType.Single, to, content);
    msg.setConfig(con);
    handlerMessage(msg);
}

快速集成文档参考蓝莺快速集成指南android版, 详细文档可参考floo-android reference

祝玩得开心😊

About

蓝莺 IM + UCloud RTC SDK,安卓原生库

Resources

Stars

Watchers

Forks

Packages

No packages published