Kotlin高仿微信-第14篇-单聊-视频通话

news2024/11/28 8:48:01

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点,包括:注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。

Kotlin高仿微信-项目实践58篇,点击查看详情

效果图:

 

实现代码:

/**
 * 视频通话、语音通话
 */
private fun showVideoPopupWindow(){
    var popupView = layoutInflater.inflate(R.layout.wc_chat_video_pop_view , moment_root, false)
    var popupWindow = PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true)
    var popupRoot = popupView.findViewById<LinearLayout>(R.id.chat_video_pop_root)
    popupWindow.showAtLocation(popupRoot, Gravity.BOTTOM, 0, 0)

    var window = requireActivity().window
    //popupWindow在弹窗的时候背景半透明
    val params = window.attributes
    params.alpha = 0.5f
    window.attributes = params
    popupWindow.setOnDismissListener {
        params.alpha = 1.0f
        window.attributes = params
    }

    //视频通话
    popupView.findViewById<AppCompatTextView>(R.id.chat_pop_video_call).setOnClickListener {
        popupWindow.dismiss()
        CallSingleActivity.openActivity( requireActivity(),toUserId, true, toUserName, false, false)
    }

    //语音通话
    popupView.findViewById<AppCompatTextView>(R.id.chat_pop_voice_call).setOnClickListener {
        popupWindow.dismiss()
        CallSingleActivity.openActivity( requireActivity(),toUserId, true, toUserName, true, false)
    }

    //取消
    popupView.findViewById<AppCompatTextView>(R.id.chat_pop_cancel).setOnClickListener {
        popupWindow.dismiss()
    }

}

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/5/15 13:49
 * Description :
 */
class CallSingleActivity : AppCompatActivity(), CallSession.CallSessionCallback {


    companion object {
        val EXTRA_TARGET = "targetId"
        val EXTRA_MO = "isOutGoing"
        val EXTRA_AUDIO_ONLY = "audioOnly"
        val EXTRA_USER_NAME = "userName"
        val EXTRA_FROM_FLOATING_VIEW = "fromFloatingView"

        fun openActivity(
            context: Context, targetId: String, isOutgoing: Boolean, inviteUserName: String,
            isAudioOnly: Boolean, isClearTop: Boolean
        ) {
            val intent = getCallIntent(context, targetId, isOutgoing, inviteUserName, isAudioOnly, isClearTop)
            if (context is Activity) {
                context.startActivity(intent)
            } else {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                context.startActivity(intent)
            }
        }

        fun getCallIntent(
            context: Context, targetId: String, isOutgoing: Boolean, inviteUserName: String,
            isAudioOnly: Boolean, isClearTop: Boolean
        ): Intent {
            val voip = Intent(context, CallSingleActivity::class.java)
            voip.putExtra(EXTRA_MO, isOutgoing)
            voip.putExtra(EXTRA_TARGET, targetId)
            voip.putExtra(EXTRA_USER_NAME, inviteUserName)
            voip.putExtra(EXTRA_AUDIO_ONLY, isAudioOnly)
            voip.putExtra(EXTRA_FROM_FLOATING_VIEW, false)
            if (isClearTop) {
                voip.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            }
            return voip
        }

    }

    private val TAG = "CallSingleActivity"

    private val handler = Handler(Looper.getMainLooper())
    private var isOutgoing = false
    private var targetId: String = ""
    private var inviteUserName: String = ""
    var isAudioOnly = false
    private var isFromFloatingView = false

    private var gEngineKit: SkyEngineKit? = null

    private var currentFragment: SingleCallFragment? = null
    private var room: String = ""


    private var activity:CallSingleActivity? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStatusBarOrScreenStatus(this)
        setContentView(R.layout.activity_single_call)
        activity = this


