如何实现一套可切换的声网+阿里的直播引擎

Posted BennuCTech

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现一套可切换的声网+阿里的直播引擎相关的知识,希望对你有一定的参考价值。

前言

小盒的直播业务一开始是打算用两套引擎切换使用的,所以需要封装一下。而且因为声网和阿里的直播sdk的官方文档都不是很全面,甚至有的还有错误(可能是文档没及时更新)导致无法正常运行,接入时问题多多,所以同时记录一下的接入过程中的问题及处理。

定义接口

首先因为需要两个引擎切换使用,所以定义了接口,定义常用的行为

public interface RtcEngine 
    void init(Context context, RtcInfo config);
    void join();
    void leave();
    void setRtcListener(RtcListener rtcListener);

这里RtcInfo是两个sdk需要用到的参数,由服务端提供。我们是初始化时一次性提供,当然也可以实时提供,如果实时提供,join函数也需要一些添加必要参数。

RtcInfo的定义如下:

public class RtcInfo 
    public AgoraConfig agoraConfig;
    public AliConfig aliConfig;
    public String rtcType;

public class AgoraConfig 
    public String liveChannel;
    public String appId;
    public int avatarUID;
    public int liveUID;
    public String liveToken;

public class AliConfig 
    public String liveChannel;
    public String appId;
    public String avatarUID;
    public String liveUID;
    public String liveToken;
    ...

另外还有一个监听RtcListener,统一了两个sdk的回调,可以自行丰富

public interface RtcListener 
    void remoteOnline(View remoteView); //当收到流之后,将remoteView加入页面中展示
    void remoteOffline();

接入声网

声网的封装类,实现RtcEngine接口:

public class AgoraEngine implements RtcEngine 
    private final String TAG = this.getClass().getSimpleName();
    private Context mContext;
    private io.agora.rtc.RtcEngine engine;
    private RtcInfo mConfig;
    private RtcListener listener;
    private SurfaceView mRemoteView;
    private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler() 
        @Override
        public void onJoinChannelSuccess(String s, int i, int i1) 
            super.onJoinChannelSuccess(s, i, i1);
        

        @Override
        public void onLeaveChannel(RtcStats rtcStats) 
            super.onLeaveChannel(rtcStats);
        

        @Override
        public void onUserOffline(int i, int i1) 
            super.onUserOffline(i, i1);
        

        @Override
        public void onWarning(int i) 
            super.onWarning(i);
        

        @Override
        public void onError(int i) 
            super.onError(i);
        

        @Override
        public void onUserJoined(final int uid, int elapsed) 
            super.onUserJoined(uid, elapsed);
            //这里获取到流,设置RemoteVideo并展示
            //因为有两路流,我们只使用了一路,所以需要判断一下,只展示老师的流
            if (uid != mConfig.agoraConfig.avatarUID && uid < xxxx) 
                ...
            
            if (uid == mConfig.agoraConfig.avatarUID) 
                //发现uid与老师id一致,创建设置RemoteVideo并展示
                mRemoteView.setActivated(true);
                mRemoteView.setEnabled(true);
                new Handler(mContext.getMainLooper()).post(new Runnable() 
                    @Override
                    public void run() 
                        mRemoteView = io.agora.rtc.RtcEngine.CreateRendererView(mContext);
                        mRemoteView.setActivated(true);
                        mRemoteView.setEnabled(true);
                        if(listener != null)
                            //交给页面处理,一般是将播放器展示出来
                            listener.joinSuccess(mRemoteView);
                        
                        ...
                    
                );
            
        

        @Override
        public void onFirstRemoteVideoFrame(final int uid, int w, int h, int i3) 
            //官方文档表明在这里会获取第一祯流,然后设置RemoteVideo并展示。实际使用中发现这里根本不回调,而且在onUserJoined中处理RemoteVideo
        
    ;
    @Override
    public void init(Context context, RtcInfo config) 
        mConfig = config;
        mContext = context.getApplicationContext();

        try 
            engine = io.agora.rtc.RtcEngine.create(mContext, config.agoraConfig.appId, iRtcEngineEventHandler);
            engine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
            engine.setVideoProfile(Constants.VIDEO_PROFILE_240P_4, false);
            engine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
            engine.enableVideo();
            engine.setParameters("\\"che.audio.keep.audiosession\\":true");
         catch (Exception e) 
            Log.e(TAG, TAG, e);
            engine = null;
        
    

    @Override
    public void join() 
        if(engine != null)
            engine.joinChannel(mConfig.agoraConfig.liveToken, mConfig.agoraConfig.liveChannel, "", mConfig.agoraConfig.liveUID);
        
    

    @Override
    public void leave() 
        if(engine != null)
            engine.leaveChannel();
            io.agora.rtc.RtcEngine.destroy();
            engine = null;
        
    

    @Override
    public void setRtcListener(RtcListener rtcListener) 
        listener = rtcListener;
    

重点注意onFirstRemoteVideoFrame在官方文档表明在这里会获取第一祯流,然后设置RemoteVideo并展示。实际使用中发现这里根本不回调,而且在onUserJoined中处理RemoteVideo,在官方Demo里也是这么处理的,应该是文档更新滞后了。(不知道现在更没更新)。

