前言
小盒的直播业务一开始是打算用两套引擎切换使用的,所以需要封装一下。而且因为声网和阿里的直播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; } } 复制代码
与声网的很类似,注意事项也差不多,因为关键部分都有注释,这里就不细说了。