davidliu
Committed by GitHub

PeerConnectionState instead of IceConnectionState (#43)

* connection state enum

* proper cleanup of signal client

* fix tests

* Observe peer connection state instead of ice connection

* fix tests
  1 +package io.livekit.android.room
  2 +
  3 +enum class ConnectionState {
  4 + CONNECTING,
  5 + CONNECTED,
  6 + DISCONNECTED,
  7 + RECONNECTING;
  8 +}
@@ -12,8 +12,7 @@ class PublisherTransportObserver( @@ -12,8 +12,7 @@ class PublisherTransportObserver(
12 private val client: SignalClient, 12 private val client: SignalClient,
13 ) : PeerConnection.Observer, PeerConnectionTransport.Listener { 13 ) : PeerConnection.Observer, PeerConnectionTransport.Listener {
14 14
15 - var iceConnectionChangeListener: ((newState: PeerConnection.IceConnectionState?) -> Unit)? =  
16 - null 15 + var connectionChangeListener: ((newState: PeerConnection.PeerConnectionState?) -> Unit)? = null
17 16
18 override fun onIceCandidate(iceCandidate: IceCandidate?) { 17 override fun onIceCandidate(iceCandidate: IceCandidate?) {
19 val candidate = iceCandidate ?: return 18 val candidate = iceCandidate ?: return
@@ -27,7 +26,6 @@ class PublisherTransportObserver( @@ -27,7 +26,6 @@ class PublisherTransportObserver(
27 26
28 override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) { 27 override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
29 LKLog.v { "onIceConnection new state: $newState" } 28 LKLog.v { "onIceConnection new state: $newState" }
30 - iceConnectionChangeListener?.invoke(newState)  
31 } 29 }
32 30
33 override fun onOffer(sd: SessionDescription) { 31 override fun onOffer(sd: SessionDescription) {
@@ -38,6 +36,8 @@ class PublisherTransportObserver( @@ -38,6 +36,8 @@ class PublisherTransportObserver(
38 } 36 }
39 37
40 override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) { 38 override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
  39 + LKLog.v { "onConnection new state: $newState" }
  40 + connectionChangeListener?.invoke(newState)
41 } 41 }
42 42
43 override fun onSelectedCandidatePairChanged(event: CandidatePairChangeEvent?) { 43 override fun onSelectedCandidatePairChanged(event: CandidatePairChangeEvent?) {
@@ -10,6 +10,7 @@ import io.livekit.android.util.CloseableCoroutineScope @@ -10,6 +10,7 @@ import io.livekit.android.util.CloseableCoroutineScope
10 import io.livekit.android.util.Either 10 import io.livekit.android.util.Either
11 import io.livekit.android.util.LKLog 11 import io.livekit.android.util.LKLog
12 import io.livekit.android.webrtc.isConnected 12 import io.livekit.android.webrtc.isConnected
  13 +import io.livekit.android.webrtc.isDisconnected
13 import io.livekit.android.webrtc.toProtoSessionDescription 14 import io.livekit.android.webrtc.toProtoSessionDescription
14 import kotlinx.coroutines.* 15 import kotlinx.coroutines.*
15 import livekit.LivekitModels 16 import livekit.LivekitModels
@@ -36,7 +37,11 @@ internal constructor( @@ -36,7 +37,11 @@ internal constructor(
36 @Named(InjectionNames.DISPATCHER_IO) ioDispatcher: CoroutineDispatcher, 37 @Named(InjectionNames.DISPATCHER_IO) ioDispatcher: CoroutineDispatcher,
37 ) : SignalClient.Listener, DataChannel.Observer { 38 ) : SignalClient.Listener, DataChannel.Observer {
38 internal var listener: Listener? = null 39 internal var listener: Listener? = null
39 - internal var iceState: IceState = IceState.DISCONNECTED 40 +
  41 + /**
  42 + * Reflects the combined connection state of SignalClient and primary PeerConnection.
  43 + */
  44 + internal var connectionState: ConnectionState = ConnectionState.DISCONNECTED
40 set(value) { 45 set(value) {
41 val oldVal = field 46 val oldVal = field
42 field = value 47 field = value
@@ -44,18 +49,18 @@ internal constructor( @@ -44,18 +49,18 @@ internal constructor(
44 return 49 return
45 } 50 }
46 when (value) { 51 when (value) {
47 - IceState.CONNECTED -> {  
48 - if (oldVal == IceState.DISCONNECTED) { 52 + ConnectionState.CONNECTED -> {
  53 + if (oldVal == ConnectionState.DISCONNECTED) {
49 LKLog.d { "primary ICE connected" } 54 LKLog.d { "primary ICE connected" }
50 - listener?.onIceConnected()  
51 - } else if (oldVal == IceState.RECONNECTING) { 55 + listener?.onEngineConnected()
  56 + } else if (oldVal == ConnectionState.RECONNECTING) {
52 LKLog.d { "primary ICE reconnected" } 57 LKLog.d { "primary ICE reconnected" }
53 - listener?.onIceReconnected() 58 + listener?.onEngineReconnected()
54 } 59 }
55 } 60 }
56 - IceState.DISCONNECTED -> { 61 + ConnectionState.DISCONNECTED -> {
57 LKLog.d { "primary ICE disconnected" } 62 LKLog.d { "primary ICE disconnected" }
58 - if (oldVal == IceState.CONNECTED) { 63 + if (oldVal == ConnectionState.CONNECTED) {
59 reconnect() 64 reconnect()
60 } 65 }
61 } 66 }
@@ -167,17 +172,14 @@ internal constructor( @@ -167,17 +172,14 @@ internal constructor(
167 null, 172 null,
168 ) 173 )
169 174
170 - val iceConnectionStateListener: (PeerConnection.IceConnectionState?) -> Unit = { newState -> 175 + val connectionStateListener: (PeerConnection.PeerConnectionState?) -> Unit = { newState ->
171 val state = 176 val state =
172 newState ?: throw NullPointerException("unexpected null new state, what do?") 177 newState ?: throw NullPointerException("unexpected null new state, what do?")
173 LKLog.v { "onIceConnection new state: $newState" } 178 LKLog.v { "onIceConnection new state: $newState" }
174 if (state.isConnected()) { 179 if (state.isConnected()) {
175 - iceState = IceState.CONNECTED  
176 - } else if (state == PeerConnection.IceConnectionState.DISCONNECTED ||  
177 - state == PeerConnection.IceConnectionState.FAILED  
178 - ) {  
179 - // when we publish tracks, some WebRTC versions will send out disconnected events periodically  
180 - iceState = IceState.DISCONNECTED 180 + connectionState = ConnectionState.CONNECTED
  181 + } else if (state.isDisconnected()) {
  182 + connectionState = ConnectionState.DISCONNECTED
181 } 183 }
182 } 184 }
183 185
@@ -191,9 +193,10 @@ internal constructor( @@ -191,9 +193,10 @@ internal constructor(
191 } 193 }
192 dataChannel.registerObserver(this) 194 dataChannel.registerObserver(this)
193 } 195 }
194 - subscriberObserver.iceConnectionChangeListener = iceConnectionStateListener 196 +
  197 + subscriberObserver.connectionChangeListener = connectionStateListener
195 } else { 198 } else {
196 - publisherObserver.iceConnectionChangeListener = iceConnectionStateListener 199 + publisherObserver.connectionChangeListener = connectionStateListener
197 } 200 }
198 201
199 // data channels 202 // data channels
@@ -270,7 +273,7 @@ internal constructor( @@ -270,7 +273,7 @@ internal constructor(
270 } 273 }
271 274
272 val job = coroutineScope.launch { 275 val job = coroutineScope.launch {
273 - listener?.onReconnecting() 276 + listener?.onEngineReconnecting()
274 277
275 for (wsRetries in 0 until MAX_SIGNAL_RETRIES) { 278 for (wsRetries in 0 until MAX_SIGNAL_RETRIES) {
276 var startDelay = wsRetries.toLong() * wsRetries * 500 279 var startDelay = wsRetries.toLong() * wsRetries * 500
@@ -292,7 +295,7 @@ internal constructor( @@ -292,7 +295,7 @@ internal constructor(
292 listener?.onSignalConnected() 295 listener?.onSignalConnected()
293 296
294 subscriber.prepareForIceRestart() 297 subscriber.prepareForIceRestart()
295 - iceState = IceState.RECONNECTING 298 + connectionState = ConnectionState.RECONNECTING
296 // trigger publisher reconnect 299 // trigger publisher reconnect
297 // only restart publisher if it's needed 300 // only restart publisher if it's needed
298 if (hasPublished) { 301 if (hasPublished) {
@@ -302,20 +305,20 @@ internal constructor( @@ -302,20 +305,20 @@ internal constructor(
302 // wait until ICE connected 305 // wait until ICE connected
303 val endTime = SystemClock.elapsedRealtime() + MAX_ICE_CONNECT_TIMEOUT_MS; 306 val endTime = SystemClock.elapsedRealtime() + MAX_ICE_CONNECT_TIMEOUT_MS;
304 while (SystemClock.elapsedRealtime() < endTime) { 307 while (SystemClock.elapsedRealtime() < endTime) {
305 - if (iceState == IceState.CONNECTED) { 308 + if (connectionState == ConnectionState.CONNECTED) {
306 LKLog.v { "reconnected to ICE" } 309 LKLog.v { "reconnected to ICE" }
307 break 310 break
308 } 311 }
309 delay(100) 312 delay(100)
310 } 313 }
311 314
312 - if (iceState == IceState.CONNECTED) { 315 + if (connectionState == ConnectionState.CONNECTED) {
313 return@launch 316 return@launch
314 } 317 }
315 } 318 }
316 319
317 320
318 - listener?.onDisconnect("failed reconnecting.") 321 + listener?.onEngineDisconnected("failed reconnecting.")
319 close() 322 close()
320 } 323 }
321 324
@@ -409,7 +412,7 @@ internal constructor( @@ -409,7 +412,7 @@ internal constructor(
409 MediaConstraintKeys.FALSE 412 MediaConstraintKeys.FALSE
410 ) 413 )
411 ) 414 )
412 - if (iceState == IceState.RECONNECTING) { 415 + if (connectionState == ConnectionState.RECONNECTING) {
413 add( 416 add(
414 MediaConstraints.KeyValuePair( 417 MediaConstraints.KeyValuePair(
415 MediaConstraintKeys.ICE_RESTART, 418 MediaConstraintKeys.ICE_RESTART,
@@ -422,8 +425,11 @@ internal constructor( @@ -422,8 +425,11 @@ internal constructor(
422 } 425 }
423 426
424 internal interface Listener { 427 internal interface Listener {
425 - fun onIceConnected()  
426 - fun onIceReconnected() 428 + fun onEngineConnected()
  429 + fun onEngineReconnected()
  430 + fun onEngineReconnecting()
  431 + fun onEngineDisconnected(reason: String)
  432 + fun onFailToConnect(error: Throwable)
427 fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>) 433 fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>)
428 fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>) 434 fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>)
429 fun onActiveSpeakersUpdate(speakers: List<LivekitModels.SpeakerInfo>) 435 fun onActiveSpeakersUpdate(speakers: List<LivekitModels.SpeakerInfo>)
@@ -431,14 +437,11 @@ internal constructor( @@ -431,14 +437,11 @@ internal constructor(
431 fun onRoomUpdate(update: LivekitModels.Room) 437 fun onRoomUpdate(update: LivekitModels.Room)
432 fun onConnectionQuality(updates: List<LivekitRtc.ConnectionQualityInfo>) 438 fun onConnectionQuality(updates: List<LivekitRtc.ConnectionQualityInfo>)
433 fun onSpeakersChanged(speakers: List<LivekitModels.SpeakerInfo>) 439 fun onSpeakersChanged(speakers: List<LivekitModels.SpeakerInfo>)
434 - fun onDisconnect(reason: String)  
435 - fun onFailToConnect(error: Throwable)  
436 fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind) 440 fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind)
437 fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) 441 fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>)
438 fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate) 442 fun onSubscribedQualityUpdate(subscribedQualityUpdate: LivekitRtc.SubscribedQualityUpdate)
439 fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate) 443 fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate)
440 fun onSignalConnected() 444 fun onSignalConnected()
441 - fun onReconnecting()  
442 } 445 }
443 446
444 companion object { 447 companion object {
@@ -564,7 +567,7 @@ internal constructor( @@ -564,7 +567,7 @@ internal constructor(
564 567
565 override fun onLeave(leave: LivekitRtc.LeaveRequest) { 568 override fun onLeave(leave: LivekitRtc.LeaveRequest) {
566 close() 569 close()
567 - listener?.onDisconnect("server leave") 570 + listener?.onEngineDisconnected("server leave")
568 } 571 }
569 572
570 // Signal error 573 // Signal error
@@ -627,10 +630,4 @@ internal constructor( @@ -627,10 +630,4 @@ internal constructor(
627 630
628 client.sendSyncState(syncState) 631 client.sendSyncState(syncState)
629 } 632 }
630 -}  
631 -  
632 -internal enum class IceState {  
633 - DISCONNECTED,  
634 - RECONNECTING,  
635 - CONNECTED,  
636 } 633 }
@@ -419,17 +419,17 @@ constructor( @@ -419,17 +419,17 @@ constructor(
419 419
420 420
421 //----------------------------------- RTCEngine.Listener ------------------------------------// 421 //----------------------------------- RTCEngine.Listener ------------------------------------//
422 - override fun onIceConnected() { 422 + override fun onEngineConnected() {
423 state = State.CONNECTED 423 state = State.CONNECTED
424 } 424 }
425 425
426 - override fun onIceReconnected() { 426 + override fun onEngineReconnected() {
427 state = State.CONNECTED 427 state = State.CONNECTED
428 listener?.onReconnected(this) 428 listener?.onReconnected(this)
429 eventBus.postEvent(RoomEvent.Reconnected(this), coroutineScope) 429 eventBus.postEvent(RoomEvent.Reconnected(this), coroutineScope)
430 } 430 }
431 431
432 - override fun onReconnecting() { 432 + override fun onEngineReconnecting() {
433 state = State.RECONNECTING 433 state = State.RECONNECTING
434 listener?.onReconnecting(this) 434 listener?.onReconnecting(this)
435 eventBus.postEvent(RoomEvent.Reconnecting(this), coroutineScope) 435 eventBus.postEvent(RoomEvent.Reconnecting(this), coroutineScope)
@@ -546,7 +546,7 @@ constructor( @@ -546,7 +546,7 @@ constructor(
546 /** 546 /**
547 * @suppress 547 * @suppress
548 */ 548 */
549 - override fun onDisconnect(reason: String) { 549 + override fun onEngineDisconnected(reason: String) {
550 LKLog.v { "engine did disconnect: $reason" } 550 LKLog.v { "engine did disconnect: $reason" }
551 handleDisconnect() 551 handleDisconnect()
552 } 552 }
@@ -46,7 +46,7 @@ constructor( @@ -46,7 +46,7 @@ constructor(
46 @Named(InjectionNames.SIGNAL_JSON_ENABLED) 46 @Named(InjectionNames.SIGNAL_JSON_ENABLED)
47 private val useJson: Boolean, 47 private val useJson: Boolean,
48 @Named(InjectionNames.DISPATCHER_IO) 48 @Named(InjectionNames.DISPATCHER_IO)
49 - ioDispatcher: CoroutineDispatcher, 49 + private val ioDispatcher: CoroutineDispatcher,
50 ) : WebSocketListener() { 50 ) : WebSocketListener() {
51 var isConnected = false 51 var isConnected = false
52 private set 52 private set
@@ -57,7 +57,7 @@ constructor( @@ -57,7 +57,7 @@ constructor(
57 private var lastUrl: String? = null 57 private var lastUrl: String? = null
58 58
59 private var joinContinuation: CancellableContinuation<Either<LivekitRtc.JoinResponse, Unit>>? = null 59 private var joinContinuation: CancellableContinuation<Either<LivekitRtc.JoinResponse, Unit>>? = null
60 - private val coroutineScope = CloseableCoroutineScope(SupervisorJob() + ioDispatcher) 60 + private lateinit var coroutineScope: CloseableCoroutineScope
61 61
62 private val responseFlow = MutableSharedFlow<LivekitRtc.SignalResponse>(Int.MAX_VALUE) 62 private val responseFlow = MutableSharedFlow<LivekitRtc.SignalResponse>(Int.MAX_VALUE)
63 63
@@ -109,13 +109,10 @@ constructor( @@ -109,13 +109,10 @@ constructor(
109 109
110 LKLog.i { "connecting to $wsUrlString" } 110 LKLog.i { "connecting to $wsUrlString" }
111 111
112 - isConnected = false  
113 - currentWs?.cancel()  
114 - currentWs = null  
115 -  
116 - joinContinuation?.cancel()  
117 - joinContinuation = null 112 + // Clean up any pre-existing connection.
  113 + close()
118 114
  115 + coroutineScope = CloseableCoroutineScope(SupervisorJob() + ioDispatcher)
119 lastUrl = wsUrlString 116 lastUrl = wsUrlString
120 117
121 val request = Request.Builder() 118 val request = Request.Builder()
@@ -142,6 +139,7 @@ constructor( @@ -142,6 +139,7 @@ constructor(
142 //--------------------------------- WebSocket Listener --------------------------------------// 139 //--------------------------------- WebSocket Listener --------------------------------------//
143 override fun onOpen(webSocket: WebSocket, response: Response) { 140 override fun onOpen(webSocket: WebSocket, response: Response) {
144 if (isReconnecting) { 141 if (isReconnecting) {
  142 + // no need to wait for join response on reconnection.
145 isReconnecting = false 143 isReconnecting = false
146 isConnected = true 144 isConnected = true
147 joinContinuation?.resumeWith(Result.success(Either.Right(Unit))) 145 joinContinuation?.resumeWith(Result.success(Either.Right(Unit)))
@@ -482,10 +480,15 @@ constructor( @@ -482,10 +480,15 @@ constructor(
482 }.safe() 480 }.safe()
483 } 481 }
484 482
485 - fun close() { 483 + fun close(code: Int = 1000, reason: String = "Normal Closure") {
486 isConnected = false 484 isConnected = false
487 - coroutineScope.close()  
488 - currentWs?.close(1000, "Normal Closure") 485 + if(::coroutineScope.isInitialized) {
  486 + coroutineScope.close()
  487 + }
  488 + currentWs?.close(code, reason)
  489 + currentWs = null
  490 + joinContinuation?.cancel()
  491 + joinContinuation = null
489 } 492 }
490 493
491 interface Listener { 494 interface Listener {
@@ -13,7 +13,7 @@ class SubscriberTransportObserver( @@ -13,7 +13,7 @@ class SubscriberTransportObserver(
13 ) : PeerConnection.Observer { 13 ) : PeerConnection.Observer {
14 14
15 var dataChannelListener: ((DataChannel) -> Unit)? = null 15 var dataChannelListener: ((DataChannel) -> Unit)? = null
16 - var iceConnectionChangeListener: ((PeerConnection.IceConnectionState?) -> Unit)? = null 16 + var connectionChangeListener: ((PeerConnection.PeerConnectionState?) -> Unit)? = null
17 17
18 override fun onIceCandidate(candidate: IceCandidate) { 18 override fun onIceCandidate(candidate: IceCandidate) {
19 LKLog.v { "onIceCandidate: $candidate" } 19 LKLog.v { "onIceCandidate: $candidate" }
@@ -43,6 +43,7 @@ class SubscriberTransportObserver( @@ -43,6 +43,7 @@ class SubscriberTransportObserver(
43 43
44 override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) { 44 override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
45 LKLog.v { "onConnectionChange new state: $newState" } 45 LKLog.v { "onConnectionChange new state: $newState" }
  46 + connectionChangeListener?.invoke(newState)
46 } 47 }
47 48
48 override fun onSelectedCandidatePairChanged(event: CandidatePairChangeEvent?) { 49 override fun onSelectedCandidatePairChanged(event: CandidatePairChangeEvent?) {
@@ -53,7 +54,6 @@ class SubscriberTransportObserver( @@ -53,7 +54,6 @@ class SubscriberTransportObserver(
53 54
54 override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) { 55 override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
55 LKLog.v { "onIceConnection new state: $newState" } 56 LKLog.v { "onIceConnection new state: $newState" }
56 - iceConnectionChangeListener?.invoke(newState)  
57 } 57 }
58 58
59 override fun onIceConnectionReceivingChange(p0: Boolean) { 59 override fun onIceConnectionReceivingChange(p0: Boolean) {
@@ -6,13 +6,22 @@ import org.webrtc.PeerConnection @@ -6,13 +6,22 @@ import org.webrtc.PeerConnection
6 * Completed state is a valid state for a connected connection, so this should be used 6 * Completed state is a valid state for a connected connection, so this should be used
7 * when checking for a connected state 7 * when checking for a connected state
8 */ 8 */
9 -internal fun PeerConnection.isConnected(): Boolean = iceConnectionState().isConnected() 9 +internal fun PeerConnection.isConnected(): Boolean = connectionState().isConnected()
10 10
11 -internal fun PeerConnection.IceConnectionState.isConnected(): Boolean { 11 +internal fun PeerConnection.isDisconnected(): Boolean = connectionState().isDisconnected()
  12 +
  13 +internal fun PeerConnection.PeerConnectionState.isConnected(): Boolean {
  14 + return this == PeerConnection.PeerConnectionState.CONNECTED
  15 +}
  16 +
  17 +internal fun PeerConnection.PeerConnectionState.isDisconnected(): Boolean {
12 return when (this) { 18 return when (this) {
13 - PeerConnection.IceConnectionState.CONNECTED,  
14 - PeerConnection.IceConnectionState.COMPLETED -> true 19 + /**
  20 + * [PeerConnection.PeerConnectionState.DISCONNECTED] is explicitly not included here,
  21 + * as that is a temporary state and may return to connected state by itself.
  22 + */
  23 + PeerConnection.PeerConnectionState.FAILED,
  24 + PeerConnection.PeerConnectionState.CLOSED -> true
15 else -> false 25 else -> false
16 } 26 }
17 } 27 }
18 -  
@@ -29,7 +29,7 @@ abstract class MockE2ETest { @@ -29,7 +29,7 @@ abstract class MockE2ETest {
29 @get:Rule 29 @get:Rule
30 var coroutineRule = TestCoroutineRule() 30 var coroutineRule = TestCoroutineRule()
31 31
32 - lateinit var component: TestLiveKitComponent 32 + internal lateinit var component: TestLiveKitComponent
33 lateinit var context: Context 33 lateinit var context: Context
34 lateinit var room: Room 34 lateinit var room: Room
35 lateinit var wsFactory: MockWebSocketFactory 35 lateinit var wsFactory: MockWebSocketFactory
@@ -10,6 +10,7 @@ class MockPeerConnection( @@ -10,6 +10,7 @@ class MockPeerConnection(
10 val observer: PeerConnection.Observer? 10 val observer: PeerConnection.Observer?
11 ) : PeerConnection(MockNativePeerConnectionFactory()) { 11 ) : PeerConnection(MockNativePeerConnectionFactory()) {
12 12
  13 + private var closed = false
13 var localDesc: SessionDescription? = null 14 var localDesc: SessionDescription? = null
14 var remoteDesc: SessionDescription? = null 15 var remoteDesc: SessionDescription? = null
15 override fun getLocalDescription(): SessionDescription? = localDesc 16 override fun getLocalDescription(): SessionDescription? = localDesc
@@ -140,7 +141,27 @@ class MockPeerConnection( @@ -140,7 +141,27 @@ class MockPeerConnection(
140 } 141 }
141 142
142 override fun signalingState(): SignalingState { 143 override fun signalingState(): SignalingState {
143 - return super.signalingState() 144 + if (closed) {
  145 + return SignalingState.CLOSED
  146 + }
  147 +
  148 + if ((localDesc?.type == null && localDesc?.type == null) ||
  149 + (localDesc?.type == SessionDescription.Type.OFFER &&
  150 + remoteDesc?.type == SessionDescription.Type.ANSWER) ||
  151 + (localDesc?.type == SessionDescription.Type.ANSWER &&
  152 + remoteDesc?.type == SessionDescription.Type.OFFER)
  153 + ) {
  154 + return SignalingState.STABLE
  155 + }
  156 +
  157 + if (localDesc?.type == SessionDescription.Type.OFFER && remoteDesc?.type == null) {
  158 + return SignalingState.HAVE_LOCAL_OFFER
  159 + }
  160 + if (remoteDesc?.type == SessionDescription.Type.OFFER && localDesc?.type == null) {
  161 + return SignalingState.HAVE_REMOTE_OFFER
  162 + }
  163 +
  164 + throw IllegalStateException("Illegal signalling state? localDesc: $localDesc, remoteDesc: $remoteDesc")
144 } 165 }
145 166
146 private var iceConnectionState = IceConnectionState.NEW 167 private var iceConnectionState = IceConnectionState.NEW
@@ -148,12 +169,34 @@ class MockPeerConnection( @@ -148,12 +169,34 @@ class MockPeerConnection(
148 if (field != value) { 169 if (field != value) {
149 field = value 170 field = value
150 observer?.onIceConnectionChange(field) 171 observer?.onIceConnectionChange(field)
  172 +
  173 + connectionState = when (field) {
  174 + IceConnectionState.NEW -> PeerConnectionState.NEW
  175 + IceConnectionState.CHECKING -> PeerConnectionState.CONNECTING
  176 + IceConnectionState.CONNECTED,
  177 + IceConnectionState.COMPLETED -> PeerConnectionState.CONNECTED
  178 + IceConnectionState.DISCONNECTED -> PeerConnectionState.DISCONNECTED
  179 + IceConnectionState.FAILED -> PeerConnectionState.FAILED
  180 + IceConnectionState.CLOSED -> PeerConnectionState.CLOSED
  181 + }
  182 + }
  183 + }
  184 +
  185 + private var connectionState = PeerConnectionState.NEW
  186 + set(value) {
  187 + if (field != value) {
  188 + field = value
  189 + observer?.onConnectionChange(field)
151 } 190 }
152 } 191 }
153 192
154 override fun iceConnectionState(): IceConnectionState = iceConnectionState 193 override fun iceConnectionState(): IceConnectionState = iceConnectionState
155 194
156 fun moveToIceConnectionState(newState: IceConnectionState) { 195 fun moveToIceConnectionState(newState: IceConnectionState) {
  196 + if (closed && newState != IceConnectionState.CLOSED) {
  197 + throw IllegalArgumentException("peer connection closed, but attempting to move to $newState")
  198 + }
  199 +
157 when (newState) { 200 when (newState) {
158 IceConnectionState.NEW, 201 IceConnectionState.NEW,
159 IceConnectionState.CHECKING, 202 IceConnectionState.CHECKING,
@@ -189,9 +232,12 @@ class MockPeerConnection( @@ -189,9 +232,12 @@ class MockPeerConnection(
189 } 232 }
190 233
191 override fun close() { 234 override fun close() {
  235 + dispose()
192 } 236 }
193 237
194 override fun dispose() { 238 override fun dispose() {
  239 + iceConnectionState = IceConnectionState.CLOSED
  240 + closed = true
195 } 241 }
196 242
197 override fun getNativePeerConnection(): Long = 0L 243 override fun getNativePeerConnection(): Long = 0L
@@ -55,7 +55,7 @@ class RTCEngineMockE2ETest : MockE2ETest() { @@ -55,7 +55,7 @@ class RTCEngineMockE2ETest : MockE2ETest() {
55 55
56 subPeerConnection.moveToIceConnectionState(PeerConnection.IceConnectionState.CONNECTED) 56 subPeerConnection.moveToIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
57 57
58 - Assert.assertEquals(IceState.CONNECTED, rtcEngine.iceState) 58 + Assert.assertEquals(ConnectionState.CONNECTED, rtcEngine.connectionState)
59 } 59 }
60 60
61 @Test 61 @Test
@@ -104,7 +104,7 @@ class RoomTest { @@ -104,7 +104,7 @@ class RoomTest {
104 connect() 104 connect()
105 105
106 val eventCollector = EventCollector(room.events, coroutineRule.scope) 106 val eventCollector = EventCollector(room.events, coroutineRule.scope)
107 - room.onDisconnect("") 107 + room.onEngineDisconnected("")
108 val events = eventCollector.stopCollecting() 108 val events = eventCollector.stopCollecting()
109 109
110 Assert.assertEquals(1, events.size) 110 Assert.assertEquals(1, events.size)