「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」
前言
小盒的直播业务一开始是打算用两套引擎切换使用的,所以需要封装一下。而且因为声网和阿里的直播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;
public String gslb;
public List<String> gslbList;
public long timeStamp;
public String nonce;
}
复制代码
另外还有一个监听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 (engine != null) {
engine.muteRemoteAudioStream(uid, true);
engine.muteRemoteVideoStream(uid, true);
}
return;
}
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);
}
engine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid));
engine.setRemoteRenderMode(uid, VideoCanvas.RENDER_MODE_HIDDEN, Constants.VIDEO_MIRROR_MODE_ENABLED); //设置渲染和镜像模式(默认不镜像),本函数必须在setupRemoteVideo之后,可以多次调用
}
});
}
}
@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() {
@Override
public void onJoinChannelResult(int result) {
super.onJoinChannelResult(result);
}
@Override
public void onLeaveChannelResult(int result) {
super.onLeaveChannelResult(result);
}
@Override
public void onNetworkQualityChanged(String uid, AliRtcEngine.AliRtcNetworkQuality upQuality, AliRtcEngine.AliRtcNetworkQuality downQuality) {
super.onNetworkQualityChanged(uid, upQuality, downQuality);
}
@Override
public void onOccurWarning(int warn) {
super.onOccurWarning(warn);
}
@Override
public void onOccurError(int error) {
super.onOccurError(error);
}
};
private AliRtcEngineNotify aliRtcEngineNotify = new AliRtcEngineNotify() {
@Override
public void onRemoteUserOnLineNotify(String uid) {
super.onRemoteUserOnLineNotify(uid);
}
@Override
public void onRemoteUserOffLineNotify(String uid) {
super.onRemoteUserOffLineNotify(uid);
}
@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;
//设置渲染模式
mCanvas.renderMode = AliRtcEngine.AliRtcRenderMode.AliRtcRenderModeFill;
//设置镜像模式,默认不镜像
mCanvas.mirrorMode = AliRtcEngine.AliRtcRenderMirrorMode.AliRtcRenderMirrorModeAllEnabled;
screenCanvas = null;
cameraCanvas = mCanvas;
mEngine.setRemoteViewConfig(cameraCanvas, uid, AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera);
}
}
});
}
}
@Override
public void onFirstRemoteVideoFrameDrawn(String uid, AliRtcEngine.AliRtcVideoTrack videoTrack) {
super.onFirstRemoteVideoFrameDrawn(uid, videoTrack);
}
@Override
public void onBye(int code) {
super.onBye(code);
}
@Override
public void onAliRtcStats(AliRtcEngine.AliRtcStats aliRtcStats) {
super.onAliRtcStats(aliRtcStats);
}
@Override
public void onMessage(String tid, String contentType, String content) {
super.onMessage(tid, contentType, content);
}
};
@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.setNonce(mConfig.aliConfig.nonce);
info.setTimestamp(mConfig.aliConfig.timeStamp);
info.setUserId(mConfig.aliConfig.liveUID);
int size = mConfig.aliConfig.gslbList.size();
info.setGslb(mConfig.aliConfig.gslbList.toArray(new String[size]));
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;
}
}
复制代码
与声网的很类似,注意事项也差不多,因为关键部分都有注释,这里就不细说了。
扫描二维码关注公众号,回复:
13469572 查看本文章

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