        try {
            gEngineKit = SkyEngineKit.Instance()
        } catch (e: NotInitializedException) {
            SkyEngineKit.init(VoipEvent()) //重新初始化
            try {
                gEngineKit = SkyEngineKit.Instance()
            } catch (ex: NotInitializedException) {
                finish()
            }
        }
        val intent = intent
        targetId = intent.getStringExtra(EXTRA_TARGET)!!
        inviteUserName = intent.getStringExtra(EXTRA_USER_NAME)!!
        isFromFloatingView = intent.getBooleanExtra(EXTRA_FROM_FLOATING_VIEW, false)
        isOutgoing = intent.getBooleanExtra(EXTRA_MO, false)
        isAudioOnly = intent.getBooleanExtra(EXTRA_AUDIO_ONLY, false)
        if (isFromFloatingView) {
            val serviceIntent = Intent(this, FloatingVoipService::class.java)
            stopService(serviceIntent)
            init(targetId, false, isAudioOnly, false)
        } else {
            // 权限检测
            val per: Array<String>
            per = if (isAudioOnly) {
                arrayOf(Manifest.permission.RECORD_AUDIO)
            } else {
                arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
            }
            Permissions.request(this, per, object : Consumer<Int> {
                override fun accept(t: Int) {
                    if (t === 0) {
                        // 权限同意
                        init(targetId, isOutgoing, isAudioOnly, false)
                    } else {
                        Toast.makeText(activity, "权限被拒绝", Toast.LENGTH_SHORT).show()
                        // 权限拒绝
                        finish()
                    }
                }
            })
        }
    }

    override fun onBackPressed() {
    }

    private fun init(targetId: String, outgoing: Boolean, audioOnly: Boolean, isReplace: Boolean) {
        val fragment: SingleCallFragment
        if (audioOnly) {
            fragment = FragmentAudio()
        } else {
            fragment = FragmentVideo()
        }
        val fragmentManager: FragmentManager = getSupportFragmentManager()
        currentFragment = fragment
        if (isReplace) {
            fragmentManager.beginTransaction()
                .replace(android.R.id.content, fragment)
                .commit()
        } else {
            fragmentManager.beginTransaction()
                .add(android.R.id.content, fragment)
                .commit()
        }
        if (outgoing && !isReplace) {
            // 创建会话
            room = UUID.randomUUID().toString() + System.currentTimeMillis()
            val b: Boolean = gEngineKit?.startOutCall(applicationContext, room, targetId, audioOnly)!!
            TagUtils.d("创建房间返回:${room} , ${targetId} , ${b}")
            if (!b) {
                finish()
                return
            }
            WcApp.getInstance().roomId = room
            WcApp.getInstance().otherUserId = targetId
            val session: CallSession? = gEngineKit?.getCurrentSession()
            if (session == null) {
                finish()
            } else {
                session.setSessionCallback(this)
            }
        } else {
            val session: CallSession? = gEngineKit?.getCurrentSession()
            if (session == null) {
                finish()
            } else {
                if (session.isAudioOnly() && !audioOnly) { //这种情况是,对方切换成音频的时候,activity还没启动,这里启动后需要切换一下
                    isAudioOnly = session.isAudioOnly()
                    fragment.didChangeMode(true)
                }
                session.setSessionCallback(this)
            }
        }
    }

    fun getEngineKit(): SkyEngineKit? {
        return gEngineKit
    }

    fun isOutgoing(): Boolean {
        return isOutgoing
    }

    fun getInviteUserName(): String {
        return inviteUserName
    }

    fun getToUserId(): String {
        return targetId
    }

    fun isFromFloatingView(): Boolean {
        return isFromFloatingView
    }

    // 显示小窗
    fun showFloatingView() {
        if (!checkOverlayPermission()) {
            return
        }
        val intent = Intent(this, FloatingVoipService::class.java)
        intent.putExtra(EXTRA_TARGET, targetId)
        intent.putExtra(EXTRA_USER_NAME, inviteUserName)
        intent.putExtra(EXTRA_AUDIO_ONLY, isAudioOnly)
        intent.putExtra(EXTRA_MO, isOutgoing)
        startService(intent)
        finish()
    }

    // 切换到语音通话
    fun switchAudio() {
        init(targetId, isOutgoing, true, true)
    }

    fun getRoomId(): String {
        return room
    }

    // ======================================界面回调================================
    override fun didCallEndWithReason(reason: EnumType.CallEndReason) {
        WcApp.getInstance().otherUserId = "0"
        //交给fragment去finish
//        finish();
        handler.post { currentFragment?.didCallEndWithReason(reason) }
    }

    override fun didChangeState(callState: EnumType.CallState) {
        if (callState === EnumType.CallState.Connected) {
            isOutgoing = false
        }
        handler.post { currentFragment?.didChangeState(callState) }
    }

    override fun didChangeMode(var1: Boolean) {
        handler.post { currentFragment?.didChangeMode(var1) }
    }

    override fun didCreateLocalVideoTrack() {
        handler.post { currentFragment?.didCreateLocalVideoTrack() }
    }

    override fun didReceiveRemoteVideoTrack(userId: String) {
        handler.post { currentFragment?.didReceiveRemoteVideoTrack(userId) }
    }

    override fun didUserLeave(userId: String) {
        handler.post { currentFragment?.didUserLeave(userId) }
    }

    override fun didError(var1: String) {
        handler.post { currentFragment?.didError(var1) }
//        finish();
    }

    override fun didDisconnected(userId: String) {
        handler.post { currentFragment?.didDisconnected(userId) }
    }


    // ========================================================================================

    // ========================================================================================
    private fun checkOverlayPermission(): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            SettingsCompat.setDrawOverlays(this, true)
            if (!SettingsCompat.canDrawOverlays(this)) {
                Toast.makeText(this, "需要悬浮窗权限", Toast.LENGTH_LONG).show()
                SettingsCompat.manageDrawOverlays(this)
                return false
            }
        }
        return true
    }


    @TargetApi(19)
    private fun getSystemUiVisibility(): Int {
        var flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            flags = flags or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
        }
        return flags
    }

    /**
     * 设置状态栏透明
     */
    @TargetApi(19)
    fun setStatusBarOrScreenStatus(activity: Activity) {
        val window = activity.window
        //全屏+锁屏+常亮显示
        window.addFlags(
            WindowManager.LayoutParams.FLAG_FULLSCREEN or
                    WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
                    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
                    WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
        )
        window.decorView.systemUiVisibility = getSystemUiVisibility()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val layoutParams = getWindow().attributes
            layoutParams.layoutInDisplayCutoutMode =
                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
            window.attributes = layoutParams
        }
        // 5.0以上系统状态栏透明
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //清除透明状态栏
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
            //设置状态栏颜色必须添加
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
            window.statusBarColor = Color.TRANSPARENT //设置透明
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //19
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
    }
}

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/5/15 16:58
 * Description : 视频通话控制界面
 */
