mirror of
https://github.com/oxen-io/session-android.git
synced 2023-12-14 02:53:01 +01:00
feat: state machine reconnect logic wip
This commit is contained in:
parent
a90bd89c9a
commit
3fc7654b81
|
@ -6,6 +6,8 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Network
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.ResultReceiver
|
import android.os.ResultReceiver
|
||||||
import android.telephony.PhoneStateListener
|
import android.telephony.PhoneStateListener
|
||||||
|
@ -190,8 +192,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
private var wantsToAnswer = false
|
private var wantsToAnswer = false
|
||||||
private var currentTimeouts = 0
|
private var currentTimeouts = 0
|
||||||
private var isNetworkAvailable = true
|
private var isNetworkAvailable = true
|
||||||
|
private var activeNetwork: Network? = null
|
||||||
private var scheduledTimeout: ScheduledFuture<*>? = null
|
private var scheduledTimeout: ScheduledFuture<*>? = null
|
||||||
private var scheduledReconnectTimeout: ScheduledFuture<*>? = null
|
|
||||||
private var scheduledReconnect: ScheduledFuture<*>? = null
|
private var scheduledReconnect: ScheduledFuture<*>? = null
|
||||||
|
|
||||||
private val lockManager by lazy { LockManager(this) }
|
private val lockManager by lazy { LockManager(this) }
|
||||||
|
@ -216,6 +218,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
wantsToAnswer = false
|
wantsToAnswer = false
|
||||||
currentTimeouts = 0
|
currentTimeouts = 0
|
||||||
isNetworkAvailable = true
|
isNetworkAvailable = true
|
||||||
|
activeNetwork = null
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +244,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
callManager.recipient?.let { recipient ->
|
callManager.recipient?.let { recipient ->
|
||||||
insertMissedCall(recipient, true)
|
insertMissedCall(recipient, true)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
}
|
}
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
|
@ -250,9 +254,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
if (intent == null || intent.action == null) return START_NOT_STICKY
|
if (intent == null || intent.action == null) return START_NOT_STICKY
|
||||||
serviceExecutor.execute {
|
serviceExecutor.execute {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
Log.d("Loki", "Handling ${intent.action}")
|
Log.i("Loki", "Handling ${intent.action}")
|
||||||
when {
|
when {
|
||||||
action == ACTION_INCOMING_RING && isSameCall(intent) && !isPreOffer() -> handleNewOffer(intent)
|
action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(intent)
|
||||||
action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent)
|
action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent)
|
||||||
action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent)
|
action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent)
|
||||||
action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent)
|
action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent)
|
||||||
|
@ -271,7 +275,6 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
|
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
|
||||||
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
|
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
|
||||||
action == ACTION_CHECK_RECONNECT -> handleCheckReconnect(intent)
|
action == ACTION_CHECK_RECONNECT -> handleCheckReconnect(intent)
|
||||||
action == ACTION_CHECK_RECONNECT_TIMEOUT -> handleCheckReconnectTimeout(intent)
|
|
||||||
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
|
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
|
||||||
action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent)
|
action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent)
|
||||||
}
|
}
|
||||||
|
@ -283,16 +286,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
callManager.registerListener(this)
|
callManager.registerListener(this)
|
||||||
wantsToAnswer = false
|
wantsToAnswer = false
|
||||||
isNetworkAvailable = false
|
isNetworkAvailable = true
|
||||||
registerIncomingPstnCallReceiver()
|
registerIncomingPstnCallReceiver()
|
||||||
registerWiredHeadsetStateReceiver()
|
registerWiredHeadsetStateReceiver()
|
||||||
registerWantsToAnswerReceiver()
|
registerWantsToAnswerReceiver()
|
||||||
getSystemService(TelephonyManager::class.java)
|
getSystemService(TelephonyManager::class.java)
|
||||||
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
||||||
registerUncaughtExceptionHandler()
|
registerUncaughtExceptionHandler()
|
||||||
networkChangedReceiver = NetworkChangeReceiver { available ->
|
networkChangedReceiver = NetworkChangeReceiver(::networkChange)
|
||||||
networkChange(available)
|
|
||||||
}
|
|
||||||
networkChangedReceiver!!.register(this)
|
networkChangedReceiver!!.register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,12 +347,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
|
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
|
||||||
val callId = getCallId(intent)
|
val callId = getCallId(intent)
|
||||||
val recipient = getRemoteRecipient(intent)
|
val recipient = getRemoteRecipient(intent)
|
||||||
callManager.onNewOffer(offer, callId, recipient)
|
callManager.onNewOffer(offer, callId, recipient).fail {
|
||||||
|
Log.e("Loki", "Error handling new offer", it)
|
||||||
|
callManager.postConnectionError()
|
||||||
|
terminate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePreOffer(intent: Intent) {
|
private fun handlePreOffer(intent: Intent) {
|
||||||
if (!callManager.isIdle()) {
|
if (!callManager.isIdle()) {
|
||||||
Log.d(TAG, "Handling pre-offer from non-idle state")
|
Log.w(TAG, "Handling pre-offer from non-idle state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val callId = getCallId(intent)
|
val callId = getCallId(intent)
|
||||||
|
@ -580,6 +585,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
callManager.startCommunication(lockManager)
|
callManager.startCommunication(lockManager)
|
||||||
}
|
}
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
|
Log.e("Loki", "Error handling ice connected state transition")
|
||||||
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -604,21 +611,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
val numTimeouts = ++currentTimeouts
|
val numTimeouts = ++currentTimeouts
|
||||||
|
|
||||||
if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) {
|
if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) {
|
||||||
Log.d("Loki", "Trying to re-connect")
|
Log.i("Loki", "Trying to re-connect")
|
||||||
callManager.networkReestablished()
|
callManager.networkReestablished()
|
||||||
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
} else {
|
} else if (numTimeouts < MAX_RECONNECTS) {
|
||||||
Log.d("Loki", "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS")
|
Log.i("Loki", "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS")
|
||||||
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
|
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
|
||||||
}
|
} else {
|
||||||
}
|
Log.i("Loki", "Network isn't available, timing out")
|
||||||
|
|
||||||
private fun handleCheckReconnectTimeout(intent: Intent) {
|
|
||||||
val callId = callManager.callId ?: return
|
|
||||||
val callState = callManager.currentConnectionState
|
|
||||||
|
|
||||||
if (callId == getCallId(intent) && (callState !in arrayOf(CallState.Connected, CallState.Connecting))) {
|
|
||||||
Log.w(TAG, "Timing out reconnect: $callId")
|
|
||||||
handleLocalHangup(intent)
|
handleLocalHangup(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -687,11 +687,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
uncaughtExceptionHandlerManager?.unregister()
|
uncaughtExceptionHandlerManager?.unregister()
|
||||||
wantsToAnswer = false
|
wantsToAnswer = false
|
||||||
currentTimeouts = 0
|
currentTimeouts = 0
|
||||||
isNetworkAvailable = true
|
isNetworkAvailable = false
|
||||||
|
activeNetwork = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun networkChange(networkAvailable: Boolean) {
|
private fun networkChange(networkAvailable: Boolean) {
|
||||||
|
Log.d("Loki", "flipping network available to $networkAvailable")
|
||||||
isNetworkAvailable = networkAvailable
|
isNetworkAvailable = networkAvailable
|
||||||
if (networkAvailable && !callManager.isReestablishing && callManager.currentConnectionState == CallState.Connected) {
|
if (networkAvailable && !callManager.isReestablishing && callManager.currentConnectionState == CallState.Connected) {
|
||||||
Log.d("Loki", "Should reconnected")
|
Log.d("Loki", "Should reconnected")
|
||||||
|
@ -791,30 +793,41 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
||||||
|
|
||||||
override fun onSignalingChange(p0: PeerConnection.SignalingState?) {}
|
override fun onSignalingChange(p0: PeerConnection.SignalingState?) {}
|
||||||
|
|
||||||
|
fun Context.getCurrentNetwork(): Network? {
|
||||||
|
val cm = this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
return cm.activeNetwork
|
||||||
|
}
|
||||||
|
|
||||||
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
|
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
|
||||||
if (newState == CONNECTED) {
|
if (newState == CONNECTED) {
|
||||||
scheduledTimeout?.cancel(false)
|
scheduledTimeout?.cancel(false)
|
||||||
scheduledReconnect?.cancel(false)
|
scheduledReconnect?.cancel(false)
|
||||||
scheduledReconnectTimeout?.cancel(false)
|
|
||||||
scheduledTimeout = null
|
scheduledTimeout = null
|
||||||
scheduledReconnect = null
|
scheduledReconnect = null
|
||||||
scheduledReconnectTimeout = null
|
activeNetwork = getCurrentNetwork()
|
||||||
|
|
||||||
val intent = Intent(this, WebRtcCallService::class.java)
|
val intent = Intent(this, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_ICE_CONNECTED)
|
.setAction(ACTION_ICE_CONNECTED)
|
||||||
startService(intent)
|
startService(intent)
|
||||||
} else if (newState in arrayOf(FAILED, DISCONNECTED) && scheduledReconnectTimeout == null) {
|
} else if (newState in arrayOf(FAILED, DISCONNECTED) && scheduledReconnect == null) {
|
||||||
callManager.callId?.let { callId ->
|
callManager.callId?.let { callId ->
|
||||||
callManager.postConnectionEvent(Event.IceDisconnect) {
|
callManager.postConnectionEvent(Event.IceDisconnect) {
|
||||||
|
val currentNetwork = getCurrentNetwork()
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING)
|
callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING)
|
||||||
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
|
if (activeNetwork != currentNetwork || currentNetwork == null) {
|
||||||
scheduledReconnectTimeout = timeoutExecutor.schedule(ReconnectTimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
Log.i("Loki", "Starting reconnect timer")
|
||||||
|
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
} else {
|
||||||
|
Log.i("Loki", "Starting timeout, awaiting new reconnect")
|
||||||
|
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
val intent = hangupIntent(this)
|
val intent = hangupIntent(this)
|
||||||
startService(intent)
|
startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(TAG, "onIceConnectionChange: $newState")
|
Log.i("Loki", "onIceConnectionChange: $newState")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onIceConnectionReceivingChange(p0: Boolean) {}
|
override fun onIceConnectionReceivingChange(p0: Boolean) {}
|
||||||
|
|
|
@ -396,18 +396,22 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise<Unit, Exception> {
|
fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise<Unit, Exception> {
|
||||||
// TODO transition to ice reestablished
|
|
||||||
if (callId != this.callId) return Promise.ofFail(NullPointerException("No callId"))
|
if (callId != this.callId) return Promise.ofFail(NullPointerException("No callId"))
|
||||||
if (recipient != this.recipient) return Promise.ofFail(NullPointerException("No recipient"))
|
if (recipient != this.recipient) return Promise.ofFail(NullPointerException("No recipient"))
|
||||||
|
|
||||||
val connection = peerConnection ?: return Promise.ofFail(NullPointerException("No peer connection"))
|
val connection = peerConnection ?: return Promise.ofFail(NullPointerException("No peer connection"))
|
||||||
|
|
||||||
connection.setNewOffer(SessionDescription(SessionDescription.Type.OFFER, offer))
|
val reconnected = stateProcessor.processEvent(Event.NetworkReconnect)
|
||||||
val answer = connection.createAnswer(MediaConstraints().apply {
|
return if (reconnected) {
|
||||||
mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true"))
|
connection.setNewOffer(SessionDescription(SessionDescription.Type.OFFER, offer))
|
||||||
})
|
val answer = connection.createAnswer(MediaConstraints().apply {
|
||||||
val answerMessage = CallMessage.answer(answer.description, callId)
|
mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true"))
|
||||||
return MessageSender.sendNonDurably(answerMessage, recipient.address)
|
})
|
||||||
|
val answerMessage = CallMessage.answer(answer.description, callId)
|
||||||
|
MessageSender.sendNonDurably(answerMessage, recipient.address)
|
||||||
|
} else {
|
||||||
|
Promise.ofFail(Exception("Couldn't reconnect from current state"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long, onSuccess: () -> Unit) {
|
fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long, onSuccess: () -> Unit) {
|
||||||
|
@ -552,6 +556,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
||||||
CallState.RemoteRing -> postViewModelState(CallViewModel.State.RECIPIENT_UNAVAILABLE)
|
CallState.RemoteRing -> postViewModelState(CallViewModel.State.RECIPIENT_UNAVAILABLE)
|
||||||
else -> postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
|
else -> postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
|
||||||
}
|
}
|
||||||
|
if (!stateProcessor.processEvent(Event.Hangup)) {
|
||||||
|
Log.e("Loki", "")
|
||||||
|
stateProcessor.processEvent(Event.Error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleSetMuteAudio(muted: Boolean) {
|
fun handleSetMuteAudio(muted: Boolean) {
|
||||||
|
|
|
@ -6,8 +6,6 @@ import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.os.Build
|
|
||||||
import android.util.SparseArray
|
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
class NetworkChangeReceiver(private val onNetworkChangedCallback: (Boolean)->Unit) {
|
class NetworkChangeReceiver(private val onNetworkChangedCallback: (Boolean)->Unit) {
|
||||||
|
@ -20,58 +18,58 @@ class NetworkChangeReceiver(private val onNetworkChangedCallback: (Boolean)->Uni
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkNetworks() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultObserver = object: ConnectivityManager.NetworkCallback() {
|
val defaultObserver = object: ConnectivityManager.NetworkCallback() {
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
Log.d("Loki", "onAvailable: $network")
|
Log.i("Loki", "onAvailable: $network")
|
||||||
networkList += network
|
networkList += network
|
||||||
|
onNetworkChangedCallback(networkList.isNotEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLosing(network: Network, maxMsToLive: Int) {
|
override fun onLosing(network: Network, maxMsToLive: Int) {
|
||||||
Log.d("Loki", "onLosing: $network, maxMsToLive: $maxMsToLive")
|
Log.i("Loki", "onLosing: $network, maxMsToLive: $maxMsToLive")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
Log.d("Loki", "onLost: $network")
|
Log.i("Loki", "onLost: $network")
|
||||||
networkList -= network
|
networkList -= network
|
||||||
onNetworkChangedCallback(networkList.isNotEmpty())
|
onNetworkChangedCallback(networkList.isNotEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnavailable() {
|
override fun onUnavailable() {
|
||||||
Log.d("Loki", "onUnavailable")
|
Log.i("Loki", "onUnavailable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun receiveBroadcast(context: Context, intent: Intent) {
|
fun receiveBroadcast(context: Context, intent: Intent) {
|
||||||
Log.d("Loki", intent.toString())
|
val connected = context.isConnected()
|
||||||
onNetworkChangedCallback(context.isConnected())
|
Log.i("Loki", "received broadcast, network connected: $connected")
|
||||||
|
onNetworkChangedCallback(connected)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.isConnected() : Boolean {
|
fun Context.isConnected() : Boolean {
|
||||||
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
return cm.activeNetworkInfo?.isConnected ?: false
|
return cm.activeNetwork != null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun register(context: Context) {
|
fun register(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
val intentFilter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")
|
||||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
context.registerReceiver(broadcastDelegate, intentFilter)
|
||||||
cm.registerDefaultNetworkCallback(defaultObserver)
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
} else {
|
// val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
val intentFilter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")
|
// cm.registerDefaultNetworkCallback(defaultObserver)
|
||||||
context.registerReceiver(broadcastDelegate, intentFilter)
|
// } else {
|
||||||
}
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregister(context: Context) {
|
fun unregister(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
context.unregisterReceiver(broadcastDelegate)
|
||||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
cm.unregisterNetworkCallback(defaultObserver)
|
// val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
} else {
|
// cm.unregisterNetworkCallback(defaultObserver)
|
||||||
context.unregisterReceiver(broadcastDelegate)
|
// } else {
|
||||||
}
|
//
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,31 +7,46 @@ import org.thoughtcrime.securesms.webrtc.video.Camera
|
||||||
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
||||||
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
||||||
import org.thoughtcrime.securesms.webrtc.video.RotationVideoSink
|
import org.thoughtcrime.securesms.webrtc.video.RotationVideoSink
|
||||||
import org.webrtc.*
|
import org.webrtc.AudioSource
|
||||||
|
import org.webrtc.AudioTrack
|
||||||
|
import org.webrtc.DataChannel
|
||||||
|
import org.webrtc.EglBase
|
||||||
|
import org.webrtc.IceCandidate
|
||||||
|
import org.webrtc.MediaConstraints
|
||||||
|
import org.webrtc.MediaStream
|
||||||
|
import org.webrtc.PeerConnection
|
||||||
|
import org.webrtc.PeerConnectionFactory
|
||||||
|
import org.webrtc.SdpObserver
|
||||||
|
import org.webrtc.SessionDescription
|
||||||
|
import org.webrtc.SurfaceTextureHelper
|
||||||
|
import org.webrtc.VideoSink
|
||||||
|
import org.webrtc.VideoSource
|
||||||
|
import org.webrtc.VideoTrack
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import kotlin.random.asKotlinRandom
|
import kotlin.random.asKotlinRandom
|
||||||
|
|
||||||
class PeerConnectionWrapper(context: Context,
|
class PeerConnectionWrapper(private val context: Context,
|
||||||
factory: PeerConnectionFactory,
|
private val factory: PeerConnectionFactory,
|
||||||
observer: PeerConnection.Observer,
|
private val observer: PeerConnection.Observer,
|
||||||
localRenderer: VideoSink,
|
private val localRenderer: VideoSink,
|
||||||
private val cameraEventListener: CameraEventListener,
|
private val cameraEventListener: CameraEventListener,
|
||||||
eglBase: EglBase,
|
private val eglBase: EglBase,
|
||||||
relay: Boolean = false): CameraEventListener {
|
private val relay: Boolean = false): CameraEventListener {
|
||||||
|
|
||||||
private val peerConnection: PeerConnection
|
private var peerConnection: PeerConnection? = null
|
||||||
private val audioTrack: AudioTrack
|
private val audioTrack: AudioTrack
|
||||||
private val audioSource: AudioSource
|
private val audioSource: AudioSource
|
||||||
private val camera: Camera
|
private val camera: Camera
|
||||||
|
private val mediaStream: MediaStream
|
||||||
private val videoSource: VideoSource?
|
private val videoSource: VideoSource?
|
||||||
private val videoTrack: VideoTrack?
|
private val videoTrack: VideoTrack?
|
||||||
private val rotationVideoSink = RotationVideoSink()
|
private val rotationVideoSink = RotationVideoSink()
|
||||||
|
|
||||||
val readyForIce
|
val readyForIce
|
||||||
get() = peerConnection.localDescription != null && peerConnection.remoteDescription != null
|
get() = peerConnection?.localDescription != null && peerConnection?.remoteDescription != null
|
||||||
|
|
||||||
init {
|
private fun initPeerConnection() {
|
||||||
val random = SecureRandom().asKotlinRandom()
|
val random = SecureRandom().asKotlinRandom()
|
||||||
val iceServers = listOf("freyr","fenrir","frigg","angus","hereford","holstein", "brahman").shuffled(random).take(2).map { sub ->
|
val iceServers = listOf("freyr","fenrir","frigg","angus","hereford","holstein", "brahman").shuffled(random).take(2).map { sub ->
|
||||||
PeerConnection.IceServer.builder("turn:$sub.getsession.org")
|
PeerConnection.IceServer.builder("turn:$sub.getsession.org")
|
||||||
|
@ -43,9 +58,6 @@ class PeerConnectionWrapper(context: Context,
|
||||||
val constraints = MediaConstraints().apply {
|
val constraints = MediaConstraints().apply {
|
||||||
optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
|
optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
|
||||||
}
|
}
|
||||||
val audioConstraints = MediaConstraints().apply {
|
|
||||||
optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val configuration = PeerConnection.RTCConfiguration(iceServers).apply {
|
val configuration = PeerConnection.RTCConfiguration(iceServers).apply {
|
||||||
bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
|
bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
|
||||||
|
@ -55,36 +67,49 @@ class PeerConnectionWrapper(context: Context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConnection = factory.createPeerConnection(configuration, constraints, observer)!!
|
val newPeerConnection = factory.createPeerConnection(configuration, constraints, observer)!!
|
||||||
peerConnection.setAudioPlayout(true)
|
peerConnection = newPeerConnection
|
||||||
peerConnection.setAudioRecording(true)
|
newPeerConnection.setAudioPlayout(true)
|
||||||
|
newPeerConnection.setAudioRecording(true)
|
||||||
|
|
||||||
val mediaStream = factory.createLocalMediaStream("ARDAMS")
|
newPeerConnection.addStream(mediaStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val audioConstraints = MediaConstraints().apply {
|
||||||
|
optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaStream = factory.createLocalMediaStream("ARDAMS")
|
||||||
audioSource = factory.createAudioSource(audioConstraints)
|
audioSource = factory.createAudioSource(audioConstraints)
|
||||||
audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource)
|
audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource)
|
||||||
audioTrack.setEnabled(true)
|
audioTrack.setEnabled(true)
|
||||||
mediaStream.addTrack(audioTrack)
|
mediaStream.addTrack(audioTrack)
|
||||||
|
|
||||||
camera = Camera(context, this)
|
val newCamera = Camera(context, this)
|
||||||
if (camera.capturer != null) {
|
camera = newCamera
|
||||||
videoSource = factory.createVideoSource(false)
|
|
||||||
videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource)
|
if (newCamera.capturer != null) {
|
||||||
rotationVideoSink.setObserver(videoSource.capturerObserver)
|
val newVideoSource = factory.createVideoSource(false)
|
||||||
camera.capturer.initialize(
|
videoSource = newVideoSource
|
||||||
SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.eglBaseContext),
|
val newVideoTrack = factory.createVideoTrack("ARDAMSv0", newVideoSource)
|
||||||
context,
|
videoTrack = newVideoTrack
|
||||||
rotationVideoSink
|
|
||||||
|
rotationVideoSink.setObserver(newVideoSource.capturerObserver)
|
||||||
|
newCamera.capturer.initialize(
|
||||||
|
SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.eglBaseContext),
|
||||||
|
context,
|
||||||
|
rotationVideoSink
|
||||||
)
|
)
|
||||||
rotationVideoSink.mirrored = camera.activeDirection == CameraState.Direction.FRONT
|
rotationVideoSink.mirrored = newCamera.activeDirection == CameraState.Direction.FRONT
|
||||||
rotationVideoSink.setSink(localRenderer)
|
rotationVideoSink.setSink(localRenderer)
|
||||||
videoTrack.setEnabled(false)
|
newVideoTrack.setEnabled(false)
|
||||||
mediaStream.addTrack(videoTrack)
|
mediaStream.addTrack(newVideoTrack)
|
||||||
} else {
|
} else {
|
||||||
videoSource = null
|
videoSource = null
|
||||||
videoTrack = null
|
videoTrack = null
|
||||||
}
|
}
|
||||||
|
initPeerConnection()
|
||||||
peerConnection.addStream(mediaStream)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCameraState(): CameraState {
|
fun getCameraState(): CameraState {
|
||||||
|
@ -97,12 +122,12 @@ class PeerConnectionWrapper(context: Context,
|
||||||
negotiated = true
|
negotiated = true
|
||||||
id = 548
|
id = 548
|
||||||
}
|
}
|
||||||
return peerConnection.createDataChannel(channelName, dataChannelConfiguration)
|
return peerConnection!!.createDataChannel(channelName, dataChannelConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addIceCandidate(candidate: IceCandidate) {
|
fun addIceCandidate(candidate: IceCandidate) {
|
||||||
// TODO: filter logic based on known servers
|
// TODO: filter logic based on known servers
|
||||||
peerConnection.addIceCandidate(candidate)
|
peerConnection!!.addIceCandidate(candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
|
@ -111,14 +136,17 @@ class PeerConnectionWrapper(context: Context,
|
||||||
videoSource?.dispose()
|
videoSource?.dispose()
|
||||||
|
|
||||||
audioSource.dispose()
|
audioSource.dispose()
|
||||||
peerConnection.close()
|
peerConnection?.close()
|
||||||
peerConnection.dispose()
|
peerConnection?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNewOffer(description: SessionDescription) {
|
fun setNewOffer(description: SessionDescription) {
|
||||||
val future = SettableFuture<Boolean>()
|
val future = SettableFuture<Boolean>()
|
||||||
|
|
||||||
peerConnection.setRemoteDescription(object: SdpObserver {
|
peerConnection?.close()
|
||||||
|
initPeerConnection()
|
||||||
|
|
||||||
|
peerConnection!!.setRemoteDescription(object: SdpObserver {
|
||||||
override fun onCreateSuccess(p0: SessionDescription?) {
|
override fun onCreateSuccess(p0: SessionDescription?) {
|
||||||
throw AssertionError()
|
throw AssertionError()
|
||||||
}
|
}
|
||||||
|
@ -148,7 +176,7 @@ class PeerConnectionWrapper(context: Context,
|
||||||
fun setRemoteDescription(description: SessionDescription) {
|
fun setRemoteDescription(description: SessionDescription) {
|
||||||
val future = SettableFuture<Boolean>()
|
val future = SettableFuture<Boolean>()
|
||||||
|
|
||||||
peerConnection.setRemoteDescription(object: SdpObserver {
|
peerConnection!!.setRemoteDescription(object: SdpObserver {
|
||||||
override fun onCreateSuccess(p0: SessionDescription?) {
|
override fun onCreateSuccess(p0: SessionDescription?) {
|
||||||
throw AssertionError()
|
throw AssertionError()
|
||||||
}
|
}
|
||||||
|
@ -178,7 +206,7 @@ class PeerConnectionWrapper(context: Context,
|
||||||
fun createAnswer(mediaConstraints: MediaConstraints) : SessionDescription {
|
fun createAnswer(mediaConstraints: MediaConstraints) : SessionDescription {
|
||||||
val future = SettableFuture<SessionDescription>()
|
val future = SettableFuture<SessionDescription>()
|
||||||
|
|
||||||
peerConnection.createAnswer(object:SdpObserver {
|
peerConnection!!.createAnswer(object:SdpObserver {
|
||||||
override fun onCreateSuccess(sdp: SessionDescription?) {
|
override fun onCreateSuccess(sdp: SessionDescription?) {
|
||||||
future.set(sdp)
|
future.set(sdp)
|
||||||
}
|
}
|
||||||
|
@ -215,7 +243,7 @@ class PeerConnectionWrapper(context: Context,
|
||||||
fun createOffer(mediaConstraints: MediaConstraints): SessionDescription {
|
fun createOffer(mediaConstraints: MediaConstraints): SessionDescription {
|
||||||
val future = SettableFuture<SessionDescription>()
|
val future = SettableFuture<SessionDescription>()
|
||||||
|
|
||||||
peerConnection.createOffer(object:SdpObserver {
|
peerConnection!!.createOffer(object:SdpObserver {
|
||||||
override fun onCreateSuccess(sdp: SessionDescription?) {
|
override fun onCreateSuccess(sdp: SessionDescription?) {
|
||||||
future.set(sdp)
|
future.set(sdp)
|
||||||
}
|
}
|
||||||
|
@ -245,7 +273,7 @@ class PeerConnectionWrapper(context: Context,
|
||||||
fun setLocalDescription(sdp: SessionDescription) {
|
fun setLocalDescription(sdp: SessionDescription) {
|
||||||
val future = SettableFuture<Boolean>()
|
val future = SettableFuture<Boolean>()
|
||||||
|
|
||||||
peerConnection.setLocalDescription(object: SdpObserver {
|
peerConnection!!.setLocalDescription(object: SdpObserver {
|
||||||
override fun onCreateSuccess(p0: SessionDescription?) {
|
override fun onCreateSuccess(p0: SessionDescription?) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -271,8 +299,8 @@ class PeerConnectionWrapper(context: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCommunicationMode() {
|
fun setCommunicationMode() {
|
||||||
peerConnection.setAudioPlayout(true)
|
peerConnection?.setAudioPlayout(true)
|
||||||
peerConnection.setAudioRecording(true)
|
peerConnection?.setAudioRecording(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAudioEnabled(isEnabled: Boolean) {
|
fun setAudioEnabled(isEnabled: Boolean) {
|
||||||
|
|
Loading…
Reference in a new issue