davidliu
Committed by GitHub

Forward leave reason of disconnected events (#191)

@@ -6,6 +6,7 @@ import io.livekit.android.room.track.LocalTrackPublication @@ -6,6 +6,7 @@ import io.livekit.android.room.track.LocalTrackPublication
6 import io.livekit.android.room.track.RemoteTrackPublication 6 import io.livekit.android.room.track.RemoteTrackPublication
7 import io.livekit.android.room.track.Track 7 import io.livekit.android.room.track.Track
8 import io.livekit.android.room.track.TrackPublication 8 import io.livekit.android.room.track.TrackPublication
  9 +import livekit.LivekitModels
9 10
10 sealed class RoomEvent(val room: Room) : Event() { 11 sealed class RoomEvent(val room: Room) : Event() {
11 /** 12 /**
@@ -22,7 +23,7 @@ sealed class RoomEvent(val room: Room) : Event() { @@ -22,7 +23,7 @@ sealed class RoomEvent(val room: Room) : Event() {
22 /** 23 /**
23 * Disconnected from room 24 * Disconnected from room
24 */ 25 */
25 - class Disconnected(room: Room, val error: Exception?) : RoomEvent(room) 26 + class Disconnected(room: Room, val error: Exception?, val reason: DisconnectReason) : RoomEvent(room)
26 27
27 /** 28 /**
28 * When a [RemoteParticipant] joins after the local participant. It will not emit events 29 * When a [RemoteParticipant] joins after the local participant. It will not emit events
@@ -168,4 +169,30 @@ sealed class RoomEvent(val room: Room) : Event() { @@ -168,4 +169,30 @@ sealed class RoomEvent(val room: Room) : Event() {
168 val newPermissions: ParticipantPermission?, 169 val newPermissions: ParticipantPermission?,
169 val oldPermissions: ParticipantPermission?, 170 val oldPermissions: ParticipantPermission?,
170 ) : RoomEvent(room) 171 ) : RoomEvent(room)
  172 +}
  173 +
  174 +enum class DisconnectReason {
  175 + UNKNOWN_REASON,
  176 + CLIENT_INITIATED,
  177 + DUPLICATE_IDENTITY,
  178 + SERVER_SHUTDOWN,
  179 + PARTICIPANT_REMOVED,
  180 + ROOM_DELETED,
  181 + STATE_MISMATCH,
  182 + JOIN_FAILURE;
  183 +}
  184 +
  185 +fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
  186 + return when (this) {
  187 + LivekitModels.DisconnectReason.CLIENT_INITIATED -> DisconnectReason.CLIENT_INITIATED
  188 + LivekitModels.DisconnectReason.DUPLICATE_IDENTITY -> DisconnectReason.DUPLICATE_IDENTITY
  189 + LivekitModels.DisconnectReason.SERVER_SHUTDOWN -> DisconnectReason.SERVER_SHUTDOWN
  190 + LivekitModels.DisconnectReason.PARTICIPANT_REMOVED -> DisconnectReason.PARTICIPANT_REMOVED
  191 + LivekitModels.DisconnectReason.ROOM_DELETED -> DisconnectReason.ROOM_DELETED
  192 + LivekitModels.DisconnectReason.STATE_MISMATCH -> DisconnectReason.STATE_MISMATCH
  193 + LivekitModels.DisconnectReason.JOIN_FAILURE -> DisconnectReason.JOIN_FAILURE
  194 + LivekitModels.DisconnectReason.UNKNOWN_REASON,
  195 + LivekitModels.DisconnectReason.UNRECOGNIZED,
  196 + null -> DisconnectReason.UNKNOWN_REASON
  197 + }
171 } 198 }
@@ -5,6 +5,8 @@ import com.google.protobuf.ByteString @@ -5,6 +5,8 @@ import com.google.protobuf.ByteString
5 import io.livekit.android.ConnectOptions 5 import io.livekit.android.ConnectOptions
6 import io.livekit.android.RoomOptions 6 import io.livekit.android.RoomOptions
7 import io.livekit.android.dagger.InjectionNames 7 import io.livekit.android.dagger.InjectionNames
  8 +import io.livekit.android.events.DisconnectReason
  9 +import io.livekit.android.events.convert
8 import io.livekit.android.room.participant.ParticipantTrackPermission 10 import io.livekit.android.room.participant.ParticipantTrackPermission
9 import io.livekit.android.room.track.TrackException 11 import io.livekit.android.room.track.TrackException
10 import io.livekit.android.room.util.MediaConstraintKeys 12 import io.livekit.android.room.util.MediaConstraintKeys
@@ -456,7 +458,7 @@ internal constructor( @@ -456,7 +458,7 @@ internal constructor(
456 } 458 }
457 459
458 close("Failed reconnecting") 460 close("Failed reconnecting")
459 - listener?.onEngineDisconnected("failed reconnecting.") 461 + listener?.onEngineDisconnected(DisconnectReason.UNKNOWN_REASON)
460 } 462 }
461 463
462 reconnectingJob = job 464 reconnectingJob = job
@@ -566,7 +568,7 @@ internal constructor( @@ -566,7 +568,7 @@ internal constructor(
566 fun onEngineConnected() 568 fun onEngineConnected()
567 fun onEngineReconnected() 569 fun onEngineReconnected()
568 fun onEngineReconnecting() 570 fun onEngineReconnecting()
569 - fun onEngineDisconnected(reason: String) 571 + fun onEngineDisconnected(reason: DisconnectReason)
570 fun onFailToConnect(error: Throwable) 572 fun onFailToConnect(error: Throwable)
571 fun onJoinResponse(response: LivekitRtc.JoinResponse) 573 fun onJoinResponse(response: LivekitRtc.JoinResponse)
572 fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>) 574 fun onAddTrack(track: MediaStreamTrack, streams: Array<out MediaStream>)
@@ -722,7 +724,8 @@ internal constructor( @@ -722,7 +724,8 @@ internal constructor(
722 fullReconnectOnNext = true 724 fullReconnectOnNext = true
723 } else { 725 } else {
724 close() 726 close()
725 - listener?.onEngineDisconnected("server leave") 727 + val disconnectReason = leave.reason.convert()
  728 + listener?.onEngineDisconnected(disconnectReason)
726 } 729 }
727 } 730 }
728 731
@@ -16,10 +16,7 @@ import io.livekit.android.RoomOptions @@ -16,10 +16,7 @@ import io.livekit.android.RoomOptions
16 import io.livekit.android.Version 16 import io.livekit.android.Version
17 import io.livekit.android.audio.AudioHandler 17 import io.livekit.android.audio.AudioHandler
18 import io.livekit.android.dagger.InjectionNames 18 import io.livekit.android.dagger.InjectionNames
19 -import io.livekit.android.events.BroadcastEventBus  
20 -import io.livekit.android.events.ParticipantEvent  
21 -import io.livekit.android.events.RoomEvent  
22 -import io.livekit.android.events.collect 19 +import io.livekit.android.events.*
23 import io.livekit.android.memory.CloseableManager 20 import io.livekit.android.memory.CloseableManager
24 import io.livekit.android.renderer.TextureViewRenderer 21 import io.livekit.android.renderer.TextureViewRenderer
25 import io.livekit.android.room.participant.* 22 import io.livekit.android.room.participant.*
@@ -250,7 +247,7 @@ constructor( @@ -250,7 +247,7 @@ constructor(
250 */ 247 */
251 fun disconnect() { 248 fun disconnect() {
252 engine.client.sendLeave() 249 engine.client.sendLeave()
253 - handleDisconnect() 250 + handleDisconnect(DisconnectReason.CLIENT_INITIATED)
254 } 251 }
255 252
256 /** 253 /**
@@ -463,7 +460,7 @@ constructor( @@ -463,7 +460,7 @@ constructor(
463 .forEach { sid -> handleParticipantDisconnect(sid) } 460 .forEach { sid -> handleParticipantDisconnect(sid) }
464 } 461 }
465 462
466 - private fun handleDisconnect() { 463 + private fun handleDisconnect(reason: DisconnectReason) {
467 if (state == State.DISCONNECTED) { 464 if (state == State.DISCONNECTED) {
468 return 465 return
469 } 466 }
@@ -485,7 +482,7 @@ constructor( @@ -485,7 +482,7 @@ constructor(
485 482
486 // Ensure all observers see the disconnected before closing scope. 483 // Ensure all observers see the disconnected before closing scope.
487 runBlocking { 484 runBlocking {
488 - eventBus.postEvent(RoomEvent.Disconnected(this@Room, null), coroutineScope).join() 485 + eventBus.postEvent(RoomEvent.Disconnected(this@Room, null, reason), coroutineScope).join()
489 } 486 }
490 coroutineScope.cancel() 487 coroutineScope.cancel()
491 } 488 }
@@ -737,9 +734,9 @@ constructor( @@ -737,9 +734,9 @@ constructor(
737 /** 734 /**
738 * @suppress 735 * @suppress
739 */ 736 */
740 - override fun onEngineDisconnected(reason: String) { 737 + override fun onEngineDisconnected(reason: DisconnectReason) {
741 LKLog.v { "engine did disconnect: $reason" } 738 LKLog.v { "engine did disconnect: $reason" }
742 - handleDisconnect() 739 + handleDisconnect(reason)
743 } 740 }
744 741
745 /** 742 /**
@@ -5,9 +5,7 @@ import android.net.ConnectivityManager @@ -5,9 +5,7 @@ import android.net.ConnectivityManager
5 import android.net.Network 5 import android.net.Network
6 import androidx.test.platform.app.InstrumentationRegistry 6 import androidx.test.platform.app.InstrumentationRegistry
7 import io.livekit.android.MockE2ETest 7 import io.livekit.android.MockE2ETest
8 -import io.livekit.android.events.EventCollector  
9 -import io.livekit.android.events.FlowCollector  
10 -import io.livekit.android.events.RoomEvent 8 +import io.livekit.android.events.*
11 import io.livekit.android.mock.MockAudioStreamTrack 9 import io.livekit.android.mock.MockAudioStreamTrack
12 import io.livekit.android.mock.MockMediaStream 10 import io.livekit.android.mock.MockMediaStream
13 import io.livekit.android.mock.TestData 11 import io.livekit.android.mock.TestData
@@ -315,9 +313,33 @@ class RoomMockE2ETest : MockE2ETest() { @@ -315,9 +313,33 @@ class RoomMockE2ETest : MockE2ETest() {
315 room.disconnect() 313 room.disconnect()
316 val events = eventCollector.stopCollecting() 314 val events = eventCollector.stopCollecting()
317 315
318 - Assert.assertEquals(2, events.size)  
319 - Assert.assertEquals(true, events[0] is RoomEvent.TrackUnpublished)  
320 - Assert.assertEquals(true, events[1] is RoomEvent.Disconnected) 316 + assertEquals(2, events.size)
  317 + assertEquals(true, events[0] is RoomEvent.TrackUnpublished)
  318 + assertEquals(true, events[1] is RoomEvent.Disconnected)
  319 + }
  320 +
  321 + @Test
  322 + fun serverDisconnectReason() = runTest {
  323 + connect()
  324 +
  325 + val eventCollector = EventCollector(room.events, coroutineRule.scope)
  326 + wsFactory.listener.onMessage(wsFactory.ws, SignalClientTest.LEAVE.toOkioByteString())
  327 + val events = eventCollector.stopCollecting()
  328 + assertEquals(1, events.size)
  329 + assertEquals(true, events[0] is RoomEvent.Disconnected)
  330 + assertEquals(SignalClientTest.LEAVE.leave.reason.convert(), (events[0] as RoomEvent.Disconnected).reason)
  331 + }
  332 +
  333 + @Test
  334 + fun clientDisconnectReason() = runTest {
  335 + connect()
  336 +
  337 + val eventCollector = EventCollector(room.events, coroutineRule.scope)
  338 + room.disconnect()
  339 + val events = eventCollector.stopCollecting()
  340 + assertEquals(1, events.size)
  341 + assertEquals(true, events[0] is RoomEvent.Disconnected)
  342 + assertEquals(DisconnectReason.CLIENT_INITIATED, (events[0] as RoomEvent.Disconnected).reason)
321 } 343 }
322 344
323 @Test 345 @Test
@@ -7,10 +7,7 @@ import androidx.test.core.app.ApplicationProvider @@ -7,10 +7,7 @@ import androidx.test.core.app.ApplicationProvider
7 import androidx.test.platform.app.InstrumentationRegistry 7 import androidx.test.platform.app.InstrumentationRegistry
8 import io.livekit.android.audio.NoAudioHandler 8 import io.livekit.android.audio.NoAudioHandler
9 import io.livekit.android.coroutines.TestCoroutineRule 9 import io.livekit.android.coroutines.TestCoroutineRule
10 -import io.livekit.android.events.EventCollector  
11 -import io.livekit.android.events.EventListenable  
12 -import io.livekit.android.events.ParticipantEvent  
13 -import io.livekit.android.events.RoomEvent 10 +import io.livekit.android.events.*
14 import io.livekit.android.memory.CloseableManager 11 import io.livekit.android.memory.CloseableManager
15 import io.livekit.android.mock.* 12 import io.livekit.android.mock.*
16 import io.livekit.android.room.participant.LocalParticipant 13 import io.livekit.android.room.participant.LocalParticipant
@@ -135,11 +132,12 @@ class RoomTest { @@ -135,11 +132,12 @@ class RoomTest {
135 connect() 132 connect()
136 133
137 val eventCollector = EventCollector(room.events, coroutineRule.scope) 134 val eventCollector = EventCollector(room.events, coroutineRule.scope)
138 - room.onEngineDisconnected("") 135 + room.onEngineDisconnected(DisconnectReason.SERVER_SHUTDOWN)
139 val events = eventCollector.stopCollecting() 136 val events = eventCollector.stopCollecting()
140 137
141 - Assert.assertEquals(1, events.size)  
142 - Assert.assertEquals(true, events[0] is RoomEvent.Disconnected) 138 + assertEquals(1, events.size)
  139 + assertEquals(true, events[0] is RoomEvent.Disconnected)
  140 + assertEquals(DisconnectReason.SERVER_SHUTDOWN, (events[0] as RoomEvent.Disconnected).reason)
143 } 141 }
144 142
145 @Test 143 @Test
@@ -160,14 +158,14 @@ class RoomTest { @@ -160,14 +158,14 @@ class RoomTest {
160 ) 158 )
161 159
162 val eventCollector = EventCollector(room.events, coroutineRule.scope) 160 val eventCollector = EventCollector(room.events, coroutineRule.scope)
163 - room.onEngineDisconnected("") 161 + room.onEngineDisconnected(DisconnectReason.CLIENT_INITIATED)
164 val events = eventCollector.stopCollecting() 162 val events = eventCollector.stopCollecting()
165 163
166 - Assert.assertEquals(4, events.size)  
167 - Assert.assertEquals(true, events[0] is RoomEvent.TrackUnsubscribed)  
168 - Assert.assertEquals(true, events[1] is RoomEvent.TrackUnpublished)  
169 - Assert.assertEquals(true, events[2] is RoomEvent.ParticipantDisconnected)  
170 - Assert.assertEquals(true, events[3] is RoomEvent.Disconnected) 164 + assertEquals(4, events.size)
  165 + assertEquals(true, events[0] is RoomEvent.TrackUnsubscribed)
  166 + assertEquals(true, events[1] is RoomEvent.TrackUnpublished)
  167 + assertEquals(true, events[2] is RoomEvent.ParticipantDisconnected)
  168 + assertEquals(true, events[3] is RoomEvent.Disconnected)
171 Assert.assertTrue(room.remoteParticipants.isEmpty()) 169 Assert.assertTrue(room.remoteParticipants.isEmpty())
172 } 170 }
173 } 171 }
@@ -442,6 +442,7 @@ class SignalClientTest : BaseTest() { @@ -442,6 +442,7 @@ class SignalClientTest : BaseTest() {
442 442
443 val LEAVE = with(LivekitRtc.SignalResponse.newBuilder()) { 443 val LEAVE = with(LivekitRtc.SignalResponse.newBuilder()) {
444 leave = with(LivekitRtc.LeaveRequest.newBuilder()) { 444 leave = with(LivekitRtc.LeaveRequest.newBuilder()) {
  445 + reason = LivekitModels.DisconnectReason.SERVER_SHUTDOWN
445 build() 446 build()
446 } 447 }
447 build() 448 build()