class FragmentVideo : SingleCallFragment(), View.OnClickListener {

    //private val TAG = "FragmentVideo"
    private var outgoingAudioOnlyImageView: ImageView? = null
    private var audioLayout: LinearLayout? = null
    private var incomingAudioOnlyImageView: ImageView? = null
    private var hangupLinearLayout: LinearLayout? = null
    private var acceptLinearLayout: LinearLayout? = null
    private var connectedAudioOnlyImageView: ImageView? = null
    private var connectedHangupImageView: ImageView? = null
    private var switchCameraImageView: ImageView? = null
    private var fullscreenRenderer: FrameLayout? = null
    private var pipRenderer: FrameLayout? = null
    private var inviteeInfoContainer: LinearLayout? = null
    private var isFromFloatingView = false
    private var localSurfaceView: SurfaceViewRenderer? = null
    private var remoteSurfaceView: SurfaceViewRenderer? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (callSingleActivity != null) {
            isFromFloatingView = callSingleActivity!!.isFromFloatingView()
        }
    }

    override fun getLayout(): Int {
        return R.layout.fragment_video
    }

    override fun initView(view: View) {
        super.initView(view)
        fullscreenRenderer = view.findViewById(R.id.fullscreen_video_view)
        pipRenderer = view.findViewById(R.id.pip_video_view)
        inviteeInfoContainer = view.findViewById(R.id.inviteeInfoContainer)
        outgoingAudioOnlyImageView = view.findViewById(R.id.outgoingAudioOnlyImageView)
        audioLayout = view.findViewById(R.id.audioLayout)
        incomingAudioOnlyImageView = view.findViewById(R.id.incomingAudioOnlyImageView)
        hangupLinearLayout = view.findViewById(R.id.hangupLinearLayout)
        acceptLinearLayout = view.findViewById(R.id.acceptLinearLayout)
        connectedAudioOnlyImageView = view.findViewById(R.id.connectedAudioOnlyImageView)
        connectedHangupImageView = view.findViewById(R.id.connectedHangupImageView)
        switchCameraImageView = view.findViewById(R.id.switchCameraImageView)
        outgoingHangupImageView?.setOnClickListener(this)
        incomingHangupImageView?.setOnClickListener(this)
        minimizeImageView?.setOnClickListener(this)
        connectedHangupImageView?.setOnClickListener(this)
        acceptImageView?.setOnClickListener(this)
        switchCameraImageView?.setOnClickListener(this)
        pipRenderer?.setOnClickListener(this)
        outgoingAudioOnlyImageView?.setOnClickListener(this)
        incomingAudioOnlyImageView?.setOnClickListener(this)
        connectedAudioOnlyImageView?.setOnClickListener(this)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M || OSUtils.isMiui() || OSUtils.isFlyme()) {
            lytParent!!.post {
                val params = inviteeInfoContainer?.getLayoutParams() as RelativeLayout.LayoutParams
                params.topMargin = (BarUtils.getStatusBarHeight() * 1.2).toInt()
                inviteeInfoContainer?.setLayoutParams(params)
                val params1 = minimizeImageView?.layoutParams as RelativeLayout.LayoutParams
                params1.topMargin = BarUtils.getStatusBarHeight()
                minimizeImageView?.layoutParams = params1
            }
            pipRenderer?.post(Runnable {
                val params2 = pipRenderer?.getLayoutParams() as FrameLayout.LayoutParams
                params2.topMargin = (BarUtils.getStatusBarHeight() * 1.2).toInt()
                pipRenderer?.setLayoutParams(params2)
            })
        }
    }


    override fun init() {
        super.init()
        val session = gEngineKit!!.getCurrentSession()
        if (session != null) {
            currentState = session.getState()
        }
        if (session == null || EnumType.CallState.Idle === session.getState()) {
            if (callSingleActivity != null) {
                callSingleActivity!!.finish()
            }
        } else if (EnumType.CallState.Connected === session.getState()) {
            incomingActionContainer!!.visibility = View.GONE
            outgoingActionContainer!!.visibility = View.GONE
            connectedActionContainer!!.visibility = View.VISIBLE
            inviteeInfoContainer!!.visibility = View.GONE
            minimizeImageView!!.visibility = View.VISIBLE
            startRefreshTime()
        } else {
            if (isOutgoing) {
                incomingActionContainer!!.visibility = View.GONE
                outgoingActionContainer!!.visibility = View.VISIBLE
                connectedActionContainer!!.visibility = View.GONE
                descTextView?.setText(R.string.av_waiting)
            } else {
                incomingActionContainer!!.visibility = View.VISIBLE
                outgoingActionContainer!!.visibility = View.GONE
                connectedActionContainer!!.visibility = View.GONE
                descTextView?.setText(R.string.av_video_invite)
                if (currentState === EnumType.CallState.Incoming) {
                    val surfaceView = gEngineKit!!.getCurrentSession()!!.setupLocalVideo(false)
                    TagUtils.d("init surfaceView != null is " + (surfaceView != null) + "; isOutgoing = " + isOutgoing + "; currentState = " + currentState)
                    if (surfaceView != null) {
                        localSurfaceView = surfaceView as SurfaceViewRenderer?
                        localSurfaceView!!.setZOrderMediaOverlay(false)
                        fullscreenRenderer!!.addView(localSurfaceView)
                    }
                }
            }
        }
        if (isFromFloatingView) {
            didCreateLocalVideoTrack()
            if (session != null) {
                didReceiveRemoteVideoTrack(session.mTargetId)
            }
        }
    }

    override fun didChangeState(state: EnumType.CallState) {
        currentState = state
        TagUtils.d( "didChangeState, state = $state")
        runOnUiThread {
            if (state === EnumType.CallState.Connected) {
                handler!!.removeMessages(WHAT_DELAY_END_CALL)
                incomingActionContainer!!.visibility = View.GONE
                outgoingActionContainer!!.visibility = View.GONE
                connectedActionContainer!!.visibility = View.VISIBLE
                inviteeInfoContainer!!.visibility = View.GONE
                descTextView!!.visibility = View.GONE
                minimizeImageView!!.visibility = View.VISIBLE
                // 开启计时器
                startRefreshTime()
            } else {
                // do nothing now
            }
        }
    }

    override fun didChangeMode(isAudio: Boolean?) {
        runOnUiThread { callSingleActivity!!.switchAudio() }
    }


    override fun didCreateLocalVideoTrack() {
        if (localSurfaceView == null) {
            val surfaceView = gEngineKit!!.getCurrentSession()!!.setupLocalVideo(true)
            localSurfaceView = if (surfaceView != null) {
                surfaceView as SurfaceViewRenderer?
            } else {
                if (callSingleActivity != null) callSingleActivity!!.finish()
                return
            }
        } else {
            localSurfaceView!!.setZOrderMediaOverlay(true)
        }
        TagUtils.d("didCreateLocalVideoTrack localSurfaceView != null is " + (localSurfaceView != null) + "; remoteSurfaceView == null = " + (remoteSurfaceView == null))
        if (localSurfaceView!!.parent != null) {
            (localSurfaceView!!.parent as ViewGroup).removeView(localSurfaceView)
        }
        if (isOutgoing && remoteSurfaceView == null) {
            if (fullscreenRenderer != null && fullscreenRenderer!!.childCount != 0) fullscreenRenderer!!.removeAllViews()
            fullscreenRenderer!!.addView(localSurfaceView)
        } else {
            if (pipRenderer!!.childCount != 0) pipRenderer!!.removeAllViews()
            pipRenderer!!.addView(localSurfaceView)
        }
    }


    override fun didReceiveRemoteVideoTrack(userId: String?) {
        pipRenderer!!.visibility = View.VISIBLE
        if (localSurfaceView != null) {
            localSurfaceView!!.setZOrderMediaOverlay(true)
            if (isOutgoing) {
                if (localSurfaceView!!.parent != null) {
                    (localSurfaceView!!.parent as ViewGroup).removeView(localSurfaceView)
                }
                pipRenderer!!.addView(localSurfaceView)
            }
        }
        val surfaceView = gEngineKit!!.getCurrentSession()!!.setupRemoteVideo(userId!!, false)
        TagUtils.d( "didReceiveRemoteVideoTrack,surfaceView = $surfaceView")
        if (surfaceView != null) {
            fullscreenRenderer!!.visibility = View.VISIBLE
            remoteSurfaceView = surfaceView as SurfaceViewRenderer?
            fullscreenRenderer!!.removeAllViews()
            if (remoteSurfaceView!!.parent != null) {
                (remoteSurfaceView!!.parent as ViewGroup).removeView(remoteSurfaceView)
            }
            fullscreenRenderer!!.addView(remoteSurfaceView)
        }
    }

    override fun didUserLeave(userId: String?) {}

    override fun didError(error: String?) {}


    override fun onClick(v: View) {
        val id = v.id
        // 接听
        val session = gEngineKit!!.getCurrentSession()
        if (id == R.id.acceptImageView) {
            TagUtils.d("接听 ")
            if (session != null && session.getState() === EnumType.CallState.Incoming) {
                TagUtils.d("接听 if  = >  ${session.getRoomId()}")
                session.joinHome(session.getRoomId()!!)
            } else if (session != null) {
                TagUtils.d("接听 else callSingleActivity =  ${callSingleActivity}")
                if (callSingleActivity != null) {
                    session.sendRefuse()
                    callSingleActivity!!.finish()
                }
            }
        }
        // 挂断电话
        if (id == R.id.incomingHangupImageView || id == R.id.outgoingHangupImageView || id == R.id.connectedHangupImageView) {
            if (session != null) {
                TagUtils.d("FragmentVideo 挂电话:endCall ")
                SkyEngineKit.Instance()?.endCall()
            }
            if (callSingleActivity != null) callSingleActivity?.finish()
        }

        // 切换摄像头
        if (id == R.id.switchCameraImageView) {
            session!!.switchCamera()
        }
        if (id == R.id.pip_video_view) {
            val isFullScreenRemote = fullscreenRenderer!!.getChildAt(0) === remoteSurfaceView
            fullscreenRenderer!!.removeAllViews()
            pipRenderer!!.removeAllViews()
            if (isFullScreenRemote) {
                remoteSurfaceView!!.setZOrderMediaOverlay(true)
                pipRenderer!!.addView(remoteSurfaceView)
                localSurfaceView!!.setZOrderMediaOverlay(false)
                fullscreenRenderer!!.addView(localSurfaceView)
            } else {
                localSurfaceView!!.setZOrderMediaOverlay(true)
                pipRenderer!!.addView(localSurfaceView)
                remoteSurfaceView!!.setZOrderMediaOverlay(false)
                fullscreenRenderer!!.addView(remoteSurfaceView)
            }
        }

        // 切换到语音拨打
        if (id == R.id.outgoingAudioOnlyImageView || id == R.id.incomingAudioOnlyImageView || id == R.id.connectedAudioOnlyImageView) {
            if (session != null) {
                if (callSingleActivity != null) callSingleActivity!!.isAudioOnly = true
                session.switchToAudio()
            }
        }

        // 小窗
        if (id == R.id.minimizeImageView) {
            if (callSingleActivity != null) callSingleActivity!!.showFloatingView()
        }
    }


    override fun onDestroyView() {
        super.onDestroyView()
        fullscreenRenderer!!.removeAllViews()
        pipRenderer!!.removeAllViews()
    }
}

