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

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

前言


小盒的直播业务一开始是打算用两套引擎切换使用的,所以需要封装一下。而且因为声网和阿里的直播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;
    }
}
复制代码


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



目录
相关文章
|
1月前
|
Web App开发 编解码 视频直播
视频直播技术干货(十二):从入门到放弃,快速学习Android端直播技术
本文详细介绍了Android端直播技术的全貌,涵盖了从实时音视频采集、编码、传输到解码与播放的各个环节。文章还探讨了直播中音视频同步、编解码器选择、传输协议以及直播延迟优化等关键问题。希望本文能为你提供有关Andriod端直播技术的深入理解和实践指导。
41 0
|
6月前
|
关系型数据库 Serverless 分布式数据库
Serverless 应用引擎常见问题之在抖音快手小程序上使用如何解决
Serverless 应用引擎(Serverless Application Engine, SAE)是一种完全托管的应用平台,它允许开发者无需管理服务器即可构建和部署应用。以下是Serverless 应用引擎使用过程中的一些常见问题及其答案的汇总:
|
定位技术 CDN
开源直播源码平台处理卡顿问题技巧方案
开源直播源码加速器功能就成功实现了,加速器功能有助于提高直播平台的竞争力,并满足用户对高质量、稳定和流畅的直播体验的需求,这也让加速器功能成为开源直播源码平台的重要功能之一。
开源直播源码平台处理卡顿问题技巧方案
|
运维 Cloud Native 容器
【直播】直播预告 | 云原生游戏第4讲:游戏服的网络接入和状态管理【直播已生成回放】
2022 年 11 月 29 日(周二)阿里云容器服务高级工程师 & 云原生游戏负责人,刘秋阳将会为大家详细介绍 OKG 的网络插件功能,一键式部署游戏服南北向网络,OKG 的自定义服务质量功能,以及自动化地感知并管理游戏服状态。
【直播】直播预告 | 云原生游戏第4讲:游戏服的网络接入和状态管理【直播已生成回放】
|
XML API 开发工具
移动端信息无障碍技术方案全解:以手淘为例
目前中国有1700多万视障人士,他们渴望购物,也希望在任何情况下都能平等的获取他们想要的信息,手淘作为全国最大的购物 App,我们也希望通过技术让视障消费者能更好的享受移动互联带来的便利,这既是公益,也是义务。 本文将和大家分享手淘在使用 DinamicX 支持无障碍的技术方案,并给出了相关示例,希望对移动端开发者有所启发。
移动端信息无障碍技术方案全解:以手淘为例
|
开发框架 开发者 前端开发
重磅首发 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
去年闲鱼发布的《Flutter in action》为开发者带去一手的实践经验总结,现在《Flutter in action》全新升级啦!这本书并非基础知识的简单罗列,而是从一线问题出发,循序渐进,娓娓道来。不仅把Flutter的重要理念讲得极为清晰, 而且给开发者提供了应对眼前各种问题的实用方法。同时,书中还给出了详尽的可以融会贯通、举一反三的思路,理论陈述和问题分析面面俱到,力求让读者可以获得全面系统的技术知识。
67610 0
重磅首发 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
|
边缘计算 编解码 监控
直播软件开发,低延时直播源码的特性分析
直播软件开发,低延时直播源码的特性分析
|
编解码 安全 算法
淘系的音视频编辑方案:非线性编辑引擎
在经历移动设备的更新换代,网络速度的持续提升和费用降低,手机用户已经经历了从文字阅读到图片浏览再到视频观看的内容消费的变革后,淘系音视频技术如何灵活根据需求做出技术创新与变革。
淘系的音视频编辑方案:非线性编辑引擎
|
监控 黑灰产治理
直播平台开发干货分享——标准直播及快、慢直播的特性
 所谓自己做直播平台开发,要结合不同的应用场景,相对应的功能、硬件、软件配套技术也不同。根据应用场景的不同,自建直播平台可以分为标准直播、快直播和慢直播。本文将简单地为大家分析一下这三点的特性。
直播平台开发干货分享——标准直播及快、慢直播的特性
|
Web App开发 机器学习/深度学习 编解码
直播新架构升级:全量支撑淘宝双11直播
淘宝直播最近连续三年直播引导成交大幅增长,2020年以来,有100多种职业转战淘宝直播间,无论达人身份还是商家身份,都在新风口的驱动下大量入场。如何应对双十一这种高峰值用户直播需求,这无疑对淘宝直播提出了更高的技术要求和挑战。同时,电商直播有强互动诉求,主播对弹幕的回复越及时,对购买越有促进效果。
直播新架构升级:全量支撑淘宝双11直播