From 03e89743b4d33b5e51c6decd8ecad16db4046224 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Nov 2020 16:32:45 +0100 Subject: [PATCH] VoIP: add select answer --- .../sdk/api/session/call/CallListener.kt | 6 +++++ .../android/sdk/api/session/call/MxCall.kt | 5 ++++ .../room/model/call/CallHangupContent.kt | 3 ++- .../session/call/CallListenersDispatcher.kt | 5 ++++ .../call/DefaultCallSignalingService.kt | 25 ++++++++++++++++++- .../internal/session/call/model/MxCallImpl.kt | 15 +++++++++++ .../call/WebRtcPeerConnectionManager.kt | 21 ++++++++++++++++ 7 files changed, 78 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt index ff5dd4ffb5..255f156f0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent +import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent interface CallListener { /** @@ -45,5 +46,10 @@ interface CallListener { */ fun onCallRejectReceived(callRejectContent: CallRejectContent) + /** + * Called when an answer has been selected + */ + fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) + fun onCallManagedByOtherSession(callId: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index 1f09f18277..2a84fd658d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -51,6 +51,11 @@ interface MxCall : MxCallDetail { */ fun accept(sdp: SessionDescription) + /** + * This has to be sent by the caller's client once it has chosen an answer. + */ + fun selectAnswer() + /** * Reject an incoming call */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt index 3e23ef0ef0..6142b817fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt @@ -40,7 +40,8 @@ data class CallHangupContent( /** * Optional error reason for the hangup. This should not be provided when the user naturally ends or rejects the call. * When there was an error in the call negotiation, this should be `ice_failed` for when ICE negotiation fails - * or `invite_timeout` for when the other party did not answer in time. One of: ["ice_failed", "invite_timeout"] + * or `invite_timeout` for when the other party did not answer in time. + * One of: ["ice_failed", "invite_timeout"] */ @Json(name = "reason") val reason: Reason? = null ) : CallSignallingContent { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt index 78437a2d69..302b5e01e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent +import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent /** * Dispatch each method safely to all listeners. @@ -54,6 +55,10 @@ class CallListenersDispatcher(private val listeners: Set) : CallLi it.onCallManagedByOtherSession(callId) } + override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) = dispatch { + it.onCallSelectAnswerReceived(callSelectAnswerContent) + } + private fun dispatch(lambda: (CallListener) -> Unit) { listeners.toList().forEach { tryOrNull { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt index a903a0e218..76b0b1d630 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt @@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent +import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable @@ -152,9 +153,31 @@ internal class DefaultCallSignalingService @Inject constructor( EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } + EventType.CALL_SELECT_ANSWER -> { + handleCallSelectAnswerEvent(event) + } } } + private fun handleCallSelectAnswerEvent(event: Event) { + val content = event.getClearContent().toModel() ?: return + val call = content.getCall() ?: return + if (call.ourPartyId == content.partyId) { + // Ignore remote echo + return + } + if (call.isOutgoing) { + Timber.v("Got selectAnswer for an outbound call: ignoring") + return + } + val selectedPartyId = content.selectedPartyId + if (selectedPartyId == null) { + Timber.w("Got nonsensical select_answer with null selected_party_id: ignoring") + return + } + callListenersDispatcher.onCallSelectAnswerReceived(content) + } + private fun handleCallCandidatesEvent(event: Event) { val content = event.getClearContent().toModel() ?: return val call = content.getCall() ?: return @@ -185,7 +208,7 @@ internal class DefaultCallSignalingService @Inject constructor( val content = event.getClearContent().toModel() ?: return val call = content.getCall() ?: return if (call.state != CallState.Terminated) { - // Need to check for party_id? + // Need to check for party_id? activeCallHandler.removeCall(content.callId) callListenersDispatcher.onCallHangupReceived(content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt index 126a527f88..f07053fb28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent +import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor @@ -143,6 +144,7 @@ internal class MxCallImpl( CallHangupContent( callId = callId, partyId = ourPartyId, + reason = CallHangupContent.Reason.USER_HANGUP ) .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } @@ -162,6 +164,19 @@ internal class MxCallImpl( .also { eventSenderProcessor.postEvent(it) } } + override fun selectAnswer() { + Timber.v("## VOIP select answer $callId") + if (isOutgoing) return + state = CallState.Answering + CallSelectAnswerContent( + callId = callId, + partyId = ourPartyId, + selectedPartyId = opponentPartyId?.getOrNull() + ) + .let { createEventAndLocalEcho(type = EventType.CALL_SELECT_ANSWER, roomId = roomId, content = it.toContent()) } + .also { eventSenderProcessor.postEvent(it) } + } + private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event { return Event( roomId = roomId, diff --git a/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt index 998c4da536..d755bed698 100644 --- a/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt @@ -31,6 +31,7 @@ import io.reactivex.disposables.Disposable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.ReplaySubject import org.matrix.android.sdk.api.MatrixCallback +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.CallState @@ -43,6 +44,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent +import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.webrtc.AudioSource import org.webrtc.AudioTrack import org.webrtc.Camera1Enumerator @@ -303,6 +305,10 @@ class WebRtcPeerConnectionManager @Inject constructor( callContext.peerConnection?.setLocalDescription(object : SdpObserverAdapter() {}, p0) // send offer to peer currentCall?.mxCall?.offerSdp(p0) + + if(currentCall?.mxCall?.opponentPartyId?.hasValue().orFalse()){ + currentCall?.mxCall?.selectAnswer() + } } }, constraints) } @@ -884,6 +890,21 @@ class WebRtcPeerConnectionManager @Inject constructor( 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 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 + call.mxCall.state = CallState.Terminated + endCall(false) + } + + } + override fun onCallManagedByOtherSession(callId: String) { Timber.v("## VOIP onCallManagedByOtherSession: $callId") currentCall = null