VoIP: add select answer

This commit is contained in:
ganfra 2020-11-13 16:32:45 +01:00
parent ba11ca0e9d
commit 03e89743b4
7 changed files with 78 additions and 2 deletions

View File

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

View File

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

View File

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

View File

@ -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<CallListener>) : CallLi
it.onCallManagedByOtherSession(callId)
}
override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) = dispatch {
it.onCallSelectAnswerReceived(callSelectAnswerContent)
}
private fun dispatch(lambda: (CallListener) -> Unit) {
listeners.toList().forEach {
tryOrNull {

View File

@ -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<CallSelectAnswerContent>() ?: 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<CallCandidatesContent>() ?: return
val call = content.getCall() ?: return
@ -185,7 +208,7 @@ internal class DefaultCallSignalingService @Inject constructor(
val content = event.getClearContent().toModel<CallHangupContent>() ?: 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)
}

View File

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

View File

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