mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 02:15:46 +03:00
VoIP: continue refactoring
This commit is contained in:
parent
7620aa4264
commit
1a9b0265dc
14 changed files with 219 additions and 249 deletions
|
@ -153,7 +153,7 @@ interface VectorComponent {
|
|||
|
||||
fun pinLocker(): PinLocker
|
||||
|
||||
fun webRtcPeerConnectionManager(): WebRtcCallManager
|
||||
fun webRtcCallManager(): WebRtcCallManager
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -63,7 +63,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationUtils = vectorComponent().notificationUtils()
|
||||
callManager = vectorComponent().webRtcPeerConnectionManager()
|
||||
callManager = vectorComponent().webRtcCallManager()
|
||||
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
|
||||
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
|
||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
|
||||
|
|
|
@ -23,7 +23,7 @@ import im.vector.app.core.utils.DebouncedClickListener
|
|||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import im.vector.app.features.call.utils.EglUtils
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import org.webrtc.RendererCommon
|
||||
import org.webrtc.SurfaceViewRenderer
|
||||
|
||||
|
@ -32,26 +32,28 @@ class ActiveCallViewHolder {
|
|||
private var activeCallPiP: SurfaceViewRenderer? = null
|
||||
private var activeCallView: ActiveCallView? = null
|
||||
private var pipWrapper: CardView? = null
|
||||
private var activeCall: WebRtcCall? = null
|
||||
|
||||
private var activeCallPipInitialized = false
|
||||
|
||||
fun updateCall(activeCall: MxCall?, callManager: WebRtcCallManager) {
|
||||
val hasActiveCall = activeCall?.state is CallState.Connected
|
||||
fun updateCall(activeCall: WebRtcCall?) {
|
||||
this.activeCall = activeCall
|
||||
val hasActiveCall = activeCall?.mxCall?.state is CallState.Connected
|
||||
if (hasActiveCall) {
|
||||
val isVideoCall = activeCall?.isVideoCall == true
|
||||
val isVideoCall = activeCall?.mxCall?.isVideoCall == true
|
||||
if (isVideoCall) initIfNeeded()
|
||||
activeCallView?.isVisible = !isVideoCall
|
||||
pipWrapper?.isVisible = isVideoCall
|
||||
activeCallPiP?.isVisible = isVideoCall
|
||||
activeCallPiP?.let {
|
||||
callManager.attachViewRenderers(null, it, null)
|
||||
activeCall?.attachViewRenderers(null, it, null)
|
||||
}
|
||||
} else {
|
||||
activeCallView?.isVisible = false
|
||||
activeCallPiP?.isVisible = false
|
||||
pipWrapper?.isVisible = false
|
||||
activeCallPiP?.let {
|
||||
callManager.detachRenderers(listOf(it))
|
||||
activeCall?.detachRenderers(listOf(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +84,9 @@ class ActiveCallViewHolder {
|
|||
)
|
||||
}
|
||||
|
||||
fun unBind(callManager: WebRtcCallManager) {
|
||||
fun unBind() {
|
||||
activeCallPiP?.let {
|
||||
callManager.detachRenderers(listOf(it))
|
||||
activeCall?.detachRenderers(listOf(it))
|
||||
}
|
||||
if (activeCallPipInitialized) {
|
||||
activeCallPiP?.release()
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.call
|
|||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import javax.inject.Inject
|
||||
|
@ -26,27 +27,27 @@ class SharedActiveCallViewModel @Inject constructor(
|
|||
private val callManager: WebRtcCallManager
|
||||
) : ViewModel() {
|
||||
|
||||
val activeCall: MutableLiveData<MxCall?> = MutableLiveData()
|
||||
val activeCall: MutableLiveData<WebRtcCall?> = MutableLiveData()
|
||||
|
||||
val callStateListener = object : MxCall.StateListener {
|
||||
val callStateListener = object : WebRtcCall.Listener {
|
||||
|
||||
override fun onStateUpdate(call: MxCall) {
|
||||
if (activeCall.value?.callId == call.callId) {
|
||||
activeCall.postValue(call)
|
||||
activeCall.postValue(callManager.getCallById(call.callId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val listener = object : WebRtcCallManager.CurrentCallListener {
|
||||
override fun onCurrentCallChange(call: MxCall?) {
|
||||
activeCall.value?.removeListener(callStateListener)
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
activeCall.value?.mxCall?.removeListener(callStateListener)
|
||||
activeCall.postValue(call)
|
||||
call?.addListener(callStateListener)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
activeCall.postValue(callManager.currentCall?.mxCall)
|
||||
activeCall.postValue(callManager.currentCall)
|
||||
callManager.addCurrentCallListener(listener)
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
|
|||
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.features.call.utils.EglUtils
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||
|
@ -52,8 +54,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
|||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.activity_call.*
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import im.vector.app.features.call.utils.EglUtils
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import org.matrix.android.sdk.api.session.call.MxCallDetail
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
|
@ -211,7 +211,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
callManager.detachRenderers(listOf(pipRenderer, fullscreenRenderer))
|
||||
callManager.getCallById(callArgs.callId)?.detachRenderers(listOf(pipRenderer, fullscreenRenderer))
|
||||
if (surfaceRenderersAreInitialized) {
|
||||
pipRenderer.release()
|
||||
fullscreenRenderer.release()
|
||||
|
@ -234,7 +234,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
callConnectingProgress.isVisible = false
|
||||
when (callState) {
|
||||
is CallState.Idle,
|
||||
is CallState.Dialing -> {
|
||||
is CallState.Dialing -> {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
callStatusText.setText(R.string.call_ring)
|
||||
|
@ -248,14 +248,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
configureCallInfo(state)
|
||||
}
|
||||
|
||||
is CallState.Answering -> {
|
||||
is CallState.Answering -> {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
callStatusText.setText(R.string.call_connecting)
|
||||
callConnectingProgress.isVisible = true
|
||||
configureCallInfo(state)
|
||||
}
|
||||
is CallState.Connected -> {
|
||||
is CallState.Connected -> {
|
||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
if (callArgs.isVideoCall) {
|
||||
callVideoGroup.isVisible = true
|
||||
|
@ -276,12 +276,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
callConnectingProgress.isVisible = true
|
||||
}
|
||||
// ensure all attached?
|
||||
callManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
|
||||
callManager.getCallById(callArgs.callId)?.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
|
||||
}
|
||||
is CallState.Terminated -> {
|
||||
is CallState.Terminated -> {
|
||||
finish()
|
||||
}
|
||||
null -> {
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -326,7 +326,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
pipRenderer.setEnableHardwareScaler(true /* enabled */)
|
||||
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
|
||||
|
||||
callManager.attachViewRenderers(pipRenderer, fullscreenRenderer,
|
||||
callManager.getCallById(callArgs.callId)?.attachViewRenderers(pipRenderer, fullscreenRenderer,
|
||||
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() })
|
||||
|
||||
pipRenderer.setOnClickListener {
|
||||
|
@ -338,14 +338,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
private fun handleViewEvents(event: VectorCallViewEvents?) {
|
||||
Timber.v("## VOIP handleViewEvents $event")
|
||||
when (event) {
|
||||
VectorCallViewEvents.DismissNoCall -> {
|
||||
VectorCallViewEvents.DismissNoCall -> {
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
}
|
||||
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||
onErrorTimoutConnect(event.turn)
|
||||
}
|
||||
null -> {
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
||||
override fun onCurrentCallChange(call: MxCall?) {
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
// we need to check the state
|
||||
if (call == null) {
|
||||
// we should dismiss, e.g handled by other session?
|
||||
|
@ -155,9 +155,9 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized,
|
||||
soundDevice = currentSoundDevice,
|
||||
availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
|
||||
isFrontCamera = callManager.currentCameraType() == CameraType.FRONT,
|
||||
canSwitchCamera = callManager.canSwitchCamera(),
|
||||
isHD = webRtcCall.mxCall.isVideoCall && callManager.currentCaptureFormat() is CaptureFormat.HD
|
||||
isFrontCamera = call?.currentCameraType() == CameraType.FRONT,
|
||||
canSwitchCamera = call?.canSwitchCamera() ?: false,
|
||||
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,17 +27,22 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
|
|||
|
||||
companion object {
|
||||
const val EXTRA_CALL_ACTION_KEY = "EXTRA_CALL_ACTION_KEY"
|
||||
const val EXTRA_CALL_ID = "EXTRA_CALL_ID"
|
||||
const val CALL_ACTION_REJECT = 0
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
val peerConnectionManager = (context.applicationContext as? HasVectorInjector)
|
||||
val webRtcCallManager = (context.applicationContext as? HasVectorInjector)
|
||||
?.injector()
|
||||
?.webRtcPeerConnectionManager()
|
||||
?.webRtcCallManager()
|
||||
?: return
|
||||
|
||||
|
||||
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
|
||||
CALL_ACTION_REJECT -> onCallRejectClicked(peerConnectionManager)
|
||||
CALL_ACTION_REJECT -> {
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
|
||||
onCallRejectClicked(webRtcCallManager, callId)
|
||||
}
|
||||
}
|
||||
|
||||
// Not sure why this should be needed
|
||||
|
@ -48,9 +53,9 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
|
|||
// context.stopService(Intent(context, CallHeadsUpService::class.java))
|
||||
}
|
||||
|
||||
private fun onCallRejectClicked(callManager: WebRtcCallManager) {
|
||||
private fun onCallRejectClicked(callManager: WebRtcCallManager, callId: String) {
|
||||
Timber.d("onCallRejectClicked")
|
||||
callManager.endCall()
|
||||
callManager.getCallById(callId)?.endCall()
|
||||
}
|
||||
|
||||
// private fun onCallAnswerClicked(context: Context) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.call.webrtc
|
|||
import im.vector.app.features.call.CallAudioManager
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||
import org.webrtc.DataChannel
|
||||
import org.webrtc.IceCandidate
|
||||
import org.webrtc.MediaStream
|
||||
|
@ -132,9 +133,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall,
|
|||
* It is, however, possible that the ICE agent did find compatible connections for some components.
|
||||
*/
|
||||
PeerConnection.IceConnectionState.FAILED -> {
|
||||
// I should not hangup here..
|
||||
// because new candidates could arrive
|
||||
// webRtcCall.mxCall.hangUp()
|
||||
webRtcCall.endCall(true, CallHangupContent.Reason.ICE_FAILED)
|
||||
}
|
||||
/**
|
||||
* The ICE agent has finished gathering candidates, has checked all pairs against one another, and has found a connection for all components.
|
||||
|
|
|
@ -42,6 +42,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
|
@ -88,9 +89,9 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
private val dispatcher: CoroutineContext,
|
||||
private val sessionProvider: Provider<Session?>,
|
||||
private val peerConnectionFactoryProvider: Provider<PeerConnectionFactory?>,
|
||||
private val onCallEnded: (WebRtcCall) -> Unit): MxCall.StateListener {
|
||||
private val onCallEnded: (WebRtcCall) -> Unit) : MxCall.StateListener {
|
||||
|
||||
interface Listener: MxCall.StateListener {
|
||||
interface Listener : MxCall.StateListener {
|
||||
fun onCaptureStateChanged() {}
|
||||
fun onCameraChange() {}
|
||||
}
|
||||
|
@ -245,7 +246,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
isVideo = mxCall.isVideoCall,
|
||||
roomName = name,
|
||||
roomId = mxCall.roomId,
|
||||
matrixId = session?.myUserId ?:"",
|
||||
matrixId = session?.myUserId ?: "",
|
||||
callId = mxCall.callId)
|
||||
}
|
||||
|
||||
|
@ -461,8 +462,13 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
?: backCamera?.also { cameraInUse = backCamera }
|
||||
?: null.also { cameraInUse = null }
|
||||
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChange() }
|
||||
}
|
||||
|
||||
if (camera != null) {
|
||||
val videoCapturer = cameraIterator.createCapturer(camera.name, object : CameraEventsHandlerAdapter() {
|
||||
|
||||
override fun onFirstFrameAvailable() {
|
||||
super.onFirstFrameAvailable()
|
||||
videoCapturerIsInError = false
|
||||
|
@ -470,12 +476,25 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
|
||||
override fun onCameraClosed() {
|
||||
super.onCameraClosed()
|
||||
Timber.v("onCameraClosed")
|
||||
// This could happen if you open the camera app in chat
|
||||
// We then register in order to restart capture as soon as the camera is available again
|
||||
videoCapturerIsInError = true
|
||||
val cameraManager = context.getSystemService<CameraManager>()
|
||||
cameraAvailabilityCallback = object : CameraManager.AvailabilityCallback() {
|
||||
|
||||
override fun onCameraUnavailable(cameraId: String) {
|
||||
super.onCameraUnavailable(cameraId)
|
||||
Timber.v("On camera unavailable: $cameraId")
|
||||
}
|
||||
|
||||
override fun onCameraAccessPrioritiesChanged() {
|
||||
super.onCameraAccessPrioritiesChanged()
|
||||
Timber.v("onCameraAccessPrioritiesChanged")
|
||||
}
|
||||
|
||||
override fun onCameraAvailable(cameraId: String) {
|
||||
Timber.v("On camera available: $cameraId")
|
||||
if (cameraId == camera.name) {
|
||||
videoCapturer?.startCapture(currentCaptureFormat.width, currentCaptureFormat.height, currentCaptureFormat.fps)
|
||||
cameraManager?.unregisterAvailabilityCallback(this)
|
||||
|
@ -505,11 +524,9 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
|
||||
fun setCaptureFormat(format: CaptureFormat) {
|
||||
GlobalScope.launch(dispatcher) {
|
||||
Timber.v("## VOIP setCaptureFormat $format")
|
||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||
currentCaptureFormat = format
|
||||
}
|
||||
Timber.v("## VOIP setCaptureFormat $format")
|
||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||
currentCaptureFormat = format
|
||||
}
|
||||
|
||||
private fun updateMuteStatus() {
|
||||
|
@ -565,31 +582,41 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
|
||||
fun canSwitchCamera(): Boolean {
|
||||
return availableCamera.size > 0
|
||||
return availableCamera.size > 1
|
||||
}
|
||||
|
||||
private fun getOppositeCameraIfAny(): CameraProxy? {
|
||||
val currentCamera = cameraInUse ?: return null
|
||||
return if (currentCamera.type == CameraType.FRONT) {
|
||||
availableCamera.firstOrNull { it.type == CameraType.BACK }
|
||||
} else {
|
||||
availableCamera.firstOrNull { it.type == CameraType.FRONT }
|
||||
}
|
||||
}
|
||||
|
||||
fun switchCamera() {
|
||||
Timber.v("## VOIP switchCamera")
|
||||
if (!canSwitchCamera()) return
|
||||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
||||
videoCapturer?.switchCamera(object : CameraVideoCapturer.CameraSwitchHandler {
|
||||
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
||||
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
||||
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
||||
cameraInUse = availableCamera.first { if (isFrontCamera) it.type == CameraType.FRONT else it.type == CameraType.BACK }
|
||||
localSurfaceRenderers.forEach {
|
||||
it.get()?.setMirror(isFrontCamera)
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChange() }
|
||||
}
|
||||
val oppositeCamera = getOppositeCameraIfAny() ?: return
|
||||
videoCapturer?.switchCamera(
|
||||
object : CameraVideoCapturer.CameraSwitchHandler {
|
||||
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
||||
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
||||
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
||||
cameraInUse = oppositeCamera
|
||||
localSurfaceRenderers.forEach {
|
||||
it.get()?.setMirror(isFrontCamera)
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChange() }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCameraSwitchError(errorDescription: String?) {
|
||||
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
|
||||
}
|
||||
})
|
||||
override fun onCameraSwitchError(errorDescription: String?) {
|
||||
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
|
||||
}
|
||||
}, oppositeCamera.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -665,6 +692,9 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
|
||||
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
||||
if(mxCall.state == CallState.Terminated){
|
||||
return
|
||||
}
|
||||
mxCall.state = CallState.Terminated
|
||||
//Close tracks ASAP
|
||||
localVideoTrack?.setEnabled(false)
|
||||
|
@ -704,6 +734,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
try {
|
||||
peerConnection?.awaitSetRemoteDescription(sdp)
|
||||
} catch (failure: Throwable) {
|
||||
endCall(true, CallHangupContent.Reason.UNKWOWN_ERROR)
|
||||
return@launch
|
||||
}
|
||||
if (mxCall.opponentPartyId?.hasValue().orFalse()) {
|
||||
|
|
|
@ -31,7 +31,6 @@ import im.vector.app.features.call.VectorCallActivity
|
|||
import im.vector.app.features.call.utils.EglUtils
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.call.CallListener
|
||||
|
@ -67,7 +66,7 @@ class WebRtcCallManager @Inject constructor(
|
|||
get() = activeSessionDataSource.currentValue?.orNull()
|
||||
|
||||
interface CurrentCallListener {
|
||||
fun onCurrentCallChange(call: MxCall?)
|
||||
fun onCurrentCallChange(call: WebRtcCall?)
|
||||
fun onCaptureStateChanged() {}
|
||||
fun onAudioDevicesChange() {}
|
||||
fun onCameraChange() {}
|
||||
|
@ -118,31 +117,32 @@ class WebRtcCallManager @Inject constructor(
|
|||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryOrNull { it.onCurrentCallChange(value?.mxCall) }
|
||||
tryOrNull { it.onCurrentCallChange(value) }
|
||||
}
|
||||
}
|
||||
|
||||
private val callsByCallId = HashMap<String, WebRtcCall>()
|
||||
private val callsByRoomId = HashMap<String, ArrayList<WebRtcCall>>()
|
||||
|
||||
fun getCallById(callId: String): WebRtcCall? {
|
||||
return callsByCallId[callId]
|
||||
}
|
||||
|
||||
fun headSetButtonTapped() {
|
||||
Timber.v("## VOIP headSetButtonTapped")
|
||||
val call = currentCall?.mxCall ?: return
|
||||
if (call.state is CallState.LocalRinging) {
|
||||
// accept call
|
||||
acceptIncomingCall()
|
||||
}
|
||||
if (call.state is CallState.Connected) {
|
||||
// end call?
|
||||
endCall()
|
||||
}
|
||||
fun getCallsByRoomId(roomId: String): List<WebRtcCall> {
|
||||
return callsByRoomId[roomId] ?: emptyList()
|
||||
}
|
||||
|
||||
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
|
||||
currentCall?.attachViewRenderers(localViewRenderer, remoteViewRenderer, mode)
|
||||
fun headSetButtonTapped() {
|
||||
Timber.v("## VOIP headSetButtonTapped")
|
||||
val call = currentCall ?: return
|
||||
if (call.mxCall.state is CallState.LocalRinging) {
|
||||
// accept call
|
||||
call.acceptIncomingCall()
|
||||
}
|
||||
if (call.mxCall.state is CallState.Connected) {
|
||||
// end call?
|
||||
call.endCall()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPeerConnectionFactoryIfNeeded() {
|
||||
|
@ -174,20 +174,13 @@ class WebRtcCallManager @Inject constructor(
|
|||
.createPeerConnectionFactory()
|
||||
}
|
||||
|
||||
fun acceptIncomingCall() {
|
||||
currentCall?.acceptIncomingCall()
|
||||
}
|
||||
|
||||
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
|
||||
currentCall?.detachRenderers(renderers)
|
||||
}
|
||||
|
||||
private fun onCallEnded(call: WebRtcCall) {
|
||||
Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: ${call.mxCall.callId}")
|
||||
CallService.onNoActiveCall(context)
|
||||
callAudioManager.stop()
|
||||
currentCall = null
|
||||
callsByCallId.remove(call.mxCall.callId)
|
||||
callsByRoomId[call.mxCall.roomId]?.remove(call)
|
||||
// This must be done in this thread
|
||||
executor.execute {
|
||||
if (currentCall == null) {
|
||||
|
@ -231,9 +224,48 @@ class WebRtcCallManager @Inject constructor(
|
|||
currentCall?.onCallIceCandidateReceived(iceCandidatesContent)
|
||||
}
|
||||
|
||||
private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
|
||||
val webRtcCall = WebRtcCall(
|
||||
mxCall = mxCall,
|
||||
callAudioManager = callAudioManager,
|
||||
rootEglBase = rootEglBase,
|
||||
context = context,
|
||||
dispatcher = dispatcher,
|
||||
peerConnectionFactoryProvider = {
|
||||
createPeerConnectionFactoryIfNeeded()
|
||||
peerConnectionFactory
|
||||
},
|
||||
sessionProvider = { currentSession },
|
||||
onCallEnded = this::onCallEnded
|
||||
)
|
||||
currentCall = webRtcCall
|
||||
callsByCallId[mxCall.callId] = webRtcCall
|
||||
callsByRoomId.getOrPut(mxCall.roomId, { ArrayList() }).add(webRtcCall)
|
||||
return webRtcCall
|
||||
}
|
||||
|
||||
fun acceptIncomingCall() {
|
||||
currentCall?.acceptIncomingCall()
|
||||
}
|
||||
|
||||
fun endCall(originatedByMe: Boolean = true) {
|
||||
currentCall?.endCall(originatedByMe)
|
||||
}
|
||||
|
||||
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWiredDeviceEvent $event")
|
||||
currentCall ?: return
|
||||
// sometimes we received un-wanted unplugged...
|
||||
callAudioManager.wiredStateChange(event)
|
||||
}
|
||||
|
||||
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWirelessDeviceEvent $event")
|
||||
callAudioManager.bluetoothStateChange(event.plugged)
|
||||
}
|
||||
|
||||
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
|
||||
Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
|
||||
// to simplify we only treat one call at a time, and ignore others
|
||||
if (currentCall != null) {
|
||||
Timber.w("## VOIP receiving incoming call while already in call?")
|
||||
// Just ignore, maybe we could answer from other session?
|
||||
|
@ -267,74 +299,11 @@ class WebRtcCallManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
|
||||
val webRtcCall = WebRtcCall(
|
||||
mxCall = mxCall,
|
||||
callAudioManager = callAudioManager,
|
||||
rootEglBase = rootEglBase,
|
||||
context = context,
|
||||
dispatcher = dispatcher,
|
||||
peerConnectionFactoryProvider = {
|
||||
createPeerConnectionFactoryIfNeeded()
|
||||
peerConnectionFactory
|
||||
},
|
||||
sessionProvider = { currentSession },
|
||||
onCallEnded = this::onCallEnded
|
||||
)
|
||||
currentCall = webRtcCall
|
||||
callsByCallId[mxCall.callId] = webRtcCall
|
||||
return webRtcCall
|
||||
}
|
||||
|
||||
fun muteCall(muted: Boolean) {
|
||||
currentCall?.muteCall(muted)
|
||||
}
|
||||
|
||||
fun enableVideo(enabled: Boolean) {
|
||||
currentCall?.enableVideo(enabled)
|
||||
}
|
||||
|
||||
fun switchCamera() {
|
||||
currentCall?.switchCamera()
|
||||
}
|
||||
|
||||
fun canSwitchCamera(): Boolean {
|
||||
return currentCall?.canSwitchCamera() ?: false
|
||||
}
|
||||
|
||||
fun currentCameraType(): CameraType? {
|
||||
return currentCall?.currentCameraType()
|
||||
}
|
||||
|
||||
fun setCaptureFormat(format: CaptureFormat) {
|
||||
currentCall?.setCaptureFormat(format)
|
||||
}
|
||||
|
||||
fun currentCaptureFormat(): CaptureFormat {
|
||||
return currentCall?.currentCaptureFormat() ?: CaptureFormat.HD
|
||||
}
|
||||
|
||||
fun endCall(originatedByMe: Boolean = true) {
|
||||
currentCall?.endCall(originatedByMe)
|
||||
}
|
||||
|
||||
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWiredDeviceEvent $event")
|
||||
currentCall ?: return
|
||||
// sometimes we received un-wanted unplugged...
|
||||
callAudioManager.wiredStateChange(event)
|
||||
}
|
||||
|
||||
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWirelessDeviceEvent $event")
|
||||
callAudioManager.bluetoothStateChange(event.plugged)
|
||||
}
|
||||
|
||||
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
|
||||
val call = currentCall ?: return
|
||||
if (call.mxCall.callId != callAnswerContent.callId) return Unit.also {
|
||||
Timber.w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
|
||||
}
|
||||
val call = callsByCallId[callAnswerContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
|
||||
}
|
||||
val mxCall = call.mxCall
|
||||
// Update service state
|
||||
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
|
||||
|
@ -351,48 +320,49 @@ class WebRtcCallManager @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onCallHangupReceived(callHangupContent: CallHangupContent) {
|
||||
val call = currentCall ?: return
|
||||
// Remote echos are filtered, so it's only remote hangups that i will get here
|
||||
if (call.mxCall.callId != callHangupContent.callId) return Unit.also {
|
||||
Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
|
||||
}
|
||||
endCall(false)
|
||||
val call = callsByCallId[callHangupContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
|
||||
}
|
||||
call.endCall(false)
|
||||
}
|
||||
|
||||
override fun onCallRejectReceived(callRejectContent: CallRejectContent) {
|
||||
val call = currentCall ?: return
|
||||
// Remote echos are filtered, so it's only remote hangups that i will get here
|
||||
if (call.mxCall.callId != callRejectContent.callId) return Unit.also {
|
||||
Timber.w("onCallRejected for non active call? ${callRejectContent.callId}")
|
||||
}
|
||||
endCall(false)
|
||||
val call = callsByCallId[callRejectContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallRejectReceived for non active call? ${callRejectContent.callId}")
|
||||
}
|
||||
call.endCall(false)
|
||||
}
|
||||
|
||||
override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) {
|
||||
val call = currentCall ?: return
|
||||
if (call.mxCall.callId != callSelectAnswerContent.callId) return Unit.also {
|
||||
Timber.w("onCallSelectAnswerReceived for non active call? ${callSelectAnswerContent.callId}")
|
||||
}
|
||||
val call = callsByCallId[callSelectAnswerContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallSelectAnswerReceived for non active call? ${callSelectAnswerContent.callId}")
|
||||
}
|
||||
val selectedPartyId = callSelectAnswerContent.selectedPartyId
|
||||
if (selectedPartyId != call.mxCall.ourPartyId) {
|
||||
Timber.i("Got select_answer for party ID ${selectedPartyId}: we are party ID ${call.mxCall.ourPartyId}.");
|
||||
// The other party has picked somebody else's answer
|
||||
endCall(false)
|
||||
call.endCall(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) {
|
||||
val call = currentCall ?: return
|
||||
if (call.mxCall.callId != callNegotiateContent.callId) return Unit.also {
|
||||
Timber.w("onCallNegotiateReceived for non active call? ${callNegotiateContent.callId}")
|
||||
}
|
||||
val call = callsByCallId[callNegotiateContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallNegotiateReceived for non active call? ${callNegotiateContent.callId}")
|
||||
}
|
||||
call.onCallNegotiateReceived(callNegotiateContent)
|
||||
}
|
||||
|
||||
override fun onCallManagedByOtherSession(callId: String) {
|
||||
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
|
||||
currentCall = null
|
||||
callsByCallId.remove(callId)
|
||||
val webRtcCall = callsByCallId.remove(callId)
|
||||
if (webRtcCall != null) {
|
||||
callsByRoomId[webRtcCall.mxCall.roomId]?.remove(webRtcCall)
|
||||
}
|
||||
CallService.onNoActiveCall(context)
|
||||
|
||||
// did we start background sync? so we should stop it
|
||||
|
@ -405,24 +375,4 @@ class WebRtcCallManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether we are 'on hold' to the remote party (ie. if true,
|
||||
* they cannot hear us). Note that this will return true when we put the
|
||||
* remote on hold too due to the way hold is implemented (since we don't
|
||||
* wish to play hold music when we put a call on hold, we use 'inactive'
|
||||
* rather than 'sendonly')
|
||||
* @returns true if the other party has put us on hold
|
||||
*/
|
||||
fun isLocalOnHold(): Boolean {
|
||||
return currentCall?.isLocalOnHold().orFalse()
|
||||
}
|
||||
|
||||
fun isRemoteOnHold(): Boolean {
|
||||
return currentCall?.remoteOnHold.orFalse()
|
||||
}
|
||||
|
||||
fun setRemoteOnHold(onHold: Boolean) {
|
||||
currentCall?.updateRemoteOnHold(onHold)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ class HomeDetailFragment @Inject constructor(
|
|||
sharedCallActionViewModel
|
||||
.activeCall
|
||||
.observe(viewLifecycleOwner, Observer {
|
||||
activeCallViewHolder.updateCall(it, callManager)
|
||||
activeCallViewHolder.updateCall(it)
|
||||
invalidateOptionsMenu()
|
||||
})
|
||||
}
|
||||
|
@ -331,10 +331,10 @@ class HomeDetailFragment @Inject constructor(
|
|||
VectorCallActivity.newIntent(
|
||||
context = requireContext(),
|
||||
callId = call.callId,
|
||||
roomId = call.roomId,
|
||||
otherUserId = call.opponentUserId,
|
||||
isIncomingCall = !call.isOutgoing,
|
||||
isVideoCall = call.isVideoCall,
|
||||
roomId = call.mxCall.roomId,
|
||||
otherUserId = call.mxCall.opponentUserId,
|
||||
isIncomingCall = !call.mxCall.isOutgoing,
|
||||
isVideoCall = call.mxCall.isVideoCall,
|
||||
mode = null
|
||||
).let {
|
||||
startActivity(it)
|
||||
|
|
|
@ -315,7 +315,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
sharedCallActionViewModel
|
||||
.activeCall
|
||||
.observe(viewLifecycleOwner, Observer {
|
||||
activeCallViewHolder.updateCall(it, callManager)
|
||||
activeCallViewHolder.updateCall(it)
|
||||
invalidateOptionsMenu()
|
||||
})
|
||||
|
||||
|
@ -514,7 +514,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
activeCallViewHolder.unBind(callManager)
|
||||
activeCallViewHolder.unBind()
|
||||
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
@ -712,7 +712,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
val activeCall = sharedCallActionViewModel.activeCall.value
|
||||
if (activeCall != null) {
|
||||
// resume existing if same room, if not prompt to kill and then restart new call?
|
||||
if (activeCall.roomId == roomDetailArgs.roomId) {
|
||||
if (activeCall.mxCall.roomId == roomDetailArgs.roomId) {
|
||||
onTapToReturnToCall()
|
||||
}
|
||||
// else {
|
||||
|
@ -1961,10 +1961,10 @@ class RoomDetailFragment @Inject constructor(
|
|||
VectorCallActivity.newIntent(
|
||||
context = requireContext(),
|
||||
callId = call.callId,
|
||||
roomId = call.roomId,
|
||||
otherUserId = call.opponentUserId,
|
||||
isIncomingCall = !call.isOutgoing,
|
||||
isVideoCall = call.isVideoCall,
|
||||
roomId = call.mxCall.roomId,
|
||||
otherUserId = call.mxCall.opponentUserId,
|
||||
isIncomingCall = !call.mxCall.isOutgoing,
|
||||
isVideoCall = call.mxCall.isVideoCall,
|
||||
mode = null
|
||||
).let {
|
||||
startActivity(it)
|
||||
|
|
|
@ -565,8 +565,8 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||
R.id.open_matrix_apps -> true
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> true // always show for discoverability
|
||||
R.id.hangup_call -> callManager.currentCall != null
|
||||
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
|
||||
R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty()
|
||||
R.id.search -> true
|
||||
else -> false
|
||||
}
|
||||
|
|
|
@ -296,7 +296,6 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
builder.priority = NotificationCompat.PRIORITY_HIGH
|
||||
|
||||
//
|
||||
val requestId = Random.nextInt(1000)
|
||||
// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val contentIntent = VectorCallActivity.newIntent(
|
||||
|
@ -326,16 +325,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
)
|
||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
|
||||
}
|
||||
// val answerCallPendingIntent = PendingIntent.getBroadcast(context, requestId, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestId + 1,
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
|
||||
|
||||
builder.addAction(
|
||||
NotificationCompat.Action(
|
||||
|
@ -375,8 +365,6 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
.setLights(accentColor, 500, 500)
|
||||
.setOngoing(true)
|
||||
|
||||
val requestId = Random.nextInt(1000)
|
||||
|
||||
val contentIntent = VectorCallActivity.newIntent(
|
||||
context = context,
|
||||
callId = callId,
|
||||
|
@ -390,16 +378,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
}
|
||||
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
|
||||
|
||||
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
|
||||
}
|
||||
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
requestId + 1,
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
|
||||
|
||||
builder.addAction(
|
||||
NotificationCompat.Action(
|
||||
|
@ -446,17 +425,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
builder.setOngoing(true)
|
||||
}
|
||||
|
||||
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
|
||||
data = Uri.parse("mxcall://end?$callId")
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
|
||||
}
|
||||
|
||||
val rejectCallPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
System.currentTimeMillis().toInt(),
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
|
||||
|
||||
builder.addAction(
|
||||
NotificationCompat.Action(
|
||||
|
@ -476,6 +445,19 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
private fun buildRejectCallPendingIntent(callId: String): PendingIntent {
|
||||
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ID, callId)
|
||||
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
|
||||
}
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
System.currentTimeMillis().toInt(),
|
||||
rejectCallActionReceiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue