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,
/** Dialing. Outgoing call is signaling the remote peer */
DIALING,
/** Local ringing. Incoming call offer received */
LOCAL_RINGING,
/** Answering. Incoming call is responding to remote peer */
ANSWERING,
/** Remote ringing. Outgoing call, ICE negotiation is complete */
REMOTE_RINGING,
/** Connecting. Incoming/Outgoing Offer and answer are known, Currently checking and testing pairs of ice candidates */
CONNECTING,
/** Local ringing. Incoming call, ICE negotiation is complete */
LOCAL_RINGING,
/** Connected. Incoming/Outgoing call, the call is connected */
CONNECTED,

View File

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

View File

@ -35,17 +35,16 @@ class CallControlsView @JvmOverloads constructor(
var interactionListener: InteractionListener? = null
@BindView(R.id.incomingRingingControls)
lateinit var incomingRingingControls: ViewGroup
// @BindView(R.id.iv_icr_accept_call)
// lateinit var incomingRingingControlAccept: ImageView
// @BindView(R.id.iv_icr_end_call)
// lateinit var incomingRingingControlDecline: ImageView
@BindView(R.id.ringingControls)
lateinit var ringingControls: ViewGroup
@BindView(R.id.iv_icr_accept_call)
lateinit var ringingControlAccept: ImageView
@BindView(R.id.iv_icr_end_call)
lateinit var ringingControlDecline: ImageView
@BindView(R.id.connectedControls)
lateinit var connectedControls: ViewGroup
@BindView(R.id.iv_mute_toggle)
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)
when (callState) {
CallState.DIALING -> {
}
CallState.ANSWERING -> {
incomingRingingControls.isVisible = false
CallState.IDLE,
CallState.DIALING,
CallState.CONNECTING,
CallState.ANSWERING -> {
ringingControls.isVisible = true
ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true
connectedControls.isVisible = false
}
CallState.REMOTE_RINGING -> {
}
CallState.LOCAL_RINGING -> {
incomingRingingControls.isVisible = true
CallState.LOCAL_RINGING -> {
ringingControls.isVisible = true
ringingControlAccept.isVisible = true
ringingControlDecline.isVisible = true
connectedControls.isVisible = false
}
CallState.CONNECTED -> {
incomingRingingControls.isVisible = false
CallState.CONNECTED -> {
ringingControls.isVisible = false
connectedControls.isVisible = true
}
CallState.TERMINATED,
CallState.IDLE,
null -> {
incomingRingingControls.isVisible = false
null -> {
ringingControls.isVisible = false
connectedControls.isVisible = false
}
}
}
interface InteractionListener {

View File

@ -91,17 +91,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
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() {
// Set window styles for fullscreen-window size. Needs to be done before adding content.
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_KEEP_SCREEN_ON);
// tryThis { unbindService(serviceConnection) }
// bindService(Intent(this, CallHeadsUpService::class.java), serviceConnection, 0)
if (intent.hasExtra(MvRx.KEY_ARG)) {
callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!!
} else {
@ -184,8 +170,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
Timber.v("## VOIP renderState call $state")
callControlsView.updateForState(state)
when (state.callState.invoke()) {
CallState.IDLE -> {
}
CallState.IDLE,
CallState.DIALING -> {
callVideoGroup.isInvisible = 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)
}
}
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 -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.text = null
state.otherUserMatrixItem.invoke()?.let {
avatarRenderer.render(it, otherMemberAvatar)
participantNameText.text = it.getBestName()
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 -> {
// TODO only if is video call
callVideoGroup.isVisible = true
callInfoGroup.isVisible = false
if (callArgs.isVideoCall) {
callVideoGroup.isVisible = true
callInfoGroup.isVisible = false
} else {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.text = null
}
}
CallState.TERMINATED -> {
finish()
@ -279,20 +269,20 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
return false
}
override fun onDestroy() {
override fun onPause() {
peerConnectionManager.detachRenderers()
// tryThis { unbindService(serviceConnection) }
super.onDestroy()
super.onPause()
}
private fun handleViewEvents(event: VectorCallViewEvents?) {
when (event) {
is VectorCallViewEvents.CallAnswered -> {
}
is VectorCallViewEvents.CallHangup -> {
finish()
}
}
Timber.v("handleViewEvents $event")
// when (event) {
// is VectorCallViewEvents.CallAnswered -> {
// }
// is VectorCallViewEvents.CallHangup -> {
// finish()
// }
// }
}
companion object {

View File

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

View File

@ -211,7 +211,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
currentCall?.mxCall?.offerSdp(p0)
}
}, constraints)
}
private fun getTurnServer(callback: ((TurnServer?) -> Unit)) {
@ -374,7 +373,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
// If remote track exists, then sink it to surface
remoteSurfaceRenderer?.get()?.let { participantSurface ->
currentCall?.remoteVideoTrack?.let {
it.setEnabled(true)
it.addSink(participantSurface)
}
}
@ -551,17 +549,47 @@ class WebRtcPeerConnectionManager @Inject constructor(
override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
Timber.v("## VOIP StreamObserver onConnectionChange: $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
}
PeerConnection.PeerConnectionState.FAILED -> {
/**
* One or more of the ICE transports on the connection is in the "failed" state.
*/
PeerConnection.PeerConnectionState.FAILED -> {
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.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,
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) {
Timber.v("## VOIP StreamObserver onIceConnectionChange IceConnectionState:$newState")
when (newState) {
PeerConnection.IceConnectionState.CONNECTED -> Timber.v("## VOIP StreamObserver onIceConnectionChange.CONNECTED")
PeerConnection.IceConnectionState.DISCONNECTED -> {
Timber.v("## VOIP StreamObserver onIceConnectionChange.DISCONNECTED")
endCall()
/**
* the ICE agent is gathering addresses or is waiting to be given remote candidates through
* 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")
executor.execute {
// 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) {
val remoteVideoTrack = stream.videoTracks.first()

View File

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