package com.ydl.audioim import android.annotation.SuppressLint import android.content.Context import android.os.Handler import android.text.TextUtils import com.alibaba.android.arouter.launcher.ARouter import com.google.gson.Gson import com.ydl.audioim.bean.AgoraInvitationBean import com.ydl.audioim.http.AudioApiRequestUtil import com.ydl.audioim.http.command.ConnectExceptionCommand import com.ydl.audioim.router.AudioImIn import com.ydl.audioim.utils.AudioLogUtils import com.ydl.consultantim.ConsultantAudioHomeActivity import com.ydl.ydl_av.messge_service.YDLRTMClient import com.ydl.ydl_av.messge_service.bean.RTMMesssage import com.ydl.ydl_av.messge_service.callback.CallListener import com.ydl.ydl_av.messge_service.callback.CancelCallStatusListener import com.ydl.ydl_av.messge_service.callback.InitListener import com.ydl.ydl_av.messge_service.callback.LoginCallback import com.ydl.ydl_av.messge_service.request.LoginParam import com.ydl.ydl_av.messge_service.response.CallLocalResponse import com.ydl.ydl_av.messge_service.response.CallRemoteResponse import com.ydl.ydlcommon.modular.ModularServiceManager import com.ydl.ydlcommon.utils.ActivityManager import com.ydl.ydlcommon.utils.LogUtil import com.ydl.ydlcommon.utils.log.LogHelper import com.yidianling.common.tools.ToastUtil import com.yidianling.im.api.bean.IMRegisterObserverCustomNotificationCallBack import com.yidianling.im.api.bean.IMSendCustomNotificationResultCallBack import com.yidianling.user.api.event.UserLoginEvent import com.yidianling.user.api.event.UserLogoutEvent import com.yidianling.user.api.service.IUserService import de.greenrobot.event.EventBus import io.agora.rtm.RtmStatusCode import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import java.util.concurrent.TimeUnit /** * @author harvie * @date 2019/9/27 * 语音通话入口类 */ class YDLavManager { companion object { const val FILE_NAME = "consult" //当前sdk的登录状态 var sdkStatus = -1 val instances: YDLavManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { YDLavManager() } } private constructor() fun init(context: Context, appId: String) { YDLRTMClient.instances.init(context, appId, listener) EventBus.getDefault().register(this) //设置回调 setCallback() val uid = ModularServiceManager.provide(IUserService::class.java).getUserInfo()?.uid if (!TextUtils.isEmpty(uid) && !TextUtils.equals("0", uid)) { //不延时,可能会导致请求api报 network not unablibale Handler().postDelayed({ login(uid) }, 300) } } public fun onEvent(event: UserLoginEvent) { instances.login(event.uid) } public fun onEvent(event: UserLogoutEvent) { instances.logout() } private fun setCallback() { YDLRTMClient.instances.setCallListener(object : CallListener { override fun onCallRecivedByPeer(response: CallLocalResponse?) { //返回给主叫:被叫已收到呼叫邀请 LogUtil.e("[agora]${response?.calleeId}已收到呼叫邀请,频道号${response?.ChannelId}") val act = ActivityManager.getInstance().getTopTaskActivity() if (act is AudioHomeActivity) { act.runOnUiThread { act.playWaitingMusic() } } } override fun onCallAccepted(response: CallLocalResponse?, msg: String?) { //返回给主叫 LogUtil.e("[agora]${response?.calleeId}已接收呼叫邀请") //加入声网频道时机修改:主叫收到被叫接受邀请的回调后再加入声网频道 val act = ActivityManager.getInstance().getTopTaskActivity() if (act is AudioHomeActivity) { act.runOnUiThread { act.joinChannel() } } } override fun onCallRefused(response: CallLocalResponse?, msg: String?) { //返回给主叫 LogUtil.e("[agora]${response?.calleeId}已拒绝呼叫邀请") val act = ActivityManager.getInstance().getTopTaskActivity() if (act is AudioHomeActivity) { act.runOnUiThread { callEndStatusUpdate(response?.ChannelId!!, 2, "被叫拒绝") ToastUtil.toastShort("对方已挂断") act.writeAgoraLog("被叫拒绝了通话邀请") //通话结束或挂断时,上传日志文件 act.uploadLog() act.leaveChannel() act.uploadExceptionStatus("对方已拒绝", 2) } } } override fun onCallCanceled(response: CallLocalResponse?) { //返回给主叫 LogUtil.e("[agora]主叫已取消呼叫邀请") val act = ActivityManager.getInstance().getTopTaskActivity() if (act is AudioHomeActivity) { act.runOnUiThread { act.writeAgoraLog("主叫呼叫取消:超时或主动取消") act.uploadExceptionStatus("已取消", 1) } } } override fun onCallFailure(response: CallLocalResponse?, errorCode: Int) { //返回给主叫 LogUtil.e("[agora]呼叫${response?.calleeId}用户失败:${response?.response}") val act = ActivityManager.getInstance().getTopTaskActivity() //专家离线或者30 秒后仍未收到专家响应,重新再邀请一次 when (errorCode) { //被叫不在线 呼叫邀请发出 30 秒后被叫仍未 ACK 响应呼叫邀请 RtmStatusCode.LocalInvitationError.LOCAL_INVITATION_ERR_PEER_OFFLINE, RtmStatusCode.LocalInvitationError.LOCAL_INVITATION_ERR_PEER_NO_RESPONSE -> { if (act is AudioHomeActivity) { act.runOnUiThread { act.rtcCall() } } } RtmStatusCode.LocalInvitationError.LOCAL_INVITATION_ERR_INVITATION_EXPIRE -> { //呼叫邀请过期。被叫 ACK 响应呼叫邀请后 60 秒呼叫邀请未被取消、接受、拒绝,则呼叫邀请过期。 if (act is AudioHomeActivity) { act.runOnUiThread { sendCustomNotification( response?.calleeId!!, response?.ChannelId!!, "5" ) callEndStatusUpdate(response.ChannelId!!, 2, "被叫超时未接听") act.writeAgoraLog("呼叫失败:${errorCode}") act.uploadExceptionStatus("对方未接听", 3) // //通话结束或挂断时,上传日志文件 // act.uploadLog() // act.leaveChannel() } } } } } override fun onRemoteInvitationReceived(response: CallRemoteResponse?) { //返回给被叫 LogUtil.e("[agora]收到来自${response?.callerId}的呼叫邀请") receivedCall(response?.content, "来自RTM") } override fun onRemoteInvitationAccepted(response: CallRemoteResponse?) { //返回给被叫 LogUtil.e("[agora]接受来自${response?.callerId}的呼叫成功") } override fun onRemoteInvitationRefused(response: CallRemoteResponse?) { //返回给被叫 LogUtil.e("[agora]已拒绝来自${response?.callerId}的呼叫") val act = ActivityManager.getInstance().getTopTaskActivity() if (act is ConsultantAudioHomeActivity) { act.uploadExceptionStatus("已拒绝", 2) } } override fun onRemoteInvitationCanceled(response: CallRemoteResponse?) { callEndStatusUpdate(response?.ChannelId!!, 1, "主叫取消呼叫") //返回给被叫 LogUtil.e("[agora]主叫${response?.callerId}已取消呼叫邀请") AudioLogUtils.writeAgoraLog("呼叫邀请被取消:主叫主动取消", FILE_NAME) val act = ActivityManager.getInstance().getTopTaskActivity() if (act is ConsultantAudioHomeActivity) { act.uploadExceptionStatus("对方已取消", 1) } closePage() } override fun onRemoteInvitationFailure(response: CallRemoteResponse?, errorCode: Int) { //返回给被叫 LogUtil.e("[agora]来自主叫${response?.callerId}的呼叫邀请进程失败:${response?.response}") if (errorCode == RtmStatusCode.RemoteInvitationError.REMOTE_INVITATION_ERR_INVITATION_EXPIRE) { //呼叫邀请过期 AudioLogUtils.writeAgoraLog("呼叫邀请被取消:呼叫邀请过期", FILE_NAME) val act = ActivityManager.getInstance().getTopTaskActivity() if (act is ConsultantAudioHomeActivity) { act.uploadExceptionStatus("未接听", 3) } } else { AudioLogUtils.writeAgoraLog("呼叫邀请被取消:错误原因(${errorCode})", FILE_NAME) } callEndStatusUpdate(response?.ChannelId!!, 2, "超时未接听导致的取消呼叫") //关闭页面 closePage() } override fun onOtherMsg(error: String?) { LogUtil.e("[agora]其它消息:${error}") } }) AudioImIn.registerObserveCustomNotification(object : IMRegisterObserverCustomNotificationCallBack { override fun onObserverCustomNotification( fromUid: String, toUid: String, content: String ) { LogUtil.e("[agora]收到云信的通知消息:$content") val agoraInvitationBean = Gson().fromJson(content, AgoraInvitationBean::class.java) //1发起呼叫 2接受呼叫 3取消呼叫 4拒绝呼叫邀请 5呼叫超时 when (agoraInvitationBean.callType) { "1" -> { receivedCall(agoraInvitationBean.data, "来自云信") } "2" -> { val act = ActivityManager.getInstance().getTopTaskActivity() if (act is AudioHomeActivity) { act.runOnUiThread { act.joinChannel() } } } "3" -> { AudioLogUtils.writeAgoraLog("呼叫邀请被取消:主叫主动取消", FILE_NAME) closePage() } "4" -> { val act = ActivityManager.getInstance().getTopTaskActivity() if (act is AudioHomeActivity) { act.runOnUiThread { ToastUtil.toastShort("对方已挂断") act.writeAgoraLog("被叫拒绝了通话邀请") //通话结束或挂断时,上传日志文件 act.uploadLog() act.leaveChannel() } } } "5" -> { AudioLogUtils.writeAgoraLog("呼叫邀请被取消:呼叫邀请过期", FILE_NAME) //关闭页面 closePage() } } } }) } fun rtcCall(listenerUid: String?, channelId: String?, sendDoctocrMsg: String?) { YDLRTMClient.instances.call(listenerUid, channelId, sendDoctocrMsg) sendCustomNotification(listenerUid!!, sendDoctocrMsg!!, "1") } fun acceptCall(toUid: String, channelId: String?, data: String) { YDLRTMClient.instances.acceptCall(channelId) sendCustomNotification(toUid, data, "2") } fun refuseCall(toUid: String, channelId: String?, data: String) { YDLRTMClient.instances.refuseCall(channelId) sendCustomNotification(toUid, data, "4") } fun cancelCall( listenerUid: String, channelId: String, data: String, event: (msg: String?, code: Int) -> Unit ) { YDLRTMClient.instances.cancelCall( listenerUid, channelId, object : CancelCallStatusListener { override fun onFailure(errorMsg: String?, errorCode: Int) { event(errorMsg, errorCode) YDLRTMClient.instances.cancelCall(listenerUid, channelId, null) } override fun onSuccess() { } }) callEndStatusUpdate(channelId, 1, "主叫取消呼叫") sendCustomNotification(listenerUid, data, "3") } private fun sendCustomNotification(toUid: String, data: String, callType: String) { val infoBean = AgoraInvitationBean() infoBean.data = data infoBean.callType = callType Handler().postDelayed({ AudioImIn.sendCustomNotification(toUid, Gson().toJson(infoBean), object : IMSendCustomNotificationResultCallBack { override fun onException(throwable: Throwable) { } override fun onFailed(code: Int) { } override fun onSuccess() { } }) }, 300) } fun login(userId: String?) { login(userId) { _, _ -> } } @SuppressLint("CheckResult") fun login(userId: String?, event: (isSuccess: Boolean, msg: String?) -> Unit) { if (TextUtils.isEmpty(userId) || userId ?: "0" <= "0") { //如果uid为空或小于等于0 ,则不进行登录,因为uid为0也会登录成功,会导致后面uid正确时无法登录 LogUtil.e("[agora]login-uid:$userId") return } //登录实时消息 //获取token AudioApiRequestUtil.getAgoraToken().subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).subscribe({ if ("200".equals(it.code)) { LogUtil.e("[agora]登录av的login-uid:$userId") YDLRTMClient.instances.login( LoginParam(userId, it.data.token), object : LoginCallback { override fun onSuccess() { //登陆成功,发起呼叫 LogUtil.e("[agora]实时消息登录成功") event(true, "") } override fun onFailure(msg: String?) { LogUtil.e("[agora]实时消息登录失败:$msg") event(false, msg) } }) } else { LogUtil.e("声网token获取失败uid:" + userId + " error:" + it.msg) LogHelper.getInstance() .writeLogSync("声网token获取失败uid:" + userId + " error:" + it.msg) } }, { LogUtil.e("声网token获取异常uid:" + userId + " error:" + it.message) }) } /** * 收到邀请 */ @SuppressLint("CheckResult") fun receivedCall(content: String?, from: String = "") { if (!TextUtils.isEmpty(content)) { //如果已经接听了用户电话 再有电话进来 是不能接听的 if (!activityIsExists(ConsultantAudioHomeActivity::class.java) && !activityIsExists( AudioHomeActivity::class.java ) ) { //延时启动通话界面,防止刚打开就被main遮挡 Observable.timer(1000, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).subscribe { LogUtil.e("[agora]启动通话界面") //邀请加入频道消息,跳转通话界面 ARouter.getInstance().build("/av/ConsultantAudioHomeActivity") .withString("param", content).navigation() AudioLogUtils.writeAgoraLog("收到主叫方通话邀请($from)", FILE_NAME, false) } } else { LogUtil.d("[agora]收到声网邀请,但界面实例已存在") } } else { LogUtil.d("[agora]收到声网邀请,但response==null") } } private fun activityIsExists(cls: Class<*>): Boolean { for (activity in ActivityManager.getInstance().getActivitys()) { if (activity.javaClass == cls) { return true } } return false } /** * 关闭通话界面 */ fun closePage() { var act = ActivityManager.getInstance().getTopTaskActivity() if (act is ConsultantAudioHomeActivity) { //未接通时,收到呼叫进程失败关闭页面,已接通无需关闭 if (act.status == ConsultantAudioHomeActivity.STATUS_NOT_ANSWERED) { act.close(ConsultantAudioHomeActivity.RESULT_USER_CANCEL, "") } } } /** * 退出登录 */ fun logout() { EventBus.getDefault().unregister(this) YDLRTMClient.instances.logout(object : LoginCallback { override fun onSuccess() { //退出登陆成功 LogUtil.d("[agora]实时消息退出成功") } override fun onFailure(msg: String?) { LogUtil.d("[agora]实时消息退出失败:$msg") } }) } /** * RTM登录异常,上传错误日志 msg * 声网出现异常,上传错误日志 connectException */ @SuppressLint("CheckResult") fun uploadException( connectException: ConnectExceptionCommand, callback: UploadExceptionCallback? ) { AudioApiRequestUtil.connectException(connectException).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()).subscribe({ callback?.onSuccess() }, { LogUtil.e("agora", "声网上传异常与错误日志接口调用失败:" + it.message) }) } /** * 上传异常错误回调 */ interface UploadExceptionCallback { fun onSuccess() } @SuppressLint("CheckResult") fun callEndStatusUpdate(channelId: String, endStatus: Int, msg: String) { AudioApiRequestUtil.callEndStatusUpdate(channelId, endStatus, msg) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ }, { LogUtil.d("callEndStatusUpdate error: ${it.message}") }) } /** * 实时消息全局监听 */ private val listener = object : InitListener { override fun onTokenExpired() { LogUtil.e("[agora]onTokenExpired") instances.login(ModularServiceManager.provide(IUserService::class.java).getUserInfo()?.uid) } override fun onMessageReceived(message: RTMMesssage, userId: Int) { LogUtil.i("[agora]onMessageReceived:${message.text} -->uid:$userId") } override fun onConnectionStateChanged(state: Int, reason: Int) { sdkStatus = state LogUtil.i("[agora]onConnectionStateChanged:state:${state} -->reason:$reason") } } }