basic toggle mute and toggle video

This commit is contained in:
Valere 2020-06-11 17:45:32 +02:00
parent 46d7db8214
commit 91f28bfb8a
5 changed files with 105 additions and 44 deletions

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.call
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -44,6 +45,13 @@ class CallControlsView @JvmOverloads constructor(
@BindView(R.id.connectedControls) @BindView(R.id.connectedControls)
lateinit var connectedControls: ViewGroup 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 { init {
ConstraintLayout.inflate(context, R.layout.fragment_call_controls, this) ConstraintLayout.inflate(context, R.layout.fragment_call_controls, this)
// layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) // layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
@ -65,11 +73,21 @@ class CallControlsView @JvmOverloads constructor(
interactionListener?.didEndCall() interactionListener?.didEndCall()
} }
@OnClick(R.id.iv_end_call) @OnClick(R.id.iv_mute_toggle)
fun hangupCall() { 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) { when (callState) {
CallState.DIALING -> { CallState.DIALING -> {
} }
@ -94,11 +112,15 @@ class CallControlsView @JvmOverloads constructor(
connectedControls.isVisible = false connectedControls.isVisible = false
} }
} }
} }
interface InteractionListener { interface InteractionListener {
fun didAcceptIncomingCall() fun didAcceptIncomingCall()
fun didDeclineIncomingCall() fun didDeclineIncomingCall()
fun didEndCall() fun didEndCall()
fun didTapToggleMute()
fun didTapToggleVideo()
} }
} }

View File

@ -182,7 +182,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
private fun renderState(state: VectorCallViewState) { private fun renderState(state: VectorCallViewState) {
Timber.v("## VOIP renderState call $state") Timber.v("## VOIP renderState call $state")
callControlsView.updateForState(state.callState.invoke()) callControlsView.updateForState(state)
when (state.callState.invoke()) { when (state.callState.invoke()) {
CallState.IDLE -> { CallState.IDLE -> {
} }
@ -339,4 +339,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
override fun didEndCall() { override fun didEndCall() {
callViewModel.handle(VectorCallViewActions.EndCall) callViewModel.handle(VectorCallViewActions.EndCall)
} }
override fun didTapToggleMute() {
callViewModel.handle(VectorCallViewActions.ToggleMute)
}
override fun didTapToggleVideo() {
callViewModel.handle(VectorCallViewActions.ToggleVideo)
}
} }

View File

@ -41,6 +41,8 @@ data class VectorCallViewState(
val callId: String? = null, val callId: String? = null,
val roomId: String = "", val roomId: String = "",
val isVideoCall: Boolean, val isVideoCall: Boolean,
val isAudioMuted: Boolean = false,
val isVideoEnabled: Boolean = true,
val otherUserMatrixItem: Async<MatrixItem> = Uninitialized, val otherUserMatrixItem: Async<MatrixItem> = Uninitialized,
val callState: Async<CallState> = Uninitialized val callState: Async<CallState> = Uninitialized
) : MvRxState ) : MvRxState
@ -49,6 +51,8 @@ sealed class VectorCallViewActions : VectorViewModelAction {
object EndCall : VectorCallViewActions() object EndCall : VectorCallViewActions()
object AcceptCall : VectorCallViewActions() object AcceptCall : VectorCallViewActions()
object DeclineCall : VectorCallViewActions() object DeclineCall : VectorCallViewActions()
object ToggleMute : VectorCallViewActions()
object ToggleVideo : VectorCallViewActions()
} }
sealed class VectorCallViewEvents : VectorViewEvents { sealed class VectorCallViewEvents : VectorViewEvents {
@ -65,26 +69,6 @@ class VectorCallViewModel @AssistedInject constructor(
val webRtcPeerConnectionManager: WebRtcPeerConnectionManager val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(initialState) { ) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(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 autoReplyIfNeeded: Boolean = false
var call: MxCall? = null var call: MxCall? = null
@ -120,7 +104,6 @@ class VectorCallViewModel @AssistedInject constructor(
} }
} }
// session.callService().addCallListener(callServiceListener)
} }
override fun onCleared() { override fun onCleared() {
@ -144,6 +127,26 @@ class VectorCallViewModel @AssistedInject constructor(
} }
webRtcPeerConnectionManager.endCall() 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 }.exhaustive
} }

View File

@ -196,10 +196,10 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
private fun sendSdpOffer(callContext: CallContext) { private fun sendSdpOffer(callContext: CallContext) {
// executor.execute {
val constraints = MediaConstraints() val constraints = MediaConstraints()
constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true")) // These are deprecated options
constraints.mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", if (currentCall?.mxCall?.isVideoCall == true) "true" else "false")) // 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...") Timber.v("## VOIP creating offer...")
callContext.peerConnection?.createOffer(object : SdpObserverAdapter() { callContext.peerConnection?.createOffer(object : SdpObserverAdapter() {
@ -211,7 +211,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
currentCall?.mxCall?.offerSdp(p0) currentCall?.mxCall?.offerSdp(p0)
} }
}, constraints) }, constraints)
// }
} }
private fun getTurnServer(callback: ((TurnServer?) -> Unit)) { private fun getTurnServer(callback: ((TurnServer?) -> Unit)) {
@ -355,7 +355,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
// localViewRenderer?.let { localVideoTrack?.addSink(it) } // localViewRenderer?.let { localVideoTrack?.addSink(it) }
localMediaStream?.addTrack(localVideoTrack) localMediaStream?.addTrack(localVideoTrack)
callContext.localMediaStream = localMediaStream // callContext.localMediaStream = localMediaStream
// remoteVideoTrack?.setEnabled(true) // remoteVideoTrack?.setEnabled(true)
// remoteVideoTrack?.let { // remoteVideoTrack?.let {
// it.setEnabled(true) // it.setEnabled(true)
@ -418,20 +418,20 @@ class WebRtcPeerConnectionManager @Inject constructor(
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints().apply { private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints().apply {
// add all existing audio filters to avoid having echos // add all existing audio filters to avoid having echos
mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation", "true"))
mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation2", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googEchoCancellation2", "true"))
mandatory.add(MediaConstraints.KeyValuePair("googDAEchoCancellation", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googDAEchoCancellation", "true"))
//
mandatory.add(MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true"))
//
mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl", "true"))
mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl2", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googAutoGainControl2", "true"))
//
mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression", "true"))
mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression2", "true")) // mandatory.add(MediaConstraints.KeyValuePair("googNoiseSuppression2", "true"))
//
mandatory.add(MediaConstraints.KeyValuePair("googAudioMirroring", "false")) // mandatory.add(MediaConstraints.KeyValuePair("googAudioMirroring", "false"))
mandatory.add(MediaConstraints.KeyValuePair("googHighpassFilter", "true")) // 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() { fun endCall() {
currentCall?.mxCall?.hangUp() currentCall?.mxCall?.hangUp()
currentCall = null currentCall = null

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M10.66,5H14C14.5304,5 15.0391,5.2107 15.4142,5.5858C15.7893,5.9609 16,6.4696 16,7V10.34L17,11.34L23,7V17M16,16V17C16,17.5304 15.7893,18.0391 15.4142,18.4142C15.0391,18.7893 14.5304,19 14,19H3C2.4696,19 1.9609,18.7893 1.5858,18.4142C1.2107,18.0391 1,17.5304 1,17V7C1,6.4696 1.2107,5.9609 1.5858,5.5858C1.9609,5.2107 2.4696,5 3,5H5L16,16Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
<path
android:pathData="M1,1L23,23"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"/>
</vector>