diff --git a/vector/src/main/java/im/vector/riotx/features/call/CallControlsView.kt b/vector/src/main/java/im/vector/riotx/features/call/CallControlsView.kt index ee7a9a4797..5e53398320 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/CallControlsView.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/CallControlsView.kt @@ -19,6 +19,7 @@ package im.vector.riotx.features.call import android.content.Context import android.util.AttributeSet import android.view.ViewGroup +import android.widget.ImageView import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible @@ -44,6 +45,13 @@ class CallControlsView @JvmOverloads constructor( @BindView(R.id.connectedControls) lateinit var connectedControls: ViewGroup + + @BindView(R.id.iv_mute_toggle) + lateinit var muteIcon: ImageView + + @BindView(R.id.iv_video_toggle) + lateinit var videoToggleIcon: ImageView + init { ConstraintLayout.inflate(context, R.layout.fragment_call_controls, this) // layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -65,11 +73,21 @@ class CallControlsView @JvmOverloads constructor( interactionListener?.didEndCall() } - @OnClick(R.id.iv_end_call) - fun hangupCall() { + @OnClick(R.id.iv_mute_toggle) + fun toggleMute() { + interactionListener?.didTapToggleMute() } - fun updateForState(callState: CallState?) { + @OnClick(R.id.iv_video_toggle) + fun toggleVideo() { + interactionListener?.didTapToggleVideo() + } + + fun updateForState(state: VectorCallViewState) { + val callState = state.callState.invoke() + muteIcon.setImageResource(if (state.isAudioMuted) R.drawable.ic_microphone_off else R.drawable.ic_microphone_on) + videoToggleIcon.setImageResource(if (state.isVideoEnabled) R.drawable.ic_video else R.drawable.ic_video_off) + when (callState) { CallState.DIALING -> { } @@ -94,11 +112,15 @@ class CallControlsView @JvmOverloads constructor( connectedControls.isVisible = false } } + + } interface InteractionListener { fun didAcceptIncomingCall() fun didDeclineIncomingCall() fun didEndCall() + fun didTapToggleMute() + fun didTapToggleVideo() } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt index 4ba9af0c0e..05528536f3 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt @@ -182,7 +182,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis private fun renderState(state: VectorCallViewState) { Timber.v("## VOIP renderState call $state") - callControlsView.updateForState(state.callState.invoke()) + callControlsView.updateForState(state) when (state.callState.invoke()) { CallState.IDLE -> { } @@ -339,4 +339,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis override fun didEndCall() { callViewModel.handle(VectorCallViewActions.EndCall) } + + override fun didTapToggleMute() { + callViewModel.handle(VectorCallViewActions.ToggleMute) + } + + override fun didTapToggleVideo() { + callViewModel.handle(VectorCallViewActions.ToggleVideo) + } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt index 77366b70d0..372f5c2a8c 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt @@ -41,6 +41,8 @@ data class VectorCallViewState( val callId: String? = null, val roomId: String = "", val isVideoCall: Boolean, + val isAudioMuted: Boolean = false, + val isVideoEnabled: Boolean = true, val otherUserMatrixItem: Async = Uninitialized, val callState: Async = Uninitialized ) : MvRxState @@ -49,6 +51,8 @@ sealed class VectorCallViewActions : VectorViewModelAction { object EndCall : VectorCallViewActions() object AcceptCall : VectorCallViewActions() object DeclineCall : VectorCallViewActions() + object ToggleMute : VectorCallViewActions() + object ToggleVideo : VectorCallViewActions() } sealed class VectorCallViewEvents : VectorViewEvents { @@ -65,26 +69,6 @@ class VectorCallViewModel @AssistedInject constructor( val webRtcPeerConnectionManager: WebRtcPeerConnectionManager ) : VectorViewModel(initialState) { - // private val callServiceListener: CallsListener = object : CallsListener { -// override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) { -// withState { state -> -// if (callAnswerContent.callId == state.callId) { -// _viewEvents.post(VectorCallViewEvents.CallAnswered(callAnswerContent)) -// } -// } -// } -// -// override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) { -// } -// -// override fun onCallHangupReceived(callHangupContent: CallHangupContent) { -// withState { state -> -// if (callHangupContent.callId == state.callId) { -// _viewEvents.post(VectorCallViewEvents.CallHangup(callHangupContent)) -// } -// } -// } -// } var autoReplyIfNeeded: Boolean = false var call: MxCall? = null @@ -120,7 +104,6 @@ class VectorCallViewModel @AssistedInject constructor( } } - // session.callService().addCallListener(callServiceListener) } override fun onCleared() { @@ -144,6 +127,26 @@ class VectorCallViewModel @AssistedInject constructor( } webRtcPeerConnectionManager.endCall() } + VectorCallViewActions.ToggleMute -> { + withState { + val muted = it.isAudioMuted + webRtcPeerConnectionManager.muteCall(!muted) + setState { + copy(isAudioMuted = !muted) + } + } + } + VectorCallViewActions.ToggleVideo -> { + withState { + if(it.isVideoCall) { + val videoEnabled = it.isVideoEnabled + webRtcPeerConnectionManager.enableVideo(!videoEnabled) + setState { + copy(isVideoEnabled = !videoEnabled) + } + } + } + } }.exhaustive } diff --git a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt index 9d2b8cb2e0..c9a15a30a4 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt @@ -196,10 +196,10 @@ class WebRtcPeerConnectionManager @Inject constructor( } private fun sendSdpOffer(callContext: CallContext) { -// executor.execute { val constraints = MediaConstraints() - constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")) - constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", if (currentCall?.mxCall?.isVideoCall == true) "true" else "false")) + // These are deprecated options +// constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")) +// constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", if (currentCall?.mxCall?.isVideoCall == true) "true" else "false")) Timber.v("## VOIP creating offer...") callContext.peerConnection?.createOffer(object : SdpObserverAdapter() { @@ -211,7 +211,7 @@ class WebRtcPeerConnectionManager @Inject constructor( currentCall?.mxCall?.offerSdp(p0) } }, constraints) -// } + } private fun getTurnServer(callback: ((TurnServer?) -> Unit)) { @@ -355,7 +355,7 @@ class WebRtcPeerConnectionManager @Inject constructor( // localViewRenderer?.let { localVideoTrack?.addSink(it) } localMediaStream?.addTrack(localVideoTrack) - callContext.localMediaStream = localMediaStream +// callContext.localMediaStream = localMediaStream // remoteVideoTrack?.setEnabled(true) // remoteVideoTrack?.let { // it.setEnabled(true) @@ -418,20 +418,20 @@ class WebRtcPeerConnectionManager @Inject constructor( private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints().apply { // add all existing audio filters to avoid having echos - mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation", "true")) - mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation2", "true")) - mandatory.add(MediaConstraints.KeyValuePair("googDAEchoCancellation", "true")) - - mandatory.add(MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true")) - - mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl", "true")) - mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl2", "true")) - - mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression", "true")) - mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression2", "true")) - - mandatory.add(MediaConstraints.KeyValuePair("googAudioMirroring", "false")) - mandatory.add(MediaConstraints.KeyValuePair("googHighpassFilter", "true")) +// mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation", "true")) +// mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation2", "true")) +// mandatory.add(MediaConstraints.KeyValuePair("googDAEchoCancellation", "true")) +// +// mandatory.add(MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true")) +// +// mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl", "true")) +// mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl2", "true")) +// +// mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression", "true")) +// mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression2", "true")) +// +// mandatory.add(MediaConstraints.KeyValuePair("googAudioMirroring", "false")) +// mandatory.add(MediaConstraints.KeyValuePair("googHighpassFilter", "true")) } } @@ -509,6 +509,14 @@ class WebRtcPeerConnectionManager @Inject constructor( } } + fun muteCall(muted: Boolean) { + currentCall?.localAudioTrack?.setEnabled(!muted) + } + + fun enableVideo(enabled: Boolean) { + currentCall?.localVideoTrack?.setEnabled(enabled) + } + fun endCall() { currentCall?.mxCall?.hangUp() currentCall = null diff --git a/vector/src/main/res/drawable/ic_video_off.xml b/vector/src/main/res/drawable/ic_video_off.xml new file mode 100644 index 0000000000..34abdb5b51 --- /dev/null +++ b/vector/src/main/res/drawable/ic_video_off.xml @@ -0,0 +1,20 @@ + + + +