/**
 * Author : wangning
 * Email : maoning20080809@163.com
 * Date : 2022/5/15 16:14
 * Description :
 */
abstract class SingleCallFragment : Fragment() {

    private val TAG = "SingleCallFragment"
    var minimizeImageView: ImageView? = null
    // 用户头像
    var portraitImageView : ImageView? = null
    // 用户昵称
    var nameTextView : TextView? = null
    // 状态提示用语
    var descTextView : TextView? = null
    // 通话时长
    var durationTextView : Chronometer? = null

    var outgoingHangupImageView: ImageView? = null
    var incomingHangupImageView: ImageView? = null
    var acceptImageView: ImageView? = null
    var tvStatus: TextView? = null
    var outgoingActionContainer: View? = null
    var incomingActionContainer: View? = null
    var connectedActionContainer: View? = null

    var lytParent: View? = null

    var isOutgoing = false
    var inviteUserName = ""
    var toUserId = ""

    var gEngineKit: SkyEngineKit? = null

    var handler: CallHandler? = null

    companion object {
        var callSingleActivity: CallSingleActivity? = null

        val WHAT_DELAY_END_CALL = 0x01

        val WHAT_NO_NET_WORK_END_CALL = 0x02

        var currentState: EnumType.CallState? = null
        var headsetPlugReceiver: HeadsetPlugReceiver? = null

        var endWithNoAnswerFlag = false
        var isConnectionClosed = false
    }



    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this)
        }
        handler = CallHandler()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(getLayout(), container, false)
        initView(view)
        init()
        return view
    }

    override fun onDestroyView() {
        if (durationTextView != null) durationTextView!!.stop()
        refreshMessage(true)
        super.onDestroyView()
    }

    override fun onDestroy() {
        if (EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this)
        }
        super.onDestroy()
    }


    abstract fun getLayout(): Int


    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onEvent(messageEvent: MsgEvent<Any?>) {
        val code: Int = messageEvent.getCode()
        Log.d(TAG, "onEvent code = \$code; endWithNoAnswerFlag = \$endWithNoAnswerFlag")
        if (code == MsgEvent.CODE_ON_CALL_ENDED) {
            if (endWithNoAnswerFlag) {
                didCallEndWithReason(EnumType.CallEndReason.Timeout)
            } else if (isConnectionClosed) {
                didCallEndWithReason(EnumType.CallEndReason.SignalError)
            } else {
                if (callSingleActivity != null) {
                    callSingleActivity!!.finish()
                }
            }
        } else if (code == MsgEvent.CODE_ON_REMOTE_RING) {
            descTextView!!.text = "对方已响铃"
        }
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        callSingleActivity = activity as CallSingleActivity?
        TagUtils.d("SingleCallFragment  callSingleActivity 的对象: ${callSingleActivity}")
        if (callSingleActivity != null) {

            callSingleActivity?.let {
                isOutgoing = it.isOutgoing()
                gEngineKit = it.getEngineKit()
                inviteUserName = it.getInviteUserName()
                toUserId = it.getToUserId()
            }
            headsetPlugReceiver = HeadsetPlugReceiver()
            val filter = IntentFilter()
            filter.addAction(Intent.ACTION_HEADSET_PLUG)
            callSingleActivity!!.registerReceiver(headsetPlugReceiver, filter)
        }
    }

    override fun onDetach() {
        super.onDetach()
        callSingleActivity!!.unregisterReceiver(headsetPlugReceiver) //注销监听
        callSingleActivity = null
    }


    open fun initView(view: View) {
        lytParent = view.findViewById(R.id.lytParent)
        minimizeImageView = view.findViewById(R.id.minimizeImageView)
        portraitImageView = view.findViewById(R.id.portraitImageView)
        nameTextView = view.findViewById(R.id.nameTextView)
        descTextView = view.findViewById(R.id.descTextView)
        durationTextView = view.findViewById(R.id.durationTextView)
        outgoingHangupImageView = view.findViewById(R.id.outgoingHangupImageView)
        incomingHangupImageView = view.findViewById(R.id.incomingHangupImageView)
        acceptImageView = view.findViewById(R.id.acceptImageView)
        tvStatus = view.findViewById(R.id.tvStatus)
        outgoingActionContainer = view.findViewById(R.id.outgoingActionContainer)
        incomingActionContainer = view.findViewById(R.id.incomingActionContainer)
        connectedActionContainer = view.findViewById(R.id.connectedActionContainer)
        durationTextView?.setVisibility(View.GONE)
        nameTextView?.setText(inviteUserName)
        //portraitImageView.setImageResource(R.mipmap.icon_default_header);
        BaseUtils.showAvatar(toUserId, portraitImageView!!)
        TagUtils.d("邀请名称:" + inviteUserName +" , " + toUserId)
        if (isOutgoing) {
            handler!!.sendEmptyMessageDelayed(
                WHAT_DELAY_END_CALL,
                (60 * 1000).toLong()
            ) //1分钟之后未接通,则挂断电话
        }
    }

    open fun init() {}

    // ======================================界面回调================================
    fun didCallEndWithReason(callEndReason: EnumType.CallEndReason) {
        when (callEndReason) {
            EnumType.CallEndReason.Busy -> {
                tvStatus!!.text = "对方忙线中"
            }
            EnumType.CallEndReason.SignalError -> {
                tvStatus!!.text = "连接断开"
            }
            EnumType.CallEndReason.RemoteSignalError -> {
                tvStatus!!.text = "对方网络断开"
            }
            EnumType.CallEndReason.Hangup -> {
                tvStatus!!.text = "挂断"
            }
            EnumType.CallEndReason.MediaError -> {
                tvStatus!!.text = "媒体错误"
            }
            EnumType.CallEndReason.RemoteHangup -> {
                tvStatus!!.text = "对方挂断"
            }
            EnumType.CallEndReason.OpenCameraFailure -> {
                tvStatus!!.text = "打开摄像头错误"
            }
            EnumType.CallEndReason.Timeout -> {
                tvStatus!!.text = "对方未接听"
            }
            EnumType.CallEndReason.AcceptByOtherClient -> {
                tvStatus!!.text = "在其它设备接听"
            }
        }
        incomingActionContainer!!.visibility = View.GONE
        outgoingActionContainer!!.visibility = View.GONE
        if (connectedActionContainer != null) connectedActionContainer!!.visibility = View.GONE
        refreshMessage(false)
        Handler(Looper.getMainLooper()).postDelayed({
            if (callSingleActivity != null) {
                callSingleActivity!!.finish()
            }
        }, 1500)
    }

    open fun didChangeState(state: EnumType.CallState) {}

    open fun didChangeMode(isAudio: Boolean?) {}

    open fun didCreateLocalVideoTrack() {}

    open fun didReceiveRemoteVideoTrack(userId: String?) {}

    open fun didUserLeave(userId: String?) {}

    open fun didError(error: String?) {}

    fun didDisconnected(error: String?) {
        handler!!.sendEmptyMessage(WHAT_NO_NET_WORK_END_CALL)
    }

    private fun refreshMessage(isForCallTime: Boolean) {
        if (callSingleActivity == null) {
            return
        }
        // 刷新消息; demo中没有消息,不用处理这儿快逻辑
    }

    fun startRefreshTime() {
        val session = SkyEngineKit.Instance()!!.getCurrentSession() ?: return
        if (durationTextView != null) {
            durationTextView!!.visibility = View.VISIBLE
            durationTextView!!.base =
                SystemClock.elapsedRealtime() - (System.currentTimeMillis() - session.getStartTime())
            durationTextView!!.start()
        }
    }

    fun runOnUiThread(runnable: Runnable?) {
        if (callSingleActivity != null) {
            callSingleActivity!!.runOnUiThread(runnable)
        }
    }

    class CallHandler : Handler() {
        override fun handleMessage(msg: Message) {
            if (msg.what == WHAT_DELAY_END_CALL) {
                if (currentState !== EnumType.CallState.Connected) {
                    endWithNoAnswerFlag = true
                    if (callSingleActivity != null) {
                        TagUtils.d("SingleCallFragment 挂电话:endCall 555 ")
                        SkyEngineKit.Instance()?.endCall()
                    }
                }
            } else if (msg.what == WHAT_NO_NET_WORK_END_CALL) {
                isConnectionClosed = true
                if (callSingleActivity != null) {
                    TagUtils.d("SingleCallFragment 挂电话:endCall 666 ")
                    SkyEngineKit.Instance()?.endCall()
                }
            }
        }
    }


    class HeadsetPlugReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.hasExtra("state")) {
                val session = SkyEngineKit.Instance()!!.getCurrentSession() ?: return
                if (intent.getIntExtra("state", 0) == 0) { //拔出耳机
                    session.toggleHeadset(false)
                } else if (intent.getIntExtra("state", 0) == 1) { //插入耳机
                    session.toggleHeadset(true)
                }
            }
        }
    }

}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49559.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringMVC(八):SSM整合

文章目录 SSM整合 一、准备数据库表格 二、创建maven web项目并补充项目结构&#xff0c;准备好MVC模式下的主要目录 三、更新web.xml 文件和准备包结构 四、导入依赖 五、log4j2.xml 六、jdbc.properties 七、springMVC.xml配置文件 八、applicationContext.xml 九、…

Anaconda默认安装在C:\Users\xxx\.conda\envs中

目录 问题&#xff1a; 解决&#xff1a; 更改默认安装位置 移动已安装环境 问题&#xff1a; 解决&#xff1a; 更改默认安装位置 用记事本打开 C:\Users\zqk\.condarc 在最后插入 envs_dirs: - D://anzhuang//Anaconda3//envs 如若需更改pkgs&#xff0c;插入如下代…

如何使用OpenCV作图像或矩阵的逻辑运算

所谓逻辑运算&#xff0c;主要是指逻辑与运算、逻辑或运算、逻辑非运算、逻辑异或运算。 可用函数bitwise_and()实现图像或矩阵的逻辑与运算&#xff1b; 可用函数bitwise_or()实现图像或矩阵的逻辑或运算&#xff1b; 可用函数bitwise_not()实现图像或矩阵的逻辑非运算&#x…

oh-my-zsh 为 ls 命令自定义颜色

