VoIP: add select answer
This commit is contained in:
parent
ba11ca0e9d
commit
03e89743b4
@ -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.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
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.CallRejectContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||||
|
|
||||||
interface CallListener {
|
interface CallListener {
|
||||||
/**
|
/**
|
||||||
@ -45,5 +46,10 @@ interface CallListener {
|
|||||||
*/
|
*/
|
||||||
fun onCallRejectReceived(callRejectContent: CallRejectContent)
|
fun onCallRejectReceived(callRejectContent: CallRejectContent)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an answer has been selected
|
||||||
|
*/
|
||||||
|
fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent)
|
||||||
|
|
||||||
fun onCallManagedByOtherSession(callId: String)
|
fun onCallManagedByOtherSession(callId: String)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,11 @@ interface MxCall : MxCallDetail {
|
|||||||
*/
|
*/
|
||||||
fun accept(sdp: SessionDescription)
|
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
|
* Reject an incoming call
|
||||||
*/
|
*/
|
||||||
|
@ -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.
|
* 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
|
* 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
|
@Json(name = "reason") val reason: Reason? = null
|
||||||
) : CallSignallingContent {
|
) : CallSignallingContent {
|
||||||
|
@ -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.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
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.CallRejectContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch each method safely to all listeners.
|
* Dispatch each method safely to all listeners.
|
||||||
@ -54,6 +55,10 @@ class CallListenersDispatcher(private val listeners: Set<CallListener>) : CallLi
|
|||||||
it.onCallManagedByOtherSession(callId)
|
it.onCallManagedByOtherSession(callId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) = dispatch {
|
||||||
|
it.onCallSelectAnswerReceived(callSelectAnswerContent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun dispatch(lambda: (CallListener) -> Unit) {
|
private fun dispatch(lambda: (CallListener) -> Unit) {
|
||||||
listeners.toList().forEach {
|
listeners.toList().forEach {
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
|
@ -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.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
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.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.session.room.model.call.CallSignallingContent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||||
@ -152,9 +153,31 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||||||
EventType.CALL_CANDIDATES -> {
|
EventType.CALL_CANDIDATES -> {
|
||||||
handleCallCandidatesEvent(event)
|
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) {
|
private fun handleCallCandidatesEvent(event: Event) {
|
||||||
val content = event.getClearContent().toModel<CallCandidatesContent>() ?: return
|
val content = event.getClearContent().toModel<CallCandidatesContent>() ?: return
|
||||||
val call = content.getCall() ?: return
|
val call = content.getCall() ?: return
|
||||||
@ -185,7 +208,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||||||
val content = event.getClearContent().toModel<CallHangupContent>() ?: return
|
val content = event.getClearContent().toModel<CallHangupContent>() ?: return
|
||||||
val call = content.getCall() ?: return
|
val call = content.getCall() ?: return
|
||||||
if (call.state != CallState.Terminated) {
|
if (call.state != CallState.Terminated) {
|
||||||
// Need to check for party_id?
|
// Need to check for party_id?
|
||||||
activeCallHandler.removeCall(content.callId)
|
activeCallHandler.removeCall(content.callId)
|
||||||
callListenersDispatcher.onCallHangupReceived(content)
|
callListenersDispatcher.onCallHangupReceived(content)
|
||||||
}
|
}
|
||||||
|
@ -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.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
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.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.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
@ -143,6 +144,7 @@ internal class MxCallImpl(
|
|||||||
CallHangupContent(
|
CallHangupContent(
|
||||||
callId = callId,
|
callId = callId,
|
||||||
partyId = ourPartyId,
|
partyId = ourPartyId,
|
||||||
|
reason = CallHangupContent.Reason.USER_HANGUP
|
||||||
)
|
)
|
||||||
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
||||||
.also { eventSenderProcessor.postEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
@ -162,6 +164,19 @@ internal class MxCallImpl(
|
|||||||
.also { eventSenderProcessor.postEvent(it) }
|
.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 {
|
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||||
return Event(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
@ -31,6 +31,7 @@ import io.reactivex.disposables.Disposable
|
|||||||
import io.reactivex.subjects.PublishSubject
|
import io.reactivex.subjects.PublishSubject
|
||||||
import io.reactivex.subjects.ReplaySubject
|
import io.reactivex.subjects.ReplaySubject
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
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.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
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.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
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.CallRejectContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
|
||||||
import org.webrtc.AudioSource
|
import org.webrtc.AudioSource
|
||||||
import org.webrtc.AudioTrack
|
import org.webrtc.AudioTrack
|
||||||
import org.webrtc.Camera1Enumerator
|
import org.webrtc.Camera1Enumerator
|
||||||
@ -303,6 +305,10 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
callContext.peerConnection?.setLocalDescription(object : SdpObserverAdapter() {}, p0)
|
callContext.peerConnection?.setLocalDescription(object : SdpObserverAdapter() {}, p0)
|
||||||
// send offer to peer
|
// send offer to peer
|
||||||
currentCall?.mxCall?.offerSdp(p0)
|
currentCall?.mxCall?.offerSdp(p0)
|
||||||
|
|
||||||
|
if(currentCall?.mxCall?.opponentPartyId?.hasValue().orFalse()){
|
||||||
|
currentCall?.mxCall?.selectAnswer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, constraints)
|
}, constraints)
|
||||||
}
|
}
|
||||||
@ -884,6 +890,21 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
endCall(false)
|
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) {
|
override fun onCallManagedByOtherSession(callId: String) {
|
||||||
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
|
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
|
||||||
currentCall = null
|
currentCall = null
|
||||||
|
Loading…
x
Reference in New Issue
Block a user