From 71bb04cb34c7e322123a8abe51a9bf006cbca806 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 28 Oct 2021 17:06:14 +1100 Subject: [PATCH] refactor: moving call code around to service and viewmodel interactions --- app/build.gradle | 10 +-- .../securesms/calls/WebRtcTestsActivity.kt | 36 ++------- .../securesms/service/WebRtcCallService.kt | 11 +++ .../securesms/webrtc/CallManager.kt | 78 ++++++++++++++++++- .../securesms/webrtc/CallViewModel.kt | 49 +++++++++++- 5 files changed, 148 insertions(+), 36 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3a33250aa1..74b905f34f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,11 +37,11 @@ dependencies { implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.exifinterface:exifinterface:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-common-java8:2.3.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' + implementation 'androidx.lifecycle:lifecycle-extensions:2.4.0' + implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' implementation 'androidx.activity:activity-ktx:1.2.2' implementation 'androidx.fragment:fragment-ktx:1.3.2' implementation "androidx.core:core-ktx:1.3.2" diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcTestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcTestsActivity.kt index 0168b33e79..86f830fb1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcTestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcTestsActivity.kt @@ -7,8 +7,10 @@ import android.content.Intent import android.content.IntentFilter import android.os.Bundle import android.view.MenuItem +import androidx.activity.viewModels import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.activity_webrtc_tests.* import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -22,10 +24,11 @@ import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.permissions.Permissions +import org.thoughtcrime.securesms.webrtc.CallViewModel import org.webrtc.* import java.util.* - +@AndroidEntryPoint class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection.Observer, SdpObserver, RTCStatsCollectorCallback { @@ -45,25 +48,14 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection } - private val eglBase by lazy { EglBase.create() } - private val surfaceHelper by lazy { SurfaceTextureHelper.create(Thread.currentThread().name, eglBase.eglBaseContext) } + private val viewModel by viewModels() + + private val surfaceHelper by lazy { SurfaceTextureHelper.create(Thread.currentThread().name, viewModel.eglBaseContext) } private val audioSource by lazy { connectionFactory.createAudioSource(MediaConstraints()) } private val videoCapturer by lazy { createCameraCapturer(Camera2Enumerator(this)) } private val acceptedCallMessageHashes = mutableSetOf() - private val connectionFactory by lazy { - - val decoderFactory = DefaultVideoDecoderFactory(eglBase.eglBaseContext) - val encoderFactory = DefaultVideoEncoderFactory(eglBase.eglBaseContext, true, true) - - PeerConnectionFactory.builder() - .setVideoDecoderFactory(decoderFactory) - .setVideoEncoderFactory(encoderFactory) - .setOptions(PeerConnectionFactory.Options()) - .createPeerConnectionFactory() - } - private val candidates: MutableList = mutableListOf() private val iceDebouncer = Debouncer(2_000) @@ -90,20 +82,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection private lateinit var callAddress: Address private lateinit var callId: UUID - private val peerConnection by lazy { - // TODO: in a lokinet world, ice servers shouldn't be needed as .loki addresses should suffice to p2p - val stun = PeerConnection.IceServer.builder("stun:freyr.getsession.org:5349").setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK).createIceServer() - val turn = PeerConnection.IceServer.builder("turn:freyr.getsession.org:5349").setUsername("webrtc").setPassword("webrtc").setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK).createIceServer() - val iceServers = mutableListOf(turn, stun) - val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply { - this.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.ENABLED - this.candidateNetworkPolicy = PeerConnection.CandidateNetworkPolicy.ALL -// this.iceTransportsType = PeerConnection.IceTransportsType.RELAY - } - rtcConfig.keyType = PeerConnection.KeyType.ECDSA - connectionFactory.createPeerConnection(rtcConfig, this)!! - } - override fun onBackPressed() { endCall() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index abf56860a1..5224e5c3ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -11,8 +11,11 @@ import org.thoughtcrime.securesms.dependencies.CallComponent import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.CallManager import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager +import java.sql.CallableStatement import java.util.* import javax.inject.Inject +import kotlin.properties.Delegates +import kotlin.properties.Delegates.observable @AndroidEntryPoint class WebRtcCallService: Service(), SignalAudioManager.EventListener { @@ -22,6 +25,10 @@ class WebRtcCallService: Service(), SignalAudioManager.EventListener { SignalAudioManager(this, this, CallComponent.get(this).callManagerCompat()) } + private enum class CallState { + STATE_IDLE, STATE_DIALING, STATE_ANSWERING, STATE_REMOTE_RINGING, STATE_LOCAL_RINGING, STATE_CONNECTED + } + companion object { private const val ACTION_UPDATE = "UPDATE" private const val ACTION_STOP = "STOP" @@ -74,6 +81,10 @@ class WebRtcCallService: Service(), SignalAudioManager.EventListener { } } + private var state: CallState by observable(CallState.STATE_IDLE) { _, previousValue, newValue -> + + } + override fun onBind(intent: Intent?): IBinder? = null override fun onCreate() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 9526a5a168..1b307cee37 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -3,16 +3,44 @@ package org.thoughtcrime.securesms.webrtc import android.content.Context import com.android.mms.transaction.MessageSender import org.thoughtcrime.securesms.database.Storage +import org.webrtc.* import java.util.concurrent.Executors import javax.inject.Inject class CallManager(private val context: Context, - private val storage: Storage) { + private val storage: Storage): PeerConnection.Observer { private val serviceExecutor = Executors.newSingleThreadExecutor() private val networkExecutor = Executors.newSingleThreadExecutor() + private val eglBase: EglBase = EglBase.create() + private val connectionFactory by lazy { + + val decoderFactory = DefaultVideoDecoderFactory(eglBase.eglBaseContext) + val encoderFactory = DefaultVideoEncoderFactory(eglBase.eglBaseContext, true, true) + + PeerConnectionFactory.builder() + .setVideoDecoderFactory(decoderFactory) + .setVideoEncoderFactory(encoderFactory) + .setOptions(PeerConnectionFactory.Options()) + .createPeerConnectionFactory()!! + } + + private var peerConnection: PeerConnection? = null + + private fun getPeerConnection(): PeerConnection { + val stun = PeerConnection.IceServer.builder("stun:freyr.getsession.org:5349").setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK).createIceServer() + val turn = PeerConnection.IceServer.builder("turn:freyr.getsession.org:5349").setUsername("webrtc").setPassword("webrtc").setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK).createIceServer() + val iceServers = mutableListOf(turn, stun) + val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply { + this.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.ENABLED + this.candidateNetworkPolicy = PeerConnection.CandidateNetworkPolicy.ALL + // this.iceTransportsType = PeerConnection.IceTransportsType.RELAY + } + rtcConfig.keyType = PeerConnection.KeyType.ECDSA + return connectionFactory.createPeerConnection(rtcConfig, this)!! + } fun networkChange(networkAvailable: Boolean) { @@ -26,6 +54,11 @@ class CallManager(private val context: Context, } + fun callEnded() { + peerConnection?.close() + peerConnection = null + } + fun setAudioEnabled(isEnabled: Boolean) { } @@ -34,4 +67,47 @@ class CallManager(private val context: Context, } + override fun onSignalingChange(p0: PeerConnection.SignalingState?) { + TODO("Not yet implemented") + } + + override fun onIceConnectionChange(p0: PeerConnection.IceConnectionState?) { + TODO("Not yet implemented") + } + + override fun onIceConnectionReceivingChange(p0: Boolean) { + TODO("Not yet implemented") + } + + override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) { + TODO("Not yet implemented") + } + + override fun onIceCandidate(p0: IceCandidate?) { + TODO("Not yet implemented") + } + + override fun onIceCandidatesRemoved(p0: Array?) { + TODO("Not yet implemented") + } + + override fun onAddStream(p0: MediaStream?) { + TODO("Not yet implemented") + } + + override fun onRemoveStream(p0: MediaStream?) { + TODO("Not yet implemented") + } + + override fun onDataChannel(p0: DataChannel?) { + TODO("Not yet implemented") + } + + override fun onRenegotiationNeeded() { + TODO("Not yet implemented") + } + + override fun onAddTrack(p0: RtpReceiver?, p1: Array?) { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt index b73a7b1920..b942d13377 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -6,12 +6,13 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import org.webrtc.* import javax.inject.Inject @HiltViewModel class CallViewModel @Inject constructor( private val callManager: CallManager -): ViewModel() { +): ViewModel(), PeerConnection.Observer { sealed class StateEvent { data class AudioEnabled(val isEnabled: Boolean): StateEvent() @@ -21,6 +22,8 @@ class CallViewModel @Inject constructor( private val audioEnabledState = MutableStateFlow(StateEvent.AudioEnabled(true)) private val videoEnabledState = MutableStateFlow(StateEvent.VideoEnabled(false)) + private val peerConnection = callManager.getPeerConnection(this) + // set up listeners for establishing connection toggling video / audio init { audioEnabledState.onEach { (enabled) -> callManager.setAudioEnabled(enabled) } @@ -29,4 +32,48 @@ class CallViewModel @Inject constructor( .launchIn(viewModelScope) } + override fun onSignalingChange(p0: PeerConnection.SignalingState?) { + + } + + override fun onIceConnectionChange(p0: PeerConnection.IceConnectionState?) { + + } + + override fun onIceConnectionReceivingChange(p0: Boolean) { + + } + + override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) { + + } + + override fun onIceCandidate(p0: IceCandidate?) { + + } + + override fun onIceCandidatesRemoved(p0: Array?) { + + } + + override fun onAddStream(p0: MediaStream?) { + + } + + override fun onRemoveStream(p0: MediaStream?) { + + } + + override fun onDataChannel(p0: DataChannel?) { + + } + + override fun onRenegotiationNeeded() { + + } + + override fun onAddTrack(p0: RtpReceiver?, p1: Array?) { + + } + } \ No newline at end of file