ls 命令默认显示的颜色是&#xff1a; 白色&#xff1a; 表示普通文件 蓝色&#xff1a; 表示目录 绿色&#xff1a; 表示可执行文件 红色&#xff1a; 表示压缩文件 蓝绿色&#xff1a; 链接文件 红色闪烁&#xff1a;表示链接的文件有问题 黄色&#xff1a; 表示设备文件 灰…

Java实现3DES加密解密(DESede/ECB/PKCS5Padding使用)

一、简介 3DES&#xff08;又叫Triple DES&#xff09;是三重数据加密算法&#xff08;TDEA&#xff0c;Triple Data Encryption Algorithm&#xff09;块密码的通称。 它相当于是对每个数据块应用三次DES加密算法。密钥长度是128位&#xff0c;192位(bit)&#xff0c;如果密…

如何最简洁的使用iOS 开发证书 和 Profile 文件

如果你想在 iOS 设备&#xff08;iPhone/iPad/iTouch&#xff09;上调试&#xff0c; 需要有 iOS 开发证书和 Profile 文件。 在你拿到这两个文件之后&#xff0c;该如何使用呢&#xff1f; 证书使用说明&#xff1a; 1. iOS 开发证书&#xff1a;开发证书 &#xff08;Devel…

ROS——构建一个模拟的两轮ROS机器人

