Cleaning call states

This commit is contained in:
Valere 2020-06-12 13:15:06 +02:00
parent 56ed56a986
commit a1907aaddb
7 changed files with 164 additions and 91 deletions

View File

@ -21,17 +21,20 @@ enum class CallState {
/** Idle, setting up objects */ /** Idle, setting up objects */
IDLE, IDLE,
/** Dialing. Outgoing call is signaling the remote peer */ /** Dialing. Outgoing call is signaling the remote peer */
DIALING, DIALING,
/** Local ringing. Incoming call offer received */
LOCAL_RINGING,
/** Answering. Incoming call is responding to remote peer */ /** Answering. Incoming call is responding to remote peer */
ANSWERING, ANSWERING,
/** Remote ringing. Outgoing call, ICE negotiation is complete */ /** Connecting. Incoming/Outgoing Offer and answer are known, Currently checking and testing pairs of ice candidates */
REMOTE_RINGING, CONNECTING,
/** Local ringing. Incoming call, ICE negotiation is complete */
LOCAL_RINGING,
/** Connected. Incoming/Outgoing call, the call is connected */ /** Connected. Incoming/Outgoing call, the call is connected */
CONNECTED, CONNECTED,

View File

@ -74,15 +74,16 @@ internal class MxCallImpl(
init { init {
if (isOutgoing) { if (isOutgoing) {
state = CallState.DIALING state = CallState.IDLE
} else { } else {
// because it's created on reception of an offer
state = CallState.LOCAL_RINGING state = CallState.LOCAL_RINGING
} }
} }
override fun offerSdp(sdp: SessionDescription) { override fun offerSdp(sdp: SessionDescription) {
if (!isOutgoing) return if (!isOutgoing) return
state = CallState.REMOTE_RINGING state = CallState.DIALING
CallInviteContent( CallInviteContent(
callId = callId, callId = callId,
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS, lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
@ -108,6 +109,7 @@ internal class MxCallImpl(
} }
override fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>) { override fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>) {
// For now we don't support this flow
} }
override fun hangUp() { override fun hangUp() {

View File

@ -35,17 +35,16 @@ class CallControlsView @JvmOverloads constructor(
var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null
@BindView(R.id.incomingRingingControls) @BindView(R.id.ringingControls)
lateinit var incomingRingingControls: ViewGroup lateinit var ringingControls: ViewGroup
// @BindView(R.id.iv_icr_accept_call) @BindView(R.id.iv_icr_accept_call)
// lateinit var incomingRingingControlAccept: ImageView lateinit var ringingControlAccept: ImageView
// @BindView(R.id.iv_icr_end_call) @BindView(R.id.iv_icr_end_call)
// lateinit var incomingRingingControlDecline: ImageView lateinit var ringingControlDecline: ImageView
@BindView(R.id.connectedControls) @BindView(R.id.connectedControls)
lateinit var connectedControls: ViewGroup lateinit var connectedControls: ViewGroup
@BindView(R.id.iv_mute_toggle) @BindView(R.id.iv_mute_toggle)
lateinit var muteIcon: ImageView lateinit var muteIcon: ImageView
@ -89,31 +88,31 @@ class CallControlsView @JvmOverloads constructor(
videoToggleIcon.setImageResource(if (state.isVideoEnabled) R.drawable.ic_video else R.drawable.ic_video_off) videoToggleIcon.setImageResource(if (state.isVideoEnabled) R.drawable.ic_video else R.drawable.ic_video_off)
when (callState) { when (callState) {
CallState.DIALING -> { CallState.IDLE,
} CallState.DIALING,
CallState.ANSWERING -> { CallState.CONNECTING,
incomingRingingControls.isVisible = false CallState.ANSWERING -> {
ringingControls.isVisible = true
ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true
connectedControls.isVisible = false connectedControls.isVisible = false
} }
CallState.REMOTE_RINGING -> { CallState.LOCAL_RINGING -> {
} ringingControls.isVisible = true
CallState.LOCAL_RINGING -> { ringingControlAccept.isVisible = true
incomingRingingControls.isVisible = true ringingControlDecline.isVisible = true
connectedControls.isVisible = false connectedControls.isVisible = false
} }
CallState.CONNECTED -> { CallState.CONNECTED -> {
incomingRingingControls.isVisible = false ringingControls.isVisible = false
connectedControls.isVisible = true connectedControls.isVisible = true
} }
CallState.TERMINATED, CallState.TERMINATED,
CallState.IDLE, null -> {
null -> { ringingControls.isVisible = false
incomingRingingControls.isVisible = false
connectedControls.isVisible = false connectedControls.isVisible = false
} }
} }
} }
interface InteractionListener { interface InteractionListener {

View File

@ -91,17 +91,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
private var rootEglBase: EglBase? = null private var rootEglBase: EglBase? = null
// var callHeadsUpService: CallHeadsUpService? = null
// private val serviceConnection = object : ServiceConnection {
// override fun onServiceDisconnected(name: ComponentName?) {
// finish()
// }
//
// override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// callHeadsUpService = (service as? CallHeadsUpService.CallHeadsUpServiceBinder)?.getService()
// }
// }
override fun doBeforeSetContentView() { override fun doBeforeSetContentView() {
// Set window styles for fullscreen-window size. Needs to be done before adding content. // Set window styles for fullscreen-window size. Needs to be done before adding content.
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
@ -130,9 +119,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
// window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// tryThis { unbindService(serviceConnection) }
// bindService(Intent(this, CallHeadsUpService::class.java), serviceConnection, 0)
if (intent.hasExtra(MvRx.KEY_ARG)) { if (intent.hasExtra(MvRx.KEY_ARG)) {
callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!! callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!!
} else { } else {
@ -184,8 +170,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
Timber.v("## VOIP renderState call $state") Timber.v("## VOIP renderState call $state")
callControlsView.updateForState(state) callControlsView.updateForState(state)
when (state.callState.invoke()) { when (state.callState.invoke()) {
CallState.IDLE -> { CallState.IDLE,
}
CallState.DIALING -> { CallState.DIALING -> {
callVideoGroup.isInvisible = true callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true callInfoGroup.isVisible = true
@ -196,35 +181,40 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call) callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
} }
} }
CallState.ANSWERING -> {
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting)
state.otherUserMatrixItem.invoke()?.let {
avatarRenderer.render(it, otherMemberAvatar)
}
// fullscreenRenderer.isVisible = true
// pipRenderer.isVisible = true
}
CallState.REMOTE_RINGING -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(
if (state.isVideoCall) R.string.incoming_video_call else R.string.incoming_voice_call
)
}
CallState.LOCAL_RINGING -> { CallState.LOCAL_RINGING -> {
callVideoGroup.isInvisible = true callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true callInfoGroup.isVisible = true
callStatusText.text = null
state.otherUserMatrixItem.invoke()?.let { state.otherUserMatrixItem.invoke()?.let {
avatarRenderer.render(it, otherMemberAvatar) avatarRenderer.render(it, otherMemberAvatar)
participantNameText.text = it.getBestName() participantNameText.text = it.getBestName()
callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call) callTypeText.setText(if (state.isVideoCall) R.string.action_video_call else R.string.action_voice_call)
} }
} }
CallState.ANSWERING -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting)
state.otherUserMatrixItem.invoke()?.let {
avatarRenderer.render(it, otherMemberAvatar)
}
}
CallState.CONNECTING -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting)
}
CallState.CONNECTED -> { CallState.CONNECTED -> {
// TODO only if is video call if (callArgs.isVideoCall) {
callVideoGroup.isVisible = true callVideoGroup.isVisible = true
callInfoGroup.isVisible = false callInfoGroup.isVisible = false
} else {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.text = null
}
} }
CallState.TERMINATED -> { CallState.TERMINATED -> {
finish() finish()
@ -279,20 +269,20 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
return false return false
} }
override fun onDestroy() { override fun onPause() {
peerConnectionManager.detachRenderers() peerConnectionManager.detachRenderers()
// tryThis { unbindService(serviceConnection) } super.onPause()
super.onDestroy()
} }
private fun handleViewEvents(event: VectorCallViewEvents?) { private fun handleViewEvents(event: VectorCallViewEvents?) {
when (event) { Timber.v("handleViewEvents $event")
is VectorCallViewEvents.CallAnswered -> { // when (event) {
} // is VectorCallViewEvents.CallAnswered -> {
is VectorCallViewEvents.CallHangup -> { // }
finish() // is VectorCallViewEvents.CallHangup -> {
} // finish()
} // }
// }
} }
companion object { companion object {

View File

@ -57,9 +57,9 @@ sealed class VectorCallViewActions : VectorViewModelAction {
sealed class VectorCallViewEvents : VectorViewEvents { sealed class VectorCallViewEvents : VectorViewEvents {
data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() // data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() // data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
object CallAccepted : VectorCallViewEvents() // object CallAccepted : VectorCallViewEvents()
} }
class VectorCallViewModel @AssistedInject constructor( class VectorCallViewModel @AssistedInject constructor(

View File

@ -211,7 +211,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
currentCall?.mxCall?.offerSdp(p0) currentCall?.mxCall?.offerSdp(p0)
} }
}, constraints) }, constraints)
} }
private fun getTurnServer(callback: ((TurnServer?) -> Unit)) { private fun getTurnServer(callback: ((TurnServer?) -> Unit)) {
@ -374,7 +373,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
// If remote track exists, then sink it to surface // If remote track exists, then sink it to surface
remoteSurfaceRenderer?.get()?.let { participantSurface -> remoteSurfaceRenderer?.get()?.let { participantSurface ->
currentCall?.remoteVideoTrack?.let { currentCall?.remoteVideoTrack?.let {
it.setEnabled(true)
it.addSink(participantSurface) it.addSink(participantSurface)
} }
} }
@ -551,17 +549,47 @@ class WebRtcPeerConnectionManager @Inject constructor(
override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) { override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
Timber.v("## VOIP StreamObserver onConnectionChange: $newState") Timber.v("## VOIP StreamObserver onConnectionChange: $newState")
when (newState) { when (newState) {
PeerConnection.PeerConnectionState.CONNECTED -> { /**
* Every ICE transport used by the connection is either in use (state "connected" or "completed")
* or is closed (state "closed"); in addition, at least one transport is either "connected" or "completed"
*/
PeerConnection.PeerConnectionState.CONNECTED -> {
callContext.mxCall.state = CallState.CONNECTED callContext.mxCall.state = CallState.CONNECTED
} }
PeerConnection.PeerConnectionState.FAILED -> { /**
* One or more of the ICE transports on the connection is in the "failed" state.
*/
PeerConnection.PeerConnectionState.FAILED -> {
endCall() endCall()
} }
/**
* At least one of the connection's ICE transports (RTCIceTransports or RTCDtlsTransports) are in the "new" state,
* and none of them are in one of the following states: "connecting", "checking", "failed", or "disconnected",
* or all of the connection's transports are in the "closed" state.
*/
PeerConnection.PeerConnectionState.NEW, PeerConnection.PeerConnectionState.NEW,
PeerConnection.PeerConnectionState.CONNECTING,
PeerConnection.PeerConnectionState.DISCONNECTED, /**
* One or more of the ICE transports are currently in the process of establishing a connection;
* that is, their RTCIceConnectionState is either "checking" or "connected", and no transports are in the "failed" state
*/
PeerConnection.PeerConnectionState.CONNECTING -> {
callContext.mxCall.state = CallState.CONNECTING
}
/**
* The RTCPeerConnection is closed.
* This value was in the RTCSignalingState enum (and therefore found by reading the value of the signalingState)
* property until the May 13, 2016 draft of the specification.
*/
PeerConnection.PeerConnectionState.CLOSED, PeerConnection.PeerConnectionState.CLOSED,
null -> { /**
* At least one of the ICE transports for the connection is in the "disconnected" state and none of the other transports are in the state "failed",
* "connecting", or "checking".
*/
PeerConnection.PeerConnectionState.DISCONNECTED -> {
}
null -> {
} }
} }
} }
@ -580,14 +608,60 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState) { override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState) {
Timber.v("## VOIP StreamObserver onIceConnectionChange IceConnectionState:$newState")
when (newState) { when (newState) {
PeerConnection.IceConnectionState.CONNECTED -> Timber.v("## VOIP StreamObserver onIceConnectionChange.CONNECTED")
PeerConnection.IceConnectionState.DISCONNECTED -> { /**
Timber.v("## VOIP StreamObserver onIceConnectionChange.DISCONNECTED") * the ICE agent is gathering addresses or is waiting to be given remote candidates through
endCall() * calls to RTCPeerConnection.addIceCandidate() (or both).
*/
PeerConnection.IceConnectionState.NEW -> {
}
/**
* The ICE agent has been given one or more remote candidates and is checking pairs of local and remote candidates
* against one another to try to find a compatible match, but has not yet found a pair which will allow
* the peer connection to be made. It's possible that gathering of candidates is also still underway.
*/
PeerConnection.IceConnectionState.CHECKING -> {
}
/**
* A usable pairing of local and remote candidates has been found for all components of the connection,
* and the connection has been established.
* It's possible that gathering is still underway, and it's also possible that the ICE agent is still checking
* candidates against one another looking for a better connection to use.
*/
PeerConnection.IceConnectionState.CONNECTED -> {
}
/**
* Checks to ensure that components are still connected failed for at least one component of the RTCPeerConnection.
* This is a less stringent test than "failed" and may trigger intermittently and resolve just as spontaneously on less reliable networks,
* or during temporary disconnections. When the problem resolves, the connection may return to the "connected" state.
*/
PeerConnection.IceConnectionState.DISCONNECTED -> {
}
/**
* The ICE candidate has checked all candidates pairs against one another and has failed to find compatible matches for all components of the connection.
* It is, however, possible that the ICE agent did find compatible connections for some components.
*/
PeerConnection.IceConnectionState.FAILED -> {
callContext.mxCall.hangUp()
}
/**
* The ICE agent has finished gathering candidates, has checked all pairs against one another, and has found a connection for all components.
*/
PeerConnection.IceConnectionState.COMPLETED -> {
}
/**
* The ICE agent for this RTCPeerConnection has shut down and is no longer handling requests.
*/
PeerConnection.IceConnectionState.CLOSED -> {
} }
PeerConnection.IceConnectionState.FAILED -> Timber.v("## VOIP StreamObserver onIceConnectionChange.FAILED")
else -> Timber.v("## VOIP StreamObserver onIceConnectionChange.$newState")
} }
} }
@ -595,7 +669,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
Timber.v("## VOIP StreamObserver onAddStream: $stream") Timber.v("## VOIP StreamObserver onAddStream: $stream")
executor.execute { executor.execute {
// reportError("Weird-looking stream: " + stream); // reportError("Weird-looking stream: " + stream);
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) return@execute if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
Timber.e("## VOIP StreamObserver weird looking stream: $stream")
//TODO maybe do something more??
callContext.mxCall.hangUp()
return@execute
}
if (stream.videoTracks.size == 1) { if (stream.videoTracks.size == 1) {
val remoteVideoTrack = stream.videoTracks.first() val remoteVideoTrack = stream.videoTracks.first()

View File

@ -8,7 +8,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/incomingRingingControls" android:id="@+id/ringingControls"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:padding="16dp"