Merge branch 'feature/fga/voip_v1_start' into feature/fga/voip_timeline
This commit is contained in:
commit
e817844c5d
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
|
#Mon Dec 07 18:05:35 CET 2020
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=22449f5231796abd892c98b2a07c9ceebe4688d192cd2d6763f8e3bf8acbedeb
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.call
|
package org.matrix.android.sdk.api.session.call
|
||||||
|
|
||||||
|
|
||||||
sealed class CallState {
|
sealed class CallState {
|
||||||
|
|
||||||
/** Idle, setting up objects */
|
/** Idle, setting up objects */
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -26,5 +26,5 @@ public enum MxPeerConnectionState {
|
|||||||
CONNECTED,
|
CONNECTED,
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
FAILED,
|
FAILED,
|
||||||
CLOSED;
|
CLOSED
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -35,7 +35,7 @@ data class CallNegotiateContent(
|
|||||||
/**
|
/**
|
||||||
* Required. The time in milliseconds that the negotiation is valid for. Once exceeded the sender
|
* 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.
|
* of the negotiate event should consider the negotiation failed (timed out) and the recipient should ignore it.
|
||||||
**/
|
**/
|
||||||
@Json(name = "lifetime") val lifetime: Int?,
|
@Json(name = "lifetime") val lifetime: Int?,
|
||||||
/**
|
/**
|
||||||
* Required. The session description object
|
* Required. The session description object
|
||||||
@ -45,9 +45,9 @@ data class CallNegotiateContent(
|
|||||||
/**
|
/**
|
||||||
* Required. The version of the VoIP specification this message adheres to.
|
* Required. The version of the VoIP specification this message adheres to.
|
||||||
*/
|
*/
|
||||||
@Json(name = "version") override val version: String?,
|
@Json(name = "version") override val version: String?
|
||||||
|
|
||||||
): CallSignallingContent {
|
): CallSignallingContent {
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Description(
|
data class Description(
|
||||||
/**
|
/**
|
||||||
@ -59,6 +59,4 @@ data class CallNegotiateContent(
|
|||||||
*/
|
*/
|
||||||
@Json(name = "sdp") val sdp: String?
|
@Json(name = "sdp") val sdp: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,5 +36,5 @@ data class CallRejectContent(
|
|||||||
/**
|
/**
|
||||||
* Required. The version of the VoIP specification this message adheres to.
|
* Required. The version of the VoIP specification this message adheres to.
|
||||||
*/
|
*/
|
||||||
@Json(name = "version") override val version: String?,
|
@Json(name = "version") override val version: String?
|
||||||
):CallSignallingContent
|
):CallSignallingContent
|
||||||
|
@ -40,5 +40,5 @@ data class CallSelectAnswerContent(
|
|||||||
/**
|
/**
|
||||||
* Required. The version of the VoIP specification this message adheres to.
|
* Required. The version of the VoIP specification this message adheres to.
|
||||||
*/
|
*/
|
||||||
@Json(name = "version") override val version: String?,
|
@Json(name = "version") override val version: String?
|
||||||
): CallSignallingContent
|
): CallSignallingContent
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -71,7 +71,6 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
val domainEvent = event.asDomain()
|
val domainEvent = event.asDomain()
|
||||||
// decryptIfNeeded(domainEvent)
|
|
||||||
processors.filter {
|
processors.filter {
|
||||||
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
||||||
}.forEach {
|
}.forEach {
|
||||||
@ -83,6 +82,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||||||
.findAll()
|
.findAll()
|
||||||
.deleteAllFromRealm()
|
.deleteAllFromRealm()
|
||||||
}
|
}
|
||||||
|
processors.forEach { it.onPostProcess() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,4 +25,12 @@ internal interface EventInsertLiveProcessor {
|
|||||||
fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean
|
fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean
|
||||||
|
|
||||||
suspend fun process(realm: Realm, event: Event)
|
suspend fun process(realm: Realm, event: Event)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after transaction.
|
||||||
|
* Maybe you prefer to process the events outside of the realm transaction.
|
||||||
|
*/
|
||||||
|
suspend fun onPostProcess() {
|
||||||
|
// Noop by default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,16 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.call
|
package org.matrix.android.sdk.internal.session.call
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
import io.realm.Realm
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class CallEventProcessor @Inject constructor(
|
internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
|
||||||
@UserId private val userId: String,
|
: EventInsertLiveProcessor {
|
||||||
private val callService: DefaultCallSignalingService
|
|
||||||
) : EventInsertLiveProcessor {
|
|
||||||
|
|
||||||
private val allowedTypes = listOf(
|
private val allowedTypes = listOf(
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
@ -41,6 +38,8 @@ internal class CallEventProcessor @Inject constructor(
|
|||||||
EventType.ENCRYPTED
|
EventType.ENCRYPTED
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val eventsToPostProcess = mutableListOf<Event>()
|
||||||
|
|
||||||
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
||||||
if (insertType != EventInsertType.INCREMENTAL_SYNC) {
|
if (insertType != EventInsertType.INCREMENTAL_SYNC) {
|
||||||
return false
|
return false
|
||||||
@ -49,10 +48,17 @@ internal class CallEventProcessor @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun process(realm: Realm, event: Event) {
|
override suspend fun process(realm: Realm, event: Event) {
|
||||||
update(realm, event)
|
eventsToPostProcess.add(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update(realm: Realm, event: Event) {
|
override suspend fun onPostProcess() {
|
||||||
|
eventsToPostProcess.forEach {
|
||||||
|
dispatchToCallSignalingHandlerIfNeeded(it)
|
||||||
|
}
|
||||||
|
eventsToPostProcess.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
// TODO might check if an invite is not closed (hangup/answsered) in the same event batch?
|
// TODO might check if an invite is not closed (hangup/answsered) in the same event batch?
|
||||||
event.roomId ?: return Unit.also {
|
event.roomId ?: return Unit.also {
|
||||||
@ -63,10 +69,6 @@ internal class CallEventProcessor @Inject constructor(
|
|||||||
// To old to ring?
|
// To old to ring?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
event.ageLocalTs
|
callSignalingHandler.onCallEvent(event)
|
||||||
if (EventType.isCallEvent(event.getClearType())) {
|
|
||||||
callService.onCallEvent(event)
|
|
||||||
}
|
|
||||||
Timber.v("$realm : $userId")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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.session.call.CallListener
|
||||||
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
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.CallNegotiateContent
|
||||||
|
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.Optional
|
||||||
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
|
||||||
|
private val mxCallFactory: MxCallFactory,
|
||||||
|
@UserId private val userId: String) {
|
||||||
|
|
||||||
|
private val callListeners = mutableSetOf<CallListener>()
|
||||||
|
private val callListenersDispatcher = CallListenersDispatcher(callListeners)
|
||||||
|
|
||||||
|
fun addCallListener(listener: CallListener) {
|
||||||
|
callListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCallListener(listener: CallListener) {
|
||||||
|
callListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCallEvent(event: Event) {
|
||||||
|
when (event.getClearType()) {
|
||||||
|
EventType.CALL_ANSWER -> {
|
||||||
|
handleCallAnswerEvent(event)
|
||||||
|
}
|
||||||
|
EventType.CALL_INVITE -> {
|
||||||
|
handleCallInviteEvent(event)
|
||||||
|
}
|
||||||
|
EventType.CALL_HANGUP -> {
|
||||||
|
handleCallHangupEvent(event)
|
||||||
|
}
|
||||||
|
EventType.CALL_REJECT -> {
|
||||||
|
handleCallRejectEvent(event)
|
||||||
|
}
|
||||||
|
EventType.CALL_CANDIDATES -> {
|
||||||
|
handleCallCandidatesEvent(event)
|
||||||
|
}
|
||||||
|
EventType.CALL_SELECT_ANSWER -> {
|
||||||
|
handleCallSelectAnswerEvent(event)
|
||||||
|
}
|
||||||
|
EventType.CALL_NEGOTIATE -> {
|
||||||
|
handleCallNegotiateEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCallNegotiateEvent(event: Event) {
|
||||||
|
val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return
|
||||||
|
val call = content.getCall() ?: return
|
||||||
|
if (call.ourPartyId == content.partyId) {
|
||||||
|
// Ignore remote echo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callListenersDispatcher.onCallNegotiateReceived(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (call.ourPartyId == content.partyId) {
|
||||||
|
// Ignore remote echo
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
|
||||||
|
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
|
||||||
|
// party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen
|
||||||
|
// a partner yet but we're treating the hangup as a reject as per VoIP v0)
|
||||||
|
if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
|
||||||
|
Timber.v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (call.state != CallState.Terminated) {
|
||||||
|
activeCallHandler.removeCall(content.callId)
|
||||||
|
callListenersDispatcher.onCallHangupReceived(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCallInviteEvent(event: Event) {
|
||||||
|
if (event.senderId == userId) {
|
||||||
|
// ignore invites you send
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.roomId == null || event.senderId == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val content = event.getClearContent().toModel<CallInviteContent>() ?: return
|
||||||
|
val incomingCall = mxCallFactory.createIncomingCall(
|
||||||
|
roomId = event.roomId,
|
||||||
|
senderId = event.senderId,
|
||||||
|
content = content
|
||||||
|
) ?: return
|
||||||
|
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 MxCall.partyIdsMatches(contentSignallingContent: CallSignallingContent): Boolean {
|
||||||
|
return opponentPartyId?.getOrNull() == contentSignallingContent.partyId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CallSignallingContent.getCall(): MxCall? {
|
||||||
|
val currentCall = callId?.let {
|
||||||
|
activeCallHandler.getCallWithId(it)
|
||||||
|
}
|
||||||
|
if (currentCall == null) {
|
||||||
|
Timber.v("Call for content: $this is null")
|
||||||
|
}
|
||||||
|
return currentCall
|
||||||
|
}
|
||||||
|
}
|
@ -16,116 +16,46 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.call
|
package org.matrix.android.sdk.internal.session.call
|
||||||
|
|
||||||
import android.os.SystemClock
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.call.CallListener
|
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.CallSignalingService
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
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.call.TurnServerResponse
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
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.CallNegotiateContent
|
|
||||||
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.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.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
|
||||||
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.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultCallSignalingService @Inject constructor(
|
internal class DefaultCallSignalingService @Inject constructor(
|
||||||
@UserId
|
private val callSignalingHandler: CallSignalingHandler,
|
||||||
private val userId: String,
|
private val mxCallFactory: MxCallFactory,
|
||||||
@DeviceId
|
|
||||||
private val deviceId: String?,
|
|
||||||
private val activeCallHandler: ActiveCallHandler,
|
private val activeCallHandler: ActiveCallHandler,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
|
||||||
private val eventSenderProcessor: EventSenderProcessor,
|
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val turnServerTask: GetTurnServerTask
|
private val turnServerDataSource: TurnServerDataSource
|
||||||
) : CallSignalingService {
|
) : CallSignalingService {
|
||||||
|
|
||||||
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.
|
|
||||||
private val MIN_TTL = 60
|
|
||||||
|
|
||||||
private val now = { SystemClock.elapsedRealtime() / 1000 }
|
|
||||||
|
|
||||||
private var expiresAt: Long = 0
|
|
||||||
|
|
||||||
var data: TurnServerResponse? = null
|
|
||||||
get() = if (expiresAt > now()) field else null
|
|
||||||
set(value) {
|
|
||||||
expiresAt = now() + (value?.ttl ?: 0) - MIN_TTL
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
|
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
|
||||||
if (cachedTurnServerResponse.data != null) {
|
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) {
|
||||||
cachedTurnServerResponse.data?.let { callback.onSuccess(it) }
|
turnServerDataSource.getTurnServer()
|
||||||
return NoOpCancellable
|
|
||||||
}
|
}
|
||||||
return turnServerTask
|
|
||||||
.configureWith(GetTurnServerTask.Params) {
|
|
||||||
this.callback = object : MatrixCallback<TurnServerResponse> {
|
|
||||||
override fun onSuccess(data: TurnServerResponse) {
|
|
||||||
cachedTurnServerResponse.data = data
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
||||||
val call = MxCallImpl(
|
return mxCallFactory.createOutgoingCall(roomId, otherUserId, isVideoCall).also {
|
||||||
callId = UUID.randomUUID().toString(),
|
activeCallHandler.addCall(it)
|
||||||
isOutgoing = true,
|
|
||||||
roomId = roomId,
|
|
||||||
userId = userId,
|
|
||||||
ourPartyId = deviceId ?: "",
|
|
||||||
opponentUserId = otherUserId,
|
|
||||||
isVideoCall = isVideoCall,
|
|
||||||
localEchoEventFactory = localEchoEventFactory,
|
|
||||||
eventSenderProcessor = eventSenderProcessor
|
|
||||||
)
|
|
||||||
activeCallHandler.addCall(call).also {
|
|
||||||
return call
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addCallListener(listener: CallListener) {
|
override fun addCallListener(listener: CallListener) {
|
||||||
callListeners.add(listener)
|
callSignalingHandler.addCallListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeCallListener(listener: CallListener) {
|
override fun removeCallListener(listener: CallListener) {
|
||||||
callListeners.remove(listener)
|
callSignalingHandler.removeCallListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCallWithId(callId: String): MxCall? {
|
override fun getCallWithId(callId: String): MxCall? {
|
||||||
@ -137,154 +67,6 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||||||
return activeCallHandler.getActiveCallsLiveData().value?.isNotEmpty() == true
|
return activeCallHandler.getActiveCallsLiveData().value?.isNotEmpty() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun onCallEvent(event: Event) {
|
|
||||||
when (event.getClearType()) {
|
|
||||||
EventType.CALL_ANSWER -> {
|
|
||||||
handleCallAnswerEvent(event)
|
|
||||||
}
|
|
||||||
EventType.CALL_INVITE -> {
|
|
||||||
handleCallInviteEvent(event)
|
|
||||||
}
|
|
||||||
EventType.CALL_HANGUP -> {
|
|
||||||
handleCallHangupEvent(event)
|
|
||||||
}
|
|
||||||
EventType.CALL_REJECT -> {
|
|
||||||
handleCallRejectEvent(event)
|
|
||||||
}
|
|
||||||
EventType.CALL_CANDIDATES -> {
|
|
||||||
handleCallCandidatesEvent(event)
|
|
||||||
}
|
|
||||||
EventType.CALL_SELECT_ANSWER -> {
|
|
||||||
handleCallSelectAnswerEvent(event)
|
|
||||||
}
|
|
||||||
EventType.CALL_NEGOTIATE -> {
|
|
||||||
handleCallNegotiateEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleCallNegotiateEvent(event: Event) {
|
|
||||||
val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return
|
|
||||||
val call = content.getCall() ?: return
|
|
||||||
if (call.ourPartyId == content.partyId) {
|
|
||||||
// Ignore remote echo
|
|
||||||
return
|
|
||||||
}
|
|
||||||
callListenersDispatcher.onCallNegotiateReceived(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
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 handleCallInviteEvent(event: Event) {
|
|
||||||
if (event.senderId == userId) {
|
|
||||||
// ignore invites you send
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val content = event.getClearContent().toModel<CallInviteContent>() ?: 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 CallSignallingContent.getCall(): MxCall? {
|
|
||||||
val currentCall = callId?.let {
|
|
||||||
activeCallHandler.getCallWithId(it)
|
|
||||||
}
|
|
||||||
if (currentCall == null) {
|
|
||||||
Timber.v("Call for content: $this is null")
|
|
||||||
}
|
|
||||||
return currentCall
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CALL_TIMEOUT_MS = 120_000
|
const val CALL_TIMEOUT_MS = 120_000
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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.session.call.MxCall
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||||
|
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.call.model.MxCallImpl
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class MxCallFactory @Inject constructor(
|
||||||
|
@DeviceId private val deviceId: String?,
|
||||||
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
|
@UserId private val userId: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun createIncomingCall(roomId: String, senderId: String, content: CallInviteContent): MxCall? {
|
||||||
|
if (content.callId == null) return null
|
||||||
|
return MxCallImpl(
|
||||||
|
callId = content.callId,
|
||||||
|
isOutgoing = false,
|
||||||
|
roomId = roomId,
|
||||||
|
userId = userId,
|
||||||
|
ourPartyId = deviceId ?: "",
|
||||||
|
opponentUserId = senderId,
|
||||||
|
isVideoCall = content.isVideo(),
|
||||||
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
|
eventSenderProcessor = eventSenderProcessor
|
||||||
|
).apply {
|
||||||
|
opponentPartyId = Optional.from(content.partyId)
|
||||||
|
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
||||||
|
return MxCallImpl(
|
||||||
|
callId = UUID.randomUUID().toString(),
|
||||||
|
isOutgoing = true,
|
||||||
|
roomId = roomId,
|
||||||
|
userId = userId,
|
||||||
|
ourPartyId = deviceId ?: "",
|
||||||
|
opponentUserId = otherUserId,
|
||||||
|
isVideoCall = isVideoCall,
|
||||||
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
|
eventSenderProcessor = eventSenderProcessor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 android.os.SystemClock
|
||||||
|
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class TurnServerDataSource @Inject constructor(private val turnServerTask: GetTurnServerTask) {
|
||||||
|
|
||||||
|
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.
|
||||||
|
private val MIN_TTL = 60
|
||||||
|
|
||||||
|
private val now = { SystemClock.elapsedRealtime() / 1000 }
|
||||||
|
|
||||||
|
private var expiresAt: Long = 0
|
||||||
|
|
||||||
|
var data: TurnServerResponse? = null
|
||||||
|
get() = if (expiresAt > now()) field else null
|
||||||
|
set(value) {
|
||||||
|
expiresAt = now() + (value?.ttl ?: 0) - MIN_TTL
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getTurnServer(): TurnServerResponse {
|
||||||
|
return cachedTurnServerResponse.data ?: turnServerTask.execute(GetTurnServerTask.Params).also {
|
||||||
|
cachedTurnServerResponse.data = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -121,8 +121,8 @@ internal class MxCallImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun reject() {
|
override fun reject() {
|
||||||
if(opponentVersion < 1){
|
if (opponentVersion < 1) {
|
||||||
Timber.v("Opponent version is less than 1 (${opponentVersion}): sending hangup instead of reject")
|
Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
|
||||||
hangUp()
|
hangUp()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -203,5 +203,4 @@ internal class MxCallImpl(
|
|||||||
)
|
)
|
||||||
.also { localEchoEventFactory.createLocalEcho(it) }
|
.also { localEchoEventFactory.createLocalEcho(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import android.view.View
|
|||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.core.utils.DebouncedClickListener
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import im.vector.app.features.call.utils.EglUtils
|
import im.vector.app.features.call.utils.EglUtils
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||||
|
@ -30,7 +30,6 @@ import im.vector.app.R
|
|||||||
import kotlinx.android.synthetic.main.view_call_controls.view.*
|
import kotlinx.android.synthetic.main.view_call_controls.view.*
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||||
import org.webrtc.PeerConnection
|
|
||||||
|
|
||||||
class CallControlsView @JvmOverloads constructor(
|
class CallControlsView @JvmOverloads constructor(
|
||||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
|
@ -36,7 +36,6 @@ import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
|||||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import java.util.TimerTask
|
import java.util.TimerTask
|
||||||
|
|
||||||
@ -141,7 +140,6 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -45,6 +45,4 @@ data class VectorCallViewState(
|
|||||||
roomId = callArgs.roomId,
|
roomId = callArgs.roomId,
|
||||||
isVideoCall = callArgs.isVideoCall
|
isVideoCall = callArgs.isVideoCall
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
|
|||||||
?.webRtcCallManager()
|
?.webRtcCallManager()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
|
|
||||||
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
|
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
|
||||||
CALL_ACTION_REJECT -> {
|
CALL_ACTION_REJECT -> {
|
||||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
|
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
|
||||||
|
@ -36,5 +36,3 @@ fun SdpType.asWebRTC(): SessionDescription.Type {
|
|||||||
SessionDescription.Type.ANSWER
|
SessionDescription.Type.ANSWER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
// 2. Access camera (if video call) + microphone, create local stream
|
// 2. Access camera (if video call) + microphone, create local stream
|
||||||
createLocalStream()
|
createLocalStream()
|
||||||
attachViewRenderersInternal()
|
attachViewRenderersInternal()
|
||||||
Timber.v("## VOIP remoteCandidateSource ${remoteCandidateSource}")
|
Timber.v("## VOIP remoteCandidateSource $remoteCandidateSource")
|
||||||
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
|
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
|
||||||
Timber.v("## VOIP adding remote ice candidate $it")
|
Timber.v("## VOIP adding remote ice candidate $it")
|
||||||
peerConnection?.addIceCandidate(it)
|
peerConnection?.addIceCandidate(it)
|
||||||
@ -383,7 +383,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
createAnswer()?.also {
|
createAnswer()?.also {
|
||||||
mxCall.accept(it.description)
|
mxCall.accept(it.description)
|
||||||
}
|
}
|
||||||
Timber.v("## VOIP remoteCandidateSource ${remoteCandidateSource}")
|
Timber.v("## VOIP remoteCandidateSource $remoteCandidateSource")
|
||||||
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
|
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
|
||||||
Timber.v("## VOIP adding remote ice candidate $it")
|
Timber.v("## VOIP adding remote ice candidate $it")
|
||||||
peerConnection?.addIceCandidate(it)
|
peerConnection?.addIceCandidate(it)
|
||||||
@ -554,9 +554,9 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
for (transceiver in peerConnection?.transceivers ?: emptyList()) {
|
for (transceiver in peerConnection?.transceivers ?: emptyList()) {
|
||||||
val trackOnHold = transceiver.currentDirection == RtpTransceiver.RtpTransceiverDirection.INACTIVE
|
val trackOnHold = transceiver.currentDirection == RtpTransceiver.RtpTransceiverDirection.INACTIVE
|
||||||
|| transceiver.currentDirection == RtpTransceiver.RtpTransceiverDirection.RECV_ONLY
|
|| transceiver.currentDirection == RtpTransceiver.RtpTransceiverDirection.RECV_ONLY
|
||||||
if (!trackOnHold) callOnHold = false;
|
if (!trackOnHold) callOnHold = false
|
||||||
}
|
}
|
||||||
return callOnHold;
|
return callOnHold
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRemoteOnHold(onHold: Boolean) {
|
fun updateRemoteOnHold(onHold: Boolean) {
|
||||||
@ -704,7 +704,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
mxCall.state = CallState.Terminated
|
mxCall.state = CallState.Terminated
|
||||||
//Close tracks ASAP
|
// Close tracks ASAP
|
||||||
localVideoTrack?.setEnabled(false)
|
localVideoTrack?.setEnabled(false)
|
||||||
localVideoTrack?.setEnabled(false)
|
localVideoTrack?.setEnabled(false)
|
||||||
cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
||||||
@ -760,7 +760,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||||||
val type = description?.type
|
val type = description?.type
|
||||||
val sdpText = description?.sdp
|
val sdpText = description?.sdp
|
||||||
if (type == null || sdpText == null) {
|
if (type == null || sdpText == null) {
|
||||||
Timber.i("Ignoring invalid m.call.negotiate event");
|
Timber.i("Ignoring invalid m.call.negotiate event")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val peerConnection = peerConnection ?: return@launch
|
val peerConnection = peerConnection ?: return@launch
|
||||||
|
@ -25,8 +25,6 @@ import im.vector.app.core.services.BluetoothHeadsetReceiver
|
|||||||
import im.vector.app.core.services.CallService
|
import im.vector.app.core.services.CallService
|
||||||
import im.vector.app.core.services.WiredHeadsetStateReceiver
|
import im.vector.app.core.services.WiredHeadsetStateReceiver
|
||||||
import im.vector.app.features.call.CallAudioManager
|
import im.vector.app.features.call.CallAudioManager
|
||||||
import im.vector.app.features.call.CameraType
|
|
||||||
import im.vector.app.features.call.CaptureFormat
|
|
||||||
import im.vector.app.features.call.VectorCallActivity
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
import im.vector.app.features.call.utils.EglUtils
|
import im.vector.app.features.call.utils.EglUtils
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
import im.vector.app.push.fcm.FcmHelper
|
||||||
@ -46,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerConten
|
|||||||
import org.webrtc.DefaultVideoDecoderFactory
|
import org.webrtc.DefaultVideoDecoderFactory
|
||||||
import org.webrtc.DefaultVideoEncoderFactory
|
import org.webrtc.DefaultVideoEncoderFactory
|
||||||
import org.webrtc.PeerConnectionFactory
|
import org.webrtc.PeerConnectionFactory
|
||||||
import org.webrtc.SurfaceViewRenderer
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -230,7 +227,8 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
)
|
)
|
||||||
currentCall = webRtcCall
|
currentCall = webRtcCall
|
||||||
callsByCallId[mxCall.callId] = webRtcCall
|
callsByCallId[mxCall.callId] = webRtcCall
|
||||||
callsByRoomId.getOrPut(mxCall.roomId, { ArrayList() }).add(webRtcCall)
|
callsByRoomId.getOrPut(mxCall.roomId) { ArrayList() }
|
||||||
|
.add(webRtcCall)
|
||||||
return webRtcCall
|
return webRtcCall
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +330,7 @@ class WebRtcCallManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
val selectedPartyId = callSelectAnswerContent.selectedPartyId
|
val selectedPartyId = callSelectAnswerContent.selectedPartyId
|
||||||
if (selectedPartyId != call.mxCall.ourPartyId) {
|
if (selectedPartyId != call.mxCall.ourPartyId) {
|
||||||
Timber.i("Got select_answer for party ID ${selectedPartyId}: we are party ID ${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
|
// The other party has picked somebody else's answer
|
||||||
call.endCall(false)
|
call.endCall(false)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user