构建一个模拟的两轮ROS机器人1. 确保已经安装了Rviz2. 生成并构建ROS功能包2.1 生成一个catkin工作空间2.2 漏了一个懒得改2.3 构建差分驱动的机器人的URDF2.3.1 创建机器人底座2.3.2 使用roslaunch2.3.3 添加轮子2.3.4 添加颜色2.3.5 添加小脚轮2.3.6 添加碰撞属性2.3.7 移动轮…

CHS寻址

盘片&#xff08;platter&#xff09; 、磁头&#xff08;head&#xff09;、 磁道&#xff08;track&#xff09;、 扇区&#xff08;sector&#xff09;、 柱面&#xff08;cylinder&#xff09;。 CHS&#xff1a;通过柱面&#xff08;cylinder&#xff09;、磁头&#xff0…

成都易佰特的坑——E103-W06

写这篇博客&#xff0c;是为了记录&#xff0c;为了防止技术同行踩坑。 因为什么原因&#xff0c;就不说了。反正就是买了这个E103-W06模块来进行测试wifi透传的性能. 结果&#xff0c;好家伙&#xff0c;买回来就不能用。TMD虚拟串口都识别不了。还他妈卖的挺贵。95元~99元一…

【附源码】计算机毕业设计JAVA装修网站

【附源码】计算机毕业设计JAVA装修网站 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA mybatis M…

