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.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()
}
}

View file

@ -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)
}
}

View file

@ -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<MatrixItem> = Uninitialized,
val callState: Async<CallState> = 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<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 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
}

View file

@ -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

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>