feat: some connection and service launching / ring lifecycle

This commit is contained in:
jubb
2021-11-09 17:15:22 +11:00
parent 3e4bab678b
commit 2e973146a3
5 changed files with 218 additions and 94 deletions

View File

@@ -10,6 +10,7 @@ import android.os.Bundle
import android.view.MenuItem
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@@ -17,6 +18,7 @@ import network.loki.messenger.R
import org.session.libsession.utilities.Address
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.webrtc.CallViewModel
import org.webrtc.IceCandidate
import java.util.*
@@ -72,7 +74,11 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
// repeat on start or something
}
if (intent.action == ACTION_ANSWER) {
// answer via ViewModel
val answerIntent = WebRtcCallService.acceptCallIntent(this)
startService(answerIntent)
}
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {

View File

@@ -13,6 +13,8 @@ import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import androidx.core.content.ContextCompat
import dagger.hilt.android.AndroidEntryPoint
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.FutureTaskListener
import org.session.libsession.utilities.Util
@@ -46,7 +48,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
private val TAG = Log.tag(WebRtcCallService::class.java)
const val ACTION_INCOMING_CALL = "CALL_INCOMING"
const val ACTION_INCOMING_RING = "RING_INCOMING"
const val ACTION_OUTGOING_CALL = "CALL_OUTGOING"
const val ACTION_ANSWER_CALL = "ANSWER_CALL"
const val ACTION_DENY_CALL = "DENY_CALL"
@@ -90,16 +92,35 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
.setAction(ACTION_OUTGOING_CALL)
.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID) =
fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID, callTime: Long) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_INCOMING_CALL)
.setAction(ACTION_INCOMING_RING)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
.putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis())
.putExtra(EXTRA_TIMESTAMP, callTime)
fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_RESPONSE_MESSAGE)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
fun iceCandidates(context: Context, iceCandidates: List<IceCandidate>, callId: UUID) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_ICE_MESSAGE)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray())
.putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray())
.putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray())
fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
fun remoteHangupIntent(context: Context, callId: UUID) = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_REMOTE_HANGUP)
.putExtra(EXTRA_CALL_ID, callId)
fun hangupIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP)
fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) {
@@ -149,10 +170,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
if (intent == null || intent.action == null) return START_NOT_STICKY
serviceExecutor.execute {
val action = intent.action
Log.d("Loki", "Handling ${intent.action}")
when {
action == ACTION_INCOMING_CALL && isBusy() -> handleBusyCall(intent)
action == ACTION_INCOMING_RING && isBusy() -> handleBusyCall(intent)
action == ACTION_REMOTE_BUSY -> handleBusyMessage(intent)
action == ACTION_INCOMING_CALL -> handleIncomingCall(intent)
action == ACTION_INCOMING_RING && isIdle() -> handleIncomingRing(intent)
action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent)
action == ACTION_ANSWER_CALL -> handleAnswerCall(intent)
action == ACTION_DENY_CALL -> handleDenyCall(intent)
@@ -204,7 +226,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
private fun handleBusyCall(intent: Intent) {
val recipient = getRemoteRecipient(intent)
val callId = getCallId(intent)
val callState = callManager.currentConnectionState
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -215,7 +236,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
STATE_ANSWERING -> setCallInProgressNotification(TYPE_INCOMING_CONNECTING, callManager.recipient)
STATE_LOCAL_RINGING -> setCallInProgressNotification(TYPE_INCOMING_RINGING, callManager.recipient)
STATE_CONNECTED -> setCallInProgressNotification(TYPE_ESTABLISHED, callManager.recipient)
else -> throw AssertionError()
}
}
@@ -244,48 +264,19 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
}, WebRtcCallActivity.BUSY_SIGNAL_DELAY_FINISH)
}
private fun handleIncomingCall(intent: Intent) {
if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming on non-idle")
private fun handleIncomingRing(intent: Intent) {
if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming ring on non-idle")
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
callManager.postConnectionEvent(STATE_ANSWERING)
val callId = getCallId(intent)
callManager.callId = callId
callManager.clearPendingIceUpdates()
val recipient = getRemoteRecipient(intent)
callManager.recipient = recipient
if (isIncomingMessageExpired(intent)) {
insertMissedCall(recipient, true)
terminate()
return
}
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
}
timeoutExecutor.schedule(TimeoutRunnable(callId, this), 2, TimeUnit.MINUTES)
callManager.initializeVideo(this)
val expectedState = callManager.currentConnectionState
val expectedCallId = callManager.callId
try {
val answerFuture = callManager.onIncomingCall(offer, this) // add is always turn here
answerFuture.fail { e ->
if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) {
Log.e(TAG, e)
insertMissedCall(recipient, true)
terminate()
}
}
callManager.postViewModelState(CallViewModel.State.CALL_INCOMING)
// lock manager update phone state processing here
} catch (e: Exception) {
Log.e(TAG,e)
terminate()
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient)
}
callManager.onIncomingRing(offer, callId, recipient, timestamp)
callManager.clearPendingIceUpdates()
callManager.postConnectionEvent(STATE_LOCAL_RINGING)
}
private fun handleOutgoingCall(intent: Intent) {
@@ -304,7 +295,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING)
setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient)
// TODO: DatabaseComponent.get(this).insertOutgoingCall(callManager.recipient!!.address)
timeoutExecutor.schedule(TimeoutRunnable(callId, this), 2, TimeUnit.MINUTES)
timeoutExecutor.schedule(TimeoutRunnable(callId, this), 5, TimeUnit.MINUTES)
val expectedState = callManager.currentConnectionState
val expectedCallId = callManager.callId
@@ -330,17 +321,50 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
return
}
if (callManager.callNotSetup()) {
throw AssertionError("assert")
}
// DatabaseComponent.get(this).smsDatabase().insertReceivedCall(recipient)
val (callId, recipient) = callManager.handleAnswerCall()
val pending = callManager.pendingOffer ?: return
val callId = callManager.callId ?: return
val recipient = callManager.recipient ?: return
val timestamp = callManager.pendingOfferTime
intent.putExtra(EXTRA_CALL_ID, callId)
intent.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
handleCallConnected(intent)
intent.putExtra(EXTRA_REMOTE_DESCRIPTION, pending)
intent.putExtra(EXTRA_TIMESTAMP, timestamp)
callManager.postConnectionEvent(STATE_ANSWERING)
if (isIncomingMessageExpired(intent)) {
insertMissedCall(recipient, true)
terminate()
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
}
timeoutExecutor.schedule(TimeoutRunnable(callId, this), 5, TimeUnit.MINUTES)
callManager.initializeVideo(this)
val expectedState = callManager.currentConnectionState
val expectedCallId = callManager.callId
try {
val answerFuture = callManager.onIncomingCall(this) // add is always turn here
answerFuture.fail { e ->
if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) {
Log.e(TAG, e)
insertMissedCall(recipient, true)
terminate()
}
}
callManager.postViewModelState(CallViewModel.State.CALL_INCOMING)
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING)
} catch (e: Exception) {
Log.e(TAG,e)
terminate()
}
// DatabaseComponent.get(this).smsDatabase().insertReceivedCall(recipient)
}
private fun handleDenyCall(intent: Intent) {
@@ -367,16 +391,18 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
private fun handleRemoteHangup(intent: Intent) {
if (callManager.callId != getCallId(intent)) {
Log.e(TAG, "Hangup for non-active call...")
terminate()
return
}
callManager.handleRemoteHangup()
if (callManager.currentConnectionState in arrayOf(STATE_ANSWERING, STATE_LOCAL_RINGING)) {
if (callManager.currentConnectionState in arrayOf(STATE_REMOTE_RINGING, STATE_ANSWERING, STATE_LOCAL_RINGING)) {
callManager.recipient?.let { recipient ->
insertMissedCall(recipient, true)
}
}
terminate()
}
private fun handleSetMuteAudio(intent: Intent) {
@@ -445,7 +471,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
}
private fun handleIceConnected(intent: Intent) {
if (callManager.currentConnectionState == STATE_ANSWERING) {
val recipient = callManager.recipient ?: return
callManager.postConnectionEvent(STATE_CONNECTED)
}
}
private fun handleCallConnected(intent: Intent) {

View File

@@ -9,9 +9,11 @@ import kotlinx.serialization.json.put
import nl.komponents.kovenant.Promise
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.Debouncer
import org.session.libsession.utilities.Util
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
@@ -86,12 +88,15 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
private var localCameraState: CameraState = CameraState.UNKNOWN
private var bluetoothAvailable = false
val currentConnectionState = (_connectionEvents.value as StateEvent.CallStateUpdate).state
val currentConnectionState
get() = (_connectionEvents.value as StateEvent.CallStateUpdate).state
private val networkExecutor = Executors.newSingleThreadExecutor()
private var eglBase: EglBase? = null
var pendingOffer: String? = null
var pendingOfferTime: Long = -1
var callId: UUID? = null
var recipient: Recipient? = null
@@ -103,6 +108,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
private val pendingOutgoingIceUpdates = ArrayDeque<IceCandidate>()
private val pendingIncomingIceUpdates = ArrayDeque<IceCandidate>()
private val outgoingIceDebouncer = Debouncer(2_000L)
private var localRenderer: SurfaceViewRenderer? = null
private var remoteRenderer: SurfaceViewRenderer? = null
private var peerConnectionFactory: PeerConnectionFactory? = null
@@ -117,6 +124,9 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
}
fun startOutgoingRinger(ringerType: OutgoingRinger.Type) {
if (ringerType == OutgoingRinger.Type.RINGING) {
signalAudioManager.handleCommand(AudioManagerCommand.UpdateAudioDeviceState)
}
signalAudioManager.handleCommand(AudioManagerCommand.StartOutgoingRinger(ringerType))
}
@@ -209,16 +219,49 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
peerConnectionObservers.forEach { listener -> listener.onIceGatheringChange(newState) }
}
override fun onIceCandidate(iceCandidate: IceCandidate?) {
override fun onIceCandidate(iceCandidate: IceCandidate) {
peerConnectionObservers.forEach { listener -> listener.onIceCandidate(iceCandidate) }
val expectedCallId = this.callId ?: return
val expectedRecipient = this.recipient ?: return
pendingOutgoingIceUpdates.add(iceCandidate)
outgoingIceDebouncer.publish {
val currentCallId = this.callId ?: return@publish
val currentRecipient = this.recipient ?: return@publish
if (currentCallId == expectedCallId && expectedRecipient == currentRecipient) {
val currentPendings = mutableSetOf<IceCandidate>()
while (pendingOutgoingIceUpdates.isNotEmpty()) {
currentPendings.add(pendingOutgoingIceUpdates.pop())
}
val sdps = currentPendings.map { it.sdp }
val sdpMLineIndexes = currentPendings.map { it.sdpMLineIndex }
val sdpMids = currentPendings.map { it.sdpMid }
MessageSender.sendNonDurably(CallMessage(
ICE_CANDIDATES,
sdps = sdps,
sdpMLineIndexes = sdpMLineIndexes,
sdpMids = sdpMids,
currentCallId
), currentRecipient.address)
}
}
}
override fun onIceCandidatesRemoved(candidates: Array<out IceCandidate>?) {
peerConnectionObservers.forEach { listener -> listener.onIceCandidatesRemoved(candidates) }
}
override fun onAddStream(p0: MediaStream?) {
peerConnectionObservers.forEach { listener -> listener.onAddStream(p0) }
override fun onAddStream(stream: MediaStream) {
peerConnectionObservers.forEach { listener -> listener.onAddStream(stream) }
for (track in stream.audioTracks) {
track.setEnabled(true)
}
if (stream.videoTracks != null && stream.videoTracks.size == 1) {
val videoTrack = stream.videoTracks.first()
videoTrack.setEnabled(true)
videoTrack.addSink(remoteRenderer)
}
}
override fun onRemoveStream(p0: MediaStream?) {
@@ -254,13 +297,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
signalAudioManager.handleCommand(AudioManagerCommand())
}
private fun CallMessage.iceCandidates(): List<IceCandidate> {
val candidateSize = sdpMids.size
return (0 until candidateSize).map { i ->
IceCandidate(sdpMids[i], sdpMLineIndexes[i], sdps[i])
}
}
private fun CallState.withState(vararg expected: CallState, transition: () -> Unit) {
if (this in expected) transition()
else Log.w(TAG,"Tried to transition state $this but expected $expected")
@@ -293,9 +329,19 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
localCameraState = newCameraState
}
fun onIncomingCall(offer: String, context: Context, isAlwaysTurn: Boolean = false): Promise<Unit, Exception> {
fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long) {
if (currentConnectionState != CallState.STATE_IDLE) return
this.callId = callId
this.recipient = recipient
this.pendingOffer = offer
this.pendingOfferTime = callTime
}
fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise<Unit, Exception> {
val callId = callId ?: return Promise.ofFail(NullPointerException("callId is null"))
val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null"))
val offer = pendingOffer ?: return Promise.ofFail(NullPointerException("pendingOffer is null"))
val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null"))
val local = localRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null"))
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
@@ -310,6 +356,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
)
peerConnection = connection
localCameraState = connection.getCameraState()
val dataChannel = connection.createDataChannel(DATA_CHANNEL_NAME)
dataChannel.registerObserver(this)
connection.setRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer))
val answer = connection.createAnswer(MediaConstraints())
connection.setLocalDescription(answer)
@@ -323,7 +371,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
val candidate = pendingIncomingIceUpdates.pop() ?: break
connection.addIceCandidate(candidate)
}
return answerMessage // TODO: maybe add success state update
return answerMessage.success {
pendingOffer = null
pendingOfferTime = -1
} // TODO: maybe add success state update
}
fun onOutgoingCall(context: Context, isAlwaysTurn: Boolean = false): Promise<Unit, Exception> {
@@ -346,6 +397,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
isAlwaysTurn
)
peerConnection = connection
localCameraState = connection.getCameraState()
val dataChannel = connection.createDataChannel(DATA_CHANNEL_NAME)
dataChannel.registerObserver(this)
@@ -363,14 +415,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
fun callNotSetup(): Boolean =
peerConnection == null || dataChannel == null || recipient == null || callId == null
fun handleAnswerCall(): Pair<UUID, Recipient> {
peerConnection?.let { connection ->
connection.setAudioEnabled(true)
connection.setVideoEnabled(true)
}
return callId!! to recipient!!
}
fun handleDenyCall() {
val callId = callId ?: return
val recipient = recipient ?: return
@@ -476,6 +520,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
fun handleRemoteIceCandidate(iceCandidates: List<IceCandidate>, callId: UUID) {
if (callId != this.callId) {
Log.w(TAG, "Got remote ice candidates for a call that isn't active")
return
}
peerConnection?.let { connection ->

View File

@@ -1,18 +1,18 @@
package org.thoughtcrime.securesms.webrtc
import android.content.Context
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.coroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.utilities.Address
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.*
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.service.WebRtcCallService
import javax.inject.Inject
import org.webrtc.IceCandidate
class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
@@ -21,25 +21,75 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
lifecycle.coroutineScope.launch {
while (isActive) {
val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive()
when {
// TODO: handle messages as they come in
nextMessage.type == OFFER -> incomingCall(nextMessage)
Log.d("Loki", nextMessage.toString())
when (nextMessage.type) {
OFFER -> incomingCall(nextMessage)
ANSWER -> incomingAnswer(nextMessage)
END_CALL -> incomingHangup(nextMessage)
ICE_CANDIDATES -> handleIceCandidates(nextMessage)
PRE_OFFER -> incomingCall(nextMessage)
PROVISIONAL_ANSWER -> {} // TODO: if necessary
}
}
}
}
private fun incomingHangup(callMessage: CallMessage) {
val callId = callMessage.callId ?: return
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)
context.startService(hangupIntent)
}
private fun incomingAnswer(callMessage: CallMessage) {
val recipientAddress = callMessage.sender ?: return
val callId = callMessage.callId ?: return
val sdp = callMessage.sdps.firstOrNull() ?: return
val answerIntent = WebRtcCallService.incomingAnswer(
context = context,
address = Address.fromSerialized(recipientAddress),
sdp = sdp,
callId = callId
)
context.startService(answerIntent)
}
private fun handleIceCandidates(callMessage: CallMessage) {
val callId = callMessage.callId ?: return
val iceCandidates = callMessage.iceCandidates()
if (iceCandidates.isEmpty()) return
val iceIntent = WebRtcCallService.iceCandidates(
context = context,
iceCandidates = iceCandidates,
callId = callId
)
context.startService(iceIntent)
}
private fun incomingCall(callMessage: CallMessage) {
val recipientAddress = callMessage.recipient ?: return
val recipientAddress = callMessage.sender ?: return
val callId = callMessage.callId ?: return
val sdp = callMessage.sdps.firstOrNull() ?: return
val incomingIntent = WebRtcCallService.incomingCall(
context = context,
address = Address.fromSerialized(recipientAddress),
sdp = sdp,
callId = callId
callId = callId,
callTime = callMessage.sentTimestamp ?: -1L
)
context.startService(incomingIntent)
ContextCompat.startForegroundService(context, incomingIntent)
}
private fun CallMessage.iceCandidates(): List<IceCandidate> {
if (sdpMids.size != sdpMLineIndexes.size || sdpMLineIndexes.size != sdps.size) {
return listOf() // uneven sdp numbers
}
val candidateSize = sdpMids.size
return (0 until candidateSize).map { i ->
IceCandidate(sdpMids[i], sdpMLineIndexes[i], sdps[i])
}
}
}

View File

@@ -1,14 +1,8 @@
package org.thoughtcrime.securesms.webrtc
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.session.libsession.messaging.messages.control.CallMessage
import org.webrtc.*
import javax.inject.Inject
@HiltViewModel