mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
VoIP: add partyId and handle version as string
This commit is contained in:
parent
f2cb6ed82c
commit
ba11ca0e9d
@ -20,8 +20,9 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
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
|
||||
|
||||
interface CallsListener {
|
||||
interface CallListener {
|
||||
/**
|
||||
* Called when there is an incoming call within the room.
|
||||
*/
|
||||
@ -39,5 +40,10 @@ interface CallsListener {
|
||||
*/
|
||||
fun onCallHangupReceived(callHangupContent: CallHangupContent)
|
||||
|
||||
/**
|
||||
* Called when a called has been rejected
|
||||
*/
|
||||
fun onCallRejectReceived(callRejectContent: CallRejectContent)
|
||||
|
||||
fun onCallManagedByOtherSession(callId: String)
|
||||
}
|
@ -28,9 +28,9 @@ interface CallSignalingService {
|
||||
*/
|
||||
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall
|
||||
|
||||
fun addCallListener(listener: CallsListener)
|
||||
fun addCallListener(listener: CallListener)
|
||||
|
||||
fun removeCallListener(listener: CallsListener)
|
||||
fun removeCallListener(listener: CallListener)
|
||||
|
||||
fun getCallWithId(callId: String): MxCall?
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.api.session.call
|
||||
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.webrtc.IceCandidate
|
||||
import org.webrtc.SessionDescription
|
||||
|
||||
@ -23,8 +24,12 @@ interface MxCallDetail {
|
||||
val callId: String
|
||||
val isOutgoing: Boolean
|
||||
val roomId: String
|
||||
val otherUserId: String
|
||||
val opponentUserId: String
|
||||
val ourPartyId: String
|
||||
val isVideoCall: Boolean
|
||||
|
||||
var opponentPartyId: Optional<String>?
|
||||
var opponentVersion: Int
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,6 +37,12 @@ interface MxCallDetail {
|
||||
*/
|
||||
interface MxCall : MxCallDetail {
|
||||
|
||||
companion object {
|
||||
const val VOIP_PROTO_VERSION = 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
var state: CallState
|
||||
|
||||
/**
|
||||
@ -42,9 +53,8 @@ interface MxCall : MxCallDetail {
|
||||
|
||||
/**
|
||||
* Reject an incoming call
|
||||
* It's an alias to hangUp
|
||||
*/
|
||||
fun reject() = hangUp()
|
||||
fun reject()
|
||||
|
||||
/**
|
||||
* End the call
|
||||
|
@ -27,11 +27,11 @@ data class CallAnswerContent(
|
||||
/**
|
||||
* Required. The ID of the call this event relates to.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String,
|
||||
@Json(name = "call_id") override val callId: String,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The session description object
|
||||
*/
|
||||
@ -39,8 +39,8 @@ data class CallAnswerContent(
|
||||
/**
|
||||
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") val version: String? = "0"
|
||||
) {
|
||||
@Json(name = "version") override val version: String? = "0"
|
||||
): CallSignallingContent {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Answer(
|
||||
|
@ -28,11 +28,11 @@ data class CallCandidatesContent(
|
||||
/**
|
||||
* Required. The ID of the call this event relates to.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String,
|
||||
@Json(name = "call_id") override val callId: String,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. Array of objects describing the candidates.
|
||||
*/
|
||||
@ -40,8 +40,8 @@ data class CallCandidatesContent(
|
||||
/**
|
||||
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") val version: String? = "0"
|
||||
) {
|
||||
@Json(name = "version") override val version: String? = "0"
|
||||
): CallSignallingContent {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Candidate(
|
||||
|
@ -28,22 +28,22 @@ data class CallHangupContent(
|
||||
/**
|
||||
* Required. The ID of the call this event relates to.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String,
|
||||
@Json(name = "call_id") override val callId: String,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") val version: String? = "0",
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
/**
|
||||
* 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"]
|
||||
*/
|
||||
@Json(name = "reason") val reason: Reason? = null
|
||||
) {
|
||||
) : CallSignallingContent {
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Reason {
|
||||
@Json(name = "ice_failed")
|
||||
|
@ -27,11 +27,11 @@ data class CallInviteContent(
|
||||
/**
|
||||
* Required. A unique identifier for the call.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String?,
|
||||
@Json(name = "call_id") override val callId: String?,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The session description object
|
||||
*/
|
||||
@ -39,14 +39,14 @@ data class CallInviteContent(
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") val version: String? = "0",
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
/**
|
||||
* Required. The time in milliseconds that the invite is valid for.
|
||||
* Once the invite age exceeds this value, clients should discard it.
|
||||
* They should also no longer show the call as awaiting an answer in the UI.
|
||||
*/
|
||||
@Json(name = "lifetime") val lifetime: Int?
|
||||
) {
|
||||
): CallSignallingContent {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Offer(
|
||||
/**
|
||||
|
@ -27,11 +27,11 @@ data class CallNegociateContent(
|
||||
/**
|
||||
* Required. The ID of the call this event relates to.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String,
|
||||
@Json(name = "call_id") override val callId: String,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The time in milliseconds that the negotiation is valid for. Once exceeded the sender
|
||||
* of the negotiate event should consider the negotiation failed (timed out) and the recipient should ignore it.
|
||||
@ -41,7 +41,13 @@ data class CallNegociateContent(
|
||||
* Required. The session description object
|
||||
*/
|
||||
@Json(name = "description") val description: Description? = null,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
|
||||
): CallSignallingContent {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Description(
|
||||
/**
|
||||
|
@ -28,13 +28,13 @@ data class CallRejectContent(
|
||||
/**
|
||||
* Required. The ID of the call this event relates to.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String,
|
||||
@Json(name = "call_id") override val callId: String,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") val version: String? = "0",
|
||||
)
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
):CallSignallingContent
|
||||
|
@ -27,13 +27,18 @@ data class CallSelectAnswerContent(
|
||||
/**
|
||||
* Required. The ID of the call this event relates to.
|
||||
*/
|
||||
@Json(name = "call_id") val callId: String,
|
||||
@Json(name = "call_id") override val callId: String,
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
@Json(name = "party_id") val partyId: String? = null,
|
||||
@Json(name = "party_id") override val partyId: String? = null,
|
||||
/**
|
||||
* Required. Indicates the answer user has chosen.
|
||||
*/
|
||||
@Json(name = "selected_party_id") val selectedPartyId: String? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
*/
|
||||
@Json(name = "version") override val version: String? = "0",
|
||||
): CallSignallingContent
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.model.call
|
||||
|
||||
interface CallSignallingContent {
|
||||
/**
|
||||
* Required. A unique identifier for the call.
|
||||
*/
|
||||
val callId: String?
|
||||
|
||||
/**
|
||||
* Required. ID to let user identify remote echo of their own events
|
||||
*/
|
||||
val partyId: String?
|
||||
|
||||
/**
|
||||
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||
*/
|
||||
val version: String?
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.call
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.call.CallListener
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
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
|
||||
|
||||
/**
|
||||
* Dispatch each method safely to all listeners.
|
||||
*/
|
||||
class CallListenersDispatcher(private val listeners: Set<CallListener>) : CallListener {
|
||||
|
||||
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) = dispatch {
|
||||
it.onCallInviteReceived(mxCall, callInviteContent)
|
||||
}
|
||||
|
||||
override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) = dispatch {
|
||||
it.onCallIceCandidateReceived(mxCall, iceCandidatesContent)
|
||||
}
|
||||
|
||||
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) = dispatch {
|
||||
it.onCallAnswerReceived(callAnswerContent)
|
||||
}
|
||||
|
||||
override fun onCallHangupReceived(callHangupContent: CallHangupContent) = dispatch {
|
||||
it.onCallHangupReceived(callHangupContent)
|
||||
}
|
||||
|
||||
override fun onCallRejectReceived(callRejectContent: CallRejectContent) = dispatch {
|
||||
it.onCallRejectReceived(callRejectContent)
|
||||
}
|
||||
|
||||
override fun onCallManagedByOtherSession(callId: String) = dispatch {
|
||||
it.onCallManagedByOtherSession(callId)
|
||||
}
|
||||
|
||||
private fun dispatch(lambda: (CallListener) -> Unit) {
|
||||
listeners.toList().forEach {
|
||||
tryOrNull {
|
||||
lambda(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,10 +18,9 @@ package org.matrix.android.sdk.internal.session.call
|
||||
|
||||
import android.os.SystemClock
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.call.CallListener
|
||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.CallsListener
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
@ -31,16 +30,21 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
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.CallSignallingContent
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -48,6 +52,8 @@ import javax.inject.Inject
|
||||
internal class DefaultCallSignalingService @Inject constructor(
|
||||
@UserId
|
||||
private val userId: String,
|
||||
@DeviceId
|
||||
private val deviceId: String?,
|
||||
private val activeCallHandler: ActiveCallHandler,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val eventSenderProcessor: EventSenderProcessor,
|
||||
@ -55,7 +61,8 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||
private val turnServerTask: GetTurnServerTask
|
||||
) : CallSignalingService {
|
||||
|
||||
private val callListeners = mutableSetOf<CallsListener>()
|
||||
private val callListeners = mutableSetOf<CallListener>()
|
||||
private val callListenersDispatcher = CallListenersDispatcher(callListeners)
|
||||
|
||||
private val cachedTurnServerResponse = object {
|
||||
// Keep one minute safe to avoid considering the data is valid and then actually it is not when effectively using it.
|
||||
@ -100,7 +107,8 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||
isOutgoing = true,
|
||||
roomId = roomId,
|
||||
userId = userId,
|
||||
otherUserId = otherUserId,
|
||||
ourPartyId = deviceId ?: "",
|
||||
opponentUserId = otherUserId,
|
||||
isVideoCall = isVideoCall,
|
||||
localEchoEventFactory = localEchoEventFactory,
|
||||
eventSenderProcessor = eventSenderProcessor
|
||||
@ -110,11 +118,11 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun addCallListener(listener: CallsListener) {
|
||||
override fun addCallListener(listener: CallListener) {
|
||||
callListeners.add(listener)
|
||||
}
|
||||
|
||||
override fun removeCallListener(listener: CallsListener) {
|
||||
override fun removeCallListener(listener: CallListener) {
|
||||
callListeners.remove(listener)
|
||||
}
|
||||
|
||||
@ -129,125 +137,115 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||
|
||||
internal fun onCallEvent(event: Event) {
|
||||
when (event.getClearType()) {
|
||||
EventType.CALL_ANSWER -> {
|
||||
event.getClearContent().toModel<CallAnswerContent>()?.let {
|
||||
if (event.senderId == userId) {
|
||||
// ok it's an answer from me.. is it remote echo or other session
|
||||
val knownCall = getCallWithId(it.callId)
|
||||
if (knownCall == null) {
|
||||
Timber.d("## VOIP onCallEvent ${event.getClearType()} id ${it.callId} send by me")
|
||||
} else if (!knownCall.isOutgoing) {
|
||||
// incoming call
|
||||
// if it was anwsered by this session, the call state would be in Answering(or connected) state
|
||||
if (knownCall.state == CallState.LocalRinging) {
|
||||
// discard current call, it's answered by another of my session
|
||||
onCallManageByOtherSession(it.callId)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
onCallAnswer(it)
|
||||
}
|
||||
EventType.CALL_ANSWER -> {
|
||||
handleCallAnswerEvent(event)
|
||||
}
|
||||
EventType.CALL_INVITE -> {
|
||||
if (event.senderId == userId) {
|
||||
// Always ignore local echos of invite
|
||||
return
|
||||
}
|
||||
|
||||
event.getClearContent().toModel<CallInviteContent>()?.let { content ->
|
||||
val incomingCall = MxCallImpl(
|
||||
callId = content.callId ?: return@let,
|
||||
isOutgoing = false,
|
||||
roomId = event.roomId ?: return@let,
|
||||
userId = userId,
|
||||
otherUserId = event.senderId ?: return@let,
|
||||
isVideoCall = content.isVideo(),
|
||||
localEchoEventFactory = localEchoEventFactory,
|
||||
eventSenderProcessor = eventSenderProcessor
|
||||
)
|
||||
activeCallHandler.addCall(incomingCall)
|
||||
onCallInvite(incomingCall, content)
|
||||
}
|
||||
EventType.CALL_INVITE -> {
|
||||
handleCallInviteEvent(event)
|
||||
}
|
||||
EventType.CALL_HANGUP -> {
|
||||
event.getClearContent().toModel<CallHangupContent>()?.let { content ->
|
||||
|
||||
if (event.senderId == userId) {
|
||||
// ok it's an answer from me.. is it remote echo or other session
|
||||
val knownCall = getCallWithId(content.callId)
|
||||
if (knownCall == null) {
|
||||
Timber.d("## VOIP onCallEvent ${event.getClearType()} id ${content.callId} send by me")
|
||||
} else if (!knownCall.isOutgoing) {
|
||||
// incoming call
|
||||
if (knownCall.state == CallState.LocalRinging) {
|
||||
// discard current call, it's answered by another of my session
|
||||
onCallManageByOtherSession(content.callId)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
activeCallHandler.removeCall(content.callId)
|
||||
onCallHangup(content)
|
||||
}
|
||||
EventType.CALL_HANGUP -> {
|
||||
handleCallHangupEvent(event)
|
||||
}
|
||||
EventType.CALL_REJECT -> {
|
||||
handleCallRejectEvent(event)
|
||||
}
|
||||
EventType.CALL_CANDIDATES -> {
|
||||
if (event.senderId == userId) {
|
||||
// Always ignore local echos of invite
|
||||
return
|
||||
}
|
||||
event.getClearContent().toModel<CallCandidatesContent>()?.let { content ->
|
||||
activeCallHandler.getCallWithId(content.callId)?.let {
|
||||
onCallIceCandidate(it, content)
|
||||
}
|
||||
}
|
||||
handleCallCandidatesEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallHangup(hangup: CallHangupContent) {
|
||||
callListeners.toList().forEach {
|
||||
tryOrNull {
|
||||
it.onCallHangupReceived(hangup)
|
||||
}
|
||||
private fun handleCallCandidatesEvent(event: Event) {
|
||||
val content = event.getClearContent().toModel<CallCandidatesContent>() ?: return
|
||||
val call = content.getCall() ?: return
|
||||
if (call.ourPartyId == content.partyId) {
|
||||
// Ignore remote echo
|
||||
return
|
||||
}
|
||||
if (call.opponentPartyId != Optional.from(content.partyId)) {
|
||||
Timber.v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
|
||||
return
|
||||
}
|
||||
callListenersDispatcher.onCallIceCandidateReceived(call, content)
|
||||
}
|
||||
|
||||
private fun handleCallRejectEvent(event: Event) {
|
||||
val content = event.getClearContent().toModel<CallRejectContent>() ?: return
|
||||
val call = content.getCall() ?: return
|
||||
activeCallHandler.removeCall(content.callId)
|
||||
// No need to check party_id for reject because if we'd received either
|
||||
// an answer or reject, we wouldn't be in state InviteSent
|
||||
if (call.state != CallState.Dialing) {
|
||||
return
|
||||
}
|
||||
callListenersDispatcher.onCallRejectReceived(content)
|
||||
}
|
||||
|
||||
private fun handleCallHangupEvent(event: Event) {
|
||||
val content = event.getClearContent().toModel<CallHangupContent>() ?: return
|
||||
val call = content.getCall() ?: return
|
||||
if (call.state != CallState.Terminated) {
|
||||
// Need to check for party_id?
|
||||
activeCallHandler.removeCall(content.callId)
|
||||
callListenersDispatcher.onCallHangupReceived(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallAnswer(answer: CallAnswerContent) {
|
||||
callListeners.toList().forEach {
|
||||
tryOrNull {
|
||||
it.onCallAnswerReceived(answer)
|
||||
private fun handleCallInviteEvent(event: Event) {
|
||||
val content = event.getClearContent().toModel<CallInviteContent>() ?: return
|
||||
if (content.partyId == deviceId) {
|
||||
// Ignore remote echo
|
||||
return
|
||||
}
|
||||
val incomingCall = MxCallImpl(
|
||||
callId = content.callId ?: return,
|
||||
isOutgoing = false,
|
||||
roomId = event.roomId ?: return,
|
||||
userId = userId,
|
||||
ourPartyId = deviceId ?: "",
|
||||
opponentUserId = event.senderId ?: return,
|
||||
isVideoCall = content.isVideo(),
|
||||
localEchoEventFactory = localEchoEventFactory,
|
||||
eventSenderProcessor = eventSenderProcessor
|
||||
).apply {
|
||||
opponentPartyId = Optional.from(content.partyId)
|
||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||
}
|
||||
activeCallHandler.addCall(incomingCall)
|
||||
callListenersDispatcher.onCallInviteReceived(incomingCall, content)
|
||||
}
|
||||
|
||||
private fun handleCallAnswerEvent(event: Event) {
|
||||
val content = event.getClearContent().toModel<CallAnswerContent>() ?: return
|
||||
val call = content.getCall() ?: return
|
||||
if (call.ourPartyId == content.partyId) {
|
||||
// Ignore remote echo
|
||||
return
|
||||
}
|
||||
if (event.senderId == userId) {
|
||||
// discard current call, it's answered by another of my session
|
||||
callListenersDispatcher.onCallManagedByOtherSession(content.callId)
|
||||
} else {
|
||||
if (call.opponentPartyId != null) {
|
||||
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
|
||||
return
|
||||
}
|
||||
call.apply {
|
||||
opponentPartyId = Optional.from(content.partyId)
|
||||
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||
}
|
||||
callListenersDispatcher.onCallAnswerReceived(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallManageByOtherSession(callId: String) {
|
||||
callListeners.toList().forEach {
|
||||
tryOrNull {
|
||||
it.onCallManagedByOtherSession(callId)
|
||||
}
|
||||
private fun CallSignallingContent.getCall(): MxCall? {
|
||||
val currentCall = callId?.let {
|
||||
activeCallHandler.getCallWithId(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallInvite(incomingCall: MxCall, invite: CallInviteContent) {
|
||||
// Ignore the invitation from current user
|
||||
if (incomingCall.otherUserId == userId) return
|
||||
|
||||
callListeners.toList().forEach {
|
||||
tryOrNull {
|
||||
it.onCallInviteReceived(incomingCall, invite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallIceCandidate(incomingCall: MxCall, candidates: CallCandidatesContent) {
|
||||
callListeners.toList().forEach {
|
||||
tryOrNull {
|
||||
it.onCallIceCandidateReceived(incomingCall, candidates)
|
||||
}
|
||||
if (currentCall == null) {
|
||||
Timber.v("Call for content: $this is null")
|
||||
}
|
||||
return currentCall
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
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.util.Optional
|
||||
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.LocalEchoEventFactory
|
||||
@ -40,12 +42,16 @@ internal class MxCallImpl(
|
||||
override val isOutgoing: Boolean,
|
||||
override val roomId: String,
|
||||
private val userId: String,
|
||||
override val otherUserId: String,
|
||||
override val opponentUserId: String,
|
||||
override val isVideoCall: Boolean,
|
||||
override val ourPartyId: String,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val eventSenderProcessor: EventSenderProcessor
|
||||
) : MxCall {
|
||||
|
||||
override var opponentPartyId: Optional<String>? = null
|
||||
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
|
||||
|
||||
override var state: CallState = CallState.Idle
|
||||
set(value) {
|
||||
field = value
|
||||
@ -87,6 +93,7 @@ internal class MxCallImpl(
|
||||
state = CallState.Dialing
|
||||
CallInviteContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
|
||||
offer = CallInviteContent.Offer(sdp = sdp.description)
|
||||
)
|
||||
@ -97,6 +104,7 @@ internal class MxCallImpl(
|
||||
override fun sendLocalIceCandidates(candidates: List<IceCandidate>) {
|
||||
CallCandidatesContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
candidates = candidates.map {
|
||||
CallCandidatesContent.Candidate(
|
||||
sdpMid = it.sdpMid,
|
||||
@ -113,10 +121,28 @@ internal class MxCallImpl(
|
||||
// For now we don't support this flow
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
if(opponentVersion < 1){
|
||||
Timber.v("Opponent version is less than 1 (${opponentVersion}): sending hangup instead of reject")
|
||||
hangUp()
|
||||
return
|
||||
}
|
||||
Timber.v("## VOIP reject $callId")
|
||||
CallRejectContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
version = MxCall.VOIP_PROTO_VERSION.toString()
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
state = CallState.Terminated
|
||||
}
|
||||
|
||||
override fun hangUp() {
|
||||
Timber.v("## VOIP hangup $callId")
|
||||
CallHangupContent(
|
||||
callId = callId
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
||||
.also { eventSenderProcessor.postEvent(it) }
|
||||
@ -129,6 +155,7 @@ internal class MxCallImpl(
|
||||
state = CallState.Answering
|
||||
CallAnswerContent(
|
||||
callId = callId,
|
||||
partyId = ourPartyId,
|
||||
answer = CallAnswerContent.Answer(sdp = sdp.description)
|
||||
)
|
||||
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
|
||||
@ -147,4 +174,5 @@ internal class MxCallImpl(
|
||||
)
|
||||
.also { localEchoEventFactory.createLocalEcho(it) }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
||||
return Intent(context, VectorCallActivity::class.java).apply {
|
||||
// what could be the best flags?
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.otherUserId, !mxCall.isOutgoing, mxCall.isVideoCall))
|
||||
putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.opponentUserId, !mxCall.isOutgoing, mxCall.isVideoCall))
|
||||
putExtra(EXTRA_MODE, OUTGOING_CREATED)
|
||||
}
|
||||
}
|
||||
|
@ -136,8 +136,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
|
||||
session.callSignalingService().getCallWithId(it)?.let { mxCall ->
|
||||
this.call = mxCall
|
||||
mxCall.otherUserId
|
||||
val item: MatrixItem? = session.getUser(mxCall.otherUserId)?.toMatrixItem()
|
||||
mxCall.opponentUserId
|
||||
val item: MatrixItem? = session.getUser(mxCall.opponentUserId)?.toMatrixItem()
|
||||
|
||||
mxCall.addListener(callStateListener)
|
||||
|
||||
|
@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.MatrixCallback
|
||||
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
|
||||
import org.matrix.android.sdk.api.session.call.CallsListener
|
||||
import org.matrix.android.sdk.api.session.call.CallListener
|
||||
import org.matrix.android.sdk.api.session.call.EglUtils
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||
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.webrtc.AudioSource
|
||||
import org.webrtc.AudioTrack
|
||||
import org.webrtc.Camera1Enumerator
|
||||
@ -76,7 +77,7 @@ import javax.inject.Singleton
|
||||
class WebRtcPeerConnectionManager @Inject constructor(
|
||||
private val context: Context,
|
||||
private val activeSessionDataSource: ActiveSessionDataSource
|
||||
) : CallsListener, LifecycleObserver {
|
||||
) : CallListener, LifecycleObserver {
|
||||
|
||||
private val currentSession: Session?
|
||||
get() = activeSessionDataSource.currentValue?.orNull()
|
||||
@ -330,7 +331,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
currentCall?.mxCall
|
||||
?.takeIf { it.state is CallState.Connected }
|
||||
?.let { mxCall ->
|
||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
|
||||
?: mxCall.roomId
|
||||
// Start background service with notification
|
||||
CallService.onPendingCall(
|
||||
@ -388,7 +389,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
val mxCall = callContext.mxCall
|
||||
// Update service state
|
||||
|
||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
|
||||
?: mxCall.roomId
|
||||
CallService.onPendingCall(
|
||||
context = context,
|
||||
@ -576,8 +577,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
?.let { mxCall ->
|
||||
// Start background service with notification
|
||||
|
||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.otherUserId
|
||||
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
|
||||
?: mxCall.opponentUserId
|
||||
CallService.onOnGoingCallBackground(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
@ -650,8 +651,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
callAudioManager.startForCall(createdCall)
|
||||
currentCall = callContext
|
||||
|
||||
val name = currentSession?.getUser(createdCall.otherUserId)?.getBestName()
|
||||
?: createdCall.otherUserId
|
||||
val name = currentSession?.getUser(createdCall.opponentUserId)?.getBestName()
|
||||
?: createdCall.opponentUserId
|
||||
CallService.onOutgoingCallRinging(
|
||||
context = context.applicationContext,
|
||||
isVideo = createdCall.isVideoCall,
|
||||
@ -706,8 +707,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
}
|
||||
|
||||
// Start background service with notification
|
||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.otherUserId
|
||||
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
|
||||
?: mxCall.opponentUserId
|
||||
CallService.onIncomingCallRinging(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
@ -845,8 +846,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
}
|
||||
val mxCall = call.mxCall
|
||||
// Update service state
|
||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
||||
?: mxCall.otherUserId
|
||||
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
|
||||
?: mxCall.opponentUserId
|
||||
CallService.onPendingCall(
|
||||
context = context,
|
||||
isVideo = mxCall.isVideoCall,
|
||||
@ -873,6 +874,16 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
endCall(false)
|
||||
}
|
||||
|
||||
override fun onCallRejectReceived(callRejectContent: CallRejectContent) {
|
||||
val call = currentCall ?: return
|
||||
// Remote echos are filtered, so it's only remote hangups that i will get here
|
||||
if (call.mxCall.callId != callRejectContent.callId) return Unit.also {
|
||||
Timber.w("onCallRejected for non active call? ${callRejectContent.callId}")
|
||||
}
|
||||
call.mxCall.state = CallState.Terminated
|
||||
endCall(false)
|
||||
}
|
||||
|
||||
override fun onCallManagedByOtherSession(callId: String) {
|
||||
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
|
||||
currentCall = null
|
||||
|
@ -332,7 +332,7 @@ class HomeDetailFragment @Inject constructor(
|
||||
context = requireContext(),
|
||||
callId = call.callId,
|
||||
roomId = call.roomId,
|
||||
otherUserId = call.otherUserId,
|
||||
otherUserId = call.opponentUserId,
|
||||
isIncomingCall = !call.isOutgoing,
|
||||
isVideoCall = call.isVideoCall,
|
||||
mode = null
|
||||
|
@ -1962,7 +1962,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
context = requireContext(),
|
||||
callId = call.callId,
|
||||
roomId = call.roomId,
|
||||
otherUserId = call.otherUserId,
|
||||
otherUserId = call.opponentUserId,
|
||||
isIncomingCall = !call.isOutgoing,
|
||||
isVideoCall = call.isVideoCall,
|
||||
mode = null
|
||||
|
Loading…
Reference in New Issue
Block a user