package com.ydl.audioim import android.Manifest import android.annotation.SuppressLint import android.app.Service import android.content.Context import android.content.Intent import android.graphics.Color import android.graphics.Paint import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import android.media.MediaPlayer import android.net.Uri import android.os.Handler import android.os.PowerManager import android.os.Vibrator import android.provider.Settings import android.support.v4.content.ContextCompat import android.text.TextUtils import android.view.View import android.view.animation.AccelerateInterpolator import com.alibaba.android.arouter.facade.annotation.Route import com.google.gson.Gson import com.tbruyelle.rxpermissions2.RxPermissions import com.ydl.audioim.bean.AgoraLogInfoBean import com.ydl.audioim.contract.IAudioHomeActivityContract import com.ydl.audioim.http.command.ConnectCommand import com.ydl.audioim.http.command.ConnectExceptionCommand import com.ydl.audioim.http.command.NoticePushCommand import com.ydl.audioim.http.command.PayLoad import com.ydl.audioim.player.AudioPlayer import com.ydl.audioim.presenter.AudioHomePresenterImpl import com.ydl.audioim.utils.AudioLogUtils import com.ydl.audioim.utils.DateUtils import com.ydl.audioim.widget.AxbConfirmDialog import com.ydl.webview.H5Params import com.ydl.webview.NewH5Activity import com.ydl.webview.RefreshWebEvent import com.ydl.ydl_av.chat.bean.AudioMessageBean import com.ydl.ydl_av.voice.listener.IYDLVoiceEventHandler import com.ydl.ydl_av.voice.manager.YDLVoiceManager import com.ydl.ydl_image.config.SimpleImageOpConfiger import com.ydl.ydl_image.manager.YDLImageCacheManager import com.ydl.ydlcommon.base.BaseMvpActivity import com.ydl.ydlcommon.modular.ModularServiceManager import com.ydl.ydlcommon.router.YdlCommonRouterManager import com.ydl.ydlcommon.utils.LogUtil import com.ydl.ydlcommon.utils.StatusBarUtils import com.ydl.ydlcommon.utils.Utils import com.ydl.ydlcommon.utils.actionutil.ActionCountUtils import com.ydl.ydlcommon.utils.log.LogHelper import com.ydl.ydlcommon.utils.remind.ToastHelper import com.yidianling.common.tools.ToastUtil import com.yidianling.user.api.service.IUserService import de.greenrobot.event.EventBus import io.agora.rtc.Constants import io.agora.rtc.IRtcEngineEventHandler import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.audioim_activity_audio_home.* import java.util.* import java.util.concurrent.TimeUnit /** * @author jiucheng * @描述: 倾诉声网通话页面(拨打电话界面) * @Copyright Copyright (c) 2018 * @Company 壹点灵 * @date 2018/10/30 */ @Route(path = "/av/AudioHomeActivity") class AudioHomeActivity : BaseMvpActivity<IAudioHomeActivityContract.View, IAudioHomeActivityContract.Presenter>(), IAudioHomeActivityContract.View, SensorEventListener { /** * 专家头像地址 */ private var expertHeadUrl: String? = null /** * 专家姓名 */ private var expertName: String? = null /** * 专家文案 */ private var expertTips: String? = null /** * 声网点对点聊天房间id */ private var channelId: String? = null /** * 通话开始时间(接通) */ private var callStartTime: Long? = null /** * 倾诉剩余时长(时长单位s,eg:剩余2min15s,返回135) */ private var remainTime: String? = null /** *聆听者id(不是聆听者的uid) */ private var listenId: String? = null /** * token */ private var token: String? = null /** * commentUrl 评价页URL */ private var commentUrl: String? = null /** * callId */ private var callId: String? = null /** * relation_id */ private var relationId: String? = null /** * listenerUid专家uid */ private var listenerUid: String? = null /** * 倾述总时长 */ private var totalDuration: Int? = 0 /** * 本地记录的当前剩余时间 */ private var localRemainTime: Int? = 0 /** * 50s自动挂断倒计时 */ private var waitDisposable: Disposable? = null /** * 45s倒计时 */ private var disposable: Disposable? = null /** * 本次倾述倒计时 */ private var totalDisposable: Disposable? = null /** * 是否连接成功 */ private var isConnectSuccess: Boolean = false //电源管理对象 private var localPowerManager: PowerManager? = null //电源锁 private var localWakeLock: PowerManager.WakeLock? = null private var sensorManager: SensorManager? = null private var sendDoctocrMsg: String? = null private var axbPhone: String? = null //是否跳转到拨号页面 private var isJumpDail: Boolean = false private var isShowAXB: Boolean = true private var mPlayer: AudioPlayer? = null private var vibrator: Vibrator? = null private var handler: Handler? = null //声网 private var voiceManage: YDLVoiceManager? = null //频道管理器 // private var channelManager: ChannelManager? = null private var isLeavelChannel: Boolean = false private var hasUpLoadLog = false private var callStatus: Int = -1 /** * dialStatus 专家通话状态。 */ private var dialStatus: String? = null /** * 声网事件回调 (SDK 通过指定的事件通知应用程序 SDK 的运行事件,如: 加入或离开频道,新用户加入频道等) */ private val mRtcEventHandler = object : IYDLVoiceEventHandler() { override fun onUserMuteAudio(uid: Int, muted: Boolean) { } override fun onWarning(warn: Int) { super.onWarning(warn) uploadException("mRtcEventHandler-onWarning:warnCode--%${warn}", callback = null) LogUtil.e("[agora]发生警告回调$warn") writeAgoraLog("声网警告回调($warn)") //103:没有可用的频道资源。可能是因为服务端没法分配频道资源 //104:查找频道超时。在加入频道时 SDK 先要查找指定的频道,出现该警告一般是因为网络太差,连接不到服务器 //105:查找频道请求被服务器拒绝。服务器可能没有办法处理这个请求或请求是非法的 //106:打开频道超时。查找到指定频道后,SDK 接着打开该频道,超时一般是因为网络太差,连接不到服务器 //107:打开频道请求被服务器拒绝。服务器可能没有办法处理该请求或该请求是非法的 // 声网发出警告错误码,不应该直接离开房间 // runOnUiThread { // when (warn) { // 103, 105, 107 -> { // writeAgoraLog("通话挂断:网络异常(${warn})") // showToast("当前网络较差,请更换网络!") // //通话结束或挂断时,上传日志文件 // uploadLog() // leaveChannel() // YDLavManager.instances.callEndStatusUpdate( // channelId!!, // 4, // "收到频道回调警告信息$warn" // ) // } // } // } } override fun onError(err: Int) { super.onError(err) uploadException("mRtcEventHandler-onError:errorCode--%${err}", callback = null) writeAgoraLog("声网错误回调errorCode--%${err}") //3:SDK 初始化失败。Agora 建议尝试以下处理方法 //7:SDK 尚未初始化,就调用其 API。请确认在调用 API 之前已创建 RtcEngine 对象并完成初始化 //10:API 调用超时。有些 API 调用需要 SDK 返回结果,如果 SDK 处理时间过长,超过 10 秒没有返回,会出现此错误 //17:加入频道被拒绝。一般有以下原因: //用户已进入频道,再次调用加入频道的 API,例如 joinChannel,会返回此错误。停止调用该方法即可。 //用户在做 Echo 测试时尝试加入频道。等待 Echo test 结束后再加入频道即可。 //101:不是有效的 APP ID。请更换有效的 APP ID 重新加入频道 //102:不是有效的 频道名。请更换有效的频道名重新加入频道 //109:当前使用的 Token 过期,不再有效 //110:生成的 Token 无效 //123:此用户被服务器禁止 LogUtil.e("[agora]发生错误回调$err") runOnUiThread { when (err) { 3, 7, 109, 110 -> { showToast("请退出应用,重新打开") leaveChannel() } 10 -> { showToast("当前网络较差,请更换网络") leaveChannel() } 101 -> { showToast("安装包有问题,请联系技术") leaveChannel() } 102 -> { showToast("频道错误,请联系技术") leaveChannel() } 123 -> { // showToast("当前用户不允许接听电话,请联系客服") // leaveChannel() } } YDLavManager.instances.callEndStatusUpdate(channelId!!, 4, "频道的错误回调信息$err") } } override fun onApiCallExecuted(error: Int, api: String?, result: String?) { super.onApiCallExecuted(error, api, result) // LogUtil.e("[agora]$api 已执行回调 $result") } override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) { super.onJoinChannelSuccess(channel, uid, elapsed) LogUtil.e("[agora]$uid 用户声网加入频道成功:channel=$channel") //更新ui // onJoinChannelSuccess() //更新:现在专家先加入频道,所以不会有等待的过程, runOnUiThread { writeAgoraLog("主叫加入频道成功") //自己加入频道成功 connectSuccess() } } override fun onRejoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) { super.onRejoinChannelSuccess(channel, uid, elapsed) LogUtil.e("[agora]$uid 用户声网重新加入频道成功:channel=$channel") runOnUiThread { //自己加入频道成功 if (!isConnectSuccess) { writeAgoraLog("主叫重新加入频道成功") connectSuccess() } } } override fun onRtcStats(stats: IRtcEngineEventHandler.RtcStats?) { super.onRtcStats(stats) writeAgoraLog("声网onRtcStats:users:${stats?.users}") // 不需要移动端做离开房间逻辑,服务端会判断进行踢人逻辑 //因为用户端直接加入了频道,防止该回调执行时,专家还未加入频道,因此在连接成功之后,才进行频道人数判断 // if (isConnectSuccess && null != stats?.users && stats.users == 1) { // writeAgoraLog("通话结束:用户加入了频道,但频道内只有一个人") // com.yidianling.common.tools.ToastUtil.toastShort("专家已挂断") // //通话结束或挂断时,上传日志文件 // uploadLog() // leaveChannel() // } } override fun onConnectionStateChanged(state: Int, reason: Int) { super.onConnectionStateChanged(state, reason) // 3 网络连接被服务器中止 该情况现在是因为后端踢人逻辑 if (reason == 3) { writeAgoraLog("通话结束:网络连接被服务器中止 该情况现在是因为后端踢人逻辑,原因(${reason})") // com.yidianling.common.tools.ToastUtil.toastShort("专家已挂断") //通话结束或挂断时,上传日志文件 uploadLog() leaveChannel() YDLavManager.instances.callEndStatusUpdate(channelId!!, 4, "服务端踢人触发的回调") } } override fun onLeaveChannel(stats: IRtcEngineEventHandler.RtcStats?) { super.onLeaveChannel(stats) LogUtil.e("[agora]离开频道回调") //通话结束或挂断时,上传日志文件 uploadLog() } override fun onUserJoined(uid: Int, elapsed: Int) { super.onUserJoined(uid, elapsed) LogUtil.e("[agora]远端用户/主播加入频道回调") } override fun onUserOffline(uid: Int, elapsed: Int) { super.onUserOffline(uid, elapsed) LogUtil.e("[agora]远端用户$uid 离开频道回调") writeAgoraLog("接通后通话结束:对方已挂断") //通话结束或挂断时,上传日志文件 uploadLog() showToast("专家已挂断") //UserOffLine之后,销毁界面,解决,userOffline有回调之后,onConnectionStateChanged(服务端踢人方法没有调),导致记录时长异常。 leaveChannel() YDLavManager.instances.callEndStatusUpdate(channelId!!, 4, "对方离开频道") if (totalDisposable != null) { totalDisposable!!.dispose() } handler!!.postDelayed({ //另一方离开频道 updateExpertStatus(false, 1) }, 500) } override fun onNetworkQuality(uid: Int, txQuality: Int, rxQuality: Int) { super.onNetworkQuality(uid, txQuality, rxQuality) LogUtil.e("onNetworkQuality:-------uid=$uid,txQuality=$txQuality,rxQuality=$rxQuality") runOnUiThread { var status = -1 var netStatus = when (uid) { 0 -> { if (txQuality in 1..2 && rxQuality in 1..2) { "" } else if (txQuality >= 5 || rxQuality >= 5) { "您的网络已断开" } else { status = if (txQuality >= 4 || rxQuality >= 4) { 0 } else { 1 } "您的网络状况不佳" } } else -> { if (txQuality in 1..2 && rxQuality in 1..2) { "" } else if (txQuality >= 5 || rxQuality >= 5) { "对方的网络已断开" } else { status = if (txQuality >= 4 || rxQuality >= 4) { 0 } else { 1 } "对方的网络状况不佳" } } } showNetStatus(netStatus, status) } } } // override fun createPresenter(): IAudioHomeActivityContract.Presenter { return AudioHomePresenterImpl() } override fun layoutResId(): Int { return R.layout.audioim_activity_audio_home } override fun initDataAndEvent() { //状态栏颜色 setWindowStatusBarColor() //初始化传感器 initSensorManager() //页面传递数据初始化 initIntentData() writeAgoraLog("通话页面打开的时候,RTM登录状态码:${YDLavManager.sdkStatus}") if (YDLavManager.sdkStatus != Constants.CONNECTION_STATE_CONNECTED) { //再次登录声网,确保声网登录状态 reLoginRTM() } //view初始化 initView() //点击事件 setClickEvent() //权限申请 requestPermission() } private fun setWindowStatusBarColor() { StatusBarUtils.setWindowStatusBarColor(this, R.color.audioim_color_40353535) } @SuppressLint("InvalidWakeLockTag") private fun initSensorManager() { sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager? localPowerManager = getSystemService(POWER_SERVICE) as PowerManager? localWakeLock = localPowerManager!!.newWakeLock( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "yidianling" ) // 获取电源管理器对象 val screenOn = localPowerManager!!.isScreenOn if (!screenOn) { // 获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是LogCat里用的Tag val wl = localPowerManager!!.newWakeLock( PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright" ) wl.acquire(10000) // 点亮屏幕 wl.release() // 释放 } } private fun initIntentData() { expertHeadUrl = intent.getStringExtra(IntentConstants.INTENT_EXPERT_HEAD_URL) expertName = intent.getStringExtra(IntentConstants.INTENT_EXPERT_NAME) expertTips = intent.getStringExtra(IntentConstants.INTENT_EXPERT_TIPS) totalDuration = intent.getStringExtra(IntentConstants.INTENT_TOTAL_DURATION).toInt() channelId = intent.getStringExtra(IntentConstants.INTENT_ROOM_ID) remainTime = intent.getStringExtra(IntentConstants.INTENT_REMAIN_TIME) callId = intent.getStringExtra(IntentConstants.INTENT_CALL_ID) relationId = intent.getStringExtra(IntentConstants.INTENT_RELATION_ID) token = intent.getStringExtra(IntentConstants.INTENT_TOKEN) listenerUid = intent.getStringExtra(IntentConstants.INTENT_LISTENER_UID) listenId = intent.getStringExtra(IntentConstants.INTENT_LISTEN_ID) commentUrl = intent.getStringExtra(IntentConstants.INTENT_COMMENT_URL) dialStatus = intent.getStringExtra(IntentConstants.INTENT_DIALSTATUS) writeAgoraLog("专家的通话状态dialStatus:$dialStatus", false) isShowAXB = intent.getBooleanExtra(IntentConstants.INTENT_ISSHOWAXB, true) val logBean = AgoraLogInfoBean( expertHeadUrl, expertName, channelId, remainTime, listenerUid, totalDuration, callId, listenId ) val content = Gson().toJson(logBean) writeAgoraLog("主叫方发送的邀请通话消息内容:$content", true) localRemainTime = remainTime?.toInt() handler = Handler() vibrator = getSystemService(Service.VIBRATOR_SERVICE) as Vibrator? } private fun reLoginRTM() { writeAgoraLog("RMT状态:${YDLavManager.sdkStatus},重新登录RMT") val uid = YdlCommonRouterManager.getYdlCommonRoute().getUid().toString() YDLavManager.instances.login(uid) { _isSuccess, _msg -> // writeAgoraLog("拨打电话界面打开RTM重新登录,uid=${uid}") val result = if (_isSuccess) "拨打电话界面打开RMT重新登录成功,uid=${uid}" else "拨打电话界面打开RMT登录失败:$_msg,uid=${uid}" writeAgoraLog(result) } } private fun initView() { writeAgoraLog("用户拨打电话界面开启") tv_change_route.isEnabled = false //水波纹view初始化 wave_view.setDuration(6000) wave_view.setStyle(Paint.Style.STROKE) wave_view.setSpeed(1000) wave_view.setColor(Color.WHITE) wave_view.setInitialRadius(140f) wave_view.setInterpolator(AccelerateInterpolator(1.2f)) //挂断按钮默认不可点击 iv_hang_up.isEnabled = false //设置默认关闭扬声器 iv_hands_free.isSelected = false tv_name.text = expertName tv_tips.text = expertTips tv_remain_time.text = DateUtils.formatTime(remainTime) if (!TextUtils.isEmpty(expertHeadUrl)) { var option = SimpleImageOpConfiger() option.errorPic = R.drawable.audioim_head_place_hold_pic option.loadingPic = R.drawable.audioim_head_place_hold_pic option.transform = 0 YDLImageCacheManager.showImage(this, expertHeadUrl, iv_head, option) } if (!isShowAXB) { tv_change_route.visibility = View.GONE tv_change_time_counter.visibility = View.GONE } } private fun setClickEvent() { //切换线路 tv_change_route.setOnClickListener { showChooseDialog(1) } //挂断 iv_hang_up.setOnClickListener { if (Utils.isFastClick()) { //防止连击 return@setOnClickListener } if (isConnectSuccess) { writeAgoraLog("已接通:主叫主动挂断") updateExpertStatus(false, 1) leaveChannel() uploadLog() YDLavManager.instances.callEndStatusUpdate(channelId!!, 3, "主叫主动挂断") } else { userCloseCalling() } ActionCountUtils.count( "shengwang_popup_layer_page|shengwang_popup_layer_refuse_click", YdlCommonRouterManager.getYdlCommonRoute().getUid().toString(), uid = YdlCommonRouterManager.getYdlCommonRoute().getUid().toString() ) } //免提 iv_hands_free.setOnClickListener { if (iv_hands_free.isSelected) { iv_hands_free.isSelected = false iv_hands_free.setImageResource(R.drawable.audioim_img_hands_free_unuse) } else { iv_hands_free.isSelected = true iv_hands_free.setImageResource(R.drawable.audioim_img_hands_free) } //已经接通 if (isConnectSuccess) { //是否开启外放 voiceManage!!.getVoiceApi().setEnableSpeakerphone(iv_hands_free.isSelected) } else { //未接通 if (mPlayer != null) { mPlayer!!.switchPlayType(iv_hands_free.isSelected) } } } } /** * 请求权限 */ @SuppressLint("CheckResult") private fun requestPermission() { val rxPermissions = RxPermissions(this) rxPermissions.requestEach(Manifest.permission.RECORD_AUDIO).subscribe { permission -> when { permission.granted -> { writeAgoraLog("请求音频权限通过") init() } permission.shouldShowRequestPermissionRationale -> { requestPermission() } else -> { // 拒绝权限操作发送给服务端 uploadException("AudioNotAuth","zhu",YDLavManager.AUDIO_NO_AUTH_ERROR_CODE,null) writeAgoraLog("拒绝请求音频权限") uploadLog() ToastHelper.show(getString(R.string.audioim_need_storage_permission_hint)) val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) val uri = Uri.fromParts("package", packageName, null) intent.data = uri startActivity(intent) finish() } } } } @SuppressLint("CheckResult", "SetTextI18n") private fun init() { wave_view.start() //初始化声网 initializeAgoraEngine() //发起呼叫 var msgBean = AudioMessageBean( 1, channelId!!, YdlCommonRouterManager.getYdlCommonRoute().getUid().toString(), YdlCommonRouterManager.getYdlCommonRoute().getUserInfo()!!.headUrl, YdlCommonRouterManager.getYdlCommonRoute().getUserInfo()!!.userName, remainTime!!.toInt(), relationId, callId, null, channelId ) sendDoctocrMsg = Gson().toJson(msgBean) writeAgoraLog("主叫(用户)发送通话邀请") rtcCall() //开始50s等待倒计时 waitDisposable = Observable.interval(0, 100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()) .take(600).observeOn(AndroidSchedulers.mainThread()).subscribe({ val remainTime = 50 - it / 10 if (remainTime <= 0) { waittingStatus() } else { tv_change_time_counter.text = "${remainTime}s" } var result = it.toFloat() / 2.5f progress_view.setProgress(result) if (result >= 100f && !iv_hang_up.isEnabled) { //挂断按钮可点击 iv_hang_up.isEnabled = true iv_hang_up.setImageResource(R.drawable.audioim_img_hang_up) } }, { LogUtil.d(it.message) }) { // waittingStatus() if (!isConnectSuccess) { //关闭音乐 stopPlaying() writeAgoraLog("未接通挂断:50s等待倒计时结束挂断") //提示切换传统线路 //发送消息通知专家用户已挂断 YDLavManager.instances.cancelCall( listenerUid!!, channelId!!, sendDoctocrMsg!! ) { msg, code -> writeAgoraLog("未接听时:主叫(用户)主动挂断失败,msg=$msg($code),再次挂断") } //通话结束或挂断时,上传日志文件 uploadLog() showToast("对方手机暂时不在身边,请稍后再试") userCloseCalling() } } //双重保险:加入频道成功,通过服务端发推送给专家 noticeServerPush(true) } fun rtcCall() { YDLavManager.instances.rtcCall(listenerUid, channelId, sendDoctocrMsg) } /** * 声网初始化 */ private fun initializeAgoraEngine() { /** * 创建一个实例 * param appId 应用id * param mRtcEventHandler 事件回调(SDK 通过指定的事件通知应用程序 SDK 的运行事件,如: 加入或离开频道,新用户加入频道等) */ voiceManage = YDLVoiceManager(this, BuildConfig.AGORA_APPID, mRtcEventHandler) voiceManage!!.init() } /** * 声网加入频道 */ fun joinChannel() { writeAgoraLog("对方(专家)接受了通话邀请,主叫(用户)开始加入频道:$channelId") voiceManage!!.getVoiceApi().joinChannel( token!!, channelId!!, "Extra Optional Data", YdlCommonRouterManager.getYdlCommonRoute().getUid() ) } /** * 用户主动挂断(包括:60s专家未接听自动挂断) * 注意:这个方法只在专家还未接听的状态才能调用 * */ private fun userCloseCalling() { writeAgoraLog("未接听时:主叫(用户)主动挂断,取消呼叫") LogUtil.e("未接听挂断") //发送消息通知专家用户已挂断 YDLavManager.instances.cancelCall( listenerUid!!, channelId!!, sendDoctocrMsg!! ) { msg, code -> writeAgoraLog("未接听时:主叫(用户)主动挂断失败,msg=$msg($code),再次挂断") } //通话结束或挂断时,上传日志文件 uploadLog() handler!!.postDelayed({ LogUtil.e("离开频道") leaveChannel() }, 100) } /** * 更新专家状态 *@param isSwitchAxb 是否切换axb * @param finishStatus 状态值: 0:开始 1:结束 */ private fun updateExpertStatus(isSwitchAxb: Boolean, finishStatus: Int) { if (isSwitchAxb) { dialPhone() } if (finishStatus == 0) { callStartTime = System.currentTimeMillis() } // if(finishStatus==1){ // var param = ConnectFinishCommand(listenerUid!!, relationId!!, "0", // remainTime!!.toInt() - localRemainTime!!, callId!!, // "0","0","$callStartTime", // "${System.currentTimeMillis()}",3) // mPresenter.connectFinish(param) // }else{ // 接通开始回调 // callStartTime = System.currentTimeMillis() // var param = ConnectStartCommand(listenerUid!!, relationId!!, callId!!, // "${System.currentTimeMillis()}","3","0","0","0","0") // mPresenter.connectStart(param) // } } /** * 线路选择弹窗 */ private fun showChooseDialog(type: Int) { val dialog = AxbConfirmDialog(mContext, type, object : AxbConfirmDialog.OnClickEnsureListener { override fun onClickEnsure() { writeAgoraLog("主叫点击切换AXB按钮") switchAXB() } override fun onClose() { // 如果声网未连接成功,切换axb的弹框是自动弹出的,当关闭弹框的时候,执行用户挂断操作 // 如果声网连接成功,点击右上角按钮弹出切换axb弹框,但是关闭时用户不执行挂断操作 if (!isConnectSuccess) { userCloseCalling() } } }) dialog.show() } /** * 切换成axb路线请求 */ private fun switchAXB() { if (isConnectSuccess) { uploadException("", "zhu", "108", object : YDLavManager.UploadExceptionCallback { override fun onSuccess() { writeAgoraLog("离开房间成功,主叫切换AXB之后") mPresenter.getAXBPhone(ConnectCommand(listenId!!, "1")) } }) YDLavManager.instances.callEndStatusUpdate(channelId!!, 3, "接通中:主叫主动切换AXB") } else { mPresenter.getAXBPhone(ConnectCommand(listenId!!, "1")) } } /** * 切换axb网络请求结果:axb电话 *w */ override fun switchAXBResponse(axbPhone: String) { this.axbPhone = axbPhone isJumpDail = true if (isConnectSuccess) { //通话结束或挂断时,上传日志文件 uploadLog() //已经连接成功,切换axb时需要更新专家状态 updateExpertStatus(true, 1) } else { //未连接成功,切换axb时:需发送消息通知专家端用户已挂断 //发送消息通知专家用户已挂断 //发送消息通知专家用户已挂断 YDLavManager.instances.cancelCall( listenerUid!!, channelId!!, sendDoctocrMsg!! ) { msg, code -> writeAgoraLog("未接听时:主叫主动挂断失败,msg=$msg($code),再次挂断") } leaveChannel() } } /** * 跳转拨号界面 */ override fun dialPhone() { var phoneIntent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$axbPhone")) startActivity(phoneIntent) finish() } /** * 60s等待完成,专家未接听 */ private fun waittingStatus() { tv_change_time_counter.visibility = View.GONE //挂断按钮可点击 iv_hang_up.isEnabled = true iv_hang_up.setImageResource(R.drawable.audioim_img_hang_up) // if (!isConnectSuccess) { // //页面等待文案调整 // tv_waiting.visibility = View.GONE // tv_change_doctor.text = "对方暂无应答,正在为您切换线路重播" // tv_change_doctor.visibility = View.VISIBLE // } if (!isShowAXB) { tv_change_route.visibility = View.GONE } else { //切换线路按钮可见 tv_change_route.isEnabled = true tv_change_route.text = "切换至传统电话" tv_change_route.setTextColor(ContextCompat.getColor(this, R.color.platform_white)) } } /** * 播放等待音频 */ fun playWaitingMusic() { if (mPlayer == null) { mPlayer = AudioPlayer(this) } if ((Math.random() * 10 + 1).toInt() >= 5) { mPlayer!!.setTwoDataAndStart(R.raw.audioim_audio_music_1, R.raw.audioim_loop_music) } else { mPlayer!!.setTwoDataAndStart(R.raw.audioim_audio_music_2, R.raw.audioim_loop_music) } } /** * 播放结束音频,音频播放结束后,自动关闭页面 */ private fun playFinishMusic() { if (mPlayer == null) { mPlayer = AudioPlayer(this) } mPlayer!!.setDataSource(R.raw.audioim_hand_down_music) mPlayer!!.switchPlayType(iv_hands_free.isSelected) mPlayer!!.setCompletionListener(MediaPlayer.OnCompletionListener { LogUtil.e("播放结束") //通话剩余时间不足60s时,默认 if (localRemainTime!! < 60 && !TextUtils.isEmpty(commentUrl)) { val h5Params = H5Params(commentUrl!!, "评价") NewH5Activity.start(this@AudioHomeActivity, h5Params) } if (isJumpDail) { //跳转拨号界面 dialPhone() } finish() }) mPlayer!!.start(false, true) } /** * 停止播放 */ private fun stopPlaying() { if (mPlayer != null) { mPlayer!!.pause() } } /** * sdk通话链接成功 */ private fun connectSuccess() { isConnectSuccess = true //通知服务端,此次通话已经接通,服务端开始订单 updateExpertStatus(false, 0) //是否开启外放 voiceManage!!.getVoiceApi().setEnableSpeakerphone(false) iv_hands_free.isSelected = false iv_hands_free.setImageResource(R.drawable.audioim_img_hands_free_unuse) // if (waitDisposable != null) { // waitDisposable!!.dispose() // } if (disposable != null) { disposable!!.dispose() } stopPlaying() vibrator!!.vibrate(1000) //等待状态的相关ui隐藏(挂断按钮的倒计时不清除,挂断按钮必须在30s后才能点击) tv_change_doctor.visibility = View.GONE tv_waiting.visibility = View.GONE //显示通话相关ui rl_remain_time.visibility = View.VISIBLE showToast("已接通") tv_tips.visibility = View.GONE //剩余倾诉时长倒计时 totalDisposable = Observable.interval(0, 1, TimeUnit.SECONDS).subscribeOn(Schedulers.computation()) .take(remainTime!!.toLong() + 1).observeOn(AndroidSchedulers.mainThread()) .subscribe({ localRemainTime = remainTime!!.toInt() - it.toInt() if (localRemainTime == 180) { playNoticeMusic(3) } if (localRemainTime == 60) { playNoticeMusic(1) } if (localRemainTime!! <= 60) { if (tv_change_route.isEnabled) { tv_change_route.isEnabled = false tv_change_route.setTextColor( ContextCompat.getColor( this, R.color.audioim_color_50ffffff ) ) tv_change_route.setCompoundDrawablesWithIntrinsicBounds( null, null, ContextCompat.getDrawable( this, R.drawable.audioim_img_choose_arrow_unuse ), null ) } } tv_remain_time.text = DateUtils.formatTime(localRemainTime.toString()) }, { LogUtil.d(it.message) }, { YDLavManager.instances.callEndStatusUpdate(channelId!!, 3, "倾诉时间已用完") //注意:自动挂断时,如果对方离开频道的回调已经触发,就不要再重复调用接口 showToast("通话已结束") //通话结束或挂断时,上传日志文件 uploadLog() updateExpertStatus(false, 1) }) } /** * 剩余3min和1min时提示音 * @param min 1 :还剩1min 3:还剩3min * */ private fun playNoticeMusic(min: Int) { if (mPlayer == null) { mPlayer = AudioPlayer(this) } if (3 == min) { mPlayer!!.setDataSource(R.raw.audioim_last_3_min) } else { mPlayer!!.setDataSource(R.raw.audioim_last_1_min) } mPlayer!!.switchPlayType(iv_hands_free.isSelected) mPlayer!!.start(false, false) } /** * 用户加入频道后 *通知服务端发送推送 * 通知专家加入频道 * 这个功能已经有用信令通知,为了解决专家信令掉线的问题,增加推送逻辑 * * @param isCall true 拨打 false 取消拨打 */ private fun noticeServerPush(isCall: Boolean) { var msgBean = AudioMessageBean( 1, channelId!!, YdlCommonRouterManager.getYdlCommonRoute().getUid().toString(), YdlCommonRouterManager.getYdlCommonRoute().getUserInfo()!!.headUrl, YdlCommonRouterManager.getYdlCommonRoute().getUserInfo()!!.userName, remainTime!!.toInt(), relationId, callId, null, channelId ) var cmd = NoticePushCommand() cmd.data = msgBean cmd.pushId = listenerUid if (isCall) { cmd.status = "CALL" } else { cmd.status = "CALL_OFF" } mPresenter.noticeServerPush(cmd) } /** * 声网离开频道 */ fun leaveChannel() { LogUtil.e("调用leaveChannel方法,isLeavelChannel=$isLeavelChannel") if (!isLeavelChannel) { isLeavelChannel = true //刷新h5页面 EventBus.getDefault().post(RefreshWebEvent(false)) stopPlaying() LogUtil.e("播放结束音频") //播放结束音频 playFinishMusic() } } override fun onResume() { super.onResume() sensorManager!!.registerListener( this, sensorManager!!.getDefaultSensor(Sensor.TYPE_PROXIMITY), SensorManager.SENSOR_DELAY_NORMAL ) ActionCountUtils.count( "shengwang_popup_layer_page|shengwang_popup_layer_page_visit", "", uid = YdlCommonRouterManager.getYdlCommonRoute().getUid().toString() ) } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { } override fun onSensorChanged(event: SensorEvent?) { val values = event!!.values when (event.sensor.type) { Sensor.TYPE_PROXIMITY -> { if (values[0] == 0.0f) { //贴近手机 if (!localWakeLock!!.isHeld) { localWakeLock!!.acquire() } } else { //离开手机 //唤醒设备 if (localWakeLock!!.isHeld) { localWakeLock!!.release() } } } } } /** * 显示自定义弹窗 */ private fun showToast(msg: String?) { runOnUiThread { ToastHelper.show(msg ?: "") } } /** * 网络状态 */ private fun showNetStatus(msg: String, status: Int = 0) { LogUtil.e("showNetStatus: msg=$msg,status=$status") if (tv_nte_status.visibility == View.VISIBLE) { return } if (TextUtils.isEmpty(msg)) { tv_nte_status.visibility = View.GONE } else { tv_nte_status.text = msg if (status == 0) { tv_nte_status.setCompoundDrawablesWithIntrinsicBounds( ContextCompat.getDrawable( this, R.drawable.av_audio_wifi_normal ), null, null, null ) } if (status == 1) { tv_nte_status.setCompoundDrawablesWithIntrinsicBounds( ContextCompat.getDrawable( this, R.drawable.av_audio_wifi_better ), null, null, null ) } if (status == -1) { tv_nte_status.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) } tv_nte_status.visibility = View.VISIBLE tv_nte_status.postDelayed({ tv_nte_status.visibility = View.GONE }, 2000) } } override fun showProgressView() { try { showProgressDialog() } catch (e: Exception) { e.printStackTrace() } } override fun dismissProgressView() { try { dismissProgressDialog() } catch (e: Exception) { e.printStackTrace() } } fun uploadLog() { if (!hasUpLoadLog) { hasUpLoadLog = true LogHelper.getInstance().uploadLog(false) } } fun uploadExceptionStatus(msg: String, status: Int) { callStatus = status uploadException(msg, zhu = "zhu", eventType = "108", callback = null) } /** * 上传错误日志 * zhu 洪平要的,判别是移动端主动调的还是声网返的 */ private fun uploadException( message: String, zhu: String = "", eventType: String = "99", callback: YDLavManager.UploadExceptionCallback? ) { var time: String = (System.currentTimeMillis() / 1000).toString() var uid: String = ModularServiceManager.provide(IUserService::class.java).getUserInfo()?.uid!! var payLoad = PayLoad(channelId ?: "0", time, uid, "1", "999", message) var connectException = ConnectExceptionCommand(time + zhu, "2", eventType, payLoad, callStatus) YDLavManager.instances.uploadException(connectException, callback) } fun writeAgoraLog(content: String, isAppend: Boolean = true) { try { AudioLogUtils.writeAgoraLog("$content-------Time:${AudioLogUtils.format.format(Calendar.getInstance().time)}", "confide.log", isAppend) } catch (e: Exception) { } } override fun finishActivity() { leaveChannel() } override fun onDestroy() { super.onDestroy() uploadLog() ToastUtil.toastShort("拨打电话界面销毁") if (isConnectSuccess) { uploadException("", "zhu", "108", callback = null) } if (handler != null) { handler = null } hasUpLoadLog = false if (waitDisposable != null) { waitDisposable!!.dispose() } if (disposable != null) { disposable!!.dispose() } if (totalDisposable != null) { totalDisposable!!.dispose() } if (sensorManager != null) { sensorManager!!.unregisterListener(this) } //唤醒设备 if (localWakeLock != null && localWakeLock!!.isHeld) { localWakeLock!!.release() } sensorManager = null localWakeLock = null localPowerManager = null if (null != voiceManage && null != voiceManage!!.getVoiceApi()) { voiceManage!!.getVoiceApi().leaveChannel() voiceManage!!.getVoiceApi().destroy() voiceManage = null } if (mPlayer != null) { mPlayer!!.clear() mPlayer = null } } //重写物理返回按钮 override fun onBackPressed() { } }