2分能出线,6分却不能出线?世界杯小组赛的出线规则这次真被我整明白了

写在前面 这个月世界杯正是进行得火热&#xff0c;我这样的十几年的老球迷自然是场场不落&#xff0c;周边不少不看球的朋友甚至连我那一场比赛上多少人都弄不明白的老婆也能说上几句&#xff1a;“听说梅西输了&#xff1f;”&#xff0c;“听说德国队要回家了”&#xff0c;“…

mybatis实战:一、mybatis入门

出自《MyBatis从入门到精通》刘增辉&#xff0c;精简 1.pom.xml 1.设置源码编码方式为 UTF -8 2.设置编译源代码的 JDK 版本 3.添加mybatis依赖 4.还需要添加会用到的 Log4j JUnit ySql 驱动的依赖。 <?xml version"1.0" encoding"UTF-8"?> <pr…

基于BM1684X 架构实现 Faiss 的两个查询接口

文章目录Faiss 简介距离度量在 Sophon TPU 上的接口实现Sophon TPUindexflat 实现indexPQ 实现 Faiss 简介 Faiss 库是 Facebook 开发的一个用于稠密向量相似性搜索和聚类的库&#xff0c;该库包含有诸多向量相似性搜索的算法。向量相似性搜索是将一个向量与底库中的向量集合…

[附源码]计算机毕业设计springboot教务管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

2022科大讯飞A.I.开发者大赛 柑橘花果梢识别挑战赛冠军方案

2022科大讯飞A.I.开发者大赛 柑橘花果梢识别挑战赛冠军方案PPT

游戏道具平台|基于Springboot+Vue实现游戏道具平台系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

Vagrant 搭建虚拟机环境

用 Vagrant 和 VirtualBox 来快速搭建一个开发环境是非常方便的。简单整理一下 Vagrant 搭建 VirtualBox 虚拟机的记录。 首先安装 Vagrant 和 VirtualBox&#xff0c;这一步就直接省略了。自行安装即可。 什么是 Vagrant&#xff0c;我们这里引用 OSChina 上的一段话来进行解释…

浏览器IndexedDB模块损坏及解决办法

浏览器IndexedDB模块损坏及解决办法 表现 提示错误码&#xff1a; UnknownError Internal error opening backing store for indexedDB.open. indexedDB无法展开。 复现方式 进入以下路径 Mac C:\用户\xxx\AppData\Local\Google\Chrome\User Data\Default\IndexedDB Windo…

吴恩达2022机器学习——第二部分高级学习算法第三周笔记

目录1.1决定下一步做什么&#xff08;构建机器学习的实用建议&#xff09;1.2模型评估1.3模型选择&交叉验证测试集的训练方法模型选择总结举例2.1通过偏差与方法进行诊断2.2正则化、偏差、方差1.1决定下一步做什么&#xff08;构建机器学习的实用建议&#xff09; 从一个例…

C++运算符重载【加号、左移、递增、指针、赋值、中括号、关系、函数调用】,统统载了

学习目标 在c对象的学习中&#xff0c;我们会使用到运算符重载&#xff0c;接下来大家一起学习一下吧&#xff01; 学习内容 运算符重载&#xff1a; operator overloading运算符重载是一种形式的C多态 即对已有运算符进行重新定义&#xff0c;赋予新的功能&#xff0c;使其…