一、前言
視頻通話接通不難,難的是各種情況處理,且有點(diǎn)功能聲網(wǎng)SDK是不提供的,如果要完整的視頻通話,這代碼量有點(diǎn)大,這篇文章僅以接通為目標(biāo)
二、看下效果圖(設(shè)計(jì)圖)

video_calling.png

video_call.png
二、跟著我開始步驟
1、配置部分
在app下的build.gradle中配置
implementation 'io.agora.rtc:full-sdk:3.6.2'
2、撥打前或者加入頻道前先詢問權(quán)限
private fun checkPermission(isClickAcceptCallBack:(() -> Unit)?= null){
if (PermissionCheckUtil.checkPermissions(mContext,if (startCallBean.type == AUDIO_TYPE) PermissionAppUtils.Group.MICROPHONE else PermissionAppUtils.Group.CAMERA_RECORD)){
if (startCallBean.type == AUDIO_TYPE) isClickAcceptCallBack?.invoke()?:initAudioEngine() else isClickAcceptCallBack?.invoke()?:initVideoEngine()
} else {
DialogUtils.okAndCancel(
mContext,
if (startCallBean.type == AUDIO_TYPE) getString(R.string.audio_call_permission_tip) else getString(R.string.video_call_permission_tip),
ok = getString(R.string.go_open),
okListener = {
PermissionAppUtils.requestCombinedPermission(mContext,if (startCallBean.type == AUDIO_TYPE) PermissionAppUtils.Group.MICROPHONE else PermissionAppUtils.Group.CAMERA_RECORD) {
if (it.granted){
if (isClickAcceptCallBack != null){
if (startCallBean.type == AUDIO_TYPE) {
initAudioEngine()
isClickAcceptCallBack.invoke()
} else {
initVideoEngine()
isClickAcceptCallBack.invoke()
}
} else {
if (startCallBean.type == AUDIO_TYPE) initAudioEngine() else initVideoEngine()
}
} else {
if (it.shouldShowRequestPermissionRationale){
//禁止,不做操作,撥打方直接關(guān)閉界面,接聽方在點(diǎn)接聽時(shí)再詢問一次
if (isCallOutGoing || isClickAcceptCallBack != null){
show(R.string.call_permission_empty_tip)
finish()
}
} else {
//永久禁止,需求說只彈前面的彈框,永久禁止就直接跳設(shè)置
//點(diǎn)接聽時(shí)跳到設(shè)置,返回不處理,讓它再點(diǎn)一次接聽按鈕
if (isClickAcceptCallBack == null){
isJumpAppSetting = true
CUtils.startAppSettings(mContext)
}
}
}
}
})
}
}
override fun onResume() {
super.onResume()
if (isJumpAppSetting){
isJumpAppSetting = false
when(startCallBean.type){
AUDIO_TYPE -> {
if (PermissionCheckUtil.checkPermissions(mContext,PermissionAppUtils.Group.MICROPHONE)){
initAudioEngine()
}
}
VIDEO_TYPE -> {
if (PermissionCheckUtil.checkPermissions(mContext,PermissionAppUtils.Group.CAMERA_RECORD)){
initVideoEngine()
}
}
else ->{
show(R.string.call_permission_empty_tip)
finish()
}
}
}
}
3、有了權(quán)限初始化聲網(wǎng)引擎和加入頻道(顯示自己的視頻畫面)
initVideoEngine()代碼如下:
private fun initVideoEngine(){
initializeAgoraEngine()
setupVideoProfile()
initVideoModule()
setupLocalVideo(startCallBean.closeCamera)
rtcEngine?.setVideoSource(RtcVideoConsumer())
if (isCallOutGoing && !startCallBean.isFloatBackCall){
joinChannel(startCallBean.token,startCallBean.channelName,SPUtil.getUserId())
}
}
我這個(gè)本地視頻用了相芯美顏的,可參考它demo原來的代碼。上面代碼中token和channelName都是app服務(wù)器接口返回的
4、自己加入頻道后通知對(duì)方
/**
* @desc : 自己加入頻道成功回調(diào)
* @author : congge on 2022-03-24 15:07
**/
override fun onJoinChannelSuccess(channel: String, uid: Int, elapsed: Int) {
runOnUiThread {
LogUtils.i("mRtcEventHandler","onJoinChannelSuccess")
if (!isCallOutGoing){
dealViewShow(true)
dealCallTime(true)
mViewModel.postChatMediaAnswer(startCallBean.mediaId)
} else {
//撥打方加入頻道后,邀請(qǐng)對(duì)方通話
if(disposableIntervalCall == null || disposableIntervalCall!!.isDisposed){
disposableIntervalCall = Observable.interval(0,5, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.take(55)
.subscribe {
//只有第一次邀請(qǐng)要通知欄
mViewModel.getChatMediaSendInvite(startCallBean.mediaId,it == 0L)
}
}
//自己加入頻道后,給對(duì)方60s加入頻道的時(shí)間
longTimeNoResponse(timeOutNoResponse)
}
}
}
注:onJoinChannelSuccess是加入頻道成功后的回調(diào),撥打方加入頻道后,就通知對(duì)方。如果是接聽方加入頻道后就可以顯示通話中界面了。聲網(wǎng)是沒有通知對(duì)方的api的,我項(xiàng)目是用融云IM通知對(duì)方的。
5、對(duì)方加入頻道后(顯示對(duì)方的視頻畫面)
/**
* @desc : 對(duì)方加入頻道成功回調(diào)
* 此回調(diào)在以下任一情況下觸發(fā):
* 1、遠(yuǎn)程用戶/主機(jī)通過調(diào)用joinChannel方法加入通道。
* 2、遠(yuǎn)程用戶在加入通道后通過調(diào)用setClientRole方法將用戶角色切換到主機(jī)。
* 3、網(wǎng)絡(luò)中斷后,遠(yuǎn)程用戶/主機(jī)重新加入通道。
* 4、主機(jī)通過調(diào)用addInjectStreamUrl方法將在線媒體流注入頻道。
* @author : congge on 2022-03-24 15:08
**/
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread {
LogUtils.i("mRtcEventHandler","onUserJoined")
//stopRinging關(guān)閉聲音要在dealViewShow前面,里面有關(guān)揚(yáng)聲器
CallRingingUtil.getInstance().stopRinging()
dealViewShow(false)
dealCallTime(true)
//10s內(nèi)對(duì)方進(jìn)入了頻道
cancelLongTimeNoResponse()
if (startCallBean.type == VIDEO_TYPE){
videoUid = uid
setupRemoteVideo(uid)
}
}
}
private fun setupRemoteVideo(uid: Int,fl: FrameLayout? = mBinding.flVideoCallRemoteVideo,surfaceViewRemote1:SurfaceView? = null) {
if (isMBindingViewInitialised()){
fl?.let {
if (it.childCount >= 1) {
return
}
it.removeAllViews()
if (surfaceViewRemote1 == null){
surfaceViewRemote = RtcEngine.CreateRendererView(mContext)
it.addView(surfaceViewRemote)
// Initializes the video view of a remote user.
rtcEngine?.setupRemoteVideo(VideoCanvas(surfaceViewRemote, VideoCanvas.RENDER_MODE_HIDDEN, uid))
surfaceViewRemote!!.tag = uid // for mark purpose
} else {
surfaceViewRemote = surfaceViewRemote1
it.addView(surfaceViewRemote)
surfaceViewRemote!!.tag = uid
}
}
}
}
1、撥打方監(jiān)聽到接聽方進(jìn)入頻道的回調(diào)
2、接聽方進(jìn)入頻道后也會(huì)收到這回調(diào)
6、至此雙方應(yīng)該能互相看見對(duì)方了,最后就是掛斷了
掛斷就是退出頻道
rtcEngine?.leaveChannel()
然后對(duì)方就會(huì)收到以下回調(diào)
override fun onUserOffline(uid: Int, reason: Int)
如果需要完整的聲網(wǎng)音視頻通話的代碼,可私信我