代码中我们没有对onUserOffline进行处理,后续实际上是补充了相关功能,这里注意的是一定要校验uid,否则可能导致问题。比如在老师退出直播间的时候我们需要做一些页面调整,但是如果这里没有校验uid,那么其他人(特殊身份)在退出时也会执行这部分代码。

接入阿里直播

阿里的封装类,同样实现RtcEngine接口:

public class AliEngine implements RtcEngine 
    private final String TAG = this.getClass().getSimpleName();
    private Context mContext;
    private AliRtcEngine mEngine;
    private RtcInfo mConfig;
    //private SophonSurfaceView mRemoteView;
    private AliRtcEngine.AliVideoCanvas mCanvas;
    private RtcListener listener;

    private AliRtcEngineEventListener aliRtcEngineEventListener = new AliRtcEngineEventListener() 
        ...
    ;
    
    private AliRtcEngineNotify aliRtcEngineNotify = new AliRtcEngineNotify() 
        ...

        @Override
        public void onRemoteTrackAvailableNotify(final String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, final AliRtcEngine.AliRtcVideoTrack videoTrack) 
            super.onRemoteTrackAvailableNotify(uid, audioTrack, videoTrack);
            //收到流的第一祯,先判断是不是老师的流
            if(uid.equals(mConfig.aliConfig.avatarUID)) 
//                mEngine.configRemoteAudio(mConfig.aliConfig.avatarUID, true);
//                mEngine.configRemoteScreenTrack(mConfig.aliConfig.avatarUID, true);
//                mEngine.configRemoteCameraTrack(mConfig.aliConfig.avatarUID, true, true);
//                mEngine.subscribe(mConfig.aliConfig.avatarUID);

                new Handler(mContext.getMainLooper()).post(new Runnable() 
                    @Override
                    public void run() 
                        if(mEngine == null)
                            return;
                        
                        AliRtcRemoteUserInfo info = mEngine.getUserInfo(uid);
                        if(info == null)
                            return;
                        
                        AliRtcEngine.AliVideoCanvas cameraCanvas = info.getCameraCanvas();
                        AliRtcEngine.AliVideoCanvas screenCanvas = info.getScreenCanvas();
                        if(videoTrack == AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackNo)
                            screenCanvas = null;
                            cameraCanvas = null;
                        
                        else if(videoTrack == AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera)
                            //我们只需要摄像头的流。这里创建设置remoteView,并展示
                            mCanvas = new AliRtcEngine.AliVideoCanvas();
                            SophonSurfaceView mRemoteView = new SophonSurfaceView(mContext);
                            if(listener != null)
                                //交给页面处理,一般是将播放器展示出来
                                listener.joinSuccess(mRemoteView);
                            
                            mRemoteView.setZOrderOnTop(true);
                            mRemoteView.setZOrderMediaOverlay(true);
                            mCanvas.view = mRemoteView;
                            ...
                            screenCanvas = null;
                            cameraCanvas = mCanvas;
                            mEngine.setRemoteViewConfig(cameraCanvas, uid, AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera);

                        
                    
                );
            
        

        ...
    ;
    
    @Override
    public void init(Context context, RtcInfo config) 
        mContext = context;
        mConfig = config;
        mEngine = AliRtcEngine.getInstance(context);
        mEngine.setRtcEngineEventListener(aliRtcEngineEventListener);
        mEngine.setRtcEngineNotify(aliRtcEngineNotify);
        mEngine.setClientRole(AliRtcEngine.AliRTCSDK_Client_Role.AliRTCSDK_live);
        mEngine.setChannelProfile(AliRtcEngine.AliRTCSDK_Channel_Profile.AliRTCSDK_Interactive_live);
        mEngine.setAutoPublishSubscribe(false, true);
    

    @Override
    public void join() 
        AliRtcAuthInfo info = new AliRtcAuthInfo();
        info.setConferenceId(mConfig.aliConfig.liveChannel);
        info.setAppid(mConfig.aliConfig.appId);
        info.setUserId(mConfig.aliConfig.liveUID);
        ...
        info.setToken(mConfig.aliConfig.liveToken);
        if(mEngine != null)
            mEngine.joinChannel(info, "");
        
    

    @Override
    public void leave() 
        if(mEngine != null)
            mEngine.leaveChannel();
        
        mEngine = null;
    

    @Override
    public void setRtcListener(RtcListener rtcListener) 
        listener = rtcListener;
    

与声网的很类似,注意事项也差不多,因为关键部分都有注释,这里就不细说了。

总结

这样在进入直播前,通过后台获取直播配置,根据类型初始化不同的引擎来使用即可。

源码

关注公众号:BennuCTech,发送“RtcEngine”获取完整源码。

以上是关于如何实现一套可切换的声网+阿里的直播引擎的主要内容,如果未能解决你的问题,请参考以下文章

视频直播系统解决方案—是基于声网SDK实现的

让我为你介绍一个神器:Chimee,一套可扩展的 H5 视频播放器解决方案

声网发布全新直播产品“融合 CDN 直播”

怎么使用阿里云直播服务应用到现在主流直播平台中

大数据计算引擎,为什么阿里坚定地选择 Flink?| 直播预告

游戏引擎架构